1use serde::Serialize;
2use std::collections::BTreeSet;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct OmenaTestkitFixtureSeedV0 {
7 pub label: &'static str,
9 pub lane: &'static str,
11 pub raw: &'static str,
13 pub expected_products: &'static [&'static str],
15 pub promotion_target: &'static str,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct OmenaFixtureV0 {
23 pub schema_version: &'static str,
25 pub files: Vec<OmenaFixtureFileV0>,
27 pub expectations: Vec<OmenaFixtureExpectationV0>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct OmenaFixtureFileV0 {
35 pub path: String,
37 pub metadata: Vec<OmenaFixtureFileMetadataV0>,
39 pub markers: Vec<OmenaFixtureMarkerV0>,
42 pub source: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
48#[serde(rename_all = "camelCase")]
49pub struct OmenaFixtureFileMetadataV0 {
50 pub key: String,
52 pub value: String,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
58#[serde(rename_all = "camelCase")]
59pub struct OmenaFixtureMarkerV0 {
60 pub kind: &'static str,
62 pub name: Option<String>,
64 pub byte_start: usize,
66 pub byte_end: usize,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct OmenaFixtureExpectationV0 {
74 pub key: String,
76 pub value: String,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
82#[serde(rename_all = "camelCase")]
83pub enum OmenaFixtureExpectationKindV0 {
84 Product,
86 Assertion,
88 Diagnostic,
90 NoDiagnostic,
92 Count,
94 CascadeOutcome,
96 CascadeWitness,
98 BoundaryState,
100 Unknown,
102}
103
104impl OmenaFixtureExpectationV0 {
105 pub fn kind(&self) -> OmenaFixtureExpectationKindV0 {
108 omena_fixture_expectation_kind_from_key(&self.key)
109 }
110}
111
112pub fn omena_fixture_expectation_kind_from_key(key: &str) -> OmenaFixtureExpectationKindV0 {
114 match key.split_whitespace().next().unwrap_or_default() {
115 "product" => OmenaFixtureExpectationKindV0::Product,
116 "assertion" => OmenaFixtureExpectationKindV0::Assertion,
117 "diagnostic" => OmenaFixtureExpectationKindV0::Diagnostic,
118 "no-diagnostic" => OmenaFixtureExpectationKindV0::NoDiagnostic,
119 "count" => OmenaFixtureExpectationKindV0::Count,
120 "cascade-outcome" => OmenaFixtureExpectationKindV0::CascadeOutcome,
121 "cascade-witness" => OmenaFixtureExpectationKindV0::CascadeWitness,
122 "boundary-state" => OmenaFixtureExpectationKindV0::BoundaryState,
123 _ => OmenaFixtureExpectationKindV0::Unknown,
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct OmenaTestkitFixtureSeedReportV0 {
131 pub label: &'static str,
133 pub lane: &'static str,
135 pub parses: bool,
137 pub parse_error: Option<String>,
139 pub file_count: usize,
141 pub expectation_count: usize,
143 pub metadata_count: usize,
145 pub marker_count: usize,
147 pub expected_products: Vec<&'static str>,
149 pub promotion_target: &'static str,
151 pub expectation_outcomes: Vec<crate::fixture_eval::OmenaFixtureExpectationOutcomeV0>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
165#[serde(rename_all = "camelCase")]
166pub struct OmenaTestkitFixtureSeedCorpusReportV0 {
167 pub schema_version: &'static str,
169 pub product: &'static str,
171 pub fixture_grammar: &'static str,
173 pub fixture_count: usize,
175 pub lane_count: usize,
177 pub metadata_count: usize,
179 pub marker_count: usize,
181 pub all_seeds_parse: bool,
183 pub reports: Vec<OmenaTestkitFixtureSeedReportV0>,
185}
186
187pub fn summarize_omena_testkit_fixture_seed_corpus(
189 seeds: &[OmenaTestkitFixtureSeedV0],
190) -> OmenaTestkitFixtureSeedCorpusReportV0 {
191 let reports = seeds
192 .iter()
193 .copied()
194 .map(report_fixture_seed)
195 .collect::<Vec<_>>();
196 let all_seeds_parse = reports.iter().all(|report| report.parses);
197 let lane_count = reports
198 .iter()
199 .map(|report| report.lane)
200 .collect::<BTreeSet<_>>()
201 .len();
202 let metadata_count = reports.iter().map(|report| report.metadata_count).sum();
203 let marker_count = reports.iter().map(|report| report.marker_count).sum();
204
205 OmenaTestkitFixtureSeedCorpusReportV0 {
206 schema_version: "0",
207 product: "omena-testkit.fixture-seed-corpus",
208 fixture_grammar: "omena-fixture-v0",
209 fixture_count: reports.len(),
210 lane_count,
211 metadata_count,
212 marker_count,
213 all_seeds_parse,
214 reports,
215 }
216}
217
218pub fn parse_omena_fixture_v0(raw: &str) -> Result<OmenaFixtureV0, String> {
220 let mut files = Vec::new();
221 let mut expectations = Vec::new();
222 let mut current_file: Option<OmenaFixtureFileV0> = None;
223 let mut current_expectation: Option<OmenaFixtureExpectationV0> = None;
224
225 for line in raw.lines() {
226 if let Some(header) = line
227 .strip_prefix("--- file:")
228 .or_else(|| line.strip_prefix("//-"))
229 {
230 finish_fixture_section(&mut files, current_file.take());
231 finish_fixture_section(&mut expectations, current_expectation.take());
232 let (path, metadata) = parse_omena_fixture_file_header(header.trim())?;
233 current_file = Some(OmenaFixtureFileV0 {
234 path,
235 metadata,
236 markers: Vec::new(),
237 source: String::new(),
238 });
239 continue;
240 }
241
242 if let Some(key) = line.strip_prefix("--- expect:") {
243 finish_fixture_section(&mut files, current_file.take());
244 finish_fixture_section(&mut expectations, current_expectation.take());
245 current_expectation = Some(OmenaFixtureExpectationV0 {
246 key: key.trim().to_string(),
247 value: String::new(),
248 });
249 continue;
250 }
251
252 if let Some(file) = current_file.as_mut() {
253 push_fixture_line(&mut file.source, line);
254 } else if let Some(expectation) = current_expectation.as_mut() {
255 push_fixture_line(&mut expectation.value, line);
256 } else if !line.trim().is_empty() {
257 return Err("fixture content must start with a file or expect marker".to_string());
258 }
259 }
260
261 finish_fixture_section(&mut files, current_file.take());
262 finish_fixture_section(&mut expectations, current_expectation.take());
263
264 for file in &mut files {
265 let hex_encoded = file
266 .metadata
267 .iter()
268 .any(|metadata| metadata.key == "encoding" && metadata.value == "hex");
269 if hex_encoded {
270 file.source = decode_hex_omena_fixture_file(file.source.as_str())?;
271 continue;
272 }
273 let (cleaned_source, markers) = extract_omena_fixture_markers(&file.source)?;
274 file.source = cleaned_source;
275 file.markers = markers;
276 }
277
278 if files.is_empty() {
279 return Err("fixture must contain at least one file section".to_string());
280 }
281 if expectations.is_empty() {
282 return Err("fixture must contain at least one expectation section".to_string());
283 }
284
285 Ok(OmenaFixtureV0 {
286 schema_version: "0",
287 files,
288 expectations,
289 })
290}
291
292fn report_fixture_seed(seed: OmenaTestkitFixtureSeedV0) -> OmenaTestkitFixtureSeedReportV0 {
293 match parse_omena_fixture_v0(seed.raw) {
294 Ok(fixture) => {
295 let metadata_count = fixture.files.iter().map(|file| file.metadata.len()).sum();
296 let marker_count = fixture.files.iter().map(|file| file.markers.len()).sum();
297 let expectation_outcomes =
301 crate::fixture_eval::evaluate_omena_fixture_v0(&fixture, &[], &[], &[]);
302 OmenaTestkitFixtureSeedReportV0 {
303 label: seed.label,
304 lane: seed.lane,
305 parses: true,
306 parse_error: None,
307 file_count: fixture.files.len(),
308 expectation_count: fixture.expectations.len(),
309 metadata_count,
310 marker_count,
311 expected_products: seed.expected_products.to_vec(),
312 promotion_target: seed.promotion_target,
313 expectation_outcomes,
314 }
315 }
316 Err(error) => OmenaTestkitFixtureSeedReportV0 {
317 label: seed.label,
318 lane: seed.lane,
319 parses: false,
320 parse_error: Some(error),
321 file_count: 0,
322 expectation_count: 0,
323 metadata_count: 0,
324 marker_count: 0,
325 expected_products: seed.expected_products.to_vec(),
326 promotion_target: seed.promotion_target,
327 expectation_outcomes: Vec::new(),
328 },
329 }
330}
331
332fn finish_fixture_section<T>(sections: &mut Vec<T>, current: Option<T>) {
333 if let Some(section) = current {
334 sections.push(section);
335 }
336}
337
338fn push_fixture_line(buffer: &mut String, line: &str) {
339 if !buffer.is_empty() {
340 buffer.push('\n');
341 }
342 buffer.push_str(line);
343}
344
345fn parse_omena_fixture_file_header(
346 header: &str,
347) -> Result<(String, Vec<OmenaFixtureFileMetadataV0>), String> {
348 let mut parts = header.split_whitespace();
349 let path = parts
350 .next()
351 .ok_or_else(|| "fixture file header must include a path".to_string())?;
352 if path.contains(':') {
353 return Err("fixture file header path must precede metadata".to_string());
354 }
355
356 let mut metadata = Vec::new();
357 for part in parts {
358 let Some((key, value)) = part.split_once(':') else {
359 return Err(format!("fixture metadata `{part}` must use key:value"));
360 };
361 validate_omena_fixture_metadata(key, value)?;
362 metadata.push(OmenaFixtureFileMetadataV0 {
363 key: key.to_string(),
364 value: value.to_string(),
365 });
366 }
367
368 Ok((path.to_string(), metadata))
369}
370
371fn validate_omena_fixture_metadata(key: &str, value: &str) -> Result<(), String> {
372 if value.is_empty() {
373 return Err(format!("fixture metadata `{key}` must have a value"));
374 }
375 match key {
376 "dialect" => match value {
377 "css" | "scss" | "less" => Ok(()),
378 _ => Err("fixture dialect metadata must be css, scss, or less".to_string()),
379 },
380 "encoding" => match value {
381 "hex" => Ok(()),
382 _ => Err("fixture encoding metadata must be hex".to_string()),
383 },
384 "layer" | "composes-from" | "consumer-of" => Ok(()),
385 _ => Err(format!("fixture metadata key `{key}` is not supported")),
386 }
387}
388
389fn decode_hex_omena_fixture_file(source: &str) -> Result<String, String> {
390 let encoded = source.trim();
391 if !encoded.len().is_multiple_of(2) {
392 return Err("hex-encoded fixture file source must have even length".to_string());
393 }
394 let mut bytes = Vec::with_capacity(encoded.len() / 2);
395 for pair in encoded.as_bytes().chunks_exact(2) {
396 let high = hex_nibble(pair[0]).ok_or_else(|| {
397 "hex-encoded fixture file source must contain only hex digits".to_string()
398 })?;
399 let low = hex_nibble(pair[1]).ok_or_else(|| {
400 "hex-encoded fixture file source must contain only hex digits".to_string()
401 })?;
402 bytes.push((high << 4) | low);
403 }
404 String::from_utf8(bytes)
405 .map_err(|_| "hex-encoded fixture file source must decode to UTF-8".to_string())
406}
407
408fn hex_nibble(byte: u8) -> Option<u8> {
409 match byte {
410 b'0'..=b'9' => Some(byte - b'0'),
411 b'a'..=b'f' => Some(byte - b'a' + 10),
412 b'A'..=b'F' => Some(byte - b'A' + 10),
413 _ => None,
414 }
415}
416
417fn extract_omena_fixture_markers(
418 source: &str,
419) -> Result<(String, Vec<OmenaFixtureMarkerV0>), String> {
420 let mut cleaned = String::new();
421 let mut markers = Vec::new();
422 let mut cursor = 0;
423
424 while let Some(relative_start) = source[cursor..].find("/*") {
425 let start = cursor + relative_start;
426 cleaned.push_str(&source[cursor..start]);
427 let Some(relative_end) = source[start + 2..].find("*/") else {
428 return Err("fixture marker comment is unterminated".to_string());
429 };
430 let end = start + 2 + relative_end + 2;
431 let body = &source[start + 2..end - 2];
432 if let Some(marker) = parse_omena_fixture_marker(body, cleaned.len())? {
433 markers.push(marker);
434 } else {
435 cleaned.push_str(&source[start..end]);
436 }
437 cursor = end;
438 }
439
440 cleaned.push_str(&source[cursor..]);
441 Ok((cleaned, markers))
442}
443
444fn parse_omena_fixture_marker(
445 body: &str,
446 byte_offset: usize,
447) -> Result<Option<OmenaFixtureMarkerV0>, String> {
448 if body == "|" {
449 return Ok(Some(omena_fixture_marker("cursor", None, byte_offset)));
450 }
451 if let Some(name) = body.strip_prefix("at:") {
452 return Ok(Some(omena_fixture_marker(
453 "namedPoint",
454 Some(validate_omena_fixture_marker_payload("at", name)?),
455 byte_offset,
456 )));
457 }
458 if let Some(name) = body
459 .strip_prefix("</")
460 .and_then(|name| name.strip_suffix('>'))
461 {
462 return Ok(Some(omena_fixture_marker(
463 "rangeEnd",
464 Some(validate_omena_fixture_marker_payload("range end", name)?),
465 byte_offset,
466 )));
467 }
468 if let Some(name) = body
469 .strip_prefix('<')
470 .and_then(|name| name.strip_suffix('>'))
471 {
472 return Ok(Some(omena_fixture_marker(
473 "rangeStart",
474 Some(validate_omena_fixture_marker_payload("range start", name)?),
475 byte_offset,
476 )));
477 }
478 if let Some(name) = body.strip_prefix("name:") {
479 return Ok(Some(omena_fixture_marker(
480 "nameAnchor",
481 Some(validate_omena_fixture_marker_payload("name", name)?),
482 byte_offset,
483 )));
484 }
485 if let Some(target) = body.strip_prefix("from:") {
486 return Ok(Some(omena_fixture_marker(
487 "linkEnd",
488 Some(validate_omena_fixture_marker_payload("from", target)?),
489 byte_offset,
490 )));
491 }
492 Ok(None)
493}
494
495fn omena_fixture_marker(
496 kind: &'static str,
497 name: Option<String>,
498 byte_offset: usize,
499) -> OmenaFixtureMarkerV0 {
500 OmenaFixtureMarkerV0 {
501 kind,
502 name,
503 byte_start: byte_offset,
504 byte_end: byte_offset,
505 }
506}
507
508fn validate_omena_fixture_marker_payload(kind: &str, value: &str) -> Result<String, String> {
509 if value.is_empty() {
510 return Err(format!("fixture marker `{kind}` must have a value"));
511 }
512 Ok(value.to_string())
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518
519 const CROSS_LANGUAGE_FIXTURE: &str = r#"--- file: src/App.tsx
520import styles from "./Button.module.scss";
521styles.button;
522--- file: src/Button.module.scss
523.button { color: red; }
524--- expect: product
525omena-query.source-syntax-index
526--- expect: assertion
527shared fixture parser keeps source and style files in the same workspace fixture
528"#;
529
530 #[test]
531 fn parses_reusable_omena_fixture_v0_sections() -> Result<(), String> {
532 let fixture = parse_omena_fixture_v0(
533 r#"--- file: src/proof.css
534.a { color: red; }
535--- expect: product
536omena-transform-passes.cascade-proof-obligations
537--- expect: assertion
538proof obligations remain product-visible
539"#,
540 )?;
541
542 assert_eq!(fixture.schema_version, "0");
543 assert_eq!(fixture.files.len(), 1);
544 assert_eq!(fixture.files[0].path, "src/proof.css");
545 assert!(fixture.files[0].source.contains(".a"));
546 assert_eq!(fixture.expectations.len(), 2);
547 assert_eq!(fixture.expectations[0].key, "product");
548 assert_eq!(
549 fixture.expectations[0].value,
550 "omena-transform-passes.cascade-proof-obligations"
551 );
552
553 Ok(())
554 }
555
556 #[test]
557 fn keeps_source_and_style_files_in_one_workspace_fixture() -> Result<(), String> {
558 let fixture = parse_omena_fixture_v0(CROSS_LANGUAGE_FIXTURE)?;
559
560 assert_eq!(fixture.files.len(), 2);
561 assert_eq!(fixture.files[0].path, "src/App.tsx");
562 assert_eq!(fixture.files[1].path, "src/Button.module.scss");
563 assert!(
564 fixture
565 .expectations
566 .iter()
567 .any(|expectation| expectation.value == "omena-query.source-syntax-index")
568 );
569
570 Ok(())
571 }
572
573 #[test]
574 fn parses_omena_fixture_v0_metadata_and_markers() -> Result<(), String> {
575 let fixture = parse_omena_fixture_v0(
576 r#"//- src/Card.module.scss dialect:scss layer:style
577.card { color: /*|*/red; }
578.card/*at:selector*/ { color: blue; }
579.card { color: /*<colorRange>*/green/*</colorRange>*/; }
580.card { composes: item/*from:src/Base.module.scss#item*/; }
581--- expect: product
582omena-testkit.fixture-markers
583"#,
584 )?;
585
586 assert_eq!(fixture.files.len(), 1);
587 assert_eq!(fixture.files[0].path, "src/Card.module.scss");
588 assert_eq!(
589 fixture.files[0]
590 .metadata
591 .iter()
592 .map(|metadata| (metadata.key.as_str(), metadata.value.as_str()))
593 .collect::<Vec<_>>(),
594 vec![("dialect", "scss"), ("layer", "style")]
595 );
596 assert_eq!(
597 fixture.files[0]
598 .markers
599 .iter()
600 .map(|marker| (marker.kind, marker.name.as_deref()))
601 .collect::<Vec<_>>(),
602 vec![
603 ("cursor", None),
604 ("namedPoint", Some("selector")),
605 ("rangeStart", Some("colorRange")),
606 ("rangeEnd", Some("colorRange")),
607 ("linkEnd", Some("src/Base.module.scss#item"))
608 ]
609 );
610 assert!(fixture.files[0].source.contains(".card { color: red; }"));
611 assert!(!fixture.files[0].source.contains("/*|*/"));
612 assert_eq!(
613 fixture.files[0].markers[0].byte_start,
614 ".card { color: ".len()
615 );
616
617 Ok(())
618 }
619
620 #[test]
621 fn keeps_non_fixture_comments_in_source() -> Result<(), String> {
622 let fixture = parse_omena_fixture_v0(
623 r#"//- src/Card.module.css dialect:css
624.card { /* regular comment */ color: red; }
625--- expect: product
626omena-testkit.fixture-markers
627"#,
628 )?;
629
630 assert!(fixture.files[0].source.contains("/* regular comment */"));
631 assert!(fixture.files[0].markers.is_empty());
632
633 Ok(())
634 }
635
636 #[test]
637 fn hex_encoded_file_source_is_marker_inert_and_byte_preserving() -> Result<(), String> {
638 let source = concat!(
639 ".card {\n",
640 " //---- divider comment\n",
641 " content: \"--- file: nope /*|*/ /*at:point*/ /*<range>*/ /*</range>*/\";\n",
642 "}\n",
643 "/*"
644 );
645 let raw = format!(
646 "--- file: src/Card.module.scss encoding:hex\n{}\n--- expect: product\nomena-testkit.fixture-encoding\n",
647 hex_encode_for_test(source)
648 );
649 let fixture = parse_omena_fixture_v0(raw.as_str())?;
650
651 assert_eq!(fixture.files.len(), 1);
652 assert_eq!(fixture.files[0].path, "src/Card.module.scss");
653 assert_eq!(fixture.files[0].source, source);
654 assert!(fixture.files[0].markers.is_empty());
655
656 Ok(())
657 }
658
659 #[test]
660 fn hex_encoded_file_source_rejects_non_utf8_bytes() {
661 let result = parse_omena_fixture_v0(
662 "--- file: src/raw.css encoding:hex\nff\n--- expect: product\nomena-testkit.fixture-encoding\n",
663 );
664 assert_eq!(
665 result.err().as_deref(),
666 Some("hex-encoded fixture file source must decode to UTF-8")
667 );
668 }
669
670 #[test]
671 fn classifies_m7_diagnostic_cascade_and_boundary_expectations() -> Result<(), String> {
672 let fixture = parse_omena_fixture_v0(
673 r#"//- src/Nested.module.scss dialect:scss
674.article {
675 &.box { &.fill { padding: 1px; } }
676}
677--- expect: diagnostic
678code: unreachableDeclaration
679range: colorRange
680--- expect: no-diagnostic unspecifiedCascadeTie
681--- expect: count unreachableDeclaration:0
682--- expect: cascade-outcome decl-1
683--- expect: cascade-witness decl-2
684--- expect: boundary-state ext-1 Resolved
685--- expect: boundary-state ext-2 Partial
686--- expect: boundary-state ext-3 Stale
687--- expect: boundary-state ext-4 Missing
688--- expect: boundary-state ext-5 Unresolved
689"#,
690 )?;
691
692 assert_eq!(
693 fixture
694 .expectations
695 .iter()
696 .map(OmenaFixtureExpectationV0::kind)
697 .collect::<Vec<_>>(),
698 vec![
699 OmenaFixtureExpectationKindV0::Diagnostic,
700 OmenaFixtureExpectationKindV0::NoDiagnostic,
701 OmenaFixtureExpectationKindV0::Count,
702 OmenaFixtureExpectationKindV0::CascadeOutcome,
703 OmenaFixtureExpectationKindV0::CascadeWitness,
704 OmenaFixtureExpectationKindV0::BoundaryState,
705 OmenaFixtureExpectationKindV0::BoundaryState,
706 OmenaFixtureExpectationKindV0::BoundaryState,
707 OmenaFixtureExpectationKindV0::BoundaryState,
708 OmenaFixtureExpectationKindV0::BoundaryState,
709 ]
710 );
711 assert_eq!(
712 fixture.expectations[1].key,
713 "no-diagnostic unspecifiedCascadeTie"
714 );
715 assert_eq!(
716 fixture.expectations[2].key,
717 "count unreachableDeclaration:0"
718 );
719 Ok(())
720 }
721
722 #[test]
723 fn rejects_unknown_fixture_metadata() {
724 let error = parse_omena_fixture_v0(
725 r#"//- src/Card.module.css unknown:value
726.card { color: red; }
727--- expect: product
728omena-testkit.fixture-markers
729"#,
730 )
731 .err();
732
733 assert_eq!(
734 error.as_deref(),
735 Some("fixture metadata key `unknown` is not supported")
736 );
737 }
738
739 #[test]
740 fn rejects_fixture_without_sections() {
741 let error = parse_omena_fixture_v0("plain text").err();
742
743 assert_eq!(
744 error.as_deref(),
745 Some("fixture content must start with a file or expect marker")
746 );
747 }
748
749 #[test]
750 fn rejects_fixture_without_expectations() {
751 let error = parse_omena_fixture_v0(
752 r#"--- file: src/Button.module.scss
753.button { color: red; }
754"#,
755 )
756 .err();
757
758 assert_eq!(
759 error.as_deref(),
760 Some("fixture must contain at least one expectation section")
761 );
762 }
763
764 #[test]
765 fn summarizes_external_fixture_seed_corpus() {
766 let seeds = [OmenaTestkitFixtureSeedV0 {
767 label: "external",
768 lane: "consumer",
769 raw: r#"--- file: src/input.css
770.x { color: red; }
771--- expect: product
772consumer.product
773"#,
774 expected_products: &["consumer.product"],
775 promotion_target: "omena-testkit/consumer",
776 }];
777
778 let report = summarize_omena_testkit_fixture_seed_corpus(&seeds);
779
780 assert_eq!(report.product, "omena-testkit.fixture-seed-corpus");
781 assert_eq!(report.fixture_count, 1);
782 assert_eq!(report.lane_count, 1);
783 assert!(report.all_seeds_parse);
784 assert_eq!(report.reports[0].file_count, 1);
785 assert_eq!(report.reports[0].expectation_count, 1);
786 }
787
788 fn hex_encode_for_test(source: &str) -> String {
789 source
790 .as_bytes()
791 .iter()
792 .map(|byte| format!("{byte:02x}"))
793 .collect()
794 }
795}