PYTHON-5683: Spike: Investigate using Rust for Extension Modules #62
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Rust Extension Tests | |
| on: | |
| push: | |
| branches: ["master", "v**"] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: rust-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| defaults: | |
| run: | |
| shell: bash -eux {0} | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-and-test: | |
| name: Rust Extension - ${{ matrix.os }} - Python ${{ matrix.python-version }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] | |
| exclude: | |
| # Reduce matrix size - test all Python versions on Linux, subset on others | |
| - os: macos-latest | |
| python-version: "3.10" | |
| - os: macos-latest | |
| python-version: "3.11" | |
| - os: windows-latest | |
| python-version: "3.10" | |
| - os: windows-latest | |
| python-version: "3.11" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: 'pip' | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable | |
| with: | |
| toolchain: stable | |
| - name: Cache Rust dependencies | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| bson/_rbson/target/ | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('bson/_rbson/Cargo.lock') }} | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install maturin pytest | |
| - name: Build C extension | |
| run: | | |
| pip install setuptools | |
| python _setup.py build_ext -i | |
| - name: Build Rust extension | |
| run: | | |
| cd bson/_rbson | |
| bash build.sh | |
| - name: Verify both extensions are available | |
| run: | | |
| python -c " | |
| import bson | |
| print(f'C extension available: {bson.has_c()}') | |
| print(f'Rust extension available: {bson.has_rust()}') | |
| assert bson.has_c(), 'C extension should be available' | |
| assert bson.has_rust(), 'Rust extension should be available' | |
| " | |
| - name: Smoke test - C extension (default) | |
| run: | | |
| python -c " | |
| import bson | |
| assert bson.get_bson_implementation() == 'c', 'Should default to C' | |
| data = {'test': 'c_extension', 'value': 42} | |
| encoded = bson.encode(data) | |
| decoded = bson.decode(encoded) | |
| assert decoded == data, 'C extension encode/decode failed' | |
| print('C extension smoke test passed') | |
| " | |
| - name: Smoke test - Rust extension | |
| env: | |
| PYMONGO_USE_RUST: "1" | |
| run: | | |
| python -c " | |
| import bson | |
| assert bson.get_bson_implementation() == 'rust', 'Should use Rust' | |
| data = {'test': 'rust_extension', 'value': 99, 'nested': {'key': 'value'}} | |
| encoded = bson.encode(data) | |
| decoded = bson.decode(encoded) | |
| assert decoded == data, 'Rust extension encode/decode failed' | |
| print('Rust extension smoke test passed') | |
| " | |
| - name: Run BSON test suite with C extension (baseline) | |
| run: | | |
| python -m unittest test.test_bson.TestBSON -v | |
| - name: Run BSON test suite with Rust extension | |
| env: | |
| PYMONGO_USE_RUST: "1" | |
| run: | | |
| python -m unittest test.test_bson.TestBSON -v | |
| - name: Test cross-compatibility (C → Rust) | |
| run: | | |
| python -c " | |
| import os | |
| import sys | |
| # Encode with C | |
| import bson as bson_c | |
| assert bson_c.get_bson_implementation() == 'c' | |
| data = {'cross': 'compatibility', 'test': True} | |
| c_encoded = bson_c.encode(data) | |
| # Decode with Rust - preserve extension modules | |
| os.environ['PYMONGO_USE_RUST'] = '1' | |
| # Save extension modules before clearing | |
| _cbson = sys.modules.get('bson._cbson') | |
| _rbson = sys.modules.get('bson._rbson') | |
| # Clear bson modules except extensions | |
| for key in list(sys.modules.keys()): | |
| if key.startswith('bson') and not key.endswith(('_cbson', '_rbson')): | |
| del sys.modules[key] | |
| # Restore extension modules | |
| if _cbson: | |
| sys.modules['bson._cbson'] = _cbson | |
| if _rbson: | |
| sys.modules['bson._rbson'] = _rbson | |
| import bson as bson_rust | |
| assert bson_rust.get_bson_implementation() == 'rust' | |
| rust_decoded = bson_rust.decode(c_encoded) | |
| assert rust_decoded == data, 'Cross-compatibility failed' | |
| print('C to Rust cross-compatibility works') | |
| " | |
| - name: Run performance benchmark | |
| run: | | |
| FASTBENCH=1 python test/performance/perf_test.py TestRustSimpleIntEncodingC TestRustSimpleIntEncodingRust TestRustMixedTypesEncodingC TestRustMixedTypesEncodingRust -v |