From 47be09151b0e23b27dd236b0396bbde50052f2f9 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> Date: Wed, 4 Dec 2024 22:25:18 -0500 Subject: [PATCH] Run tests and lint for Rust code in CI. (#1289) Run tools like `clippy`, `cargo fmt`, `cargo doc`, and tests over our Rust code in CI. --- .github/workflows/rust_test.yml | 114 ++++++++++++++++++ rust/crates/langsmith-pyo3/src/py_run.rs | 42 ++++--- .../benches/json_serialization_benchmark.rs | 4 +- 3 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/rust_test.yml diff --git a/.github/workflows/rust_test.yml b/.github/workflows/rust_test.yml new file mode 100644 index 000000000..fd0cb0911 --- /dev/null +++ b/.github/workflows/rust_test.yml @@ -0,0 +1,114 @@ +name: Run Rust CI + +# Our Rust code depends on the vendored `orjson` and `pyo3` workspaces, +# so ensure that CI re-runs if they are modified. +on: + push: + branches: + - main + paths: + - "rust/**" + - "vendor/orjson/**" + - "vendor/pyo3/**" + - ".github/workflows/rust_test.yml" + pull_request: + paths: + - "rust/**" + - "vendor/orjson/**" + - "vendor/pyo3/**" + - ".github/workflows/rust_test.yml" + workflow_dispatch: + +permissions: + contents: read + +env: + RUST_VERSION: '1.82' # Be careful, "stable" gets you "whatever GitHub ships", which is quite old. + RUST_WORKSPACE_PATH: 'rust' # The location of the Rust workspace relative to the repo root. + +jobs: + lint: + name: Check lint and rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "${{env.RUST_VERSION}}" + components: rustfmt, clippy + cache-workspaces: "${{env.RUST_WORKSPACE_PATH}} -> target" + rustflags: "" + + - name: cargo clippy + working-directory: ${{env.RUST_WORKSPACE_PATH}} + run: cargo clippy --workspace --all-targets --all-features --no-deps -- -D warnings --allow deprecated + + - name: cargo fmt + working-directory: ${{env.RUST_WORKSPACE_PATH}} + run: cargo fmt -- --check + + - name: cargo doc + working-directory: ${{env.RUST_WORKSPACE_PATH}} + env: + RUSTDOCFLAGS: -D warnings + run: cargo doc --workspace --no-deps --document-private-items + cargo-test: + name: Run `cargo test` + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "${{env.RUST_VERSION}}" + cache-workspaces: "${{env.RUST_WORKSPACE_PATH}} -> target" + rustflags: "" + + - name: cargo test + working-directory: ${{env.RUST_WORKSPACE_PATH}} + run: | + set -euxo pipefail + + # We exclude `langsmith-pyo3` since its tests: + # - Have to be run under `nextest`, since they require one-process-per-test. + # - Require a non-default configuration in order to run. + cargo test --workspace --exclude langsmith-pyo3 --all-features + + langsmith-pyo3-tests: + name: Run langsmith-pyo3 tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "${{env.RUST_VERSION}}" + cache-workspaces: "${{env.RUST_WORKSPACE_PATH}} -> target" + rustflags: "" + + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + + - name: cargo test + working-directory: "${{env.RUST_WORKSPACE_PATH}}/crates/langsmith-pyo3" + run: | + set -euxo pipefail + + # See the langsmith-pyo3 README.md file for an explanation + # of why tests have to run under nextest & without default features. + cargo nextest run --no-default-features diff --git a/rust/crates/langsmith-pyo3/src/py_run.rs b/rust/crates/langsmith-pyo3/src/py_run.rs index b193a5d67..9133370fa 100644 --- a/rust/crates/langsmith-pyo3/src/py_run.rs +++ b/rust/crates/langsmith-pyo3/src/py_run.rs @@ -118,10 +118,13 @@ impl FromPyObject<'_> for RunCreate { let run_type = value.get_item(pyo3::intern!(value.py(), "run_type"))?.extract::()?; - let reference_example_id = extract_string_like_or_none(get_optional_value_from_mapping( - value, - pyo3::intern!(value.py(), "reference_example_id"), - ).as_ref())?; + let reference_example_id = extract_string_like_or_none( + get_optional_value_from_mapping( + value, + pyo3::intern!(value.py(), "reference_example_id"), + ) + .as_ref(), + )?; Ok(Self(langsmith_tracing_client::client::RunCreate { common, @@ -151,17 +154,16 @@ impl FromPyObject<'_> for RunCommon { extract_string_like(&value.get_item(pyo3::intern!(value.py(), "trace_id"))?)?; let dotted_order = value.get_item(pyo3::intern!(value.py(), "dotted_order"))?.extract()?; - let parent_run_id = extract_string_like_or_none(get_optional_value_from_mapping( - value, - pyo3::intern!(value.py(), "parent_run_id"), - ).as_ref())?; + let parent_run_id = extract_string_like_or_none( + get_optional_value_from_mapping(value, pyo3::intern!(value.py(), "parent_run_id")) + .as_ref(), + )?; let extra = extract_optional_value_from_mapping(value, pyo3::intern!(value.py(), "extra"))?; - let error = extract_string_like_or_none(get_optional_value_from_mapping( - value, - pyo3::intern!(value.py(), "error"), - ).as_ref())?; + let error = extract_string_like_or_none( + get_optional_value_from_mapping(value, pyo3::intern!(value.py(), "error")).as_ref(), + )?; let serialized = extract_optional_value_from_mapping(value, pyo3::intern!(value.py(), "serialized"))?; @@ -169,14 +171,14 @@ impl FromPyObject<'_> for RunCommon { extract_optional_value_from_mapping(value, pyo3::intern!(value.py(), "events"))?; let tags = extract_optional_value_from_mapping(value, pyo3::intern!(value.py(), "tags"))?; - let session_id = extract_string_like_or_none(get_optional_value_from_mapping( - value, - pyo3::intern!(value.py(), "session_id"), - ).as_ref())?; - let session_name = extract_string_like_or_none(get_optional_value_from_mapping( - value, - pyo3::intern!(value.py(), "session_name"), - ).as_ref())?; + let session_id = extract_string_like_or_none( + get_optional_value_from_mapping(value, pyo3::intern!(value.py(), "session_id")) + .as_ref(), + )?; + let session_name = extract_string_like_or_none( + get_optional_value_from_mapping(value, pyo3::intern!(value.py(), "session_name")) + .as_ref(), + )?; Ok(Self(langsmith_tracing_client::client::RunCommon { id, diff --git a/rust/crates/langsmith-tracing-client/benches/json_serialization_benchmark.rs b/rust/crates/langsmith-tracing-client/benches/json_serialization_benchmark.rs index 6e419529d..3f61adc35 100644 --- a/rust/crates/langsmith-tracing-client/benches/json_serialization_benchmark.rs +++ b/rust/crates/langsmith-tracing-client/benches/json_serialization_benchmark.rs @@ -93,7 +93,9 @@ fn benchmark_gzip_only_parallel(data: &Vec>) -> Vec> { // into par iter fn benchmark_json_only_parallel(data: &[Value]) -> Vec> { - data.par_iter().map(|json| serde_json::to_vec(json).expect("Failed to serialize JSON")).collect() + data.par_iter() + .map(|json| serde_json::to_vec(json).expect("Failed to serialize JSON")) + .collect() } fn json_benchmark_large_array(c: &mut Criterion) {