fix: make ci locked and isolate licensed tests

This commit is contained in:
2026-06-22 15:55:37 +04:00
parent d0bdbaa1ed
commit 8e5e46b7b3
19 changed files with 440 additions and 14 deletions
+1 -5
View File
@@ -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
pyrightconfig.json
Generated
+308
View File
@@ -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",
]
+1 -1
View File
@@ -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]
+1
View File
@@ -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 {
+2
View File
@@ -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<dyn Vfs> = 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<dyn Vfs> = Arc::new(DirectoryVfs::new(&root));
+7
View File
@@ -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");
}
+2
View File
@@ -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),
+1
View File
@@ -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),
+1
View File
@@ -979,6 +979,7 @@ mod tests {
}
#[test]
#[ignore = "requires licensed corpus"]
fn licensed_corpus_tma_validate() {
for (
corpus,
+2
View File
@@ -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,
+1
View File
@@ -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");
+2
View File
@@ -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");
+1
View File
@@ -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");
+4
View File
@@ -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");
+5
View File
@@ -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<dyn Vfs> = 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<dyn Vfs> = 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"));
+3
View File
@@ -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),
+2
View File
@@ -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),
+1
View File
@@ -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 {
+95 -8
View File
@@ -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<String>) -> Result<()
"--format-version",
"1",
"--offline",
"--locked",
"--no-deps",
"--manifest-path",
])
@@ -295,6 +299,62 @@ fn validate_cargo_metadata(root: &Path, failures: &mut Vec<String>) -> Result<()
Ok(())
}
fn validate_lockfile(root: &Path, failures: &mut Vec<String>) {
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<String>) -> 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<String>) -> 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<PathBuf>) -> Result<(), Str
Ok(())
}
fn parse_manifest_license(manifest: &str) -> Option<String> {
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<String> {
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(())
}