From 8e5e46b7b381608387fcd2fdd98a474a50f3d33a Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 22 Jun 2026 15:55:37 +0400 Subject: fix: make ci locked and isolate licensed tests --- .gitignore | 6 +- Cargo.lock | 308 +++++++++++++++++++++++++++++++ Cargo.toml | 2 +- apps/fparkan-game/src/main.rs | 1 + crates/fparkan-assets/src/lib.rs | 2 + crates/fparkan-corpus/src/lib.rs | 7 + crates/fparkan-fx/src/lib.rs | 2 + crates/fparkan-material/src/lib.rs | 1 + crates/fparkan-mission-format/src/lib.rs | 1 + crates/fparkan-msh/src/lib.rs | 2 + crates/fparkan-nres/src/lib.rs | 1 + crates/fparkan-prototype/src/lib.rs | 2 + crates/fparkan-resource/src/lib.rs | 1 + crates/fparkan-rsli/src/lib.rs | 4 + crates/fparkan-runtime/src/lib.rs | 5 + crates/fparkan-terrain-format/src/lib.rs | 3 + crates/fparkan-terrain/src/lib.rs | 2 + crates/fparkan-texm/src/lib.rs | 1 + xtask/src/main.rs | 103 ++++++++++- 19 files changed, 440 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 2c15862..3391914 100644 --- a/.gitignore +++ b/.gitignore @@ -69,10 +69,6 @@ $RECYCLE.BIN/ debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - # These are backup files generated by rustfmt **/*.rs.bk @@ -215,4 +211,4 @@ poetry.toml .ruff_cache/ # LSP config files -pyrightconfig.json \ No newline at end of file +pyrightconfig.json diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..edce011 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,308 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fparkan-animation" +version = "0.1.0" + +[[package]] +name = "fparkan-assets" +version = "0.1.0" +dependencies = [ + "fparkan-material", + "fparkan-msh", + "fparkan-nres", + "fparkan-path", + "fparkan-prototype", + "fparkan-resource", + "fparkan-texm", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-binary" +version = "0.1.0" + +[[package]] +name = "fparkan-cli" +version = "0.1.0" +dependencies = [ + "fparkan-corpus", + "fparkan-nres", + "fparkan-prototype", + "fparkan-resource", + "fparkan-rsli", + "fparkan-runtime", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-corpus" +version = "0.1.0" +dependencies = [ + "fparkan-path", +] + +[[package]] +name = "fparkan-diagnostics" +version = "0.1.0" + +[[package]] +name = "fparkan-fx" +version = "0.1.0" +dependencies = [ + "fparkan-binary", + "fparkan-nres", +] + +[[package]] +name = "fparkan-game" +version = "0.1.0" +dependencies = [ + "fparkan-render", + "fparkan-runtime", + "fparkan-vfs", + "fparkan-world", +] + +[[package]] +name = "fparkan-headless" +version = "0.1.0" +dependencies = [ + "fparkan-runtime", + "fparkan-vfs", + "fparkan-world", +] + +[[package]] +name = "fparkan-material" +version = "0.1.0" +dependencies = [ + "encoding_rs", + "fparkan-nres", + "fparkan-path", + "fparkan-resource", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-mission-format" +version = "0.1.0" +dependencies = [ + "encoding_rs", + "fparkan-binary", +] + +[[package]] +name = "fparkan-msh" +version = "0.1.0" +dependencies = [ + "encoding_rs", + "fparkan-animation", + "fparkan-nres", +] + +[[package]] +name = "fparkan-nres" +version = "0.1.0" +dependencies = [ + "fparkan-binary", + "fparkan-path", +] + +[[package]] +name = "fparkan-path" +version = "0.1.0" + +[[package]] +name = "fparkan-platform" +version = "0.1.0" + +[[package]] +name = "fparkan-platform-sdl" +version = "0.1.0" +dependencies = [ + "fparkan-platform", +] + +[[package]] +name = "fparkan-prototype" +version = "0.1.0" +dependencies = [ + "encoding_rs", + "fparkan-binary", + "fparkan-material", + "fparkan-msh", + "fparkan-nres", + "fparkan-path", + "fparkan-resource", + "fparkan-texm", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-render" +version = "0.1.0" +dependencies = [ + "fparkan-world", +] + +[[package]] +name = "fparkan-render-gl" +version = "0.1.0" +dependencies = [ + "fparkan-render", +] + +[[package]] +name = "fparkan-resource" +version = "0.1.0" +dependencies = [ + "fparkan-nres", + "fparkan-path", + "fparkan-rsli", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-rsli" +version = "0.1.0" +dependencies = [ + "flate2", +] + +[[package]] +name = "fparkan-runtime" +version = "0.1.0" +dependencies = [ + "fparkan-mission-format", + "fparkan-nres", + "fparkan-path", + "fparkan-platform", + "fparkan-prototype", + "fparkan-render", + "fparkan-resource", + "fparkan-terrain", + "fparkan-terrain-format", + "fparkan-vfs", + "fparkan-world", +] + +[[package]] +name = "fparkan-terrain" +version = "0.1.0" +dependencies = [ + "fparkan-nres", + "fparkan-terrain-format", +] + +[[package]] +name = "fparkan-terrain-format" +version = "0.1.0" +dependencies = [ + "fparkan-binary", + "fparkan-nres", +] + +[[package]] +name = "fparkan-test-support" +version = "0.1.0" +dependencies = [ + "fparkan-render", +] + +[[package]] +name = "fparkan-texm" +version = "0.1.0" +dependencies = [ + "fparkan-nres", +] + +[[package]] +name = "fparkan-vfs" +version = "0.1.0" +dependencies = [ + "fparkan-path", +] + +[[package]] +name = "fparkan-viewer" +version = "0.1.0" +dependencies = [ + "fparkan-msh", + "fparkan-nres", + "fparkan-render", + "fparkan-resource", + "fparkan-rsli", + "fparkan-terrain-format", + "fparkan-texm", + "fparkan-vfs", +] + +[[package]] +name = "fparkan-world" +version = "0.1.0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "fparkan-corpus", +] diff --git a/Cargo.toml b/Cargo.toml index a14eb8a..7c791db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ members = [ [workspace.package] version = "0.1.0" edition = "2021" -license = "MIT" +license = "GPL-2.0-only" repository = "https://github.com/valentineus/fparkan" [workspace.lints.rust] diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index ed12c70..2486cfc 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -262,6 +262,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn selected_is_and_is2_missions_produce_approved_render_captures() { for case in [ RenderCase { diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index 78ffb0b..20d08cb 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -426,6 +426,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn prepares_real_unit_asset_plan() { let root = fixture_root("IS"); let vfs: Arc = Arc::new(DirectoryVfs::new(&root)); @@ -448,6 +449,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn repository_plan_deduplicates_duplicate_visuals_but_graph_preserves_requests() { let root = fixture_root("IS"); let vfs: Arc = Arc::new(DirectoryVfs::new(&root)); diff --git a/crates/fparkan-corpus/src/lib.rs b/crates/fparkan-corpus/src/lib.rs index ba26c73..1504f01 100644 --- a/crates/fparkan-corpus/src/lib.rs +++ b/crates/fparkan-corpus/src/lib.rs @@ -442,6 +442,7 @@ mod tests { use std::time::{SystemTime, UNIX_EPOCH}; #[test] + #[ignore = "requires licensed corpus"] fn report_for_testdata_roots() { let root = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../..") @@ -457,6 +458,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part1_manifest_profile_and_counts_match_baseline() { let root = testdata_root("IS"); let manifest = discover(&root, DiscoverOptions::default()).expect("part 1 manifest"); @@ -473,6 +475,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part2_manifest_profile_and_counts_match_baseline() { let root = testdata_root("IS2"); let manifest = discover(&root, DiscoverOptions::default()).expect("part 2 manifest"); @@ -489,6 +492,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part1_has_no_casefold_relative_path_collisions() { let root = testdata_root("IS"); let manifest = discover(&root, DiscoverOptions::default()).expect("part 1 manifest"); @@ -497,6 +501,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part2_has_no_casefold_relative_path_collisions() { let root = testdata_root("IS2"); let manifest = discover(&root, DiscoverOptions::default()).expect("part 2 manifest"); @@ -505,11 +510,13 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part1_paths_stay_under_root() { assert_discovered_paths_stay_under_root("IS"); } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part2_paths_stay_under_root() { assert_discovered_paths_stay_under_root("IS2"); } diff --git a/crates/fparkan-fx/src/lib.rs b/crates/fparkan-fx/src/lib.rs index 9675507..fb8adff 100644 --- a/crates/fparkan-fx/src/lib.rs +++ b/crates/fparkan-fx/src/lib.rs @@ -838,6 +838,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_fxid_exact_eof_and_distribution() { for (corpus, expected_count) in [("IS", 923_usize), ("IS2", 1065_usize)] { let Some(root) = corpus_root(corpus) else { @@ -886,6 +887,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_fxid_emission_captures_are_approved() { for (corpus, expected_count, expected_emitting, expected_hash) in [ ("IS", 923_usize, 467_usize, 10_553_431_922_547_057_702_u64), diff --git a/crates/fparkan-material/src/lib.rs b/crates/fparkan-material/src/lib.rs index 780a1ae..a7ec5d7 100644 --- a/crates/fparkan-material/src/lib.rs +++ b/crates/fparkan-material/src/lib.rs @@ -1092,6 +1092,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_mat0_and_wear_parse() { for (corpus, expected_mat0, expected_archive_wear, expected_standalone_wear) in [ ("IS", 905_usize, 439_usize, 95_usize), diff --git a/crates/fparkan-mission-format/src/lib.rs b/crates/fparkan-mission-format/src/lib.rs index edbe908..0c85c39 100644 --- a/crates/fparkan-mission-format/src/lib.rs +++ b/crates/fparkan-mission-format/src/lib.rs @@ -979,6 +979,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_tma_validate() { for ( corpus, diff --git a/crates/fparkan-msh/src/lib.rs b/crates/fparkan-msh/src/lib.rs index f06c8d6..3ec3def 100644 --- a/crates/fparkan-msh/src/lib.rs +++ b/crates/fparkan-msh/src/lib.rs @@ -1236,6 +1236,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_msh_assets_validate() { for (corpus, expected) in [("IS", 435_usize), ("IS2", 511_usize)] { let Some(root) = corpus_root(corpus) else { @@ -1279,6 +1280,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_animation_streams_sample_approved_pose_captures() { for ( corpus, diff --git a/crates/fparkan-nres/src/lib.rs b/crates/fparkan-nres/src/lib.rs index f2bd106..44d9c93 100644 --- a/crates/fparkan-nres/src/lib.rs +++ b/crates/fparkan-nres/src/lib.rs @@ -1779,6 +1779,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_nres_roundtrip_gates() { let part1 = corpus_gate("IS", 120, 6_804).expect("part 1 NRes gate"); let part2 = corpus_gate("IS2", 134, 8_171).expect("part 2 NRes gate"); diff --git a/crates/fparkan-prototype/src/lib.rs b/crates/fparkan-prototype/src/lib.rs index 4efafa1..35089b0 100644 --- a/crates/fparkan-prototype/src/lib.rs +++ b/crates/fparkan-prototype/src/lib.rs @@ -1826,6 +1826,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_unit_dat_parse_counts() { let cases = [("IS", 425, 5_219), ("IS2", 676, 8_145)]; for (corpus, expected_files, expected_records) in cases { @@ -1859,6 +1860,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_registry_payloads_are_record_aligned() { for corpus in ["IS", "IS2"] { let root = corpus_root(corpus).expect("corpus root"); diff --git a/crates/fparkan-resource/src/lib.rs b/crates/fparkan-resource/src/lib.rs index aa6de70..7dd90b5 100644 --- a/crates/fparkan-resource/src/lib.rs +++ b/crates/fparkan-resource/src/lib.rs @@ -696,6 +696,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_repository_reads_nres_and_rsli() { licensed_repository_gate("IS").expect("part 1 repository gate"); licensed_repository_gate("IS2").expect("part 2 repository gate"); diff --git a/crates/fparkan-rsli/src/lib.rs b/crates/fparkan-rsli/src/lib.rs index 59b4c67..0d315ff 100644 --- a/crates/fparkan-rsli/src/lib.rs +++ b/crates/fparkan-rsli/src/lib.rs @@ -1742,6 +1742,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_rsli_roundtrip_gates() { let part1 = corpus_gate("IS", 2).expect("part 1 RsLi gate"); let part2 = corpus_gate("IS2", 2).expect("part 2 RsLi gate"); @@ -1751,6 +1752,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part1_rsli_method_distribution_baseline() { let stats = corpus_gate("IS", 2).expect("part 1 RsLi gate"); @@ -1770,6 +1772,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_part2_rsli_method_distribution_baseline() { let stats = corpus_gate("IS2", 2).expect("part 2 RsLi gate"); @@ -1789,6 +1792,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_rsli_quirk_is_only_approved_interf8_tex() { let part1 = corpus_gate("IS", 2).expect("part 1 RsLi gate"); let part2 = corpus_gate("IS2", 2).expect("part 2 RsLi gate"); diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 2a05c4a..4bc9e25 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -695,6 +695,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn load_trace_records_preparation_before_registration_and_raw_transforms() { let root = workspace_root().join("testdata").join("IS"); let vfs: Arc = Arc::new(DirectoryVfs::new(&root)); @@ -736,6 +737,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn missing_map_and_missing_reachable_resource_fail_before_registration() { let root = workspace_root().join("testdata").join("IS"); for (denied, mission) in [ @@ -779,6 +781,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn registration_phase_failure_uses_normal_teardown_and_keeps_engine_world() { let root = workspace_root().join("testdata").join("IS"); let vfs: Arc = Arc::new(DirectoryVfs::new(root)); @@ -816,6 +819,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn selected_is_and_is2_missions_execute_10000_deterministic_ticks() { for case in [ HeadlessCase { @@ -849,6 +853,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpora_load_all_mission_foundations() { let root = workspace_root(); let part1 = load_all(&root.join("testdata").join("IS")); diff --git a/crates/fparkan-terrain-format/src/lib.rs b/crates/fparkan-terrain-format/src/lib.rs index 8b97d79..8fd17ef 100644 --- a/crates/fparkan-terrain-format/src/lib.rs +++ b/crates/fparkan-terrain-format/src/lib.rs @@ -1488,6 +1488,7 @@ Generator 1 } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_land_msh_validate() { for (corpus, expected_files, expected_vertices, expected_faces) in [ ("IS", 33_usize, 299_450_usize, 275_882_usize), @@ -1536,6 +1537,7 @@ Generator 1 } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_build_dat_validate() { for (corpus, expected_ai_prefix) in [("IS", false), ("IS2", true)] { let Some(root) = corpus_root(corpus) else { @@ -1583,6 +1585,7 @@ Generator 1 } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_land_map_validate() { for (corpus, expected_files, expected_areals, expected_vertices, expected_max_hits) in [ ("IS", 33_usize, 34_662_usize, 197_698_usize, 20_usize), diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index b28fca6..92f36dd 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -794,6 +794,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_land_maps_build_navigation_worlds() { for (corpus, expected_files, expected_areals) in [ ("IS", 33_usize, 34_662_usize), @@ -849,6 +850,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_land_meshes_build_surface_worlds() { for (corpus, expected_files, expected_faces) in [ ("IS", 33_usize, 275_882_usize), diff --git a/crates/fparkan-texm/src/lib.rs b/crates/fparkan-texm/src/lib.rs index 6adc8b1..fef5369 100644 --- a/crates/fparkan-texm/src/lib.rs +++ b/crates/fparkan-texm/src/lib.rs @@ -1071,6 +1071,7 @@ mod tests { } #[test] + #[ignore = "requires licensed corpus"] fn licensed_corpus_texm_assets_validate_and_decode_mip0() { for (corpus, expected) in [("IS", 518_usize), ("IS2", 631_usize)] { let Some(root) = corpus_root(corpus) else { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 4141d92..722053a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -27,8 +27,8 @@ fn run(args: &[String]) -> Result<(), String> { [cmd] if cmd == "ci" => { run_rustfmt_check(Path::new("."))?; run_policy(Path::new("."))?; - cargo(&["test", "--workspace", "--offline"])?; - clippy_rustup(&["--workspace", "--offline"])?; + cargo(&["test", "--workspace", "--locked", "--offline"])?; + clippy_rustup(&["--workspace", "--locked", "--offline"])?; Ok(()) } [cmd] if cmd == "policy" => run_policy(Path::new(".")), @@ -46,12 +46,12 @@ fn run(args: &[String]) -> Result<(), String> { } [cmd, suite, rest @ ..] if cmd == "test" && suite == "synthetic" => { let options = parse_test_options(rest, PathBuf::from("testdata"))?; - run_stage_tests(options.stage) + run_stage_tests(options.stage, TestSuite::Synthetic) } [cmd, suite, rest @ ..] if cmd == "test" && suite == "licensed" => { let options = parse_test_options(rest, PathBuf::from("testdata"))?; validate_licensed_root(&options.root)?; - run_stage_tests(options.stage) + run_stage_tests(options.stage, TestSuite::Licensed) } [cmd, subcmd, rest @ ..] if cmd == "corpus" && subcmd == "baseline" => { let root = parse_root(rest)?; @@ -248,6 +248,7 @@ fn run_package(options: &PackageOptions) -> Result<(), String> { "-p".to_string(), options.app.package().to_string(), "--release".to_string(), + "--locked".to_string(), "--offline".to_string(), "--target".to_string(), options.target.clone(), @@ -258,6 +259,8 @@ fn run_policy(root: &Path) -> Result<(), String> { let mut failures = Vec::new(); scan_policy_dir(root, &mut failures)?; validate_cargo_metadata(root, &mut failures)?; + validate_lockfile(root, &mut failures); + validate_workspace_license(root, &mut failures)?; validate_dependency_boundaries(root, &mut failures)?; if failures.is_empty() { Ok(()) @@ -278,6 +281,7 @@ fn validate_cargo_metadata(root: &Path, failures: &mut Vec) -> Result<() "--format-version", "1", "--offline", + "--locked", "--no-deps", "--manifest-path", ]) @@ -295,6 +299,62 @@ fn validate_cargo_metadata(root: &Path, failures: &mut Vec) -> Result<() Ok(()) } +fn validate_lockfile(root: &Path, failures: &mut Vec) { + let lockfile = root.join("Cargo.lock"); + if !lockfile.is_file() { + failures.push(format!( + "{}: workspace lockfile is required for locked/offline builds", + lockfile.display() + )); + } +} + +fn validate_workspace_license(root: &Path, failures: &mut Vec) -> Result<(), String> { + let manifest = root.join("Cargo.toml"); + let license = fs::read_to_string(root.join("LICENSE.txt")) + .map_err(|err| format!("{}: {err}", root.join("LICENSE.txt").display()))?; + let expected = if license.contains("GNU GENERAL PUBLIC LICENSE") + && license.contains("Version 2, June 1991") + { + "GPL-2.0-only" + } else { + failures.push(format!( + "{}: unsupported repository license text", + root.join("LICENSE.txt").display() + )); + return Ok(()); + }; + + let mut manifests = Vec::new(); + collect_cargo_manifests(root, &mut manifests)?; + manifests.push(manifest); + manifests.sort(); + manifests.dedup(); + + for manifest in manifests { + let text = fs::read_to_string(&manifest) + .map_err(|err| format!("{}: {err}", manifest.display()))?; + let explicit_license = parse_manifest_license(&text); + let is_root = manifest == root.join("Cargo.toml"); + if is_root { + if explicit_license.as_deref() != Some(expected) { + failures.push(format!( + "{}: workspace.package license must be {expected}", + manifest.display() + )); + } + } else if let Some(license) = explicit_license { + if license != expected { + failures.push(format!( + "{}: package license {license} does not match repository license {expected}", + manifest.display() + )); + } + } + } + Ok(()) +} + fn validate_dependency_boundaries(root: &Path, failures: &mut Vec) -> Result<(), String> { let mut manifests = Vec::new(); collect_cargo_manifests(root, &mut manifests)?; @@ -357,6 +417,23 @@ fn collect_cargo_manifests(dir: &Path, out: &mut Vec) -> Result<(), Str Ok(()) } +fn parse_manifest_license(manifest: &str) -> Option { + let mut in_package = false; + let mut in_workspace_package = false; + for line in manifest.lines() { + let trimmed = line.trim(); + if trimmed.starts_with('[') { + in_package = trimmed == "[package]"; + in_workspace_package = trimmed == "[workspace.package]"; + continue; + } + if (in_package || in_workspace_package) && trimmed.starts_with("license") { + return parse_toml_string_value(trimmed); + } + } + None +} + fn parse_package_name(manifest: &str) -> Option { let mut in_package = false; for line in manifest.lines() { @@ -1082,7 +1159,7 @@ fn run_acceptance_report(options: &AcceptanceOptions) -> Result<(), String> { if options.suite == TestSuite::Licensed { validate_licensed_root(&options.root)?; } - run_stage_tests(options.stage)?; + run_stage_tests(options.stage, options.suite)?; if let Some(parent) = options.out.parent() { fs::create_dir_all(parent).map_err(|err| format!("{}: {err}", parent.display()))?; @@ -1131,12 +1208,22 @@ fn stage_report_packages(stage: Stage) -> Vec<&'static str> { } } -fn run_stage_tests(stage: Stage) -> Result<(), String> { +fn run_stage_tests(stage: Stage, suite: TestSuite) -> Result<(), String> { + let mut suffix = Vec::new(); + if suite == TestSuite::Licensed { + suffix.extend(["--", "--ignored"]); + } match stage { - Stage::All => cargo(&["test", "--workspace", "--offline"]), + Stage::All => { + let mut args = vec!["test", "--workspace", "--locked", "--offline"]; + args.extend(suffix); + cargo(&args) + } Stage::Number(number) => { for package in stage_packages(number)? { - cargo(&["test", "-p", package, "--offline"])?; + let mut args = vec!["test", "-p", package, "--locked", "--offline"]; + args.extend(suffix.iter().copied()); + cargo(&args)?; } Ok(()) } -- cgit v1.2.3