From 0fb5246525d03d47603cee21b2eac354976a4780 Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 18:37:03 -0800 Subject: [PATCH 1/6] Add Agents.md file to the repo --- AGENTS.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 AGENTS.md 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. From 9a0a7a137130bac1bc53ab8a6f08417f91271449 Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 19:45:12 -0800 Subject: [PATCH 2/6] Initial setup of cross-language regression tests --- core/BUILD.bazel | 2 +- cpp/capi/src/lib.rs | 76 +++++ cpp/include/dv.hpp | 8 + cpp/include/dv_c.h | 8 + python/BUILD.bazel | 1 - tests/BUILD.bazel | 52 +++ tests/integration_cpp_test.cpp | 508 +++++++++++++++++++++++++++++ tests/integration_python_tests.py | 241 ++++++++++++++ tests/integration_rust_tests.rs | 525 ++++++++++++++++++++++++++++++ tests/math_tests.json | 137 ++++++++ tests/schema.json | 93 ++++++ tests/units_tests.json | 46 +++ 12 files changed, 1695 insertions(+), 2 deletions(-) create mode 100644 tests/BUILD.bazel create mode 100644 tests/integration_cpp_test.cpp create mode 100644 tests/integration_python_tests.py create mode 100644 tests/integration_rust_tests.rs create mode 100644 tests/math_tests.json create mode 100644 tests/schema.json create mode 100644 tests/units_tests.json 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/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/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 0000000..80dcfb3 --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,52 @@ +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 = "integration_vectors", + srcs = [ + "units_tests.json", + "math_tests.json", + ], +) + +rust_test( + name = "integration_rust_tests", + srcs = ["integration_rust_tests.rs"], + data = [":integration_vectors"], + deps = ["//core:dv_rs"], +) + +py_test( + name = "integration_python_tests", + srcs = ["integration_python_tests.py"], + data = [":integration_vectors"], + deps = [ + "//python:dv_py", + "@pypi//pytest:pkg", + ], +) + +cc_test( + name = "integration_cpp_test", + srcs = ["integration_cpp_test.cpp"], + data = [":integration_vectors"], + deps = ["//cpp:dv_cpp"], +) + +test_suite( + name = "integration_tests", + tests = [ + ":integration_rust_tests", + ":integration_python_tests", + ":integration_cpp_test", + ], +) diff --git a/tests/integration_cpp_test.cpp b/tests/integration_cpp_test.cpp new file mode 100644 index 0000000..1f48f2e --- /dev/null +++ b/tests/integration_cpp_test.cpp @@ -0,0 +1,508 @@ +#include "dv.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +struct Json; +using JsonObject = std::map; +using JsonArray = std::vector; + +struct Json { + std::variant value; +}; + +class JsonParser { +public: + explicit JsonParser(const std::string& text) : s_(text), i_(0) {} + + Json parse() { + Json v = parse_value(); + skip_ws(); + if (i_ != s_.size()) { + throw std::runtime_error("unexpected trailing JSON content"); + } + return v; + } + +private: + Json parse_value() { + skip_ws(); + char c = peek(); + if (c == '{') return parse_object(); + if (c == '[') return parse_array(); + if (c == '"') return Json{parse_string()}; + if (c == 't' || c == 'f') return Json{parse_bool()}; + if (c == 'n') { + expect_token("null"); + return Json{nullptr}; + } + if (c == '-' || std::isdigit(static_cast(c))) return Json{parse_number()}; + throw std::runtime_error("invalid JSON value"); + } + + Json parse_object() { + expect('{'); + skip_ws(); + JsonObject obj; + if (peek() == '}') { + ++i_; + return Json{obj}; + } + + while (true) { + skip_ws(); + std::string key = parse_string(); + skip_ws(); + expect(':'); + obj.emplace(key, parse_value()); + skip_ws(); + if (peek() == ',') { + ++i_; + continue; + } + if (peek() == '}') { + ++i_; + break; + } + throw std::runtime_error("expected ',' or '}' in object"); + } + return Json{obj}; + } + + Json parse_array() { + expect('['); + skip_ws(); + JsonArray arr; + if (peek() == ']') { + ++i_; + return Json{arr}; + } + + while (true) { + arr.push_back(parse_value()); + skip_ws(); + if (peek() == ',') { + ++i_; + continue; + } + if (peek() == ']') { + ++i_; + break; + } + throw std::runtime_error("expected ',' or ']' in array"); + } + return Json{arr}; + } + + std::string parse_string() { + expect('"'); + std::string out; + while (i_ < s_.size()) { + char c = s_[i_++]; + if (c == '"') return out; + if (c == '\\') { + if (i_ >= s_.size()) throw std::runtime_error("unterminated escape sequence"); + char esc = s_[i_++]; + switch (esc) { + case '"': out.push_back('"'); break; + case '\\': out.push_back('\\'); break; + case '/': out.push_back('/'); break; + case 'b': out.push_back('\b'); break; + case 'f': out.push_back('\f'); break; + case 'n': out.push_back('\n'); break; + case 'r': out.push_back('\r'); break; + case 't': out.push_back('\t'); break; + default: throw std::runtime_error("unsupported JSON escape"); + } + continue; + } + out.push_back(c); + } + throw std::runtime_error("unterminated string literal"); + } + + bool parse_bool() { + if (starts_with("true")) { + i_ += 4; + return true; + } + if (starts_with("false")) { + i_ += 5; + return false; + } + throw std::runtime_error("invalid boolean"); + } + + double parse_number() { + const std::size_t start = i_; + if (peek() == '-') ++i_; + consume_digits(); + if (peek() == '.') { + ++i_; + consume_digits(); + } + if (peek() == 'e' || peek() == 'E') { + ++i_; + if (peek() == '+' || peek() == '-') ++i_; + consume_digits(); + } + return std::stod(s_.substr(start, i_ - start)); + } + + void consume_digits() { + while (i_ < s_.size() && std::isdigit(static_cast(s_[i_]))) { + ++i_; + } + } + + void skip_ws() { + while (i_ < s_.size()) { + char c = s_[i_]; + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { + ++i_; + continue; + } + break; + } + } + + void expect(char c) { + if (peek() != c) throw std::runtime_error(std::string("expected '") + c + "'"); + ++i_; + } + + void expect_token(const std::string& token) { + if (!starts_with(token)) throw std::runtime_error("unexpected token"); + i_ += token.size(); + } + + bool starts_with(const std::string& token) const { + return s_.compare(i_, token.size(), token) == 0; + } + + char peek() const { + if (i_ >= s_.size()) throw std::runtime_error("unexpected end of input"); + return s_[i_]; + } + + std::string s_; + std::size_t i_; +}; + +const JsonObject& as_object(const Json& v, const std::string& ctx) { + auto p = std::get_if(&v.value); + if (!p) throw std::runtime_error(ctx + ": expected object"); + return *p; +} + +const JsonArray& as_array(const Json& v, const std::string& ctx) { + auto p = std::get_if(&v.value); + if (!p) throw std::runtime_error(ctx + ": expected array"); + return *p; +} + +std::string as_string(const Json& v, const std::string& ctx) { + auto p = std::get_if(&v.value); + if (!p) throw std::runtime_error(ctx + ": expected string"); + return *p; +} + +double as_number(const Json& v, const std::string& ctx) { + auto p = std::get_if(&v.value); + if (!p) throw std::runtime_error(ctx + ": expected number"); + return *p; +} + +bool as_bool(const Json& v, const std::string& ctx) { + auto p = std::get_if(&v.value); + if (!p) throw std::runtime_error(ctx + ": expected bool"); + return *p; +} + +const Json& get_key(const JsonObject& obj, const std::string& key, const std::string& ctx) { + auto it = obj.find(key); + if (it == obj.end()) throw std::runtime_error(ctx + ": missing key " + key); + return it->second; +} + +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()); +} + +void assert_expected_dv(const std::string& case_id, const JsonObject& expect, const dv::DV& actual_dv, double abs_tol, double rel_tol) { + const std::string exp_unit = as_string(get_key(expect, "unit", case_id), case_id + ".expect.unit"); + const double exp_value = as_number(get_key(expect, "value", case_id), case_id + ".expect.value"); + 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 JsonObject& test_case, double abs_tol, double rel_tol) { + const std::string case_id = as_string(get_key(test_case, "id", "case"), "id"); + const std::string op = as_string(get_key(test_case, "op", case_id), "op"); + + const JsonObject& expect = as_object(get_key(test_case, "expect", case_id), case_id + ".expect"); + const bool ok = as_bool(get_key(expect, "ok", case_id), case_id + ".expect.ok"); + + if (op == "construct") { + const JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); + const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); + const std::string unit = as_string(get_key(input, "unit", case_id), case_id + ".input.unit"); + + 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 JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); + const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); + const std::string from_unit = as_string(get_key(input, "from_unit", case_id), case_id + ".input.from_unit"); + const std::string to_unit = as_string(get_key(input, "to_unit", case_id), case_id + ".input.to_unit"); + + dv::DV v(value, from_unit.c_str()); + if (ok) { + const double exp_value = as_number(get_key(expect, "value", case_id), case_id + ".expect.value"); + 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 JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); + const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); + const std::string unit = as_string(get_key(input, "unit", case_id), case_id + ".input.unit"); + return dv::DV(value, unit.c_str()); + }; + + if (op == "add" || op == "sub" || op == "mul" || op == "div") { + const JsonObject& left_obj = as_object(get_key(test_case, "left", case_id), case_id + ".left"); + const JsonObject& right_obj = as_object(get_key(test_case, "right", case_id), case_id + ".right"); + + dv::DV left( + as_number(get_key(left_obj, "value", case_id), case_id + ".left.value"), + as_string(get_key(left_obj, "unit", case_id), case_id + ".left.unit").c_str() + ); + dv::DV right( + as_number(get_key(right_obj, "value", case_id), case_id + ".right.value"), + as_string(get_key(right_obj, "unit", case_id), case_id + ".right.unit").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(as_number(get_key(test_case, "exponent", case_id), case_id + ".exponent")); + dv::DV result = input.powi(exponent); + assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); + return; + } + + if (op == "powf") { + double exponent = as_number(get_key(test_case, "exponent", case_id), case_id + ".exponent"); + 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 = JsonParser(content).parse(); + const JsonObject& root_obj = as_object(root, "root"); + + double abs_tol = 1e-10; + double rel_tol = 1e-10; + auto defaults_it = root_obj.find("defaults"); + if (defaults_it != root_obj.end()) { + const JsonObject& defaults = as_object(defaults_it->second, "defaults"); + abs_tol = as_number(get_key(defaults, "abs_tol", "defaults"), "defaults.abs_tol"); + rel_tol = as_number(get_key(defaults, "rel_tol", "defaults"), "defaults.rel_tol"); + } + + const JsonArray& cases = as_array(get_key(root_obj, "cases", "root"), "cases"); + for (const auto& c : cases) { + run_case(as_object(c, "case"), abs_tol, rel_tol); + } +} + +int main() { + try { + run_vector_file("units_tests.json"); + run_vector_file("math_tests.json"); + std::cout << "Integration vectors passed for C++\\n"; + return 0; + } catch (const std::exception& e) { + std::cerr << "Integration test failure: " << e.what() << "\\n"; + return 1; + } +} diff --git a/tests/integration_python_tests.py b/tests/integration_python_tests.py new file mode 100644 index 0000000..a0cee7b --- /dev/null +++ b/tests/integration_python_tests.py @@ -0,0 +1,241 @@ +"""Shared integration 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_integration_vectors() -> None: + _run_file("units_tests.json") + + +def test_math_integration_vectors() -> None: + _run_file("math_tests.json") diff --git a/tests/integration_rust_tests.rs b/tests/integration_rust_tests.rs new file mode 100644 index 0000000..c548db1 --- /dev/null +++ b/tests/integration_rust_tests.rs @@ -0,0 +1,525 @@ +use dv_rs::DimensionalVariable as Dv; +use std::collections::BTreeMap; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +#[derive(Clone, Debug)] +enum JValue { + Obj(BTreeMap), + Arr(Vec), + Str(String), + Num(f64), + Bool(bool), + Null, +} + +struct Parser<'a> { + s: &'a [u8], + i: usize, +} + +impl<'a> Parser<'a> { + fn new(s: &'a str) -> Self { + Self { s: s.as_bytes(), i: 0 } + } + + fn parse(mut self) -> Result { + let v = self.parse_value()?; + self.skip_ws(); + if self.i != self.s.len() { + return Err(format!("unexpected trailing content at byte {}", self.i)); + } + Ok(v) + } + + fn parse_value(&mut self) -> Result { + self.skip_ws(); + let b = self.peek().ok_or("unexpected EOF while parsing value")?; + match b { + b'{' => self.parse_object(), + b'[' => self.parse_array(), + b'"' => self.parse_string().map(JValue::Str), + b't' | b'f' => self.parse_bool().map(JValue::Bool), + b'n' => { + self.expect_bytes(b"null")?; + Ok(JValue::Null) + } + b'-' | b'0'..=b'9' => self.parse_number().map(JValue::Num), + _ => Err(format!("unexpected char '{}' at byte {}", b as char, self.i)), + } + } + + fn parse_object(&mut self) -> Result { + self.expect(b'{')?; + self.skip_ws(); + let mut map = BTreeMap::new(); + if self.peek() == Some(b'}') { + self.i += 1; + return Ok(JValue::Obj(map)); + } + loop { + self.skip_ws(); + let key = self.parse_string()?; + self.skip_ws(); + self.expect(b':')?; + let value = self.parse_value()?; + map.insert(key, value); + self.skip_ws(); + match self.peek() { + Some(b',') => self.i += 1, + Some(b'}') => { + self.i += 1; + break; + } + _ => return Err(format!("expected ',' or '}}' at byte {}", self.i)), + } + } + Ok(JValue::Obj(map)) + } + + fn parse_array(&mut self) -> Result { + self.expect(b'[')?; + self.skip_ws(); + let mut items = Vec::new(); + if self.peek() == Some(b']') { + self.i += 1; + return Ok(JValue::Arr(items)); + } + loop { + items.push(self.parse_value()?); + self.skip_ws(); + match self.peek() { + Some(b',') => self.i += 1, + Some(b']') => { + self.i += 1; + break; + } + _ => return Err(format!("expected ',' or ']' at byte {}", self.i)), + } + } + Ok(JValue::Arr(items)) + } + + fn parse_string(&mut self) -> Result { + self.expect(b'"')?; + let mut out = String::new(); + while let Some(b) = self.peek() { + self.i += 1; + match b { + b'"' => return Ok(out), + b'\\' => { + let esc = self.peek().ok_or("unterminated escape")?; + self.i += 1; + match esc { + b'"' => out.push('"'), + b'\\' => out.push('\\'), + b'/' => out.push('/'), + b'b' => out.push('\u{0008}'), + b'f' => out.push('\u{000C}'), + b'n' => out.push('\n'), + b'r' => out.push('\r'), + b't' => out.push('\t'), + _ => return Err(format!("unsupported escape \\{} at byte {}", esc as char, self.i - 1)), + } + } + _ => out.push(b as char), + } + } + Err("unterminated string".to_string()) + } + + fn parse_bool(&mut self) -> Result { + if self.starts_with(b"true") { + self.i += 4; + return Ok(true); + } + if self.starts_with(b"false") { + self.i += 5; + return Ok(false); + } + Err(format!("invalid boolean at byte {}", self.i)) + } + + fn parse_number(&mut self) -> Result { + let start = self.i; + if self.peek() == Some(b'-') { + self.i += 1; + } + self.consume_digits(); + if self.peek() == Some(b'.') { + self.i += 1; + self.consume_digits(); + } + if matches!(self.peek(), Some(b'e' | b'E')) { + self.i += 1; + if matches!(self.peek(), Some(b'+' | b'-')) { + self.i += 1; + } + self.consume_digits(); + } + let s = std::str::from_utf8(&self.s[start..self.i]).map_err(|_| "invalid number bytes")?; + s.parse::() + .map_err(|e| format!("failed to parse number '{}': {}", s, e)) + } + + fn consume_digits(&mut self) { + while matches!(self.peek(), Some(b'0'..=b'9')) { + self.i += 1; + } + } + + fn skip_ws(&mut self) { + while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) { + self.i += 1; + } + } + + fn expect(&mut self, ch: u8) -> Result<(), String> { + if self.peek() == Some(ch) { + self.i += 1; + Ok(()) + } else { + Err(format!("expected '{}' at byte {}", ch as char, self.i)) + } + } + + fn expect_bytes(&mut self, bytes: &[u8]) -> Result<(), String> { + if self.starts_with(bytes) { + self.i += bytes.len(); + Ok(()) + } else { + Err(format!("expected token at byte {}", self.i)) + } + } + + fn starts_with(&self, prefix: &[u8]) -> bool { + self.s.get(self.i..self.i + prefix.len()) == Some(prefix) + } + + fn peek(&self) -> Option { + self.s.get(self.i).copied() + } +} + +fn as_obj<'a>(v: &'a JValue, ctx: &str) -> &'a BTreeMap { + if let JValue::Obj(o) = v { + o + } else { + panic!("{}: expected object", ctx) + } +} + +fn as_arr<'a>(v: &'a JValue, ctx: &str) -> &'a [JValue] { + if let JValue::Arr(a) = v { + a + } else { + panic!("{}: expected array", ctx) + } +} + +fn get_str(obj: &BTreeMap, key: &str, ctx: &str) -> String { + match obj.get(key) { + Some(JValue::Str(s)) => s.clone(), + _ => panic!("{}: missing or invalid string key '{}'", ctx, key), + } +} + +fn get_num(obj: &BTreeMap, key: &str, ctx: &str) -> f64 { + match obj.get(key) { + Some(JValue::Num(n)) => *n, + _ => panic!("{}: missing or invalid number key '{}'", ctx, key), + } +} + +fn get_bool(obj: &BTreeMap, key: &str, ctx: &str) -> bool { + match obj.get(key) { + Some(JValue::Bool(b)) => *b, + _ => 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: &BTreeMap, 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 = 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: &BTreeMap, 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: &BTreeMap, 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 = Parser::new(&raw).parse().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; + if ok { + let result = dv.powi(exponent); + assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); + } else { + panic!("{}: powi with ok=false is not supported", case_id); + } + } + "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 integration_units_vectors() { + run_vectors("units_tests.json"); +} + +#[test] +fn integration_math_vectors() { + run_vectors("math_tests.json"); +} diff --git a/tests/math_tests.json b/tests/math_tests.json new file mode 100644 index 0000000..74956b5 --- /dev/null +++ b/tests/math_tests.json @@ -0,0 +1,137 @@ +{ + "suite": "math_integration_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/schema.json b/tests/schema.json new file mode 100644 index 0000000..8c2d938 --- /dev/null +++ b/tests/schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://dv/tests/integration/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..bf20a89 --- /dev/null +++ b/tests/units_tests.json @@ -0,0 +1,46 @@ +{ + "suite": "units_integration_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} + } + ] +} From 01f30d4d928b0743b79a933c8d2d4df3d1a24488 Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 20:49:53 -0800 Subject: [PATCH 3/6] a bit of refactoring --- MODULE.bazel | 10 + MODULE.bazel.lock | 259 +++++++++ tests/BUILD.bazel | 38 +- tests/integration_cpp_test.cpp | 508 ------------------ tests/math_tests.json | 2 +- tests/regression_cpp_test.cpp | 317 +++++++++++ ...on_tests.py => regression_python_tests.py} | 6 +- ...rust_tests.rs => regression_rust_tests.rs} | 316 ++--------- tests/schema.json | 2 +- tests/units_tests.json | 2 +- 10 files changed, 663 insertions(+), 797 deletions(-) delete mode 100644 tests/integration_cpp_test.cpp create mode 100644 tests/regression_cpp_test.cpp rename tests/{integration_python_tests.py => regression_python_tests.py} (97%) rename tests/{integration_rust_tests.rs => regression_rust_tests.rs} (52%) 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/tests/BUILD.bazel b/tests/BUILD.bazel index 80dcfb3..a29335c 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -11,7 +11,7 @@ exports_files([ ]) filegroup( - name = "integration_vectors", + name = "regression_vectors", srcs = [ "units_tests.json", "math_tests.json", @@ -19,16 +19,19 @@ filegroup( ) rust_test( - name = "integration_rust_tests", - srcs = ["integration_rust_tests.rs"], - data = [":integration_vectors"], - deps = ["//core:dv_rs"], + name = "regression_rust_tests", + srcs = ["regression_rust_tests.rs"], + data = [":regression_vectors"], + deps = [ + "//core:dv_rs", + "@crates//:serde_json", + ], ) py_test( - name = "integration_python_tests", - srcs = ["integration_python_tests.py"], - data = [":integration_vectors"], + name = "regression_python_tests", + srcs = ["regression_python_tests.py"], + data = [":regression_vectors"], deps = [ "//python:dv_py", "@pypi//pytest:pkg", @@ -36,17 +39,20 @@ py_test( ) cc_test( - name = "integration_cpp_test", - srcs = ["integration_cpp_test.cpp"], - data = [":integration_vectors"], - deps = ["//cpp:dv_cpp"], + name = "regression_cpp_test", + srcs = ["regression_cpp_test.cpp"], + data = [":regression_vectors"], + deps = [ + "//cpp:dv_cpp", + "@nlohmann_json//:json", + ], ) test_suite( - name = "integration_tests", + name = "regression_tests", tests = [ - ":integration_rust_tests", - ":integration_python_tests", - ":integration_cpp_test", + ":regression_rust_tests", + ":regression_python_tests", + ":regression_cpp_test", ], ) diff --git a/tests/integration_cpp_test.cpp b/tests/integration_cpp_test.cpp deleted file mode 100644 index 1f48f2e..0000000 --- a/tests/integration_cpp_test.cpp +++ /dev/null @@ -1,508 +0,0 @@ -#include "dv.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -struct Json; -using JsonObject = std::map; -using JsonArray = std::vector; - -struct Json { - std::variant value; -}; - -class JsonParser { -public: - explicit JsonParser(const std::string& text) : s_(text), i_(0) {} - - Json parse() { - Json v = parse_value(); - skip_ws(); - if (i_ != s_.size()) { - throw std::runtime_error("unexpected trailing JSON content"); - } - return v; - } - -private: - Json parse_value() { - skip_ws(); - char c = peek(); - if (c == '{') return parse_object(); - if (c == '[') return parse_array(); - if (c == '"') return Json{parse_string()}; - if (c == 't' || c == 'f') return Json{parse_bool()}; - if (c == 'n') { - expect_token("null"); - return Json{nullptr}; - } - if (c == '-' || std::isdigit(static_cast(c))) return Json{parse_number()}; - throw std::runtime_error("invalid JSON value"); - } - - Json parse_object() { - expect('{'); - skip_ws(); - JsonObject obj; - if (peek() == '}') { - ++i_; - return Json{obj}; - } - - while (true) { - skip_ws(); - std::string key = parse_string(); - skip_ws(); - expect(':'); - obj.emplace(key, parse_value()); - skip_ws(); - if (peek() == ',') { - ++i_; - continue; - } - if (peek() == '}') { - ++i_; - break; - } - throw std::runtime_error("expected ',' or '}' in object"); - } - return Json{obj}; - } - - Json parse_array() { - expect('['); - skip_ws(); - JsonArray arr; - if (peek() == ']') { - ++i_; - return Json{arr}; - } - - while (true) { - arr.push_back(parse_value()); - skip_ws(); - if (peek() == ',') { - ++i_; - continue; - } - if (peek() == ']') { - ++i_; - break; - } - throw std::runtime_error("expected ',' or ']' in array"); - } - return Json{arr}; - } - - std::string parse_string() { - expect('"'); - std::string out; - while (i_ < s_.size()) { - char c = s_[i_++]; - if (c == '"') return out; - if (c == '\\') { - if (i_ >= s_.size()) throw std::runtime_error("unterminated escape sequence"); - char esc = s_[i_++]; - switch (esc) { - case '"': out.push_back('"'); break; - case '\\': out.push_back('\\'); break; - case '/': out.push_back('/'); break; - case 'b': out.push_back('\b'); break; - case 'f': out.push_back('\f'); break; - case 'n': out.push_back('\n'); break; - case 'r': out.push_back('\r'); break; - case 't': out.push_back('\t'); break; - default: throw std::runtime_error("unsupported JSON escape"); - } - continue; - } - out.push_back(c); - } - throw std::runtime_error("unterminated string literal"); - } - - bool parse_bool() { - if (starts_with("true")) { - i_ += 4; - return true; - } - if (starts_with("false")) { - i_ += 5; - return false; - } - throw std::runtime_error("invalid boolean"); - } - - double parse_number() { - const std::size_t start = i_; - if (peek() == '-') ++i_; - consume_digits(); - if (peek() == '.') { - ++i_; - consume_digits(); - } - if (peek() == 'e' || peek() == 'E') { - ++i_; - if (peek() == '+' || peek() == '-') ++i_; - consume_digits(); - } - return std::stod(s_.substr(start, i_ - start)); - } - - void consume_digits() { - while (i_ < s_.size() && std::isdigit(static_cast(s_[i_]))) { - ++i_; - } - } - - void skip_ws() { - while (i_ < s_.size()) { - char c = s_[i_]; - if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { - ++i_; - continue; - } - break; - } - } - - void expect(char c) { - if (peek() != c) throw std::runtime_error(std::string("expected '") + c + "'"); - ++i_; - } - - void expect_token(const std::string& token) { - if (!starts_with(token)) throw std::runtime_error("unexpected token"); - i_ += token.size(); - } - - bool starts_with(const std::string& token) const { - return s_.compare(i_, token.size(), token) == 0; - } - - char peek() const { - if (i_ >= s_.size()) throw std::runtime_error("unexpected end of input"); - return s_[i_]; - } - - std::string s_; - std::size_t i_; -}; - -const JsonObject& as_object(const Json& v, const std::string& ctx) { - auto p = std::get_if(&v.value); - if (!p) throw std::runtime_error(ctx + ": expected object"); - return *p; -} - -const JsonArray& as_array(const Json& v, const std::string& ctx) { - auto p = std::get_if(&v.value); - if (!p) throw std::runtime_error(ctx + ": expected array"); - return *p; -} - -std::string as_string(const Json& v, const std::string& ctx) { - auto p = std::get_if(&v.value); - if (!p) throw std::runtime_error(ctx + ": expected string"); - return *p; -} - -double as_number(const Json& v, const std::string& ctx) { - auto p = std::get_if(&v.value); - if (!p) throw std::runtime_error(ctx + ": expected number"); - return *p; -} - -bool as_bool(const Json& v, const std::string& ctx) { - auto p = std::get_if(&v.value); - if (!p) throw std::runtime_error(ctx + ": expected bool"); - return *p; -} - -const Json& get_key(const JsonObject& obj, const std::string& key, const std::string& ctx) { - auto it = obj.find(key); - if (it == obj.end()) throw std::runtime_error(ctx + ": missing key " + key); - return it->second; -} - -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()); -} - -void assert_expected_dv(const std::string& case_id, const JsonObject& expect, const dv::DV& actual_dv, double abs_tol, double rel_tol) { - const std::string exp_unit = as_string(get_key(expect, "unit", case_id), case_id + ".expect.unit"); - const double exp_value = as_number(get_key(expect, "value", case_id), case_id + ".expect.value"); - 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 JsonObject& test_case, double abs_tol, double rel_tol) { - const std::string case_id = as_string(get_key(test_case, "id", "case"), "id"); - const std::string op = as_string(get_key(test_case, "op", case_id), "op"); - - const JsonObject& expect = as_object(get_key(test_case, "expect", case_id), case_id + ".expect"); - const bool ok = as_bool(get_key(expect, "ok", case_id), case_id + ".expect.ok"); - - if (op == "construct") { - const JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); - const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); - const std::string unit = as_string(get_key(input, "unit", case_id), case_id + ".input.unit"); - - 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 JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); - const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); - const std::string from_unit = as_string(get_key(input, "from_unit", case_id), case_id + ".input.from_unit"); - const std::string to_unit = as_string(get_key(input, "to_unit", case_id), case_id + ".input.to_unit"); - - dv::DV v(value, from_unit.c_str()); - if (ok) { - const double exp_value = as_number(get_key(expect, "value", case_id), case_id + ".expect.value"); - 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 JsonObject& input = as_object(get_key(test_case, "input", case_id), case_id + ".input"); - const double value = as_number(get_key(input, "value", case_id), case_id + ".input.value"); - const std::string unit = as_string(get_key(input, "unit", case_id), case_id + ".input.unit"); - return dv::DV(value, unit.c_str()); - }; - - if (op == "add" || op == "sub" || op == "mul" || op == "div") { - const JsonObject& left_obj = as_object(get_key(test_case, "left", case_id), case_id + ".left"); - const JsonObject& right_obj = as_object(get_key(test_case, "right", case_id), case_id + ".right"); - - dv::DV left( - as_number(get_key(left_obj, "value", case_id), case_id + ".left.value"), - as_string(get_key(left_obj, "unit", case_id), case_id + ".left.unit").c_str() - ); - dv::DV right( - as_number(get_key(right_obj, "value", case_id), case_id + ".right.value"), - as_string(get_key(right_obj, "unit", case_id), case_id + ".right.unit").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(as_number(get_key(test_case, "exponent", case_id), case_id + ".exponent")); - dv::DV result = input.powi(exponent); - assert_expected_dv(case_id, expect, result, abs_tol, rel_tol); - return; - } - - if (op == "powf") { - double exponent = as_number(get_key(test_case, "exponent", case_id), case_id + ".exponent"); - 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 = JsonParser(content).parse(); - const JsonObject& root_obj = as_object(root, "root"); - - double abs_tol = 1e-10; - double rel_tol = 1e-10; - auto defaults_it = root_obj.find("defaults"); - if (defaults_it != root_obj.end()) { - const JsonObject& defaults = as_object(defaults_it->second, "defaults"); - abs_tol = as_number(get_key(defaults, "abs_tol", "defaults"), "defaults.abs_tol"); - rel_tol = as_number(get_key(defaults, "rel_tol", "defaults"), "defaults.rel_tol"); - } - - const JsonArray& cases = as_array(get_key(root_obj, "cases", "root"), "cases"); - for (const auto& c : cases) { - run_case(as_object(c, "case"), abs_tol, rel_tol); - } -} - -int main() { - try { - run_vector_file("units_tests.json"); - run_vector_file("math_tests.json"); - std::cout << "Integration vectors passed for C++\\n"; - return 0; - } catch (const std::exception& e) { - std::cerr << "Integration test failure: " << e.what() << "\\n"; - return 1; - } -} diff --git a/tests/math_tests.json b/tests/math_tests.json index 74956b5..e6028e7 100644 --- a/tests/math_tests.json +++ b/tests/math_tests.json @@ -1,5 +1,5 @@ { - "suite": "math_integration_tests", + "suite": "math_regression_tests", "category": "math", "defaults": { "abs_tol": 1e-10, diff --git a/tests/regression_cpp_test.cpp b/tests/regression_cpp_test.cpp new file mode 100644 index 0000000..9d7de01 --- /dev/null +++ b/tests/regression_cpp_test.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/integration_python_tests.py b/tests/regression_python_tests.py similarity index 97% rename from tests/integration_python_tests.py rename to tests/regression_python_tests.py index a0cee7b..18aba57 100644 --- a/tests/integration_python_tests.py +++ b/tests/regression_python_tests.py @@ -1,4 +1,4 @@ -"""Shared integration test runner for Python bindings.""" +"""Shared regression test runner for Python bindings.""" from __future__ import annotations @@ -233,9 +233,9 @@ def _run_file(file_name: str) -> None: _run_case(case, abs_tol, rel_tol) -def test_units_integration_vectors() -> None: +def test_units_regression_vectors() -> None: _run_file("units_tests.json") -def test_math_integration_vectors() -> None: +def test_math_regression_vectors() -> None: _run_file("math_tests.json") diff --git a/tests/integration_rust_tests.rs b/tests/regression_rust_tests.rs similarity index 52% rename from tests/integration_rust_tests.rs rename to tests/regression_rust_tests.rs index c548db1..234548d 100644 --- a/tests/integration_rust_tests.rs +++ b/tests/regression_rust_tests.rs @@ -1,242 +1,37 @@ use dv_rs::DimensionalVariable as Dv; -use std::collections::BTreeMap; +use serde_json::Value; use std::env; use std::fs; use std::path::{Path, PathBuf}; -#[derive(Clone, Debug)] -enum JValue { - Obj(BTreeMap), - Arr(Vec), - Str(String), - Num(f64), - Bool(bool), - Null, +fn as_obj<'a>(v: &'a Value, ctx: &str) -> &'a serde_json::Map { + v.as_object() + .unwrap_or_else(|| panic!("{}: expected object", ctx)) } -struct Parser<'a> { - s: &'a [u8], - i: usize, +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)) } -impl<'a> Parser<'a> { - fn new(s: &'a str) -> Self { - Self { s: s.as_bytes(), i: 0 } - } - - fn parse(mut self) -> Result { - let v = self.parse_value()?; - self.skip_ws(); - if self.i != self.s.len() { - return Err(format!("unexpected trailing content at byte {}", self.i)); - } - Ok(v) - } - - fn parse_value(&mut self) -> Result { - self.skip_ws(); - let b = self.peek().ok_or("unexpected EOF while parsing value")?; - match b { - b'{' => self.parse_object(), - b'[' => self.parse_array(), - b'"' => self.parse_string().map(JValue::Str), - b't' | b'f' => self.parse_bool().map(JValue::Bool), - b'n' => { - self.expect_bytes(b"null")?; - Ok(JValue::Null) - } - b'-' | b'0'..=b'9' => self.parse_number().map(JValue::Num), - _ => Err(format!("unexpected char '{}' at byte {}", b as char, self.i)), - } - } - - fn parse_object(&mut self) -> Result { - self.expect(b'{')?; - self.skip_ws(); - let mut map = BTreeMap::new(); - if self.peek() == Some(b'}') { - self.i += 1; - return Ok(JValue::Obj(map)); - } - loop { - self.skip_ws(); - let key = self.parse_string()?; - self.skip_ws(); - self.expect(b':')?; - let value = self.parse_value()?; - map.insert(key, value); - self.skip_ws(); - match self.peek() { - Some(b',') => self.i += 1, - Some(b'}') => { - self.i += 1; - break; - } - _ => return Err(format!("expected ',' or '}}' at byte {}", self.i)), - } - } - Ok(JValue::Obj(map)) - } - - fn parse_array(&mut self) -> Result { - self.expect(b'[')?; - self.skip_ws(); - let mut items = Vec::new(); - if self.peek() == Some(b']') { - self.i += 1; - return Ok(JValue::Arr(items)); - } - loop { - items.push(self.parse_value()?); - self.skip_ws(); - match self.peek() { - Some(b',') => self.i += 1, - Some(b']') => { - self.i += 1; - break; - } - _ => return Err(format!("expected ',' or ']' at byte {}", self.i)), - } - } - Ok(JValue::Arr(items)) - } - - fn parse_string(&mut self) -> Result { - self.expect(b'"')?; - let mut out = String::new(); - while let Some(b) = self.peek() { - self.i += 1; - match b { - b'"' => return Ok(out), - b'\\' => { - let esc = self.peek().ok_or("unterminated escape")?; - self.i += 1; - match esc { - b'"' => out.push('"'), - b'\\' => out.push('\\'), - b'/' => out.push('/'), - b'b' => out.push('\u{0008}'), - b'f' => out.push('\u{000C}'), - b'n' => out.push('\n'), - b'r' => out.push('\r'), - b't' => out.push('\t'), - _ => return Err(format!("unsupported escape \\{} at byte {}", esc as char, self.i - 1)), - } - } - _ => out.push(b as char), - } - } - Err("unterminated string".to_string()) - } - - fn parse_bool(&mut self) -> Result { - if self.starts_with(b"true") { - self.i += 4; - return Ok(true); - } - if self.starts_with(b"false") { - self.i += 5; - return Ok(false); - } - Err(format!("invalid boolean at byte {}", self.i)) - } - - fn parse_number(&mut self) -> Result { - let start = self.i; - if self.peek() == Some(b'-') { - self.i += 1; - } - self.consume_digits(); - if self.peek() == Some(b'.') { - self.i += 1; - self.consume_digits(); - } - if matches!(self.peek(), Some(b'e' | b'E')) { - self.i += 1; - if matches!(self.peek(), Some(b'+' | b'-')) { - self.i += 1; - } - self.consume_digits(); - } - let s = std::str::from_utf8(&self.s[start..self.i]).map_err(|_| "invalid number bytes")?; - s.parse::() - .map_err(|e| format!("failed to parse number '{}': {}", s, e)) - } - - fn consume_digits(&mut self) { - while matches!(self.peek(), Some(b'0'..=b'9')) { - self.i += 1; - } - } - - fn skip_ws(&mut self) { - while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) { - self.i += 1; - } - } - - fn expect(&mut self, ch: u8) -> Result<(), String> { - if self.peek() == Some(ch) { - self.i += 1; - Ok(()) - } else { - Err(format!("expected '{}' at byte {}", ch as char, self.i)) - } - } - - fn expect_bytes(&mut self, bytes: &[u8]) -> Result<(), String> { - if self.starts_with(bytes) { - self.i += bytes.len(); - Ok(()) - } else { - Err(format!("expected token at byte {}", self.i)) - } - } - - fn starts_with(&self, prefix: &[u8]) -> bool { - self.s.get(self.i..self.i + prefix.len()) == Some(prefix) - } - - fn peek(&self) -> Option { - self.s.get(self.i).copied() - } +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 as_obj<'a>(v: &'a JValue, ctx: &str) -> &'a BTreeMap { - if let JValue::Obj(o) = v { - o - } else { - panic!("{}: expected object", ctx) - } +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 as_arr<'a>(v: &'a JValue, ctx: &str) -> &'a [JValue] { - if let JValue::Arr(a) = v { - a - } else { - panic!("{}: expected array", ctx) - } -} - -fn get_str(obj: &BTreeMap, key: &str, ctx: &str) -> String { - match obj.get(key) { - Some(JValue::Str(s)) => s.clone(), - _ => panic!("{}: missing or invalid string key '{}'", ctx, key), - } -} - -fn get_num(obj: &BTreeMap, key: &str, ctx: &str) -> f64 { - match obj.get(key) { - Some(JValue::Num(n)) => *n, - _ => panic!("{}: missing or invalid number key '{}'", ctx, key), - } -} - -fn get_bool(obj: &BTreeMap, key: &str, ctx: &str) -> bool { - match obj.get(key) { - Some(JValue::Bool(b)) => *b, - _ => panic!("{}: missing or invalid bool 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 { @@ -261,12 +56,15 @@ fn nearly_eq(actual: f64, expected: f64, abs_tol: f64, rel_tol: f64) -> bool { diff <= abs_tol.max(rel_tol * expected.abs()) } -fn assert_expected_dv(case_id: &str, expect: &BTreeMap, dv: &Dv, abs_tol: f64, rel_tol: f64) { +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 = dv - .value_in(&exp_unit) - .unwrap_or_else(|e| panic!("{}: value_in failed: {}", case_id, e)); + 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 {}", @@ -276,7 +74,7 @@ fn assert_expected_dv(case_id: &str, expect: &BTreeMap, dv: &Dv, ); } -fn assert_expected_scalar(case_id: &str, expect: &BTreeMap, actual: f64, abs_tol: f64, rel_tol: f64) { +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); @@ -289,7 +87,7 @@ fn assert_expected_scalar(case_id: &str, expect: &BTreeMap, actu ); } -fn build_input_dv(case_obj: &BTreeMap, case_id: &str) -> Dv { +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); @@ -299,12 +97,10 @@ fn build_input_dv(case_obj: &BTreeMap, case_id: &str) -> Dv { 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 = Parser::new(&raw).parse().expect("invalid JSON in 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 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); @@ -338,7 +134,8 @@ fn run_vectors(file_name: &str) { 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) + 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!( @@ -381,19 +178,14 @@ fn run_vectors(file_name: &str) { "powi" => { let dv = build_input_dv(case, &case_id); let exponent = get_num(case, "exponent", &case_id) as i32; - if ok { - let result = dv.powi(exponent); - assert_expected_dv(&case_id, expect_obj, &result, abs_tol, rel_tol); - } else { - panic!("{}: powi with ok=false is not supported", case_id); - } + 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)); + 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); @@ -402,8 +194,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -412,8 +203,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -422,8 +212,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -432,8 +221,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -442,8 +230,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -452,8 +239,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -462,8 +248,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -472,8 +257,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -482,8 +266,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -492,8 +275,7 @@ fn run_vectors(file_name: &str) { "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)); + 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); @@ -515,11 +297,11 @@ fn run_vectors(file_name: &str) { } #[test] -fn integration_units_vectors() { +fn regression_units_vectors() { run_vectors("units_tests.json"); } #[test] -fn integration_math_vectors() { +fn regression_math_vectors() { run_vectors("math_tests.json"); } diff --git a/tests/schema.json b/tests/schema.json index 8c2d938..719bc71 100644 --- a/tests/schema.json +++ b/tests/schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://dv/tests/integration/schema.json", + "$id": "https://dv/tests/regression/schema.json", "title": "DV Integration Test Vectors", "type": "object", "required": ["suite", "cases"], diff --git a/tests/units_tests.json b/tests/units_tests.json index bf20a89..ebee61a 100644 --- a/tests/units_tests.json +++ b/tests/units_tests.json @@ -1,5 +1,5 @@ { - "suite": "units_integration_tests", + "suite": "units_regression_tests", "category": "units", "defaults": { "abs_tol": 1e-10, From 2e79c2daac4d4c437a0ed4a0888cd32abe9da77b Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 21:22:23 -0800 Subject: [PATCH 4/6] Add tests to CI + bump versions --- .github/workflows/_c_cpp.yaml | 19 ++++++++++--------- .github/workflows/_python.yaml | 33 +++++++++++++++++++++++++-------- .github/workflows/_rust.yaml | 12 +++++++----- .github/workflows/main.yaml | 24 +++++++++++++++++++++++- VERSION.toml | 2 +- 5 files changed, 66 insertions(+), 24 deletions(-) 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..2ccc23d 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,6 +96,8 @@ 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 @@ -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/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 From bb469af79d1e2050d7a8de54d7cb2176cac0ea1c Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 21:36:03 -0800 Subject: [PATCH 5/6] fix version numbers everywhere else --- core/Cargo.toml | 2 +- python/Cargo.toml | 2 +- python/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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" From c18f0ec8bef03d863462181c79b4676e6eefca89 Mon Sep 17 00:00:00 2001 From: Alex Tacescu Date: Sat, 21 Feb 2026 21:46:18 -0800 Subject: [PATCH 6/6] fix PR comments --- .github/workflows/_python.yaml | 2 +- tests/BUILD.bazel | 6 +++--- tests/{regression_cpp_test.cpp => regression_cpp_tests.cpp} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename tests/{regression_cpp_test.cpp => regression_cpp_tests.cpp} (100%) diff --git a/.github/workflows/_python.yaml b/.github/workflows/_python.yaml index 2ccc23d..3910ca2 100644 --- a/.github/workflows/_python.yaml +++ b/.github/workflows/_python.yaml @@ -100,7 +100,7 @@ jobs: run: bazel test //tests:regression_python_tests --test_output=errors --keep_going # ---------------------------------------------------------------------------- - # Publsih Python Wheels + # Publish Python Wheels # ---------------------------------------------------------------------------- python-publish-pypi: diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index a29335c..7477001 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -39,8 +39,8 @@ py_test( ) cc_test( - name = "regression_cpp_test", - srcs = ["regression_cpp_test.cpp"], + name = "regression_cpp_tests", + srcs = ["regression_cpp_tests.cpp"], data = [":regression_vectors"], deps = [ "//cpp:dv_cpp", @@ -53,6 +53,6 @@ test_suite( tests = [ ":regression_rust_tests", ":regression_python_tests", - ":regression_cpp_test", + ":regression_cpp_tests", ], ) diff --git a/tests/regression_cpp_test.cpp b/tests/regression_cpp_tests.cpp similarity index 100% rename from tests/regression_cpp_test.cpp rename to tests/regression_cpp_tests.cpp