#[path = "support/workspace_root.rs"]
#[allow(dead_code)]
mod workspace_root;
use std::{collections::BTreeSet, path::Path};
fn read_rel(rel: &str) -> String {
std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(rel))
.unwrap_or_else(|e| panic!("read {rel}: {e}"))
}
fn manifest() -> String {
read_rel("Cargo.toml")
}
fn parity_manifest() -> String {
read_rel("../coremlit-parity/Cargo.toml")
}
fn ci_yml() -> String {
let path = workspace_root::workspace_root().join(".github/workflows/ci.yml");
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}
fn expected_features() -> Vec<(&'static str, Vec<&'static str>)> {
vec![
("default", vec![]),
("serde", vec!["dep:serde", "windit?/serde"]),
("tracing", vec!["dep:tracing"]),
(
"whisper",
vec![
"dep:libc",
"dep:mach2",
"dep:rand",
"dep:serde_json",
"dep:tokenizers",
"dep:unicode_categories",
],
),
(
"nl-recognizer",
vec!["whisper", "dep:objc2-natural-language"],
),
(
"align",
vec![
"dep:asry",
"dep:humantime",
"dep:serde_json",
"dep:serde",
"dep:tokenizers",
],
),
("align-oracle", vec!["align", "asry/alignment"]),
("speaker", vec!["dep:diaric"]),
("vad", vec!["dep:zuoer"]),
(
"clap",
vec![
"dep:rustfft",
"dep:tokenizers",
"dep:windit",
"dep:serde_json",
],
),
(
"granite",
vec![
"dep:tokenizers",
"dep:windit",
"windit/text",
"dep:sha2",
"dep:serde_json",
],
),
(
"ced",
vec!["dep:rustfft", "dep:soundevents-dataset", "dep:windit"],
),
("lid", vec!["dep:rustfft", "dep:windit"]),
("identity", vec!["dep:rustfft"]),
(
"siglip",
vec!["dep:tokenizers", "dep:pixon", "dep:sha2", "dep:serde_json"],
),
("face", vec!["dep:sha2"]),
("commercial-face-arcface", vec!["face"]),
]
}
fn expected_parity_features() -> Vec<(&'static str, Vec<&'static str>)> {
vec![
("default", vec![]),
(
"speaker-oracle",
vec![
"coremlit/speaker",
"dep:dia",
"dep:diaric",
"dia/ort",
"dia/bundled-segmentation",
],
),
("clap-oracle", vec!["coremlit/clap", "dep:textclap"]),
(
"vad-bundled",
vec!["coremlit/vad", "dep:silero", "silero/bundled"],
),
]
}
const BARE_CRATE_MAP: &[(&str, &str)] = &[
("whisperkit", "whisper"),
("alignkit", "align"),
("speakerkit", "speaker"),
("vadkit", "vad"),
("clapkit", "clap"),
];
const INTENDED_CI_COMBOS: &[&str] = &[
"", "whisper",
"align",
"speaker",
"speaker,serde",
"vad",
"whisper,vad",
"align-oracle",
"clap",
"granite",
"siglip",
"ced",
"lid",
"identity",
"identity,speaker",
"face",
"commercial-face-arcface",
"whisper,align,speaker,vad,clap,granite,siglip,ced,lid,identity,face,serde,tracing,nl-recognizer",
"whisper,align-oracle,speaker,vad,clap,granite,siglip,ced,lid,identity,face,serde,tracing,nl-recognizer",
];
const INTENDED_PARITY_CI_COMBOS: &[&str] = &[
"speaker-oracle",
"clap-oracle",
"vad-bundled",
"speaker-oracle,clap-oracle,vad-bundled",
];
fn features_block(manifest: &str) -> String {
let mut out = String::new();
let mut in_features = false;
for line in manifest.lines() {
if line.starts_with('[') {
in_features = line.trim() == "[features]";
continue;
}
if in_features {
out.push_str(line);
out.push('\n');
}
}
out
}
fn feature_names(block: &str) -> BTreeSet<String> {
let mut names = BTreeSet::new();
for line in block.lines() {
if line.starts_with(char::is_whitespace) {
continue;
}
let trimmed = line.trim_start();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some((key, _)) = line.split_once('=') {
let key = key.trim();
if !key.is_empty() && !key.contains(char::is_whitespace) {
names.insert(key.to_string());
}
}
}
names
}
fn feature_deps(block: &str, feature: &str) -> BTreeSet<String> {
let mut collecting = false;
let mut buf = String::new();
for line in block.lines() {
if collecting {
buf.push('\n');
buf.push_str(line);
if line.contains(']') {
break;
}
continue;
}
if line.starts_with(char::is_whitespace) {
continue;
}
let Some((key, rest)) = line.split_once('=') else {
continue;
};
if key.trim() != feature {
continue;
}
collecting = true;
buf.push_str(rest);
if rest.contains(']') {
break;
}
}
buf
.split('"')
.skip(1)
.step_by(2)
.map(str::to_string)
.collect()
}
const JOB_INDENT: usize = 2;
fn ci_feature_combos(yaml: &str, job: &str) -> BTreeSet<String> {
let job_key = format!("{job}:");
let mut combos = BTreeSet::new();
let mut in_job = false;
let mut seen_matrix = false;
let mut key_indent: Option<usize> = None;
for line in yaml.lines() {
let indent = line.len() - line.trim_start().len();
let trimmed = line.trim_start();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if !in_job {
in_job = indent == JOB_INDENT && trimmed == job_key;
continue;
}
if indent <= JOB_INDENT {
break;
}
let Some(ki) = key_indent else {
if trimmed == "matrix:" {
seen_matrix = true;
} else if seen_matrix && trimmed == "features:" {
key_indent = Some(indent);
}
continue;
};
if indent <= ki {
break;
}
if let Some(inner) = trimmed.strip_prefix("- ").and_then(quoted_inner) {
combos.insert(inner);
}
}
combos
}
fn quoted_inner(s: &str) -> Option<String> {
let start = s.find('"')?;
let rest = &s[start + 1..];
let end = rest.find('"')?;
Some(rest[..end].to_string())
}
fn owned(combos: &[&str]) -> BTreeSet<String> {
combos.iter().map(|s| (*s).to_string()).collect()
}
fn intended_ci_combos() -> BTreeSet<String> {
owned(INTENDED_CI_COMBOS)
}
fn rename_table_rows(doc: &str) -> Vec<Vec<String>> {
let mut rows = Vec::new();
let mut in_table = false;
for line in doc.lines() {
if let Some(heading) = line.strip_prefix("## ") {
in_table = heading.contains("Rename table");
continue;
}
if !in_table {
continue;
}
let line = line.trim();
if !line.starts_with('|') {
continue;
}
let cells: Vec<String> = line
.trim_matches('|')
.split('|')
.map(|c| c.trim().to_string())
.collect();
if cells.iter().any(|c| c == "Old crate") {
continue;
}
if cells
.iter()
.all(|c| !c.is_empty() && c.chars().all(|ch| ch == '-'))
{
continue;
}
rows.push(cells);
}
rows
}
fn unbacktick(cell: &str) -> &str {
cell.trim_matches('`')
}
#[test]
fn feature_names_match_the_pinned_set() {
let actual = feature_names(&features_block(&manifest()));
let expected: BTreeSet<String> = expected_features()
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
assert_eq!(
actual, expected,
"Cargo.toml [features] names drifted from the pinned flat feature set (FEATURE_MAP.md)"
);
}
#[test]
fn oracle_features_are_not_this_crates() {
let names = feature_names(&features_block(&manifest()));
for moved in ["speaker-oracle", "clap-oracle", "vad-bundled"] {
assert!(
!names.contains(moved),
"`{moved}` is declared on coremlit again — it belongs to coremlit-parity, whose \
oracle deps (`dia`, `textclap`) are unpublished git sources cargo publish rejects"
);
}
assert!(
names.contains("align-oracle"),
"`align-oracle` must STAY on coremlit: it only enables a feature of `asry`, a dependency \
this crate has either way, so moving it would relocate code without removing a git dep"
);
}
#[test]
fn parity_feature_names_match_the_pinned_set() {
let actual = feature_names(&features_block(&parity_manifest()));
let expected: BTreeSet<String> = expected_parity_features()
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
assert_eq!(
actual, expected,
"coremlit-parity [features] names drifted from the pinned oracle feature set"
);
}
#[test]
fn parity_feature_deps_are_pinned_with_no_cross_oracle_leakage() {
let block = features_block(&parity_manifest());
for (name, deps) in expected_parity_features() {
let actual = feature_deps(&block, name);
let expected: BTreeSet<String> = deps.iter().map(|d| (*d).to_string()).collect();
assert_eq!(
actual, expected,
"coremlit-parity feature `{name}` dependency set drifted"
);
}
}
#[test]
fn parity_crate_is_never_published() {
assert!(
parity_manifest()
.lines()
.any(|l| l.trim() == "publish = false"),
"coremlit-parity must declare `publish = false` — it depends on unpublished git sources"
);
}
#[test]
fn old_per_crate_feature_names_are_gone() {
let names = feature_names(&features_block(&manifest()));
for old in ["dia", "dia-oracle", "parity-oracle", "vadkit", "bundled"] {
assert!(
!names.contains(old),
"old per-crate feature `{old}` is still declared — the rename table maps it away"
);
}
}
#[test]
fn feature_deps_are_pinned_with_no_cross_kit_leakage() {
let block = features_block(&manifest());
for (name, deps) in expected_features() {
let actual = feature_deps(&block, name);
let expected: BTreeSet<String> = deps.iter().map(|d| (*d).to_string()).collect();
assert_eq!(
actual, expected,
"feature `{name}` dependency set drifted (cross-kit leakage or a dropped/added dep)"
);
}
}
#[test]
fn rename_table_pins_every_bare_crate_row() {
let rows = rename_table_rows(&read_rel("FEATURE_MAP.md"));
assert!(
rows.len() >= BARE_CRATE_MAP.len(),
"rename-table parse found only {} row(s) — the parser or the table shape broke",
rows.len()
);
for (kit, feature) in BARE_CRATE_MAP {
let found = rows
.iter()
.any(|r| r.len() >= 3 && r[0] == *kit && r[1] == "(crate)" && unbacktick(&r[2]) == *feature);
assert!(
found,
"FEATURE_MAP.md rename table must map bare crate `{kit}` | (crate) | `{feature}` \
(a removed or altered bare-crate row)"
);
}
}
fn assert_combo_sets_eq(actual: &BTreeSet<String>, expected: &BTreeSet<String>, what: &str) {
let missing: Vec<&String> = expected.difference(actual).collect();
let unexpected: Vec<&String> = actual.difference(expected).collect();
assert!(
missing.is_empty() && unexpected.is_empty(),
"{what} drifted from the pinned curated set — missing (pinned but not in the \
active matrix): {missing:?}; unexpected (in the active matrix but not pinned): {unexpected:?}"
);
}
#[test]
fn ci_pins_the_curated_feature_combos() {
assert_combo_sets_eq(
&ci_feature_combos(&ci_yml(), "features"),
&intended_ci_combos(),
"ci.yml `features` job matrix",
);
}
#[test]
fn ci_pins_the_curated_parity_combos() {
assert_combo_sets_eq(
&ci_feature_combos(&ci_yml(), "parity"),
&owned(INTENDED_PARITY_CI_COMBOS),
"ci.yml `parity` job matrix",
);
}
const DOCTORED_MATRIX: &str = r#"
jobs:
check:
runs-on: macos-15
steps:
- run: cargo build --features whisper --examples
features:
runs-on: macos-15
strategy:
fail-fast: false
matrix:
features:
- ""
- "whisper"
- "align"
- "speaker"
- "speaker,serde"
- "vad"
- "whisper,vad"
- "align-oracle"
- "clap"
- "granite"
- "siglip"
- "ced"
- "lid"
- "identity"
- "identity,speaker"
- "face"
- "commercial-face-arcface"
- "whisper,align,speaker,vad,clap,granite,siglip,ced,lid,identity,face,serde,tracing,nl-recognizer"
- "whisper,align-oracle,speaker,vad,clap,granite,siglip,ced,lid,identity,face,serde,tracing,nl-recognizer"
steps:
- uses: actions/checkout@v7
parity:
runs-on: macos-15
strategy:
fail-fast: false
matrix:
features:
- "speaker-oracle"
- "clap-oracle"
- "vad-bundled"
- "speaker-oracle,clap-oracle,vad-bundled"
steps:
- uses: actions/checkout@v7
"#;
#[test]
fn ci_combo_parser_reads_the_wellformed_matrix() {
assert_combo_sets_eq(
&ci_feature_combos(DOCTORED_MATRIX, "features"),
&intended_ci_combos(),
"well-formed doctored matrix",
);
}
#[test]
fn ci_combo_parser_scopes_each_job_separately() {
let features = ci_feature_combos(DOCTORED_MATRIX, "features");
let parity = ci_feature_combos(DOCTORED_MATRIX, "parity");
assert_combo_sets_eq(
&parity,
&owned(INTENDED_PARITY_CI_COMBOS),
"well-formed doctored parity matrix",
);
assert!(
features.is_disjoint(&parity),
"the two jobs' parsed combo sets bled into each other: {features:?} vs {parity:?}"
);
}
#[test]
fn ci_combo_parser_returns_empty_for_an_absent_job() {
assert!(
ci_feature_combos(DOCTORED_MATRIX, "no-such-job").is_empty(),
"an absent job must parse to no combos at all"
);
}
#[test]
fn ci_combo_check_reds_when_bare_core_is_deleted() {
let doctored = DOCTORED_MATRIX.replace(" - \"\"\n", "");
assert_ne!(
ci_feature_combos(&doctored, "features"),
intended_ci_combos(),
"deleting the bare-core `- \"\"` entry must make the parsed set differ from the pinned set"
);
}
#[test]
fn ci_combo_check_reds_when_bare_core_is_commented_out() {
let doctored = DOCTORED_MATRIX.replace(" - \"\"\n", " # - \"\"\n");
assert_ne!(
ci_feature_combos(&doctored, "features"),
intended_ci_combos(),
"commenting out the bare-core `- \"\"` entry must make the parsed set differ from the pinned set"
);
}
#[test]
fn ci_combo_check_reds_when_a_combo_is_dropped() {
let doctored = DOCTORED_MATRIX.replace(" - \"whisper,vad\"\n", "");
assert_ne!(
ci_feature_combos(&doctored, "features"),
intended_ci_combos(),
"dropping the `whisper,vad` combo must make the parsed set differ from the pinned set"
);
}