use aristo_core::canon::InstrumentationBundle;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum PresenceOutcome {
Pass,
Missing { evidence: String },
FeatureUndeclared { evidence: String },
Indeterminate { note: String },
}
impl PresenceOutcome {
pub(crate) fn is_pass(&self) -> bool {
matches!(self, PresenceOutcome::Pass)
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RecordKey {
pub accessor_id: String,
pub method: String,
}
pub(crate) fn record_keys(bundle: &InstrumentationBundle) -> Vec<RecordKey> {
bundle
.records
.iter()
.map(|r| RecordKey {
accessor_id: r.accessor_id.clone(),
method: emitted_method(&r.presence.expected_symbol, &r.accessor_id),
})
.collect()
}
pub(crate) fn emitted_method(expected_symbol: &str, accessor_id: &str) -> String {
let tail = expected_symbol.rsplit("::").next().unwrap_or("").trim();
if tail.is_empty() {
accessor_id.to_string()
} else {
tail.to_string()
}
}
pub(crate) fn classify_feature_on(
compile_ok: bool,
stderr: &str,
keys: &[RecordKey],
) -> Vec<(String, PresenceOutcome)> {
if compile_ok {
return keys
.iter()
.map(|k| (k.accessor_id.clone(), PresenceOutcome::Pass))
.collect();
}
if let Some(evidence) = feature_undeclared_evidence(stderr) {
return keys
.iter()
.map(|k| {
(
k.accessor_id.clone(),
PresenceOutcome::FeatureUndeclared {
evidence: evidence.clone(),
},
)
})
.collect();
}
let hits = missing_item_hits(stderr);
let mut attributed: Vec<&str> = Vec::new();
let mut outcomes = Vec::with_capacity(keys.len());
for key in keys {
let mine: Vec<&str> = hits
.iter()
.filter(|(name, _)| *name == key.method || *name == key.accessor_id)
.map(|(_, headline)| headline.as_str())
.collect();
if mine.is_empty() {
outcomes.push((key.accessor_id.clone(), PresenceOutcome::Pass));
} else {
attributed.extend(mine.iter().copied());
outcomes.push((
key.accessor_id.clone(),
PresenceOutcome::Missing {
evidence: mine.join("\n"),
},
));
}
}
let unattributed: Vec<&str> = error_headlines(stderr)
.into_iter()
.filter(|h| !attributed.contains(h))
.collect();
if !unattributed.is_empty() {
for (_, outcome) in outcomes.iter_mut() {
if outcome.is_pass() {
*outcome = PresenceOutcome::Indeterminate {
note: format!(
"compile failed with errors that name no accessor \
(first: {}); this record was not individually named \
by rustc — inspect the probe output",
unattributed[0]
),
};
}
}
}
outcomes
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FeatureOffVerdict {
GatedOk {
named: Vec<String>,
via_imports: bool,
},
UngatedLeak,
BuildIssue { excerpt: String },
}
pub(crate) fn classify_feature_off(
compile_ok: bool,
stderr: &str,
keys: &[RecordKey],
) -> FeatureOffVerdict {
if compile_ok {
if keys.is_empty() {
return FeatureOffVerdict::GatedOk {
named: vec![],
via_imports: false,
};
}
return FeatureOffVerdict::UngatedLeak;
}
let hits = missing_item_hits(stderr);
let named: Vec<String> = keys
.iter()
.filter(|k| {
hits.iter()
.any(|(name, _)| *name == k.method || *name == k.accessor_id)
})
.map(|k| k.accessor_id.clone())
.collect();
if !named.is_empty() {
return FeatureOffVerdict::GatedOk {
named,
via_imports: false,
};
}
let unresolved = error_headlines(stderr).into_iter().any(|h| {
h.starts_with("error[E0432]")
|| h.starts_with("error[E0433]")
|| h.starts_with("error[E0412]")
|| h.starts_with("error[E0603]")
});
if unresolved {
return FeatureOffVerdict::GatedOk {
named: vec![],
via_imports: true,
};
}
let excerpt = error_headlines(stderr)
.first()
.map(|s| (*s).to_string())
.unwrap_or_else(|| {
stderr
.lines()
.rev()
.find(|l| !l.trim().is_empty())
.unwrap_or("(empty stderr)")
.trim()
.to_string()
});
FeatureOffVerdict::BuildIssue { excerpt }
}
pub(crate) fn failed_receiver_imports(
stderr: &str,
receiver_imports: &std::collections::BTreeMap<String, String>,
) -> Vec<String> {
let headlines: Vec<&str> = stderr
.lines()
.map(str::trim)
.filter(|l| l.starts_with("error[E0603]") || l.starts_with("error[E0432]"))
.collect();
if headlines.is_empty() {
return vec![];
}
receiver_imports
.iter()
.filter(|(base, import)| {
headlines.iter().any(|h| {
let Some(name) = first_backticked(h) else {
return false;
};
import.contains(&format!("::{name}::"))
|| import.contains(&format!("::{name};"))
|| (name.contains("::") && import.contains(name.as_str()))
|| name == **base
})
})
.map(|(base, _)| base.clone())
.collect()
}
fn feature_undeclared_evidence(stderr: &str) -> Option<String> {
const PATTERNS: [&str; 3] = [
"does not have that feature",
"does not have these features",
"none of the selected packages contains these features",
];
stderr
.lines()
.find(|l| PATTERNS.iter().any(|p| l.contains(p)))
.map(|l| l.trim().to_string())
}
fn missing_item_hits(stderr: &str) -> Vec<(String, String)> {
stderr
.lines()
.map(str::trim)
.filter(|l| l.starts_with("error[E0599]") || l.starts_with("error[E0609]"))
.filter_map(|l| Some((first_backticked(l)?, l.to_string())))
.collect()
}
fn error_headlines(stderr: &str) -> Vec<&str> {
stderr
.lines()
.map(str::trim)
.filter(|l| l.starts_with("error"))
.filter(|l| !l.starts_with("error: could not compile") && !l.starts_with("error: aborting"))
.collect()
}
fn first_backticked(line: &str) -> Option<String> {
let start = line.find('`')? + 1;
let len = line[start..].find('`')?;
Some(line[start..start + len].to_string())
}
#[cfg(test)]
mod tests {
use super::*;
const S2A_STDERR: &str = include_str!("fixtures/s2a.stderr.txt");
const S2B_STDERR: &str = include_str!("fixtures/s2b.stderr.txt");
const FEATURE_UNDECLARED_STDERR: &str = include_str!("fixtures/feature-undeclared.stderr.txt");
const PRIVATE_MODULE_STDERR: &str = include_str!("fixtures/private-module-import.stderr.txt");
fn key(id: &str) -> RecordKey {
RecordKey {
accessor_id: id.into(),
method: id.into(),
}
}
fn spike_keys() -> Vec<RecordKey> {
[
"inspect_txs",
"inspect_finalized",
"inspect_tx_ids_value",
"inspect_version_id_counter_value",
"inspect_peek_next_ts",
"inspect_header_version",
"inspect_pending_running_crc",
"inspect_mv_tx",
"inspect_attached_mv_txs",
]
.iter()
.map(|id| key(id))
.collect()
}
#[test]
fn emitted_method_takes_expected_symbol_tail() {
assert_eq!(
emitted_method("LogicalLog::inspect_header_version", "x"),
"inspect_header_version"
);
assert_eq!(
emitted_method("Wal::installed_snapshot", "x"),
"installed_snapshot"
);
assert_eq!(emitted_method("", "fallback_id"), "fallback_id");
}
#[test]
fn successful_compile_classifies_all_pass() {
let outcomes = classify_feature_on(true, S2A_STDERR, &spike_keys());
assert_eq!(outcomes.len(), 9);
assert!(
outcomes.iter().all(|(_, o)| o.is_pass()),
"got: {outcomes:?}"
);
}
#[test]
fn spike_negative_run_names_every_accessor_missing() {
let outcomes = classify_feature_on(false, S2B_STDERR, &spike_keys());
for (id, outcome) in &outcomes {
match outcome {
PresenceOutcome::Missing { evidence } => {
assert!(
evidence.contains(&format!("`{id}`")),
"evidence for {id} must name it, got: {evidence}"
);
assert!(
evidence.contains("error[E0599]"),
"evidence keeps the rustc headline, got: {evidence}"
);
}
other => panic!("{id}: expected Missing, got {other:?}"),
}
}
}
#[test]
fn record_not_named_by_rustc_passes_when_all_errors_attributed() {
let mut keys = spike_keys();
keys.push(key("installed_snapshot"));
let outcomes = classify_feature_on(false, S2B_STDERR, &keys);
let (_, last) = outcomes.last().unwrap();
assert!(last.is_pass(), "got: {last:?}");
}
#[test]
fn unattributed_errors_demote_unnamed_records_to_indeterminate() {
let stderr = "\
error[E0599]: no method named `inspect_txs` found for reference `&MvStore<MvccClock>` in the current scope
error[E0412]: cannot find type `HeaderView` in this scope
error: could not compile `aristo-s2-presence-probe` (lib) due to 2 previous errors
";
let keys = vec![key("inspect_txs"), key("inspect_header_version")];
let outcomes = classify_feature_on(false, stderr, &keys);
assert!(
matches!(outcomes[0].1, PresenceOutcome::Missing { .. }),
"named record stays Missing, got: {:?}",
outcomes[0]
);
match &outcomes[1].1 {
PresenceOutcome::Indeterminate { note } => {
assert!(
note.contains("E0412"),
"note surfaces the stray error: {note}"
);
}
other => panic!("expected Indeterminate, got {other:?}"),
}
}
#[test]
fn e0609_field_error_classifies_missing() {
let stderr = "error[E0609]: no field `offset` on type `&LogicalLog`\n";
let keys = vec![RecordKey {
accessor_id: "read_logicallog_offset_crc".into(),
method: "offset".into(),
}];
let outcomes = classify_feature_on(false, stderr, &keys);
assert!(
matches!(outcomes[0].1, PresenceOutcome::Missing { .. }),
"got: {:?}",
outcomes[0]
);
}
#[test]
fn feature_resolution_error_classifies_sut_feature_undeclared() {
let keys = vec![key("inspect_header_version"), key("installed_snapshot")];
let outcomes = classify_feature_on(false, FEATURE_UNDECLARED_STDERR, &keys);
for (id, outcome) in &outcomes {
match outcome {
PresenceOutcome::FeatureUndeclared { evidence } => {
assert!(
evidence.contains("does not have that feature"),
"{id}: evidence keeps cargo's message, got: {evidence}"
);
}
other => panic!("{id}: expected FeatureUndeclared, got {other:?}"),
}
}
}
#[test]
fn older_cargo_plural_feature_phrasing_also_classifies() {
let stderr = "\
error: failed to select a version for `turso_core`.
the package `aristo-s2-presence-probe` depends on `turso_core`, with features: \
`aristo-instr` but `turso_core` does not have these features.
";
let keys = vec![key("inspect_header_version")];
let outcomes = classify_feature_on(false, stderr, &keys);
assert!(
matches!(outcomes[0].1, PresenceOutcome::FeatureUndeclared { .. }),
"got: {:?}",
outcomes[0]
);
}
#[test]
fn private_module_import_names_only_the_failing_receiver_type() {
let mut receiver_imports = std::collections::BTreeMap::new();
receiver_imports.insert(
"Wal".to_string(),
"use turso_core::storage::wal::Wal;".to_string(),
);
receiver_imports.insert(
"LogicalLog".to_string(),
"use turso_core::mvcc::persistent_storage::logical_log::LogicalLog;".to_string(),
);
let flips = failed_receiver_imports(PRIVATE_MODULE_STDERR, &receiver_imports);
assert_eq!(flips, vec!["Wal".to_string()], "got: {flips:?}");
let keys = vec![key("inspect_header_version"), key("installed_snapshot")];
let outcomes = classify_feature_on(false, PRIVATE_MODULE_STDERR, &keys);
assert!(
outcomes
.iter()
.all(|(_, o)| matches!(o, PresenceOutcome::Indeterminate { .. })),
"got: {outcomes:?}"
);
}
#[test]
fn unresolved_import_path_blames_the_matching_receiver_type() {
let stderr = "error[E0432]: unresolved import `turso_core::storage::wal`\n";
let mut receiver_imports = std::collections::BTreeMap::new();
receiver_imports.insert(
"Wal".to_string(),
"use turso_core::storage::wal::Wal;".to_string(),
);
assert_eq!(
failed_receiver_imports(stderr, &receiver_imports),
vec!["Wal".to_string()]
);
assert!(failed_receiver_imports("", &receiver_imports).is_empty());
assert!(failed_receiver_imports(
"error[E0599]: no method named `inspect_txs` found",
&receiver_imports
)
.is_empty());
}
#[test]
fn feature_off_spike_negative_run_confirms_gating() {
let verdict = classify_feature_off(false, S2B_STDERR, &spike_keys());
match verdict {
FeatureOffVerdict::GatedOk { named, via_imports } => {
assert_eq!(named.len(), 9, "all nine spike accessors named");
assert!(!via_imports);
}
other => panic!("expected GatedOk, got {other:?}"),
}
}
#[test]
fn feature_off_green_compile_is_a_gating_leak() {
let verdict = classify_feature_off(true, "", &spike_keys());
assert_eq!(verdict, FeatureOffVerdict::UngatedLeak);
}
#[test]
fn feature_off_unresolved_gated_imports_count_as_gating_evidence() {
let stderr = "\
error[E0432]: unresolved import `turso_core::mvcc::database::differential`
error: could not compile `aristo-s2-presence-probe` (lib) due to 1 previous error
";
let verdict = classify_feature_off(false, stderr, &spike_keys());
assert_eq!(
verdict,
FeatureOffVerdict::GatedOk {
named: vec![],
via_imports: true
}
);
}
#[test]
fn feature_off_unrelated_failure_is_a_build_issue() {
let stderr = "\
error[E0308]: mismatched types
error: could not compile `turso_core` (lib) due to 1 previous error
";
let verdict = classify_feature_off(false, stderr, &spike_keys());
match verdict {
FeatureOffVerdict::BuildIssue { excerpt } => {
assert!(excerpt.contains("E0308"), "got: {excerpt}");
}
other => panic!("expected BuildIssue, got {other:?}"),
}
}
}