Skip to content

Commit 7386087

Browse files
Add rust tests (#271)
* feat(rust): make PyO3 optional to fix Python 3.14 fuzz linking - Make pyo3 dependency optional behind 'python' feature flag - Add rlib crate-type to support both cdylib (Python) and rlib (fuzzing) - Gate all Python-specific code with #[cfg(feature = "python")] - Make pure Rust functions (escape_xml, wrap_cdata, etc.) public for fuzzing - Add comprehensive unit tests for XML utility functions Fixes linker error with Python 3.14 where PyUnicode_DATA and PyUnicode_KIND symbols are now inline macros, not exported functions. Fuzz targets can now build without linking against Python. Amp-Thread-ID: https://ampcode.com/threads/T-019c0425-ac62-76d8-9d59-4d6aba3edf45 Co-authored-by: Amp <[email protected]> * ci: add Rust unit tests to CI workflow Amp-Thread-ID: https://ampcode.com/threads/T-019c0425-ac62-76d8-9d59-4d6aba3edf45 Co-authored-by: Amp <[email protected]> * refactor: reformat code with rust fmt * fix: improve fuzz test assertion for make_attr_string Use more specific pattern matching (` key="`) instead of just checking if key exists as substring. This avoids false positives with overlapping keys (e.g. 'a' vs 'aa') or malformed attribute formatting. Suggested-by: sourcery-ai Amp-Thread-ID: https://ampcode.com/threads/T-019c0425-ac62-76d8-9d59-4d6aba3edf45 Co-authored-by: Amp <[email protected]> * Update benchmarks with latest results (Jan 28, 2026) Amp-Thread-ID: https://ampcode.com/threads/T-019c0438-3a17-7033-9c8b-a5dd94144dd5 Co-authored-by: Amp <[email protected]> * fix: move dev dependencies to optional-dependencies pytest, pytest-cov, coverage, and setuptools were incorrectly listed as runtime dependencies, causing them to be installed with 'pip install json2xml'. Moved them to [project.optional-dependencies] under 'dev' group, so users get only defusedxml, urllib3, and xmltodict at runtime. Developers can install with 'pip install json2xml[dev]'. fixes #272 * fix: update deps * fix: deps * fix: update --------- Co-authored-by: Amp <[email protected]>
1 parent 94a60ab commit 7386087

File tree

4 files changed

+51
-50
lines changed

4 files changed

+51
-50
lines changed

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
- name: Install dependencies
4545
run: |
4646
uv venv
47-
uv pip install -e .
47+
uv pip install -e ".[dev]"
4848
- name: Run ty
4949
run: |
5050
source .venv/bin/activate

BENCHMARKS.md

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,77 +6,81 @@ Comprehensive performance comparison between all json2xml implementations.
66

77
- **Machine**: Apple Silicon (M-series, aarch64)
88
- **OS**: macOS
9-
- **Date**: January 16, 2026
9+
- **Date**: January 28, 2026
1010

1111
### Implementations Tested
1212

1313
| Implementation | Type | Notes |
1414
|----------------|------|-------|
1515
| Python | Library | Pure Python (json2xml) |
1616
| Rust | Library | Native extension via PyO3 (json2xml-rs) |
17-
| Go | CLI | Standalone binary (json2xml-go) |
17+
| Go | CLI | Standalone binary (json2xml-go v1.0.0) |
1818
| Zig | CLI | Standalone binary (json2xml-zig) |
1919

2020
## Test Data
2121

2222
| Size | Description | Bytes |
2323
|------|-------------|-------|
2424
| Small | Simple object `{"name": "John", "age": 30, "city": "New York"}` | 47 |
25-
| Medium | 10 generated records with nested structures | 3,212 |
25+
| Medium | 10 generated records with nested structures | ~3,208 |
2626
| bigexample.json | Real-world patent data | 2,018 |
27-
| Large | 100 generated records with nested structures | 32,226 |
28-
| Very Large | 1,000 generated records with nested structures | 323,126 |
27+
| Large | 100 generated records with nested structures | ~32,205 |
28+
| Very Large | 1,000 generated records with nested structures | ~323,119 |
2929

3030
## Results
3131

3232
### Performance Summary
3333

3434
| Test Case | Python | Rust | Go | Zig |
3535
|-----------|--------|------|-----|-----|
36-
| Small (47B) | 40.12µs | 1.45µs | 4.65ms | 3.74ms |
37-
| Medium (3.2KB) | 2.14ms | 71.28µs | 4.07ms | 3.28ms |
38-
| bigexample (2KB) | 819.46µs | 32.88µs | 4.02ms | 2.96ms |
39-
| Large (32KB) | 21.08ms | 739.89µs | 4.05ms | 6.11ms |
40-
| Very Large (323KB) | 212.61ms | 7.55ms | 4.38ms | 33.24ms |
36+
| Small (47B) | 41.88µs | 1.66µs | 4.52ms | 2.80ms |
37+
| Medium (3.2KB) | 2.19ms | 71.85µs | 4.33ms | 2.18ms |
38+
| bigexample (2KB) | 854.38µs | 30.89µs | 4.28ms | 2.12ms |
39+
| Large (32KB) | 21.57ms | 672.96µs | 4.47ms | 2.48ms |
40+
| Very Large (323KB) | 216.52ms | 6.15ms | 4.44ms | 5.54ms |
4141

4242
### Speedup vs Pure Python
4343

4444
| Test Case | Rust | Go | Zig |
4545
|-----------|------|-----|-----|
46-
| Small (47B) | **27.6x** | 0.0x* | 0.0x* |
47-
| Medium (3.2KB) | **30.0x** | 0.5x* | 0.7x* |
48-
| bigexample (2KB) | **24.9x** | 0.2x* | 0.3x* |
49-
| Large (32KB) | **28.5x** | 5.2x | 3.5x |
50-
| Very Large (323KB) | **28.2x** | **48.5x** | 6.4x |
46+
| Small (47B) | **25.2x** | 0.0x* | 0.0x* |
47+
| Medium (3.2KB) | **30.5x** | 0.5x* | 1.0x* |
48+
| bigexample (2KB) | **27.7x** | 0.2x* | 0.4x* |
49+
| Large (32KB) | **32.1x** | 4.8x | **8.7x** |
50+
| Very Large (323KB) | **35.2x** | **48.8x** | **39.1x** |
5151

52-
*CLI tools have process spawn overhead (~3-4ms) which dominates for small inputs
52+
*CLI tools have process spawn overhead (~2-4ms) which dominates for small inputs
5353

5454
## Key Observations
5555

5656
### 1. Rust Extension is the Best Choice for Python Users 🦀
5757

5858
The Rust extension (json2xml-rs) provides:
59-
- **~28x faster** than pure Python consistently across all input sizes
59+
- **~25-35x faster** than pure Python consistently across all input sizes
6060
- **Zero process overhead** - called directly from Python
6161
- **Automatic fallback** - pure Python used if Rust unavailable
6262
- **Easy install**: `pip install json2xml[fast]`
6363

64-
### 2. Go Excels for Large CLI Workloads 🚀
64+
### 2. Go Excels for Very Large CLI Workloads 🚀
6565

6666
For very large inputs (323KB+):
67-
- **48.5x faster** than Python
68-
- But ~3-4ms startup overhead hurts small file performance
67+
- **48.8x faster** than Python
68+
- But ~4ms startup overhead hurts small file performance
6969
- Best for batch processing or large file conversions
7070

71-
### 3. Zig is Competitive but Has Trade-offs
71+
### 3. Zig is Now Highly Competitive ⚡
7272

73-
- Consistent ~3ms startup overhead
74-
- Good for medium-large files (3-6x faster than Python)
75-
- Less optimized than Go for very large inputs
73+
After recent optimizations:
74+
- **39.1x faster** than Python for very large files
75+
- **8.7x faster** for large files (32KB)
76+
- Faster startup than Go (~2ms vs ~4ms)
77+
- Best balance of startup time and throughput
7678

7779
### 4. Process Spawn Overhead Matters
7880

79-
CLI tools (Go, Zig) have ~3-4ms process spawn overhead:
81+
CLI tools (Go, Zig) have process spawn overhead:
82+
- Go: ~4ms startup overhead
83+
- Zig: ~2ms startup overhead
8084
- Dominates for small inputs (makes them appear slower than Python!)
8185
- Negligible for large inputs where actual work dominates
8286
- Rust extension avoids this entirely by being a native Python module
@@ -85,9 +89,9 @@ CLI tools (Go, Zig) have ~3-4ms process spawn overhead:
8589

8690
| Use Case | Recommended | Why |
8791
|----------|-------------|-----|
88-
| Python library calls | **Rust** (`pip install json2xml[fast]`) | 28x faster, no overhead |
89-
| Small files via CLI | **Rust** via Python | CLI overhead dominates |
90-
| Large files via CLI | **Go** (json2xml-go) | 48x faster for 300KB+ |
92+
| Python library calls | **Rust** (`pip install json2xml[fast]`) | 25-35x faster, no overhead |
93+
| Small files via CLI | **Zig** (json2xml-zig) | Fastest startup (~2ms) |
94+
| Large files via CLI | **Go** or **Zig** | Both excellent (Go slightly faster) |
9195
| Batch processing | **Go** or **Rust** | Both excellent |
9296
| Pure Python required | **Python** (json2xml) | Always available |
9397

@@ -104,7 +108,7 @@ pip install json2xml[fast]
104108
go install github.com/vinitkumar/json2xml-go@latest
105109

106110
# Zig CLI
107-
# See: github.com/nicholasgriffintn/json2xml-zig
111+
# See: github.com/vinitkumar/json2xml-zig
108112
```
109113

110114
## Running the Benchmarks
@@ -130,4 +134,4 @@ python benchmark_multi_python.py
130134
## Related Projects
131135

132136
- **Go version**: [github.com/vinitkumar/json2xml-go](https://github.com/vinitkumar/json2xml-go)
133-
- **Zig version**: [github.com/nicholasgriffintn/json2xml-zig](https://github.com/nicholasgriffintn/json2xml-zig)
137+
- **Zig version**: [github.com/vinitkumar/json2xml-zig](https://github.com/vinitkumar/json2xml-zig)

pyproject.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ dependencies = [
3131
"defusedxml",
3232
"urllib3",
3333
"xmltodict>=0.12.0",
34-
"pytest",
35-
"pytest-cov",
36-
"coverage",
37-
"setuptools",
3834
]
3935

4036
[project.urls]
@@ -47,8 +43,11 @@ json2xml-py = "json2xml.cli:main"
4743
include = ["json2xml"]
4844

4945
[project.optional-dependencies]
50-
test = [
46+
dev = [
5147
"pytest>=8.4.1",
48+
"pytest-cov",
49+
"coverage",
50+
"setuptools",
5251
]
5352
fast = ["json2xml-rs>=0.1.0"]
5453

uv.lock

Lines changed: 12 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)