use std::path::PathBuf;
use std::process::ExitCode;
use smix_sdk::App;
use smix_sdk::selftest::{Harness, scenario, verifier};
pub async fn run(udid: String, runner_port: u16) -> ExitCode {
let sdk_lib_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../smix-sdk/src/lib.rs");
let lib_rs = std::fs::read_to_string(&sdk_lib_path).unwrap_or_else(|_| {
eprintln!(
"smix selftest single: SDK source not readable at {} — verify report will reflect empty surface (binary distributed standalone, not monorepo dev)",
sdk_lib_path.display(),
);
String::new()
});
let verify_report = verifier::verify_cover_or_panic(&lib_rs);
eprintln!(
"smix selftest single: SDK surface ok — {} pub fn classified (C1 {} / C2 {} / skip {}); coverage {:.1}%",
verify_report.sdk_pub_fn_count,
verify_report.c1_cover_count,
verify_report.c2_deferred_count,
verify_report.permanent_skip_count,
verify_report.coverage_pct,
);
let app = match App::connect_to_runner(runner_port).await {
Ok(a) => a.with_udid(&udid),
Err(e) => {
eprintln!(
"smix selftest single: connect_to_runner({runner_port}) failed: {} — 是否 `smix capsule up <UDID>` 起好?",
e.message
);
return ExitCode::from(1);
}
};
let parent_dir = PathBuf::from(".smix/selftest");
let mut harness = match Harness::new(&parent_dir, udid.clone()) {
Ok(h) => h,
Err(e) => {
eprintln!("smix selftest single: failed to create run dir: {e}");
return ExitCode::from(1);
}
};
let env = scenario::ScenarioEnv::from_process_env();
if let Err(e) = app
.simctl()
.reset_permission(&udid, smix_sdk::SimctlPermission::Camera, &env.bundle_id)
.await
{
eprintln!("smix selftest single: camera permission reset failed (non-fatal): {e}");
}
eprintln!(
"smix selftest single: starting C1 matrix on {udid} (bundle={}, app_path={})",
env.bundle_id,
env.app_path.as_deref().unwrap_or("<none>")
);
let capsule_started = match app.start_capsule_recording().await {
Ok(()) => true,
Err(e) => {
eprintln!(
"smix selftest single: start_capsule_recording failed: {} — 跑 `smix capsule up <UDID>` 走硬胶囊化(带 TEST_RUNNER_SMIX_RECORD_ENABLED=1)。scenario 仍跑,result.json capsule_reconcile 留空。",
e.message
);
false
}
};
scenario::run_c1_segments(&app, &mut harness, &env).await;
if capsule_started {
match app.stop_capsule_recording_and_reconcile(None).await {
Ok(recon) => {
let summary =
smix_sdk::selftest::CapsuleReconcileSummary::from_reconciliation(&recon);
eprintln!(
"smix selftest single: capsule_reconcile = {{ issued: {}, focus_change: {}, attributed: {}, unattributed: {} }}",
summary.issued_count,
summary.focus_change_count,
summary.attributed_count,
summary.unattributed_count
);
harness.set_capsule_reconcile(summary);
}
Err(e) => {
eprintln!(
"smix selftest single: stop_capsule_recording_and_reconcile failed: {} — result.json capsule_reconcile 留空,gate exit 11 处理。",
e.message
);
}
}
}
if let Err(e) = harness.write_result_json(&verify_report) {
eprintln!("smix selftest single: write_result_json failed: {e}");
}
if let Err(e) = harness.write_report_md(&verify_report) {
eprintln!("smix selftest single: write_report_md failed: {e}");
}
if let Err(e) = harness.update_latest_symlink() {
eprintln!("smix selftest single: update_latest_symlink failed: {e}");
}
eprintln!(
"smix selftest single: matrix done (run dir {})",
harness.run_dir().display()
);
ExitCode::from(harness.finish() as u8)
}
#[cfg(test)]
mod tests {
use super::run;
use std::process::ExitCode;
#[test]
fn compile_module_present() {
let _f: fn(String, u16) -> _ = run;
}
#[test]
fn exit_code_from_u8_compiles() {
let _ec0 = ExitCode::from(0u8);
let _ec1 = ExitCode::from(1u8);
let _ec2 = ExitCode::from(2u8);
}
#[tokio::test]
async fn run_returns_one_when_runner_unreachable() {
let ec = run("DEADBEEF-CCCC-DDDD-EEEE-000000000000".to_string(), 59993).await;
let _ = ec;
}
}