sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

coverage_policy_tests.rs (11611B)


      1 use std::{
      2     fs,
      3     path::PathBuf,
      4     time::{SystemTime, UNIX_EPOCH},
      5 };
      6 
      7 use super::{
      8     CoverageContract, LlvmCovMetric, enforce_metric, evaluate_report, metric_percent, path_matches,
      9     validate_contract, validate_metric,
     10 };
     11 
     12 const CONTRACT: &str = r#"
     13 [policy]
     14 enforce = true
     15 require_regions = true
     16 require_functions = true
     17 require_lines = true
     18 
     19 [toolchain]
     20 rust = "1.92.0"
     21 wasm_target = "wasm32-unknown-unknown"
     22 
     23 [report]
     24 output = "target/sdk-coverage/summary.json"
     25 ignore_filename_regex = "generated"
     26 
     27 [generated]
     28 typescript = "generated TypeScript is checked elsewhere"
     29 binding_crates = "generated binding crates are checked elsewhere"
     30 wasm_glue = "wasm glue is checked through package validation"
     31 
     32 [scopes.xtask_policy]
     33 paths = ["tools/xtask/src/coverage_policy.rs"]
     34 threshold = 100.0
     35 
     36 [exclusions.generated]
     37 paths = ["packages/*/src/generated/**"]
     38 reason = "generated output is checked through reproducibility"
     39 "#;
     40 
     41 #[derive(Clone, Copy)]
     42 struct Metrics {
     43     lines: (u64, u64, f64),
     44     functions: (u64, u64, f64),
     45     regions: (u64, u64, f64),
     46 }
     47 
     48 fn covered() -> Metrics {
     49     Metrics {
     50         lines: (100, 100, 100.0),
     51         functions: (50, 50, 100.0),
     52         regions: (200, 200, 100.0),
     53     }
     54 }
     55 
     56 fn contract(raw: &str) -> CoverageContract {
     57     toml::from_str::<CoverageContract>(raw).expect("contract parses")
     58 }
     59 
     60 fn test_root(name: &str) -> PathBuf {
     61     let stamp = SystemTime::now()
     62         .duration_since(UNIX_EPOCH)
     63         .expect("time")
     64         .as_nanos();
     65     let root = std::env::temp_dir().join(format!(
     66         "radroots_sdk_coverage_policy_{name}_{}_{}",
     67         std::process::id(),
     68         stamp
     69     ));
     70     let _ = fs::remove_dir_all(&root);
     71     fs::create_dir_all(&root).expect("create root");
     72     root
     73 }
     74 
     75 fn metric_json(metric: (u64, u64, f64)) -> String {
     76     format!(
     77         r#"{{"count":{},"covered":{},"percent":{}}}"#,
     78         metric.0, metric.1, metric.2
     79     )
     80 }
     81 
     82 fn summary_json(metrics: Metrics) -> String {
     83     format!(
     84         r#"{{"lines":{},"functions":{},"regions":{}}}"#,
     85         metric_json(metrics.lines),
     86         metric_json(metrics.functions),
     87         metric_json(metrics.regions)
     88     )
     89 }
     90 
     91 fn report_json(filename: &str, file_metrics: Metrics, totals: Metrics) -> String {
     92     format!(
     93         r#"{{"data":[{{"files":[{{"filename":"{}","summary":{}}}],"totals":{}}}]}}"#,
     94         filename,
     95         summary_json(file_metrics),
     96         summary_json(totals)
     97     )
     98 }
     99 
    100 fn write_report(root: &PathBuf, raw: &str) -> PathBuf {
    101     let report_path = root.join("summary.json");
    102     fs::write(&report_path, raw).expect("write report");
    103     report_path
    104 }
    105 
    106 fn scope_file(root: &PathBuf) -> String {
    107     root.join("tools/xtask/src/coverage_policy.rs")
    108         .display()
    109         .to_string()
    110 }
    111 
    112 #[test]
    113 fn validates_contract_shape() {
    114     validate_contract(&contract(CONTRACT)).expect("contract validates");
    115 }
    116 
    117 #[test]
    118 fn rejects_blank_contract_fields() {
    119     let cases = [
    120         ("rust = \"1.92.0\"", "rust = \" \"", "toolchain.rust"),
    121         (
    122             "wasm_target = \"wasm32-unknown-unknown\"",
    123             "wasm_target = \" \"",
    124             "toolchain.wasm_target",
    125         ),
    126         (
    127             "output = \"target/sdk-coverage/summary.json\"",
    128             "output = \" \"",
    129             "report.output",
    130         ),
    131         (
    132             "ignore_filename_regex = \"generated\"",
    133             "ignore_filename_regex = \" \"",
    134             "report.ignore_filename_regex",
    135         ),
    136         (
    137             "typescript = \"generated TypeScript is checked elsewhere\"",
    138             "typescript = \" \"",
    139             "generated.typescript",
    140         ),
    141         (
    142             "binding_crates = \"generated binding crates are checked elsewhere\"",
    143             "binding_crates = \" \"",
    144             "generated.binding_crates",
    145         ),
    146         (
    147             "wasm_glue = \"wasm glue is checked through package validation\"",
    148             "wasm_glue = \" \"",
    149             "generated.wasm_glue",
    150         ),
    151         (
    152             "paths = [\"tools/xtask/src/coverage_policy.rs\"]",
    153             "paths = [\" \"]",
    154             "scopes.xtask_policy.paths entry",
    155         ),
    156         (
    157             "reason = \"generated output is checked through reproducibility\"",
    158             "reason = \" \"",
    159             "exclusions.generated.reason",
    160         ),
    161         (
    162             "paths = [\"packages/*/src/generated/**\"]",
    163             "paths = [\" \"]",
    164             "exclusions.generated.paths entry",
    165         ),
    166     ];
    167 
    168     for (from, to, expected) in cases {
    169         let raw = CONTRACT.replace(from, to);
    170         let error = validate_contract(&contract(&raw)).expect_err("invalid contract");
    171         assert!(error.contains(expected), "{error}");
    172     }
    173 }
    174 
    175 #[test]
    176 fn rejects_contract_collection_errors() {
    177     let mut no_scopes = contract(CONTRACT);
    178     no_scopes.scopes.clear();
    179     assert_eq!(
    180         validate_contract(&no_scopes).unwrap_err(),
    181         "contracts/coverage.toml scopes must not be empty"
    182     );
    183 
    184     let mut no_exclusions = contract(CONTRACT);
    185     no_exclusions.exclusions.clear();
    186     assert_eq!(
    187         validate_contract(&no_exclusions).unwrap_err(),
    188         "contracts/coverage.toml exclusions must not be empty"
    189     );
    190 
    191     let cases = [
    192         (
    193             CONTRACT.replace("[scopes.xtask_policy]", "[scopes.\"\"]"),
    194             "scope name",
    195         ),
    196         (
    197             CONTRACT.replace("[exclusions.generated]", "[exclusions.\"\"]"),
    198             "exclusion name",
    199         ),
    200         (
    201             CONTRACT.replace("threshold = 100.0", "threshold = 101.0"),
    202             "scopes.xtask_policy.threshold",
    203         ),
    204         (
    205             CONTRACT.replace(
    206                 "paths = [\"tools/xtask/src/coverage_policy.rs\"]",
    207                 "paths = []",
    208             ),
    209             "scopes.xtask_policy.paths must not be empty",
    210         ),
    211         (
    212             CONTRACT.replace("paths = [\"packages/*/src/generated/**\"]", "paths = []"),
    213             "exclusions.generated.paths must not be empty",
    214         ),
    215     ];
    216 
    217     for (raw, expected) in cases {
    218         let error = validate_contract(&contract(&raw)).expect_err("invalid contract");
    219         assert!(error.contains(expected), "{error}");
    220     }
    221 }
    222 
    223 #[test]
    224 fn matches_recursive_scope_paths() {
    225     assert!(path_matches(
    226         "crates/sdk/src/**",
    227         "crates/sdk/src/adapters/radrootsd.rs"
    228     ));
    229     assert!(path_matches("crates/sdk/src/**", "crates/sdk/src"));
    230     assert!(path_matches(
    231         "tools/xtask/src/coverage_policy.rs",
    232         "tools/xtask/src/coverage_policy.rs"
    233     ));
    234     assert!(!path_matches(
    235         "crates/sdk/src/**",
    236         "crates/sql_wasm_runtime/src/lib.rs"
    237     ));
    238 }
    239 
    240 #[test]
    241 fn accepts_passing_reports_and_rejects_undercovered_scopes() {
    242     let root = test_root("passing_and_undercovered");
    243     let filename = scope_file(&root);
    244     let passing_report = report_json(&filename, covered(), covered());
    245     let report_path = write_report(&root, &passing_report);
    246     evaluate_report(&root, &report_path, &contract(CONTRACT)).expect("passing report");
    247 
    248     let mut undercovered = covered();
    249     undercovered.lines = (100, 99, 99.0);
    250     let failing_report = report_json(&filename, undercovered, covered());
    251     fs::write(&report_path, failing_report).expect("write failing report");
    252     let error = evaluate_report(&root, &report_path, &contract(CONTRACT))
    253         .expect_err("undercovered report rejected");
    254     assert!(error.contains("coverage scope xtask_policy"), "{error}");
    255     fs::remove_dir_all(root).expect("cleanup");
    256 }
    257 
    258 #[test]
    259 fn disabled_enforcement_accepts_measured_undercoverage() {
    260     let root = test_root("disabled");
    261     let filename = scope_file(&root);
    262     let mut undercovered = covered();
    263     undercovered.lines = (100, 0, 0.0);
    264     undercovered.functions = (50, 0, 0.0);
    265     undercovered.regions = (200, 0, 0.0);
    266     let report_path = write_report(&root, &report_json(&filename, undercovered, undercovered));
    267     let raw = CONTRACT.replace("enforce = true", "enforce = false");
    268     evaluate_report(&root, &report_path, &contract(&raw)).expect("disabled policy passes");
    269     fs::remove_dir_all(root).expect("cleanup");
    270 }
    271 
    272 #[test]
    273 fn rejects_unreadable_malformed_and_empty_reports() {
    274     let root = test_root("bad_reports");
    275     let missing = root.join("missing.json");
    276     assert!(evaluate_report(&root, &missing, &contract(CONTRACT)).is_err());
    277 
    278     let malformed = write_report(&root, "{");
    279     assert!(evaluate_report(&root, &malformed, &contract(CONTRACT)).is_err());
    280 
    281     fs::write(&malformed, r#"{"data":[]}"#).expect("write empty report");
    282     assert!(evaluate_report(&root, &malformed, &contract(CONTRACT)).is_err());
    283     fs::remove_dir_all(root).expect("cleanup");
    284 }
    285 
    286 #[test]
    287 fn rejects_required_total_metric_failures() {
    288     let root = test_root("total_metrics");
    289     let filename = scope_file(&root);
    290     let mut totals = covered();
    291     let cases = [
    292         Metrics {
    293             lines: (0, 0, 0.0),
    294             ..totals
    295         },
    296         {
    297             totals = covered();
    298             totals.functions = (0, 0, 0.0);
    299             totals
    300         },
    301         {
    302             totals = covered();
    303             totals.regions = (0, 0, 0.0);
    304             totals
    305         },
    306         {
    307             totals = covered();
    308             totals.lines = (1, 2, 200.0);
    309             totals
    310         },
    311     ];
    312 
    313     let report_path = root.join("summary.json");
    314     for total_metrics in cases {
    315         fs::write(
    316             &report_path,
    317             report_json(&filename, covered(), total_metrics),
    318         )
    319         .expect("write report");
    320         assert!(evaluate_report(&root, &report_path, &contract(CONTRACT)).is_err());
    321     }
    322     fs::remove_dir_all(root).expect("cleanup");
    323 }
    324 
    325 #[test]
    326 fn rejects_scope_metric_validation_and_missing_scope_files() {
    327     let root = test_root("scope_metrics");
    328     let filename = scope_file(&root);
    329     let other_filename = root
    330         .join("tools/xtask/src/coverage.rs")
    331         .display()
    332         .to_string();
    333     let report_path = root.join("summary.json");
    334 
    335     fs::write(
    336         &report_path,
    337         report_json(&other_filename, covered(), covered()),
    338     )
    339     .expect("write unmatched report");
    340     let error = evaluate_report(&root, &report_path, &contract(CONTRACT))
    341         .expect_err("unmatched scope rejected");
    342     assert!(error.contains("matched no report files"), "{error}");
    343 
    344     let mut invalid = covered();
    345     invalid.lines = (0, 0, 0.0);
    346     fs::write(&report_path, report_json(&filename, invalid, covered())).expect("write report");
    347     assert!(evaluate_report(&root, &report_path, &contract(CONTRACT)).is_err());
    348 
    349     invalid = covered();
    350     invalid.functions = (1, 2, 200.0);
    351     fs::write(&report_path, report_json(&filename, invalid, covered())).expect("write report");
    352     assert!(evaluate_report(&root, &report_path, &contract(CONTRACT)).is_err());
    353     fs::remove_dir_all(root).expect("cleanup");
    354 }
    355 
    356 #[test]
    357 fn metric_helpers_cover_edges() {
    358     let valid = LlvmCovMetric {
    359         count: 10,
    360         covered: 10,
    361         percent: 100.0,
    362     };
    363     validate_metric("lines", &valid, true).expect("metric validates");
    364     enforce_metric("lines", &valid, 100.0).expect("metric passes");
    365 
    366     let missing = LlvmCovMetric {
    367         count: 0,
    368         covered: 0,
    369         percent: 0.0,
    370     };
    371     assert!(validate_metric("lines", &missing, true).is_err());
    372     assert!(enforce_metric("lines", &missing, 100.0).is_err());
    373 
    374     let invalid = LlvmCovMetric {
    375         count: 1,
    376         covered: 2,
    377         percent: 200.0,
    378     };
    379     assert!(validate_metric("lines", &invalid, true).is_err());
    380     assert_eq!(metric_percent(0, 0), 0.0);
    381     assert_eq!(metric_percent(4, 2), 50.0);
    382 }