diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30f2..000000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..55066aa72 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: Tests + +# Until now nothing ran the test suite on a change: the release workflows +# build the app, web-check.yml type-checks the wasm target, and pull requests +# reported their own local runs. Run the workspace tests on every push to +# main and on every pull request. There is deliberately no formatter check: +# the tree is not rustfmt-clean and is not going to be reformatted. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + # The same libraries the Linux release build needs (windowing, GL, + # fonts); the packaging tools are left out. + - name: System libraries + run: | + sudo apt-get update + sudo apt-get install -y \ + libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \ + libxkbcommon-dev libwayland-dev libfontconfig1-dev libfreetype6-dev + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + # LC_ALL=C: a few tests compare English message text. + - name: cargo test + env: + LC_ALL: C + run: cargo test --workspace --locked diff --git a/.gitignore b/.gitignore index 3684cd6dc..262043fce 100644 --- a/.gitignore +++ b/.gitignore @@ -36,9 +36,6 @@ __pycache__/ # Claude Code local preview/dev-server config .claude/launch.json -# nix-direnv -.direnv/ - # Worktrees created for isolated runs .claude/worktrees/ diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 95a880e21..000000000 --- a/flake.lock +++ /dev/null @@ -1,48 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1789149629, - "narHash": "sha256-H6GwaZzZf+4npqv0tph94w9tZddSjFjmQrVsW0z78uk=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "eaad089433ca2bb662274377d33df3d0e51ef28b", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1789196581, - "narHash": "sha256-yJr1Bt4fKkKIpPYbsKGqJ0VFdoDnURgRwuffmBQ2WzY=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "228ecefb6329d5a531b77b46b581a2f0c26ee056", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 89610a0b6..000000000 --- a/flake.nix +++ /dev/null @@ -1,156 +0,0 @@ -{ - description = "Open CAD Studio - 2D drafting and 3D modeling in Rust"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - # rust-overlay ships upstream Rust binaries rather than building them, so - # following nixpkgs costs it no binary cache. - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - outputs = { self, nixpkgs, rust-overlay }: - let - systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; - forAllSystems = f: - nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { - inherit system; - overlays = [ rust-overlay.overlays.default ]; - })); - in - { - packages = forAllSystems (pkgs: - let - inherit (pkgs) lib stdenv; - - toolchain = pkgs.rust-bin.stable.latest.default.override { - targets = [ "wasm32-unknown-unknown" ]; - }; - - # Build with the overlay's toolchain rather than the nixpkgs default, - # so the package and the devShell compile with the same rustc. - rustPlatform = pkgs.makeRustPlatform { - cargo = toolchain; - rustc = toolchain; - }; - - # winit/wgpu dlopen these at run time, so they must also be on the - # wrapped binary's LD_LIBRARY_PATH, not just at link time. - runtimeLibs = with pkgs; [ - libGL - libxkbcommon - vulkan-loader - wayland - xorg.libX11 - xorg.libXcursor - xorg.libXi - xorg.libXrandr - ]; - in - rec { - default = opencadstudio; - - opencadstudio = rustPlatform.buildRustPackage { - pname = "opencadstudio"; - version = (lib.importTOML ./Cargo.toml).package.version; - src = self; - - cargoLock = { - lockFile = ./Cargo.lock; - # One entry per distinct git source in Cargo.lock, keyed by any - # one package from it. Two of these are transitive and easy to - # miss by reading Cargo.toml alone: cryoglyph and dpi arrive - # through iced, which pins its own forks. Regenerate the list - # from the lock rather than the manifest: - # grep -B2 'source = "git+' Cargo.lock - # Each hash must be refreshed whenever the matching rev moves. - outputHashes = { - "acadrust-0.5.5" = "sha256-rYtJaJsb+lZZIDBtPTCfWRVyA1Gfinn3UqJlfzkhhGk="; - "cadkernel-0.1.0" = "sha256-7smcL9oo5mJwoCLYUbD4+lA9Xcm+/qpVswYxzoGjTDk="; - "cryoglyph-0.1.0" = "sha256-5BOJNDcjhpt17/XSri3JGtX8NwC7db18bddp5wanMes="; - "dpi-0.1.1" = "sha256-pQn1lCFSJMkjUfHoggEzMHnm5k+Chnzi5JEDjahnjUA="; - "iced-0.15.0-dev" = "sha256-Atg+nr1VgXwW5I7LiMAMqHemz/9ZiehTc2EWuSFEGHI="; - "iced_aw-0.14.1" = "sha256-OynSIjv8SgSoxpw1S4wH58vDdqga/YojOR5A9ZLKrd8="; - }; - }; - - nativeBuildInputs = with pkgs; [ - pkg-config - makeWrapper - ]; - - buildInputs = with pkgs; [ - fontconfig - freetype - ] ++ runtimeLibs - ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.apple_sdk.frameworks.AppKit ]; - - # build.rs asks git for a revision and falls back to "unknown" - # when the repo or the binary is absent, which it is in the - # sandbox. Nothing to do but let it take the fallback. - doCheck = false; - - postInstall = lib.optionalString stdenv.hostPlatform.isLinux '' - wrapProgram $out/bin/OpenCADStudio \ - --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath runtimeLibs} - ''; - - meta = { - description = "Open-source 2D drafting and 3D modeling, built with Rust"; - homepage = "https://www.opencadstudio.com"; - license = lib.licenses.gpl3Only; - mainProgram = "OpenCADStudio"; - platforms = systems; - }; - }; - }); - - devShells = forAllSystems (pkgs: - let - toolchain = pkgs.rust-bin.stable.latest.default.override { - extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; - targets = [ "wasm32-unknown-unknown" ]; - }; - runtimeLibs = with pkgs; [ - libGL - libxkbcommon - vulkan-loader - wayland - xorg.libX11 - xorg.libXcursor - xorg.libXi - xorg.libXrandr - ]; - in - { - default = pkgs.mkShell { - packages = with pkgs; [ - toolchain - pkg-config - fontconfig - freetype - git - # Web build: `trunk build` per Trunk.toml. wasm-bindgen-cli must - # match the wasm-bindgen version Cargo.lock resolves, the same - # constraint .github/workflows/pages.yml:83 reads out of the lock. - trunk - wasm-bindgen-cli - # Rasterises assets/logo.svg for packaging, as release.yml does. - librsvg - ] ++ runtimeLibs; - - # A dev binary is run straight out of ./target, so it never gets - # the wrapper the package build applies. - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; - }; - }); - - checks = forAllSystems (pkgs: { - default = self.packages.${pkgs.stdenv.hostPlatform.system}.opencadstudio; - }); - - formatter = forAllSystems (pkgs: pkgs.nixpkgs-fmt); - }; -} diff --git a/src/i18n.rs b/src/i18n.rs index 73b64c48c..1463aef6b 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -158,6 +158,13 @@ impl std::fmt::Display for Language { } fn system_languages() -> Vec { + // The unit tests compare English prompts and messages through one + // process-wide loader, and every `apply_config` of a default config asks + // for the system language, so the machine's locale must never reach + // them — not even from a test that runs in parallel. + #[cfg(test)] + return vec!["en-US".parse().expect("en-US is a valid language identifier")]; + #[cfg(target_arch = "wasm32")] let mut requested = WebLanguageRequester::requested_languages(); #[cfg(not(target_arch = "wasm32"))] diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index 0ab068550..f9c7f4aa8 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -2371,7 +2371,10 @@ mod tests { } #[test] - fn arc_grips_drive_center_start_and_end_but_not_midpoint() { + /// The center grip drives the center; the start, end and midpoint grips + /// reshape the arc, so they drive the whole curve and a paired Equal or + /// Symmetric follows the new radius and sweep. + fn arc_grips_drive_center_or_the_whole_arc() { let handle = h(8); let arc = acadrust::EntityType::Arc(acadrust::entities::Arc::from_coords( 0.0, @@ -2386,15 +2389,14 @@ mod tests { grip_solve_anchor_refs(&arc, handle, 0), vec![ParametricRef::center(handle)] ); - assert_eq!( - grip_solve_anchor_refs(&arc, handle, 1), - vec![ParametricRef::point(handle, 0)] - ); - assert_eq!( - grip_solve_anchor_refs(&arc, handle, 2), - vec![ParametricRef::point(handle, 1)] - ); - assert_eq!(grip_solve_anchor_refs(&arc, handle, 3), Vec::new()); + for grip in 1..=3 { + assert_eq!( + grip_solve_anchor_refs(&arc, handle, grip), + vec![ParametricRef::whole(handle)], + "grip {grip}" + ); + } + assert_eq!(grip_solve_anchor_refs(&arc, handle, 4), Vec::new()); } #[test]