use serde::Serialize;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OmenaTestkitFixtureSeedV0 {
pub label: &'static str,
pub lane: &'static str,
pub raw: &'static str,
pub expected_products: &'static [&'static str],
pub promotion_target: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaFixtureV0 {
pub schema_version: &'static str,
pub files: Vec<OmenaFixtureFileV0>,
pub expectations: Vec<OmenaFixtureExpectationV0>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaFixtureFileV0 {
pub path: String,
pub metadata: Vec<OmenaFixtureFileMetadataV0>,
pub markers: Vec<OmenaFixtureMarkerV0>,
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaFixtureFileMetadataV0 {
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaFixtureMarkerV0 {
pub kind: &'static str,
pub name: Option<String>,
pub byte_start: usize,
pub byte_end: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaFixtureExpectationV0 {
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum OmenaFixtureExpectationKindV0 {
Product,
Assertion,
Diagnostic,
NoDiagnostic,
Count,
CascadeOutcome,
CascadeWitness,
BoundaryState,
Unknown,
}
impl OmenaFixtureExpectationV0 {
pub fn kind(&self) -> OmenaFixtureExpectationKindV0 {
omena_fixture_expectation_kind_from_key(&self.key)
}
}
pub fn omena_fixture_expectation_kind_from_key(key: &str) -> OmenaFixtureExpectationKindV0 {
match key.split_whitespace().next().unwrap_or_default() {
"product" => OmenaFixtureExpectationKindV0::Product,
"assertion" => OmenaFixtureExpectationKindV0::Assertion,
"diagnostic" => OmenaFixtureExpectationKindV0::Diagnostic,
"no-diagnostic" => OmenaFixtureExpectationKindV0::NoDiagnostic,
"count" => OmenaFixtureExpectationKindV0::Count,
"cascade-outcome" => OmenaFixtureExpectationKindV0::CascadeOutcome,
"cascade-witness" => OmenaFixtureExpectationKindV0::CascadeWitness,
"boundary-state" => OmenaFixtureExpectationKindV0::BoundaryState,
_ => OmenaFixtureExpectationKindV0::Unknown,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaTestkitFixtureSeedReportV0 {
pub label: &'static str,
pub lane: &'static str,
pub parses: bool,
pub parse_error: Option<String>,
pub file_count: usize,
pub expectation_count: usize,
pub metadata_count: usize,
pub marker_count: usize,
pub expected_products: Vec<&'static str>,
pub promotion_target: &'static str,
pub expectation_outcomes: Vec<crate::fixture_eval::OmenaFixtureExpectationOutcomeV0>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OmenaTestkitFixtureSeedCorpusReportV0 {
pub schema_version: &'static str,
pub product: &'static str,
pub fixture_grammar: &'static str,
pub fixture_count: usize,
pub lane_count: usize,
pub metadata_count: usize,
pub marker_count: usize,
pub all_seeds_parse: bool,
pub reports: Vec<OmenaTestkitFixtureSeedReportV0>,
}
pub fn summarize_omena_testkit_fixture_seed_corpus(
seeds: &[OmenaTestkitFixtureSeedV0],
) -> OmenaTestkitFixtureSeedCorpusReportV0 {
let reports = seeds
.iter()
.copied()
.map(report_fixture_seed)
.collect::<Vec<_>>();
let all_seeds_parse = reports.iter().all(|report| report.parses);
let lane_count = reports
.iter()
.map(|report| report.lane)
.collect::<BTreeSet<_>>()
.len();
let metadata_count = reports.iter().map(|report| report.metadata_count).sum();
let marker_count = reports.iter().map(|report| report.marker_count).sum();
OmenaTestkitFixtureSeedCorpusReportV0 {
schema_version: "0",
product: "omena-testkit.fixture-seed-corpus",
fixture_grammar: "omena-fixture-v0",
fixture_count: reports.len(),
lane_count,
metadata_count,
marker_count,
all_seeds_parse,
reports,
}
}
pub fn parse_omena_fixture_v0(raw: &str) -> Result<OmenaFixtureV0, String> {
let mut files = Vec::new();
let mut expectations = Vec::new();
let mut current_file: Option<OmenaFixtureFileV0> = None;
let mut current_expectation: Option<OmenaFixtureExpectationV0> = None;
for line in raw.lines() {
if let Some(header) = line
.strip_prefix("--- file:")
.or_else(|| line.strip_prefix("//-"))
{
finish_fixture_section(&mut files, current_file.take());
finish_fixture_section(&mut expectations, current_expectation.take());
let (path, metadata) = parse_omena_fixture_file_header(header.trim())?;
current_file = Some(OmenaFixtureFileV0 {
path,
metadata,
markers: Vec::new(),
source: String::new(),
});
continue;
}
if let Some(key) = line.strip_prefix("--- expect:") {
finish_fixture_section(&mut files, current_file.take());
finish_fixture_section(&mut expectations, current_expectation.take());
current_expectation = Some(OmenaFixtureExpectationV0 {
key: key.trim().to_string(),
value: String::new(),
});
continue;
}
if let Some(file) = current_file.as_mut() {
push_fixture_line(&mut file.source, line);
} else if let Some(expectation) = current_expectation.as_mut() {
push_fixture_line(&mut expectation.value, line);
} else if !line.trim().is_empty() {
return Err("fixture content must start with a file or expect marker".to_string());
}
}
finish_fixture_section(&mut files, current_file.take());
finish_fixture_section(&mut expectations, current_expectation.take());
for file in &mut files {
let hex_encoded = file
.metadata
.iter()
.any(|metadata| metadata.key == "encoding" && metadata.value == "hex");
if hex_encoded {
file.source = decode_hex_omena_fixture_file(file.source.as_str())?;
continue;
}
let (cleaned_source, markers) = extract_omena_fixture_markers(&file.source)?;
file.source = cleaned_source;
file.markers = markers;
}
if files.is_empty() {
return Err("fixture must contain at least one file section".to_string());
}
if expectations.is_empty() {
return Err("fixture must contain at least one expectation section".to_string());
}
Ok(OmenaFixtureV0 {
schema_version: "0",
files,
expectations,
})
}
fn report_fixture_seed(seed: OmenaTestkitFixtureSeedV0) -> OmenaTestkitFixtureSeedReportV0 {
match parse_omena_fixture_v0(seed.raw) {
Ok(fixture) => {
let metadata_count = fixture.files.iter().map(|file| file.metadata.len()).sum();
let marker_count = fixture.files.iter().map(|file| file.markers.len()).sum();
let expectation_outcomes =
crate::fixture_eval::evaluate_omena_fixture_v0(&fixture, &[], &[], &[]);
OmenaTestkitFixtureSeedReportV0 {
label: seed.label,
lane: seed.lane,
parses: true,
parse_error: None,
file_count: fixture.files.len(),
expectation_count: fixture.expectations.len(),
metadata_count,
marker_count,
expected_products: seed.expected_products.to_vec(),
promotion_target: seed.promotion_target,
expectation_outcomes,
}
}
Err(error) => OmenaTestkitFixtureSeedReportV0 {
label: seed.label,
lane: seed.lane,
parses: false,
parse_error: Some(error),
file_count: 0,
expectation_count: 0,
metadata_count: 0,
marker_count: 0,
expected_products: seed.expected_products.to_vec(),
promotion_target: seed.promotion_target,
expectation_outcomes: Vec::new(),
},
}
}
fn finish_fixture_section<T>(sections: &mut Vec<T>, current: Option<T>) {
if let Some(section) = current {
sections.push(section);
}
}
fn push_fixture_line(buffer: &mut String, line: &str) {
if !buffer.is_empty() {
buffer.push('\n');
}
buffer.push_str(line);
}
fn parse_omena_fixture_file_header(
header: &str,
) -> Result<(String, Vec<OmenaFixtureFileMetadataV0>), String> {
let mut parts = header.split_whitespace();
let path = parts
.next()
.ok_or_else(|| "fixture file header must include a path".to_string())?;
if path.contains(':') {
return Err("fixture file header path must precede metadata".to_string());
}
let mut metadata = Vec::new();
for part in parts {
let Some((key, value)) = part.split_once(':') else {
return Err(format!("fixture metadata `{part}` must use key:value"));
};
validate_omena_fixture_metadata(key, value)?;
metadata.push(OmenaFixtureFileMetadataV0 {
key: key.to_string(),
value: value.to_string(),
});
}
Ok((path.to_string(), metadata))
}
fn validate_omena_fixture_metadata(key: &str, value: &str) -> Result<(), String> {
if value.is_empty() {
return Err(format!("fixture metadata `{key}` must have a value"));
}
match key {
"dialect" => match value {
"css" | "scss" | "less" => Ok(()),
_ => Err("fixture dialect metadata must be css, scss, or less".to_string()),
},
"encoding" => match value {
"hex" => Ok(()),
_ => Err("fixture encoding metadata must be hex".to_string()),
},
"layer" | "composes-from" | "consumer-of" => Ok(()),
_ => Err(format!("fixture metadata key `{key}` is not supported")),
}
}
fn decode_hex_omena_fixture_file(source: &str) -> Result<String, String> {
let encoded = source.trim();
if !encoded.len().is_multiple_of(2) {
return Err("hex-encoded fixture file source must have even length".to_string());
}
let mut bytes = Vec::with_capacity(encoded.len() / 2);
for pair in encoded.as_bytes().chunks_exact(2) {
let high = hex_nibble(pair[0]).ok_or_else(|| {
"hex-encoded fixture file source must contain only hex digits".to_string()
})?;
let low = hex_nibble(pair[1]).ok_or_else(|| {
"hex-encoded fixture file source must contain only hex digits".to_string()
})?;
bytes.push((high << 4) | low);
}
String::from_utf8(bytes)
.map_err(|_| "hex-encoded fixture file source must decode to UTF-8".to_string())
}
fn hex_nibble(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
fn extract_omena_fixture_markers(
source: &str,
) -> Result<(String, Vec<OmenaFixtureMarkerV0>), String> {
let mut cleaned = String::new();
let mut markers = Vec::new();
let mut cursor = 0;
while let Some(relative_start) = source[cursor..].find("/*") {
let start = cursor + relative_start;
cleaned.push_str(&source[cursor..start]);
let Some(relative_end) = source[start + 2..].find("*/") else {
return Err("fixture marker comment is unterminated".to_string());
};
let end = start + 2 + relative_end + 2;
let body = &source[start + 2..end - 2];
if let Some(marker) = parse_omena_fixture_marker(body, cleaned.len())? {
markers.push(marker);
} else {
cleaned.push_str(&source[start..end]);
}
cursor = end;
}
cleaned.push_str(&source[cursor..]);
Ok((cleaned, markers))
}
fn parse_omena_fixture_marker(
body: &str,
byte_offset: usize,
) -> Result<Option<OmenaFixtureMarkerV0>, String> {
if body == "|" {
return Ok(Some(omena_fixture_marker("cursor", None, byte_offset)));
}
if let Some(name) = body.strip_prefix("at:") {
return Ok(Some(omena_fixture_marker(
"namedPoint",
Some(validate_omena_fixture_marker_payload("at", name)?),
byte_offset,
)));
}
if let Some(name) = body
.strip_prefix("</")
.and_then(|name| name.strip_suffix('>'))
{
return Ok(Some(omena_fixture_marker(
"rangeEnd",
Some(validate_omena_fixture_marker_payload("range end", name)?),
byte_offset,
)));
}
if let Some(name) = body
.strip_prefix('<')
.and_then(|name| name.strip_suffix('>'))
{
return Ok(Some(omena_fixture_marker(
"rangeStart",
Some(validate_omena_fixture_marker_payload("range start", name)?),
byte_offset,
)));
}
if let Some(name) = body.strip_prefix("name:") {
return Ok(Some(omena_fixture_marker(
"nameAnchor",
Some(validate_omena_fixture_marker_payload("name", name)?),
byte_offset,
)));
}
if let Some(target) = body.strip_prefix("from:") {
return Ok(Some(omena_fixture_marker(
"linkEnd",
Some(validate_omena_fixture_marker_payload("from", target)?),
byte_offset,
)));
}
Ok(None)
}
fn omena_fixture_marker(
kind: &'static str,
name: Option<String>,
byte_offset: usize,
) -> OmenaFixtureMarkerV0 {
OmenaFixtureMarkerV0 {
kind,
name,
byte_start: byte_offset,
byte_end: byte_offset,
}
}
fn validate_omena_fixture_marker_payload(kind: &str, value: &str) -> Result<String, String> {
if value.is_empty() {
return Err(format!("fixture marker `{kind}` must have a value"));
}
Ok(value.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
const CROSS_LANGUAGE_FIXTURE: &str = r#"--- file: src/App.tsx
import styles from "./Button.module.scss";
styles.button;
--- file: src/Button.module.scss
.button { color: red; }
--- expect: product
omena-query.source-syntax-index
--- expect: assertion
shared fixture parser keeps source and style files in the same workspace fixture
"#;
#[test]
fn parses_reusable_omena_fixture_v0_sections() -> Result<(), String> {
let fixture = parse_omena_fixture_v0(
r#"--- file: src/proof.css
.a { color: red; }
--- expect: product
omena-transform-passes.cascade-proof-obligations
--- expect: assertion
proof obligations remain product-visible
"#,
)?;
assert_eq!(fixture.schema_version, "0");
assert_eq!(fixture.files.len(), 1);
assert_eq!(fixture.files[0].path, "src/proof.css");
assert!(fixture.files[0].source.contains(".a"));
assert_eq!(fixture.expectations.len(), 2);
assert_eq!(fixture.expectations[0].key, "product");
assert_eq!(
fixture.expectations[0].value,
"omena-transform-passes.cascade-proof-obligations"
);
Ok(())
}
#[test]
fn keeps_source_and_style_files_in_one_workspace_fixture() -> Result<(), String> {
let fixture = parse_omena_fixture_v0(CROSS_LANGUAGE_FIXTURE)?;
assert_eq!(fixture.files.len(), 2);
assert_eq!(fixture.files[0].path, "src/App.tsx");
assert_eq!(fixture.files[1].path, "src/Button.module.scss");
assert!(
fixture
.expectations
.iter()
.any(|expectation| expectation.value == "omena-query.source-syntax-index")
);
Ok(())
}
#[test]
fn parses_omena_fixture_v0_metadata_and_markers() -> Result<(), String> {
let fixture = parse_omena_fixture_v0(
r#"//- src/Card.module.scss dialect:scss layer:style
.card { color: /*|*/red; }
.card/*at:selector*/ { color: blue; }
.card { color: /*<colorRange>*/green/*</colorRange>*/; }
.card { composes: item/*from:src/Base.module.scss#item*/; }
--- expect: product
omena-testkit.fixture-markers
"#,
)?;
assert_eq!(fixture.files.len(), 1);
assert_eq!(fixture.files[0].path, "src/Card.module.scss");
assert_eq!(
fixture.files[0]
.metadata
.iter()
.map(|metadata| (metadata.key.as_str(), metadata.value.as_str()))
.collect::<Vec<_>>(),
vec![("dialect", "scss"), ("layer", "style")]
);
assert_eq!(
fixture.files[0]
.markers
.iter()
.map(|marker| (marker.kind, marker.name.as_deref()))
.collect::<Vec<_>>(),
vec![
("cursor", None),
("namedPoint", Some("selector")),
("rangeStart", Some("colorRange")),
("rangeEnd", Some("colorRange")),
("linkEnd", Some("src/Base.module.scss#item"))
]
);
assert!(fixture.files[0].source.contains(".card { color: red; }"));
assert!(!fixture.files[0].source.contains("/*|*/"));
assert_eq!(
fixture.files[0].markers[0].byte_start,
".card { color: ".len()
);
Ok(())
}
#[test]
fn keeps_non_fixture_comments_in_source() -> Result<(), String> {
let fixture = parse_omena_fixture_v0(
r#"//- src/Card.module.css dialect:css
.card { /* regular comment */ color: red; }
--- expect: product
omena-testkit.fixture-markers
"#,
)?;
assert!(fixture.files[0].source.contains("/* regular comment */"));
assert!(fixture.files[0].markers.is_empty());
Ok(())
}
#[test]
fn hex_encoded_file_source_is_marker_inert_and_byte_preserving() -> Result<(), String> {
let source = concat!(
".card {\n",
" //---- divider comment\n",
" content: \"--- file: nope /*|*/ /*at:point*/ /*<range>*/ /*</range>*/\";\n",
"}\n",
"/*"
);
let raw = format!(
"--- file: src/Card.module.scss encoding:hex\n{}\n--- expect: product\nomena-testkit.fixture-encoding\n",
hex_encode_for_test(source)
);
let fixture = parse_omena_fixture_v0(raw.as_str())?;
assert_eq!(fixture.files.len(), 1);
assert_eq!(fixture.files[0].path, "src/Card.module.scss");
assert_eq!(fixture.files[0].source, source);
assert!(fixture.files[0].markers.is_empty());
Ok(())
}
#[test]
fn hex_encoded_file_source_rejects_non_utf8_bytes() {
let result = parse_omena_fixture_v0(
"--- file: src/raw.css encoding:hex\nff\n--- expect: product\nomena-testkit.fixture-encoding\n",
);
assert_eq!(
result.err().as_deref(),
Some("hex-encoded fixture file source must decode to UTF-8")
);
}
#[test]
fn classifies_m7_diagnostic_cascade_and_boundary_expectations() -> Result<(), String> {
let fixture = parse_omena_fixture_v0(
r#"//- src/Nested.module.scss dialect:scss
.article {
&.box { &.fill { padding: 1px; } }
}
--- expect: diagnostic
code: unreachableDeclaration
range: colorRange
--- expect: no-diagnostic unspecifiedCascadeTie
--- expect: count unreachableDeclaration:0
--- expect: cascade-outcome decl-1
--- expect: cascade-witness decl-2
--- expect: boundary-state ext-1 Resolved
--- expect: boundary-state ext-2 Partial
--- expect: boundary-state ext-3 Stale
--- expect: boundary-state ext-4 Missing
--- expect: boundary-state ext-5 Unresolved
"#,
)?;
assert_eq!(
fixture
.expectations
.iter()
.map(OmenaFixtureExpectationV0::kind)
.collect::<Vec<_>>(),
vec![
OmenaFixtureExpectationKindV0::Diagnostic,
OmenaFixtureExpectationKindV0::NoDiagnostic,
OmenaFixtureExpectationKindV0::Count,
OmenaFixtureExpectationKindV0::CascadeOutcome,
OmenaFixtureExpectationKindV0::CascadeWitness,
OmenaFixtureExpectationKindV0::BoundaryState,
OmenaFixtureExpectationKindV0::BoundaryState,
OmenaFixtureExpectationKindV0::BoundaryState,
OmenaFixtureExpectationKindV0::BoundaryState,
OmenaFixtureExpectationKindV0::BoundaryState,
]
);
assert_eq!(
fixture.expectations[1].key,
"no-diagnostic unspecifiedCascadeTie"
);
assert_eq!(
fixture.expectations[2].key,
"count unreachableDeclaration:0"
);
Ok(())
}
#[test]
fn rejects_unknown_fixture_metadata() {
let error = parse_omena_fixture_v0(
r#"//- src/Card.module.css unknown:value
.card { color: red; }
--- expect: product
omena-testkit.fixture-markers
"#,
)
.err();
assert_eq!(
error.as_deref(),
Some("fixture metadata key `unknown` is not supported")
);
}
#[test]
fn rejects_fixture_without_sections() {
let error = parse_omena_fixture_v0("plain text").err();
assert_eq!(
error.as_deref(),
Some("fixture content must start with a file or expect marker")
);
}
#[test]
fn rejects_fixture_without_expectations() {
let error = parse_omena_fixture_v0(
r#"--- file: src/Button.module.scss
.button { color: red; }
"#,
)
.err();
assert_eq!(
error.as_deref(),
Some("fixture must contain at least one expectation section")
);
}
#[test]
fn summarizes_external_fixture_seed_corpus() {
let seeds = [OmenaTestkitFixtureSeedV0 {
label: "external",
lane: "consumer",
raw: r#"--- file: src/input.css
.x { color: red; }
--- expect: product
consumer.product
"#,
expected_products: &["consumer.product"],
promotion_target: "omena-testkit/consumer",
}];
let report = summarize_omena_testkit_fixture_seed_corpus(&seeds);
assert_eq!(report.product, "omena-testkit.fixture-seed-corpus");
assert_eq!(report.fixture_count, 1);
assert_eq!(report.lane_count, 1);
assert!(report.all_seeds_parse);
assert_eq!(report.reports[0].file_count, 1);
assert_eq!(report.reports[0].expectation_count, 1);
}
fn hex_encode_for_test(source: &str) -> String {
source
.as_bytes()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
}