diff --git a/.github/workflows/_c_cpp.yaml b/.github/workflows/_c_cpp.yaml index 4dca150..1286cda 100644 --- a/.github/workflows/_c_cpp.yaml +++ b/.github/workflows/_c_cpp.yaml @@ -4,17 +4,16 @@ on: workflow_call: inputs: os: - description: 'Operating system to run on' + description: "Operating system to run on" required: false type: string - default: 'ubuntu-latest' + default: "ubuntu-latest" -jobs: +jobs: # ---------------------------------------------------------------------------- # Build C and C++ Package # ---------------------------------------------------------------------------- - # ---------------------------------------------------------------------------- # Test C # ---------------------------------------------------------------------------- @@ -37,7 +36,7 @@ jobs: path: ~/.cache/bazel key: bazel-${{ runner.os }} restore-keys: bazel-${{ runner.os }} - + - name: Run C Example (Bazel) run: | bazel run //examples/c:c_example @@ -84,10 +83,12 @@ jobs: path: ~/.cache/bazel key: bazel-${{ runner.os }} restore-keys: bazel-${{ runner.os }} - + - name: Run C++ Example (Bazel) - run: | - bazel run //examples/cpp:cpp_example + run: bazel run //examples/cpp:cpp_example + + - name: Run C++ Regression Tests + run: bazel test //tests:regression_cpp_tests --test_output=errors --keep_going test-cpp-cmake: name: Test C++ Bindings with CMake @@ -113,4 +114,4 @@ jobs: # Publish C/C++ Package # ---------------------------------------------------------------------------- - # No publish step for C/C++ packages + # No publish step for C/C++ packages diff --git a/.github/workflows/_python.yaml b/.github/workflows/_python.yaml index 52301d1..3910ca2 100644 --- a/.github/workflows/_python.yaml +++ b/.github/workflows/_python.yaml @@ -19,13 +19,29 @@ jobs: matrix: platform: # Linux - - { os: linux, runner: ubuntu-latest, target: x86_64-unknown-linux-gnu } # x86 64-bit - - { os: linux, runner: ubuntu-latest, target: aarch64-unknown-linux-gnu } # Arm 64-bit - - { os: linux, runner: ubuntu-latest, target: armv7-unknown-linux-gnueabihf } # Arm 32-bit + - { + os: linux, + runner: ubuntu-latest, + target: x86_64-unknown-linux-gnu, + } # x86 64-bit + - { + os: linux, + runner: ubuntu-latest, + target: aarch64-unknown-linux-gnu, + } # Arm 64-bit + - { + os: linux, + runner: ubuntu-latest, + target: armv7-unknown-linux-gnueabihf, + } # Arm 32-bit # macOS - - { os: macos, runner: macos-latest, target: universal2-apple-darwin } # MacOS Universal + - { os: macos, runner: macos-latest, target: universal2-apple-darwin } # MacOS Universal # Windows - - { os: windows, runner: windows-latest, target: x86_64-pc-windows-msvc } # x86 64-bit + - { + os: windows, + runner: windows-latest, + target: x86_64-pc-windows-msvc, + } # x86 64-bit steps: - name: Checkout repository uses: actions/checkout@v4 @@ -35,7 +51,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.12' + python-version: "3.12" - name: Build wheels uses: PyO3/maturin-action@v1 @@ -50,7 +66,6 @@ jobs: name: wheels-${{ matrix.platform.os }}-${{ matrix.platform.target }} path: python/dist/*.whl - # ---------------------------------------------------------------------------- # Test Python Bindings # ---------------------------------------------------------------------------- @@ -81,9 +96,11 @@ jobs: - name: Run Python Example run: bazel run //examples/python:python_example + - name: Run Python Regression Tests + run: bazel test //tests:regression_python_tests --test_output=errors --keep_going # ---------------------------------------------------------------------------- - # Publsih Python Wheels + # Publish Python Wheels # ---------------------------------------------------------------------------- python-publish-pypi: @@ -103,4 +120,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - packages-dir: dist/ \ No newline at end of file + packages-dir: dist/ diff --git a/.github/workflows/_rust.yaml b/.github/workflows/_rust.yaml index eb2c9bb..eda98b0 100644 --- a/.github/workflows/_rust.yaml +++ b/.github/workflows/_rust.yaml @@ -4,10 +4,10 @@ on: workflow_call: inputs: os: - description: 'Operating system to run on' + description: "Operating system to run on" required: false type: string - default: 'ubuntu-latest' + default: "ubuntu-latest" secrets: CARGO_REGISTRY_TOKEN: required: true @@ -18,7 +18,7 @@ jobs: # ---------------------------------------------------------------------------- # No build step is needed for Rust in Bazel, already happened in main workflow - + # ---------------------------------------------------------------------------- # Test Rust # ---------------------------------------------------------------------------- @@ -43,8 +43,10 @@ jobs: restore-keys: bazel-${{ runner.os }} - name: Test Core - run: | - bazel test //core/... --test_output=errors --keep_going + run: bazel test //core/... --test_output=errors --keep_going + + - name: Run Regression Tests + run: bazel test //tests:regression_rust_tests --test_output=errors --keep_going # ---------------------------------------------------------------------------- # Publish Rust Crate diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 41a3a8e..1c7ccb4 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -19,7 +19,6 @@ permissions: actions: read jobs: - # ============================================================================ # General Jobs # ============================================================================ @@ -69,6 +68,29 @@ jobs: - name: Build everything run: bazel build //... --keep_going + test: + name: Test All + runs-on: ubuntu-latest + needs: build + env: + CI: true + BAZELISK_ALLOW_ENVS: Y + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: ~/.cache/bazel + key: bazel-${{ runner.os }} + restore-keys: bazel-${{ runner.os }} + + - name: Test everything + run: bazel test //... --test_output=errors --keep_going + # ============================================================================= # Language-Specific Test Jobs # ============================================================================= diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2d8c3a6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,103 @@ +# AGENTS.md - Guidance for agentic coding tools working in this repo. + +## Project Summary + +This project is a library in multiple languages for handling units and checking consistency in mathematical operations and conversions. The core functionality is implemented in Rust, with each language binding having their own folder. The entire project is managed by Bazel as a build system. + +### Common Targets (Bazel) + +- Rust core library: `//core:dv_rs` +- Rust tests: `//core:unit_tests`, `//core:units_tests`, `//core:operator_tests` +- Python test: `//python:test_dv` +- C example: `//examples/c:c_example` +- C++ example: `//examples/cpp:cpp_example` + +### Folder Structure + +- `core`: Contains the core library implemented in Rust. +- `docs`: Documentation for the project for both users and developers. +- `python`: Python bindings for the core library. +- `cpp`: C++ bindings for the core library. + +## Notes for Agents + +- If you need CI parity, mirror workflow commands in `.github/workflows/*.yaml`. + +### Code Review Guidelines + +- Focus on overall structure, design patterns, and correctness. +- Avoid nitpicking formatting or comments unless they are egregiously bad. +- Ensure adherence to coding standards and best practices. + +## Build, Test, and Lint Commands + +### Bazel (preferred for all languages) + +- Build everything: `bazel build //... --keep_going` +- Test everything: `bazel test //... --test_output=errors --keep_going` + +### Rust (core) +- Build core: `bazel build //core:dv_rs` +- Run all core tests: `bazel test //core/... --test_output=errors --keep_going` +- Run a single Rust test target: `bazel test //core:units_tests` +- Run a single Rust test function: + `bazel test //core:units_tests --test_arg=angle_conversions` + (Use Rust test name; Bazel passes args to the test binary.) +- Generate docs: `bazel build //core:docs` +- Serve docs: `bazel run //core:serve_docs` + +### Python (bindings) +- Run Python tests: `bazel test //python/... --test_output=errors --keep_going` +- Run the Python example: `bazel run //examples/python:python_example` +- Single Python test target: `bazel test //python:test_dv` +- Single Python test function (pytest -k): + `bazel test //python:test_dv --test_arg=-k --test_arg=TestMathFunctions` + +### C/C++ (bindings/examples) +- Run C example (Bazel): `bazel run //examples/c:c_example` +- Run C++ example (Bazel): `bazel run //examples/cpp:cpp_example` +- CMake demos (optional): + `cmake -S . -B build && cmake --build build --target dv_example_c dv_example_cpp` + +### Docs (Docusaurus) +- Docs live under `docs/` and are managed via Bazel + pnpm. +- Update pnpm lockfile (from `docs/`): + `bazel run -- @pnpm --dir $PWD install --lockfile-only` + +### Linting / Formatting +- No explicit lint or formatter config is enforced in this repo (yet) +- For Rust, follow `rustfmt`-style formatting if you need to reformat. +- For Python, follow PEP 8 style and keep imports sorted. + +## Code Style Guidelines +### Cross-cutting +- Prefer Bazel targets over ad-hoc build scripts. +- Keep changes minimal; avoid introducing new tools unless required. +- Favor clear, explicit error messages that help users of the bindings. + +### Rust (core) +- Edition: 2021 (see `core/BUILD.bazel`). +- Naming: `snake_case` for functions/modules, `CamelCase` for types. +- Error handling: return `Result<_, String>` with clear messages. +- Use `expect` with a shared `FAIL_MSG` in tests (see `core/tests`). +- Prefer explicit conversions and unit compatibility checks. +- Keep unit parsing rules documented in doc comments. +- Use `&DimensionalVariable` for operator implementations to avoid copies. +- Avoid unnecessary `return` but keep consistent with existing style. + +### Python (bindings/tests) +- Testing framework: `pytest`. +- Test classes named `TestSomething`, methods named `test_*`. +- Keep docstrings on test classes/methods for clarity. +- Import order: stdlib, third-party, local (`dv_py`), then specific symbols. +- Raise and assert `DVError` for invalid operations. + +### C/C++ (bindings) +- Headers live in `cpp/include`: + - `dv_c.h` for C API + - `dv.hpp` for C++ RAII wrapper +- CMake support is minimal; prefer Bazel for consistency. + +### Docs (Docusaurus) +- Source in `docs/src` and `docs/docs`. +- Follow existing structure and avoid large refactors for small changes. diff --git a/MODULE.bazel b/MODULE.bazel index 1946d4a..8faafc4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,6 +16,7 @@ bazel_dep(name = "rules_cc", version = "0.2.8") bazel_dep(name = "rules_python", version = "1.6.3") bazel_dep(name = "rules_uv", version = "0.88.0") bazel_dep(name = "aspect_rules_js", version = "2.6.0") +bazel_dep(name = "nlohmann_json", version = "3.11.3") # ============================================================================= # Rust Setup @@ -25,6 +26,15 @@ rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") rust.toolchain(edition = "2024") use_repo(rust, "rust_toolchains") +# Shared Rust crates for workspace tests/utilities +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +crate.spec( + package = "serde_json", + version = "1.0", +) +crate.from_specs() +use_repo(crate, "crates") + # ============================================================================== # PyO3 Setup # ============================================================================== diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 02b43a6..8cc0053 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -60,6 +60,8 @@ "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/MODULE.bazel": "87023db2f55fc3a9949c7b08dc711fae4d4be339a80a99d04453c4bb3998eefc", + "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/source.json": "296c63a90c6813e53b3812d24245711981fc7e563d98fe15625f55181494488a", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -679,6 +681,263 @@ ] } }, + "@@rules_rust+//crate_universe:extensions.bzl%crate": { + "general": { + "bzlTransitiveDigest": "24f89aZmQ72kZSapU+kl1MImmOMesD/oSwUItDmPu/Y=", + "usagesDigest": "VtuXxCm1hBObZsgKy+1BQ5XFuJvwK5I4l6pdTRAJ9es=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": { + "CARGO_BAZEL_DEBUG": null, + "CARGO_BAZEL_GENERATOR_SHA256": null, + "CARGO_BAZEL_GENERATOR_URL": null, + "CARGO_BAZEL_ISOLATED": null, + "CARGO_BAZEL_REPIN": null, + "CARGO_BAZEL_REPIN_ONLY": null, + "CARGO_BAZEL_TIMEOUT": null, + "REPIN": null + }, + "generatedRepoSpecs": { + "crates": { + "repoRuleId": "@@rules_rust+//crate_universe:extensions.bzl%_generate_repo", + "attributes": { + "contents": { + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"serde_json-1.0.149\",\n actual = \"@crates__serde_json-1.0.149//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json\",\n actual = \"@crates__serde_json-1.0.149//:serde_json\",\n tags = [\"manual\"],\n)\n", + "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"\": {\n _COMMON_CONDITION: {\n \"serde_json\": Label(\"@crates//:serde_json-1.0.149\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(any())\": [],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"crates__itoa-1.0.17\",\n sha256 = \"92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itoa/1.0.17/download\"],\n strip_prefix = \"itoa-1.0.17\",\n build_file = Label(\"@crates//crates:BUILD.itoa-1.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@crates//crates:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@crates//crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__quote-1.0.44\",\n sha256 = \"21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.44/download\"],\n strip_prefix = \"quote-1.0.44\",\n build_file = Label(\"@crates//crates:BUILD.quote-1.0.44.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@crates//crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__serde_json-1.0.149\",\n sha256 = \"83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json/1.0.149/download\"],\n strip_prefix = \"serde_json-1.0.149\",\n build_file = Label(\"@crates//crates:BUILD.serde_json-1.0.149.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@crates//crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@crates//crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__zmij-1.0.21\",\n sha256 = \"b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zmij/1.0.21/download\"],\n strip_prefix = \"zmij-1.0.21\",\n build_file = Label(\"@crates//crates:BUILD.zmij-1.0.21.bazel\"),\n )\n\n return [\n struct(repo=\"crates__serde_json-1.0.149\", is_dev_dep = False),\n ]\n" + } + } + }, + "crates__itoa-1.0.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itoa/1.0.17/download" + ], + "strip_prefix": "itoa-1.0.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itoa\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itoa\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.17\",\n)\n" + } + }, + "crates__memchr-2.8.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/memchr/2.8.0/download" + ], + "strip_prefix": "memchr-2.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.8.0\",\n)\n" + } + }, + "crates__proc-macro2-1.0.106": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro2/1.0.106/download" + ], + "strip_prefix": "proc-macro2-1.0.106", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro2\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:build_script_build\",\n \"@crates__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.106\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"proc-macro2\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.106\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__quote-1.0.44": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quote/1.0.44/download" + ], + "strip_prefix": "quote-1.0.44", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quote\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.44//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.44\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"quote\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.44\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__serde-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde/1.0.228/download" + ], + "strip_prefix": "serde-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde\",\n deps = [\n \"@crates__serde-1.0.228//:build_script_build\",\n \"@crates__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__serde_core-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_core/1.0.228/download" + ], + "strip_prefix": "serde_core-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_core\",\n deps = [\n \"@crates__serde_core-1.0.228//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__serde_derive-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_derive/1.0.228/download" + ], + "strip_prefix": "serde_derive-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"serde_derive\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.44//:quote\",\n \"@crates__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n" + } + }, + "crates__serde_json-1.0.149": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_json/1.0.149/download" + ], + "strip_prefix": "serde_json-1.0.149", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_json\",\n deps = [\n \"@crates__itoa-1.0.17//:itoa\",\n \"@crates__memchr-2.8.0//:memchr\",\n \"@crates__serde_core-1.0.228//:serde_core\",\n \"@crates__serde_json-1.0.149//:build_script_build\",\n \"@crates__zmij-1.0.21//:zmij\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.149\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_json\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.149\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__syn-2.0.117": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/syn/2.0.117/download" + ], + "strip_prefix": "syn-2.0.117", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syn\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.44//:quote\",\n \"@crates__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"derive\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.117\",\n)\n" + } + }, + "crates__unicode-ident-1.0.24": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-ident/1.0.24/download" + ], + "strip_prefix": "unicode-ident-1.0.24", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_ident\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-ident\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.24\",\n)\n" + } + }, + "crates__zmij-1.0.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zmij/1.0.21/download" + ], + "strip_prefix": "zmij-1.0.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'dv'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zmij\",\n deps = [\n \"@crates__zmij-1.0.21//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zmij\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.21\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"zmij\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zmij\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.21\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + } + }, + "moduleExtensionMetadata": { + "useAllRepos": "NO", + "reproducible": false + }, + "recordedRepoMappingEntries": [ + [ + "bazel_features+", + "bazel_features_globals", + "bazel_features++version_extension+bazel_features_globals" + ], + [ + "bazel_features+", + "bazel_features_version", + "bazel_features++version_extension+bazel_features_version" + ], + [ + "rules_cc+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_cc+", + "rules_cc", + "rules_cc+" + ], + [ + "rules_rust+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_rust+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "rules_rust+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_rust+", + "rules_cc", + "rules_cc+" + ], + [ + "rules_rust+", + "rules_rust", + "rules_rust+" + ] + ] + } + }, "@@rules_rust_pyo3+//private:internal_extensions.bzl%rust_ext": { "general": { "bzlTransitiveDigest": "mplWnTye6O3jkHSbTUhWt4BLjkwzs8HGGqF011UFz8Q=", diff --git a/VERSION.toml b/VERSION.toml index 922ae62..0751cca 100644 --- a/VERSION.toml +++ b/VERSION.toml @@ -6,4 +6,4 @@ major = 0 minor = 3 -patch = 2 +patch = 3 diff --git a/core/BUILD.bazel b/core/BUILD.bazel index ce25f3b..3f166d0 100644 --- a/core/BUILD.bazel +++ b/core/BUILD.bazel @@ -69,4 +69,4 @@ filegroup( name = "rust_sources", srcs = glob(["**/*.rs", "**/*.toml", "**/*.md"]), visibility = ["//python:__pkg__"], -) \ No newline at end of file +) diff --git a/core/Cargo.toml b/core/Cargo.toml index cc7a52b..17620b7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dv" -version = "0.3.2" +version = "0.3.3" edition = "2021" authors = [ "Alex Tacescu ",] description = "Core Rust library for DimensionalVariable, a multi-language library for handling physical quantities with units." diff --git a/cpp/capi/src/lib.rs b/cpp/capi/src/lib.rs index d7ee198..3e82d3a 100644 --- a/cpp/capi/src/lib.rs +++ b/cpp/capi/src/lib.rs @@ -192,6 +192,82 @@ pub extern "C" fn dv_var_sqrt(a: *const dv_var) -> *mut dv_var { } } +#[no_mangle] +pub extern "C" fn dv_var_ln(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.ln() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_log2(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.log2() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_log10(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.log10() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_sin(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.sin() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_cos(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.cos() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_tan(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + match a.inner.tan() { + Ok(v) => Box::into_raw(Box::new(dv_var { inner: DimensionalVariable { value: v, unit: [0.0; units::BASE_UNITS_SIZE] } })), + Err(e) => { set_last_error(e); ptr::null_mut() } + } +} + +#[no_mangle] +pub extern "C" fn dv_var_neg(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + let v = a.inner.neg(); + Box::into_raw(Box::new(dv_var { inner: v })) +} + +#[no_mangle] +pub extern "C" fn dv_var_abs(a: *const dv_var) -> *mut dv_var { + if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } + let a = unsafe { &(*a) }; + let v = a.inner.abs(); + Box::into_raw(Box::new(dv_var { inner: v })) +} + #[no_mangle] pub extern "C" fn dv_var_asin(a: *const dv_var) -> *mut dv_var { if a.is_null() { set_last_error("null operand".to_string()); return ptr::null_mut(); } diff --git a/cpp/include/dv.hpp b/cpp/include/dv.hpp index d84986c..4215618 100644 --- a/cpp/include/dv.hpp +++ b/cpp/include/dv.hpp @@ -51,6 +51,14 @@ class DV { DV powi(int e) const { return from_new(dv_var_powi(cptr(), e)); } DV powf(double e) const { return from_new(dv_var_powf(cptr(), e)); } DV sqrt() const { return from_new(dv_var_sqrt(cptr())); } + DV ln() const { return from_new(dv_var_ln(cptr())); } + DV log2() const { return from_new(dv_var_log2(cptr())); } + DV log10() const { return from_new(dv_var_log10(cptr())); } + DV sin() const { return from_new(dv_var_sin(cptr())); } + DV cos() const { return from_new(dv_var_cos(cptr())); } + DV tan() const { return from_new(dv_var_tan(cptr())); } + DV abs() const { return from_new(dv_var_abs(cptr())); } + friend DV operator-(const DV& a) { return from_new(dv_var_neg(a.cptr())); } /// Convert to string representation (e.g., "9.81 m/s^2") std::string to_string() const { diff --git a/cpp/include/dv_c.h b/cpp/include/dv_c.h index 376c169..e19af48 100644 --- a/cpp/include/dv_c.h +++ b/cpp/include/dv_c.h @@ -40,6 +40,14 @@ dv_var* dv_var_div_scalar(const dv_var* a, double s); dv_var* dv_var_powi(const dv_var* a, int exp); dv_var* dv_var_powf(const dv_var* a, double exp); dv_var* dv_var_sqrt(const dv_var* a); +dv_var* dv_var_ln(const dv_var* a); +dv_var* dv_var_log2(const dv_var* a); +dv_var* dv_var_log10(const dv_var* a); +dv_var* dv_var_sin(const dv_var* a); +dv_var* dv_var_cos(const dv_var* a); +dv_var* dv_var_tan(const dv_var* a); +dv_var* dv_var_neg(const dv_var* a); +dv_var* dv_var_abs(const dv_var* a); // Inverse trigonometric functions (return angle in radians) dv_var* dv_var_asin(const dv_var* a); diff --git a/python/BUILD.bazel b/python/BUILD.bazel index c8bbbb4..0fe9c52 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -43,4 +43,3 @@ py_test( "@pypi//pytest:pkg", ], ) - diff --git a/python/Cargo.toml b/python/Cargo.toml index bc7c4d6..0e52946 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dv_py" -version = "0.3.2" +version = "0.3.3" edition = "2021" authors = [ "Alex Tacescu ",] homepage = "https://dv.alextac.com" diff --git a/python/pyproject.toml b/python/pyproject.toml index f5d38bb..ffa6155 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "dv_py" -version = "0.3.2" +version = "0.3.3" description = "Python bindings for dv (DimensionalVariable) - keeping track of units and dimensions for physical quantities" readme = "pip-readme.md" requires-python = ">=3.8" diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 0000000..7477001 --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") +load("@rules_python//python:defs.bzl", "py_test") +load("@rules_rust//rust:defs.bzl", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "schema.json", + "units_tests.json", + "math_tests.json", +]) + +filegroup( + name = "regression_vectors", + srcs = [ + "units_tests.json", + "math_tests.json", + ], +) + +rust_test( + name = "regression_rust_tests", + srcs = ["regression_rust_tests.rs"], + data = [":regression_vectors"], + deps = [ + "//core:dv_rs", + "@crates//:serde_json", + ], +) + +py_test( + name = "regression_python_tests", + srcs = ["regression_python_tests.py"], + data = [":regression_vectors"], + deps = [ + "//python:dv_py", + "@pypi//pytest:pkg", + ], +) + +cc_test( + name = "regression_cpp_tests", + srcs = ["regression_cpp_tests.cpp"], + data = [":regression_vectors"], + deps = [ + "//cpp:dv_cpp", + "@nlohmann_json//:json", + ], +) + +test_suite( + name = "regression_tests", + tests = [ + ":regression_rust_tests", + ":regression_python_tests", + ":regression_cpp_tests", + ], +) diff --git a/tests/math_tests.json b/tests/math_tests.json new file mode 100644 index 0000000..e6028e7 --- /dev/null +++ b/tests/math_tests.json @@ -0,0 +1,137 @@ +{ + "suite": "math_regression_tests", + "category": "math", + "defaults": { + "abs_tol": 1e-10, + "rel_tol": 1e-10 + }, + "cases": [ + { + "id": "arith.add_length", + "op": "add", + "left": {"value": 2.0, "unit": "m"}, + "right": {"value": 30.0, "unit": "cm"}, + "expect": {"ok": true, "value": 2.3, "unit": "m"} + }, + { + "id": "arith.sub_length", + "op": "sub", + "left": {"value": 10.0, "unit": "m"}, + "right": {"value": 2.0, "unit": "m"}, + "expect": {"ok": true, "value": 8.0, "unit": "m"} + }, + { + "id": "arith.mul_velocity_time", + "op": "mul", + "left": {"value": 3.0, "unit": "m/s"}, + "right": {"value": 2.0, "unit": "s"}, + "expect": {"ok": true, "value": 6.0, "unit": "m"} + }, + { + "id": "arith.div_distance_time", + "op": "div", + "left": {"value": 100.0, "unit": "m"}, + "right": {"value": 5.0, "unit": "s"}, + "expect": {"ok": true, "value": 20.0, "unit": "m/s"} + }, + { + "id": "arith.add_incompatible", + "op": "add", + "left": {"value": 2.0, "unit": "m"}, + "right": {"value": 1.0, "unit": "s"}, + "expect": {"ok": false} + }, + { + "id": "math.powi_area", + "op": "powi", + "input": {"value": 3.0, "unit": "m"}, + "exponent": 2, + "expect": {"ok": true, "value": 9.0, "unit": "m^2"} + }, + { + "id": "math.powf_root", + "op": "powf", + "input": {"value": 4.0, "unit": "m^2"}, + "exponent": 0.5, + "expect": {"ok": true, "value": 2.0, "unit": "m"} + }, + { + "id": "math.sqrt", + "op": "sqrt", + "input": {"value": 16.0, "unit": "m^2"}, + "expect": {"ok": true, "value": 4.0, "unit": "m"} + }, + { + "id": "math.ln_unitless", + "op": "ln", + "input": {"value": 2.718281828459045, "unit": ""}, + "expect": {"ok": true, "value": 1.0, "unit": ""} + }, + { + "id": "math.ln_with_units_fails", + "op": "ln", + "input": {"value": 10.0, "unit": "m"}, + "expect": {"ok": false} + }, + { + "id": "math.log2_unitless", + "op": "log2", + "input": {"value": 8.0, "unit": ""}, + "expect": {"ok": true, "value": 3.0, "unit": ""} + }, + { + "id": "math.log10_unitless", + "op": "log10", + "input": {"value": 100.0, "unit": ""}, + "expect": {"ok": true, "value": 2.0, "unit": ""} + }, + { + "id": "math.sin_angle", + "op": "sin", + "input": {"value": 1.5707963267948966, "unit": "rad"}, + "expect": {"ok": true, "value": 1.0, "unit": ""} + }, + { + "id": "math.cos_angle", + "op": "cos", + "input": {"value": 0.0, "unit": "rad"}, + "expect": {"ok": true, "value": 1.0, "unit": ""} + }, + { + "id": "math.tan_angle", + "op": "tan", + "input": {"value": 0.7853981633974483, "unit": "rad"}, + "expect": {"ok": true, "value": 1.0, "unit": ""} + }, + { + "id": "math.asin", + "op": "asin", + "input": {"value": 0.5, "unit": ""}, + "expect": {"ok": true, "value": 0.5235987755982989, "unit": "rad"} + }, + { + "id": "math.acos", + "op": "acos", + "input": {"value": 0.5, "unit": ""}, + "expect": {"ok": true, "value": 1.0471975511965979, "unit": "rad"} + }, + { + "id": "math.atan", + "op": "atan", + "input": {"value": 1.0, "unit": ""}, + "expect": {"ok": true, "value": 0.7853981633974483, "unit": "rad"} + }, + { + "id": "math.neg", + "op": "neg", + "input": {"value": 5.0, "unit": "m"}, + "expect": {"ok": true, "value": -5.0, "unit": "m"} + }, + { + "id": "math.abs", + "op": "abs", + "input": {"value": -5.0, "unit": "m"}, + "expect": {"ok": true, "value": 5.0, "unit": "m"} + } + ] +} diff --git a/tests/regression_cpp_tests.cpp b/tests/regression_cpp_tests.cpp new file mode 100644 index 0000000..9d7de01 --- /dev/null +++ b/tests/regression_cpp_tests.cpp @@ -0,0 +1,317 @@ +#include "dv.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace fs = std::filesystem; +using json = nlohmann::json; + +bool nearly_equal(double actual, double expected, double abs_tol, double rel_tol) { + double diff = std::fabs(actual - expected); + return diff <= std::max(abs_tol, rel_tol * std::fabs(expected)); +} + +fs::path resolve_vector_path(const std::string& file_name) { + std::vector candidates; + + const char* test_srcdir = std::getenv("TEST_SRCDIR"); + const char* test_workspace = std::getenv("TEST_WORKSPACE"); + if (test_srcdir != nullptr) { + std::string workspace = test_workspace != nullptr ? test_workspace : "dv"; + candidates.push_back(fs::path(test_srcdir) / workspace / "tests" / file_name); + } + + candidates.push_back(fs::current_path() / "tests" / file_name); + candidates.push_back(fs::current_path() / file_name); + + for (const auto& p : candidates) { + if (fs::exists(p)) { + return p; + } + } + + throw std::runtime_error("could not locate tests/" + file_name); +} + +std::string read_file(const fs::path& path) { + std::ifstream in(path); + if (!in) throw std::runtime_error("failed to open " + path.string()); + return std::string((std::istreambuf_iterator(in)), std::istreambuf_iterator()); +} + +std::string require_string(const json& obj, const char* key, const std::string& ctx) { + if (!obj.contains(key) || !obj[key].is_string()) { + throw std::runtime_error(ctx + ": missing or invalid string key " + key); + } + return obj[key].get(); +} + +double require_number(const json& obj, const char* key, const std::string& ctx) { + if (!obj.contains(key) || !obj[key].is_number()) { + throw std::runtime_error(ctx + ": missing or invalid number key " + key); + } + return obj[key].get(); +} + +bool require_bool(const json& obj, const char* key, const std::string& ctx) { + if (!obj.contains(key) || !obj[key].is_boolean()) { + throw std::runtime_error(ctx + ": missing or invalid bool key " + key); + } + return obj[key].get(); +} + +void assert_expected_dv(const std::string& case_id, const json& expect, const dv::DV& actual_dv, double abs_tol, double rel_tol) { + const std::string exp_unit = require_string(expect, "unit", case_id + ".expect"); + const double exp_value = require_number(expect, "value", case_id + ".expect"); + const double actual = exp_unit.empty() ? actual_dv.value() : actual_dv.value_in(exp_unit.c_str()); + if (!nearly_equal(actual, exp_value, abs_tol, rel_tol)) { + throw std::runtime_error(case_id + ": expected " + std::to_string(exp_value) + ", got " + std::to_string(actual)); + } +} + +void run_case(const json& test_case, double abs_tol, double rel_tol) { + if (!test_case.is_object()) throw std::runtime_error("case must be object"); + + const std::string case_id = require_string(test_case, "id", "case"); + const std::string op = require_string(test_case, "op", case_id); + + if (!test_case.contains("expect") || !test_case["expect"].is_object()) { + throw std::runtime_error(case_id + ": missing expect object"); + } + const json& expect = test_case["expect"]; + const bool ok = require_bool(expect, "ok", case_id + ".expect"); + + if (op == "construct") { + const json& input = test_case.at("input"); + const double value = require_number(input, "value", case_id + ".input"); + const std::string unit = require_string(input, "unit", case_id + ".input"); + + if (ok) { + dv::DV v(value, unit.c_str()); + assert_expected_dv(case_id, expect, v, abs_tol, rel_tol); + } else { + bool threw = false; + try { + dv::DV bad(value, unit.c_str()); + } catch (const std::exception&) { + threw = true; + } + if (!threw) throw std::runtime_error(case_id + ": expected construct failure"); + } + return; + } + + if (op == "convert") { + const json& input = test_case.at("input"); + const double value = require_number(input, "value", case_id + ".input"); + const std::string from_unit = require_string(input, "from_unit", case_id + ".input"); + const std::string to_unit = require_string(input, "to_unit", case_id + ".input"); + + dv::DV v(value, from_unit.c_str()); + if (ok) { + const double exp_value = require_number(expect, "value", case_id + ".expect"); + const double actual = v.value_in(to_unit.c_str()); + if (!nearly_equal(actual, exp_value, abs_tol, rel_tol)) { + throw std::runtime_error(case_id + ": expected " + std::to_string(exp_value) + ", got " + std::to_string(actual)); + } + } else { + bool threw = false; + try { + (void)v.value_in(to_unit.c_str()); + } catch (const std::exception&) { + threw = true; + } + if (!threw) throw std::runtime_error(case_id + ": expected conversion failure"); + } + return; + } + + auto build_input = [&]() -> dv::DV { + const json& input = test_case.at("input"); + const double value = require_number(input, "value", case_id + ".input"); + const std::string unit = require_string(input, "unit", case_id + ".input"); + return dv::DV(value, unit.c_str()); + }; + + if (op == "add" || op == "sub" || op == "mul" || op == "div") { + const json& left_obj = test_case.at("left"); + const json& right_obj = test_case.at("right"); + + dv::DV left( + require_number(left_obj, "value", case_id + ".left"), + require_string(left_obj, "unit", case_id + ".left").c_str() + ); + dv::DV right( + require_number(right_obj, "value", case_id + ".right"), + require_string(right_obj, "unit", case_id + ".right").c_str() + ); + + if ((op == "add" || op == "sub") && !ok) { + bool threw = false; + try { + if (op == "add") { + (void)(left + right); + } else { + (void)(left - right); + } + } catch (const std::exception&) { + threw = true; + } + if (!threw) throw std::runtime_error(case_id + ": expected arithmetic failure"); + return; + } + + dv::DV result; + if (op == "add") { + result = left + right; + } else if (op == "sub") { + result = left - right; + } else if (op == "mul") { + result = left * right; + } else { + result = left / right; + } + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + return; + } + + dv::DV input = build_input(); + + if (op == "powi") { + int exponent = static_cast(require_number(test_case, "exponent", case_id)); + dv::DV result = input.powi(exponent); + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + return; + } + + if (op == "powf") { + double exponent = require_number(test_case, "exponent", case_id); + if (ok) { + dv::DV result = input.powf(exponent); + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + } else { + bool threw = false; + try { + (void)input.powf(exponent); + } catch (const std::exception&) { + threw = true; + } + if (!threw) throw std::runtime_error(case_id + ": expected powf failure"); + } + return; + } + + auto run_unary = [&](auto fn, const std::string& fail_label) { + if (ok) { + dv::DV result = fn(); + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + } else { + bool threw = false; + try { + (void)fn(); + } catch (const std::exception&) { + threw = true; + } + if (!threw) throw std::runtime_error(case_id + ": expected " + fail_label + " failure"); + } + }; + + if (op == "sqrt") { + run_unary([&]() { return input.sqrt(); }, "sqrt"); + return; + } + if (op == "ln") { + run_unary([&]() { return input.ln(); }, "ln"); + return; + } + if (op == "log2") { + run_unary([&]() { return input.log2(); }, "log2"); + return; + } + if (op == "log10") { + run_unary([&]() { return input.log10(); }, "log10"); + return; + } + if (op == "sin") { + run_unary([&]() { return input.sin(); }, "sin"); + return; + } + if (op == "cos") { + run_unary([&]() { return input.cos(); }, "cos"); + return; + } + if (op == "tan") { + run_unary([&]() { return input.tan(); }, "tan"); + return; + } + if (op == "asin") { + run_unary([&]() { return input.asin(); }, "asin"); + return; + } + if (op == "acos") { + run_unary([&]() { return input.acos(); }, "acos"); + return; + } + if (op == "atan") { + run_unary([&]() { return input.atan(); }, "atan"); + return; + } + if (op == "neg") { + dv::DV result = -input; + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + return; + } + if (op == "abs") { + dv::DV result = input.abs(); + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + return; + } + + throw std::runtime_error(case_id + ": unsupported op " + op); +} + +void run_vector_file(const std::string& file_name) { + const fs::path path = resolve_vector_path(file_name); + const std::string content = read_file(path); + const json root = json::parse(content); + + if (!root.is_object()) { + throw std::runtime_error("root JSON must be an object"); + } + + double abs_tol = 1e-10; + double rel_tol = 1e-10; + if (root.contains("defaults") && root["defaults"].is_object()) { + const json& defaults = root["defaults"]; + abs_tol = require_number(defaults, "abs_tol", "defaults"); + rel_tol = require_number(defaults, "rel_tol", "defaults"); + } + + if (!root.contains("cases") || !root["cases"].is_array()) { + throw std::runtime_error("root.cases must be an array"); + } + + for (const auto& c : root["cases"]) { + run_case(c, abs_tol, rel_tol); + } +} + +int main() { + try { + run_vector_file("units_tests.json"); + run_vector_file("math_tests.json"); + std::cout << "Regression vectors passed for C++\n"; + return 0; + } catch (const std::exception& e) { + std::cerr << "Regression test failure: " << e.what() << "\n"; + return 1; + } +} diff --git a/tests/regression_python_tests.py b/tests/regression_python_tests.py new file mode 100644 index 0000000..18aba57 --- /dev/null +++ b/tests/regression_python_tests.py @@ -0,0 +1,241 @@ +"""Shared regression test runner for Python bindings.""" + +from __future__ import annotations + +import json +import math +import os +from pathlib import Path + +import pytest + +from dv_py import DVError, DimensionalVariable + + +DEFAULT_ABS_TOL = 1e-10 +DEFAULT_REL_TOL = 1e-10 + + +def _candidate_paths(file_name: str) -> list[Path]: + workspace = os.environ.get("TEST_WORKSPACE", "dv") + test_srcdir = os.environ.get("TEST_SRCDIR") + here = Path(__file__).resolve() + candidates = [ + here.parent / file_name, + Path.cwd() / "tests" / file_name, + Path.cwd() / file_name, + ] + if test_srcdir: + candidates.append(Path(test_srcdir) / workspace / "tests" / file_name) + return candidates + + +def _load_vectors(file_name: str) -> dict: + for path in _candidate_paths(file_name): + if path.exists(): + with path.open("r", encoding="utf-8") as f: + return json.load(f) + attempted = "\n".join(str(p) for p in _candidate_paths(file_name)) + raise RuntimeError(f"Could not locate {file_name}. Attempted:\n{attempted}") + + +def _assert_close(actual: float, expected: float, abs_tol: float, rel_tol: float, case_id: str) -> None: + if not math.isclose(actual, expected, abs_tol=abs_tol, rel_tol=rel_tol): + raise AssertionError(f"{case_id}: expected {expected}, got {actual}") + + +def _extract_dv_value(v: DimensionalVariable, unit: str) -> float: + if unit == "": + return v.value() + return v.value_in(unit) + + +def _assert_expected_dv(case_id: str, expect: dict, dv: DimensionalVariable, abs_tol: float, rel_tol: float) -> None: + actual = _extract_dv_value(dv, expect["unit"]) + _assert_close(actual, expect["value"], abs_tol, rel_tol, case_id) + + +def _build_input(case: dict) -> DimensionalVariable: + return DimensionalVariable(case["input"]["value"], case["input"]["unit"]) + + +def _run_case(case: dict, abs_tol: float, rel_tol: float) -> None: + case_id = case["id"] + op = case["op"] + expect = case["expect"] + ok = expect["ok"] + + if op == "construct": + value = case["input"]["value"] + unit = case["input"]["unit"] + if ok: + dv = DimensionalVariable(value, unit) + _assert_expected_dv(case_id, expect, dv, abs_tol, rel_tol) + else: + with pytest.raises(DVError): + DimensionalVariable(value, unit) + return + + if op == "convert": + value = case["input"]["value"] + from_unit = case["input"]["from_unit"] + to_unit = case["input"]["to_unit"] + dv = DimensionalVariable(value, from_unit) + if ok: + actual = dv.value_in(to_unit) + _assert_close(actual, expect["value"], abs_tol, rel_tol, case_id) + else: + with pytest.raises(DVError): + dv.value_in(to_unit) + return + + if op in {"add", "sub", "mul", "div"}: + left = DimensionalVariable(case["left"]["value"], case["left"]["unit"]) + right = DimensionalVariable(case["right"]["value"], case["right"]["unit"]) + + if op == "add": + if ok: + _assert_expected_dv(case_id, expect, left + right, abs_tol, rel_tol) + else: + with pytest.raises(DVError): + _ = left + right + return + + if op == "sub": + if ok: + _assert_expected_dv(case_id, expect, left - right, abs_tol, rel_tol) + else: + with pytest.raises(DVError): + _ = left - right + return + + if op == "mul": + _assert_expected_dv(case_id, expect, left * right, abs_tol, rel_tol) + return + + if op == "div": + _assert_expected_dv(case_id, expect, left / right, abs_tol, rel_tol) + return + + input_dv = _build_input(case) + + if op == "powi": + result = input_dv ** int(case["exponent"]) + _assert_expected_dv(case_id, expect, result, abs_tol, rel_tol) + return + + if op == "powf": + exponent = float(case["exponent"]) + if ok: + result = input_dv ** exponent + _assert_expected_dv(case_id, expect, result, abs_tol, rel_tol) + else: + with pytest.raises(DVError): + _ = input_dv ** exponent + return + + if op == "sqrt": + if ok: + _assert_expected_dv(case_id, expect, input_dv.sqrt(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.sqrt() + return + + if op == "ln": + if ok: + _assert_expected_dv(case_id, expect, input_dv.ln(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.ln() + return + + if op == "log2": + if ok: + _assert_expected_dv(case_id, expect, input_dv.log2(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.log2() + return + + if op == "log10": + if ok: + _assert_expected_dv(case_id, expect, input_dv.log10(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.log10() + return + + if op == "sin": + if ok: + _assert_expected_dv(case_id, expect, input_dv.sin(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.sin() + return + + if op == "cos": + if ok: + _assert_expected_dv(case_id, expect, input_dv.cos(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.cos() + return + + if op == "tan": + if ok: + _assert_expected_dv(case_id, expect, input_dv.tan(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.tan() + return + + if op == "asin": + if ok: + _assert_expected_dv(case_id, expect, input_dv.asin(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.asin() + return + + if op == "acos": + if ok: + _assert_expected_dv(case_id, expect, input_dv.acos(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.acos() + return + + if op == "atan": + if ok: + _assert_expected_dv(case_id, expect, input_dv.atan(), abs_tol, rel_tol) + else: + with pytest.raises(DVError): + input_dv.atan() + return + + if op == "neg": + _assert_expected_dv(case_id, expect, -input_dv, abs_tol, rel_tol) + return + + if op == "abs": + _assert_expected_dv(case_id, expect, abs(input_dv), abs_tol, rel_tol) + return + + raise AssertionError(f"{case_id}: unsupported op {op}") + + +def _run_file(file_name: str) -> None: + vectors = _load_vectors(file_name) + abs_tol = vectors.get("defaults", {}).get("abs_tol", DEFAULT_ABS_TOL) + rel_tol = vectors.get("defaults", {}).get("rel_tol", DEFAULT_REL_TOL) + for case in vectors["cases"]: + _run_case(case, abs_tol, rel_tol) + + +def test_units_regression_vectors() -> None: + _run_file("units_tests.json") + + +def test_math_regression_vectors() -> None: + _run_file("math_tests.json") diff --git a/tests/regression_rust_tests.rs b/tests/regression_rust_tests.rs new file mode 100644 index 0000000..234548d --- /dev/null +++ b/tests/regression_rust_tests.rs @@ -0,0 +1,307 @@ +use dv_rs::DimensionalVariable as Dv; +use serde_json::Value; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +fn as_obj<'a>(v: &'a Value, ctx: &str) -> &'a serde_json::Map { + v.as_object() + .unwrap_or_else(|| panic!("{}: expected object", ctx)) +} + +fn as_arr<'a>(v: &'a Value, ctx: &str) -> &'a [Value] { + v.as_array() + .map(|a| a.as_slice()) + .unwrap_or_else(|| panic!("{}: expected array", ctx)) +} + +fn get_str(obj: &serde_json::Map, key: &str, ctx: &str) -> String { + obj.get(key) + .and_then(Value::as_str) + .map(ToOwned::to_owned) + .unwrap_or_else(|| panic!("{}: missing or invalid string key '{}'", ctx, key)) +} + +fn get_num(obj: &serde_json::Map, key: &str, ctx: &str) -> f64 { + obj.get(key) + .and_then(Value::as_f64) + .unwrap_or_else(|| panic!("{}: missing or invalid number key '{}'", ctx, key)) +} + +fn get_bool(obj: &serde_json::Map, key: &str, ctx: &str) -> bool { + obj.get(key) + .and_then(Value::as_bool) + .unwrap_or_else(|| panic!("{}: missing or invalid bool key '{}'", ctx, key)) +} + +fn resolve_vector_path(file_name: &str) -> PathBuf { + let workspace = env::var("TEST_WORKSPACE").unwrap_or_else(|_| "dv".to_string()); + let candidates = [ + env::var("TEST_SRCDIR") + .ok() + .map(|v| Path::new(&v).join(&workspace).join("tests").join(file_name)), + Some(Path::new("tests").join(file_name)), + Some(Path::new(file_name).to_path_buf()), + ]; + for c in candidates.into_iter().flatten() { + if c.exists() { + return c; + } + } + panic!("could not locate tests/{}", file_name) +} + +fn nearly_eq(actual: f64, expected: f64, abs_tol: f64, rel_tol: f64) -> bool { + let diff = (actual - expected).abs(); + diff <= abs_tol.max(rel_tol * expected.abs()) +} + +fn assert_expected_dv(case_id: &str, expect: &serde_json::Map, dv: &Dv, abs_tol: f64, rel_tol: f64) { + let exp_value = get_num(expect, "value", case_id); + let exp_unit = get_str(expect, "unit", case_id); + let actual = if exp_unit.is_empty() { + dv.value() + } else { + dv.value_in(&exp_unit) + .unwrap_or_else(|e| panic!("{}: value_in failed: {}", case_id, e)) + }; + assert!( + nearly_eq(actual, exp_value, abs_tol, rel_tol), + "{}: expected {}, got {}", + case_id, + exp_value, + actual + ); +} + +fn assert_expected_scalar(case_id: &str, expect: &serde_json::Map, actual: f64, abs_tol: f64, rel_tol: f64) { + let exp_value = get_num(expect, "value", case_id); + let exp_unit = get_str(expect, "unit", case_id); + assert!(exp_unit.is_empty(), "{}: scalar operations must expect unit ''", case_id); + assert!( + nearly_eq(actual, exp_value, abs_tol, rel_tol), + "{}: expected {}, got {}", + case_id, + exp_value, + actual + ); +} + +fn build_input_dv(case_obj: &serde_json::Map, case_id: &str) -> Dv { + let input = as_obj(case_obj.get("input").expect("missing input"), case_id); + let value = get_num(input, "value", case_id); + let unit = get_str(input, "unit", case_id); + Dv::new(value, &unit).unwrap_or_else(|e| panic!("{}: failed to construct input: {}", case_id, e)) +} + +fn run_vectors(file_name: &str) { + let path = resolve_vector_path(file_name); + let raw = fs::read_to_string(path).expect("failed to read vector file"); + let root: Value = serde_json::from_str(&raw).expect("invalid JSON in vector file"); + let root_obj = as_obj(&root, "root"); + + let defaults = root_obj.get("defaults").map(|v| as_obj(v, "defaults")); + let abs_tol = defaults.map(|d| get_num(d, "abs_tol", "defaults")).unwrap_or(1e-10); + let rel_tol = defaults.map(|d| get_num(d, "rel_tol", "defaults")).unwrap_or(1e-10); + + let cases = as_arr(root_obj.get("cases").expect("missing cases"), "cases"); + + for case_v in cases { + let case = as_obj(case_v, "case"); + let case_id = get_str(case, "id", "case"); + let op = get_str(case, "op", &case_id); + let expect_obj = as_obj(case.get("expect").expect("missing expect"), &case_id); + let ok = get_bool(expect_obj, "ok", &case_id); + + match op.as_str() { + "construct" => { + let input = as_obj(case.get("input").expect("missing input"), &case_id); + let value = get_num(input, "value", &case_id); + let unit = get_str(input, "unit", &case_id); + if ok { + let dv = Dv::new(value, &unit) + .unwrap_or_else(|e| panic!("{}: expected success, got error {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &dv, abs_tol, rel_tol); + } else { + assert!(Dv::new(value, &unit).is_err(), "{}: expected construct error", case_id); + } + } + "convert" => { + let input = as_obj(case.get("input").expect("missing input"), &case_id); + let value = get_num(input, "value", &case_id); + let from_unit = get_str(input, "from_unit", &case_id); + let to_unit = get_str(input, "to_unit", &case_id); + let dv = Dv::new(value, &from_unit) + .unwrap_or_else(|e| panic!("{}: input build failed: {}", case_id, e)); + if ok { + let actual = dv + .value_in(&to_unit) + .unwrap_or_else(|e| panic!("{}: expected conversion success, got {}", case_id, e)); + let exp_value = get_num(expect_obj, "value", &case_id); + assert!( + nearly_eq(actual, exp_value, abs_tol, rel_tol), + "{}: expected {}, got {}", + case_id, + exp_value, + actual + ); + } else { + assert!(dv.value_in(&to_unit).is_err(), "{}: expected conversion error", case_id); + } + } + "add" | "sub" | "mul" | "div" => { + let left_obj = as_obj(case.get("left").expect("missing left"), &case_id); + let right_obj = as_obj(case.get("right").expect("missing right"), &case_id); + let left = Dv::new(get_num(left_obj, "value", &case_id), &get_str(left_obj, "unit", &case_id)) + .unwrap_or_else(|e| panic!("{}: left build failed: {}", case_id, e)); + let right = Dv::new(get_num(right_obj, "value", &case_id), &get_str(right_obj, "unit", &case_id)) + .unwrap_or_else(|e| panic!("{}: right build failed: {}", case_id, e)); + + if !ok { + match op.as_str() { + "add" => assert!(left.try_add(&right).is_err(), "{}: expected add error", case_id), + "sub" => assert!(left.try_sub(&right).is_err(), "{}: expected sub error", case_id), + _ => panic!("{}: ok=false unsupported for {}", case_id, op), + } + continue; + } + + let result = match op.as_str() { + "add" => &left + &right, + "sub" => &left - &right, + "mul" => &left * &right, + "div" => &left / &right, + _ => unreachable!(), + }; + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } + "powi" => { + let dv = build_input_dv(case, &case_id); + let exponent = get_num(case, "exponent", &case_id) as i32; + let result = dv.powi(exponent); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } + "powf" => { + let dv = build_input_dv(case, &case_id); + let exponent = get_num(case, "exponent", &case_id); + if ok { + let result = dv.powf(exponent).unwrap_or_else(|e| panic!("{}: powf failed: {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + assert!(dv.powf(exponent).is_err(), "{}: expected powf error", case_id); + } + } + "sqrt" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.sqrt().unwrap_or_else(|e| panic!("{}: sqrt failed: {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + assert!(dv.sqrt().is_err(), "{}: expected sqrt error", case_id); + } + } + "ln" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.ln().unwrap_or_else(|e| panic!("{}: ln failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.ln().is_err(), "{}: expected ln error", case_id); + } + } + "log2" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.log2().unwrap_or_else(|e| panic!("{}: log2 failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.log2().is_err(), "{}: expected log2 error", case_id); + } + } + "log10" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.log10().unwrap_or_else(|e| panic!("{}: log10 failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.log10().is_err(), "{}: expected log10 error", case_id); + } + } + "sin" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.sin().unwrap_or_else(|e| panic!("{}: sin failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.sin().is_err(), "{}: expected sin error", case_id); + } + } + "cos" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.cos().unwrap_or_else(|e| panic!("{}: cos failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.cos().is_err(), "{}: expected cos error", case_id); + } + } + "tan" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.tan().unwrap_or_else(|e| panic!("{}: tan failed: {}", case_id, e)); + assert_expected_scalar(&case_id, expect_obj, result, abs_tol, rel_tol); + } else { + assert!(dv.tan().is_err(), "{}: expected tan error", case_id); + } + } + "asin" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.asin().unwrap_or_else(|e| panic!("{}: asin failed: {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + assert!(dv.asin().is_err(), "{}: expected asin error", case_id); + } + } + "acos" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.acos().unwrap_or_else(|e| panic!("{}: acos failed: {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + assert!(dv.acos().is_err(), "{}: expected acos error", case_id); + } + } + "atan" => { + let dv = build_input_dv(case, &case_id); + if ok { + let result = dv.atan().unwrap_or_else(|e| panic!("{}: atan failed: {}", case_id, e)); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + assert!(dv.atan().is_err(), "{}: expected atan error", case_id); + } + } + "neg" => { + let dv = build_input_dv(case, &case_id); + let result = -&dv; + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } + "abs" => { + let dv = build_input_dv(case, &case_id); + let result = dv.abs(); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } + _ => panic!("{}: unsupported op {}", case_id, op), + } + } +} + +#[test] +fn regression_units_vectors() { + run_vectors("units_tests.json"); +} + +#[test] +fn regression_math_vectors() { + run_vectors("math_tests.json"); +} diff --git a/tests/schema.json b/tests/schema.json new file mode 100644 index 0000000..719bc71 --- /dev/null +++ b/tests/schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dv/tests/regression/schema.json", + "title": "DV Integration Test Vectors", + "type": "object", + "required": ["suite", "cases"], + "properties": { + "suite": {"type": "string", "minLength": 1}, + "category": {"type": "string", "enum": ["units", "math"]}, + "defaults": { + "type": "object", + "properties": { + "abs_tol": {"type": "number", "minimum": 0}, + "rel_tol": {"type": "number", "minimum": 0} + }, + "additionalProperties": false + }, + "cases": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/case"} + } + }, + "additionalProperties": false, + "$defs": { + "operand": { + "type": "object", + "required": ["value", "unit"], + "properties": { + "value": {"type": "number"}, + "unit": {"type": "string"} + }, + "additionalProperties": false + }, + "expect": { + "type": "object", + "required": ["ok"], + "properties": { + "ok": {"type": "boolean"}, + "value": {"type": "number"}, + "unit": {"type": "string"} + }, + "additionalProperties": false + }, + "case": { + "type": "object", + "required": ["id", "op", "expect"], + "properties": { + "id": {"type": "string", "minLength": 1}, + "op": { + "type": "string", + "enum": [ + "construct", + "convert", + "add", + "sub", + "mul", + "div", + "powi", + "powf", + "sqrt", + "ln", + "log2", + "log10", + "sin", + "cos", + "tan", + "asin", + "acos", + "atan", + "neg", + "abs" + ] + }, + "input": { + "type": "object", + "properties": { + "value": {"type": "number"}, + "unit": {"type": "string"}, + "from_unit": {"type": "string"}, + "to_unit": {"type": "string"} + }, + "additionalProperties": false + }, + "left": {"$ref": "#/$defs/operand"}, + "right": {"$ref": "#/$defs/operand"}, + "exponent": {"type": "number"}, + "expect": {"$ref": "#/$defs/expect"} + }, + "additionalProperties": false + } + } +} diff --git a/tests/units_tests.json b/tests/units_tests.json new file mode 100644 index 0000000..ebee61a --- /dev/null +++ b/tests/units_tests.json @@ -0,0 +1,46 @@ +{ + "suite": "units_regression_tests", + "category": "units", + "defaults": { + "abs_tol": 1e-10, + "rel_tol": 1e-10 + }, + "cases": [ + { + "id": "construct.valid_speed", + "op": "construct", + "input": {"value": 10.0, "unit": "m/s"}, + "expect": {"ok": true, "value": 10.0, "unit": "m/s"} + }, + { + "id": "construct.invalid_unit", + "op": "construct", + "input": {"value": 10.0, "unit": "invalid_unit"}, + "expect": {"ok": false} + }, + { + "id": "convert.m_to_cm", + "op": "convert", + "input": {"value": 1.0, "from_unit": "m", "to_unit": "cm"}, + "expect": {"ok": true, "value": 100.0, "unit": "cm"} + }, + { + "id": "convert.km_to_m", + "op": "convert", + "input": {"value": 3.5, "from_unit": "km", "to_unit": "m"}, + "expect": {"ok": true, "value": 3500.0, "unit": "m"} + }, + { + "id": "convert.velocity_mps_to_mph", + "op": "convert", + "input": {"value": 10.0, "from_unit": "m/s", "to_unit": "mi/hr"}, + "expect": {"ok": true, "value": 22.369362920544025, "unit": "mi/hr"} + }, + { + "id": "convert.incompatible_units", + "op": "convert", + "input": {"value": 1.0, "from_unit": "m", "to_unit": "s"}, + "expect": {"ok": false} + } + ] +}