use std::collections::BTreeMap;
pub const C1_COVER: &[&str] = &[
"tree",
"find",
"find_one",
"find_all",
"describe",
"focused",
"screenshot",
"system_popups",
"launch",
"launch_fresh",
"terminate",
"install",
"uninstall",
"open_url",
"send_push",
"mark_fixture_action",
"foreground",
"tap",
"tap_with_mode",
"tap_xcui",
"tap_at_coord",
"fill",
"clear",
"press_key",
"scroll",
"swipe_once",
"hide_keyboard",
"go_back",
"system_popup_action",
"wait_for",
"assert_visible",
"assert_enabled",
"assert_text",
"text",
"text_regex",
"id",
"label",
"role",
"role_named",
"plan_launch_fresh_calls",
"find_norm_coord", "find_by_text_ocr", "tap_by_text_ocr", "webview_eval", "swipe_at_coord", "scroll_screen", "assert_not_visible", "wait_for_not_visible", "set_permission", "launch_app_with_options", "set_clipboard", "get_clipboard", "set_location", "travel", "set_orientation", "start_recording", "stop_recording", "assert_screenshot", "double_tap", "long_press", "copy_text_from", "set_permissions", ];
pub const C2_DEFERRED: &[&str] = &[
"paste_text",
"add_media",
];
pub const PERMANENT_SKIP: &[&str] = &[
"new",
"new_with",
"connect_to_runner",
"connect_to_runner_android",
"with_udid",
"udid",
"with_bundle_id",
"with_auto_activate",
"with_force_key_events",
"driver",
"simctl",
"device",
"start_capsule_recording",
"stop_capsule_recording_and_reconcile",
"to_driver",
"assert_screenshot_inner",
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VerifyError {
Unclassified(Vec<String>),
Duplicate {
name: String,
first_list: &'static str,
second_list: &'static str,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct VerifyReport {
pub sdk_pub_fn_count: usize,
pub classified_count: usize,
pub c1_cover_count: usize,
pub c2_deferred_count: usize,
pub permanent_skip_count: usize,
pub coverage_pct: f64,
}
impl VerifyReport {
pub fn from_counts(sdk_count: usize, c1: usize, c2: usize, skip: usize) -> Self {
let classified = c1 + c2 + skip;
let coverage_pct = if sdk_count <= skip {
0.0
} else {
(c1 as f64) / ((sdk_count - skip) as f64) * 100.0
};
Self {
sdk_pub_fn_count: sdk_count,
classified_count: classified,
c1_cover_count: c1,
c2_deferred_count: c2,
permanent_skip_count: skip,
coverage_pct,
}
}
}
pub fn grep_pub_fn(lib_rs: &str) -> Vec<String> {
let mut names = Vec::new();
for raw in lib_rs.lines() {
let line = raw.trim_start();
if line.starts_with("//") {
continue;
}
let rest = if let Some(r) = line.strip_prefix("pub fn ") {
r
} else if let Some(r) = line.strip_prefix("pub async fn ") {
r
} else {
continue;
};
let end = rest.find(['(', '<']).unwrap_or(rest.len());
let name = rest[..end].trim();
if !name.is_empty() {
names.push(name.to_string());
}
}
names
}
pub fn verify_cover(
lib_rs: &str,
c1_cover: &[&str],
c2_deferred: &[&str],
permanent_skip: &[&str],
) -> Result<VerifyReport, VerifyError> {
let mut where_listed: BTreeMap<&str, &'static str> = BTreeMap::new();
for (list_name, list) in [
("C1_COVER", c1_cover),
("C2_DEFERRED", c2_deferred),
("PERMANENT_SKIP", permanent_skip),
] {
for name in list {
if let Some(prev) = where_listed.insert(name, list_name) {
return Err(VerifyError::Duplicate {
name: (*name).to_string(),
first_list: prev,
second_list: list_name,
});
}
}
}
let pub_fns = grep_pub_fn(lib_rs);
let mut unclassified = Vec::new();
for fn_name in &pub_fns {
if !where_listed.contains_key(fn_name.as_str()) {
unclassified.push(fn_name.clone());
}
}
if !unclassified.is_empty() {
unclassified.sort();
unclassified.dedup();
return Err(VerifyError::Unclassified(unclassified));
}
Ok(VerifyReport::from_counts(
pub_fns.len(),
c1_cover.len(),
c2_deferred.len(),
permanent_skip.len(),
))
}
pub fn verify_cover_or_panic(lib_rs: &str) -> VerifyReport {
match verify_cover(lib_rs, C1_COVER, C2_DEFERRED, PERMANENT_SKIP) {
Ok(report) => report,
Err(VerifyError::Unclassified(names)) => {
panic!(
"v5.0 selftest cover allowlist out of sync with `smix-sdk/src/lib.rs`. \
Found {} unclassified pub fn: {:?}. \
Add each to crates/smix-sdk/src/selftest/verifier.rs (C1_COVER if \
the scenario exercises it this cycle, C2_DEFERRED if it is on the \
plan-cold next-checkpoint list, PERMANENT_SKIP if it is a \
builder/getter).",
names.len(),
names,
)
}
Err(VerifyError::Duplicate {
name,
first_list,
second_list,
}) => panic!(
"v5.0 selftest cover allowlist curation bug: `{name}` appears in both \
`{first_list}` and `{second_list}`. Remove from one of them — overlapping \
lists make coverage % meaningless."
),
}
}