#![allow(clippy::print_stderr)]
use crate::core::config::e2e::{CallConfig, CallOverride};
use crate::core::config::{AdapterConfig, AdapterPattern, ResolvedCrateConfig};
use crate::e2e::codegen::field_skip::FieldSkip;
use crate::e2e::codegen::{E2eCodegen, all_generators};
use crate::e2e::config::{E2eConfig, StreamingConfig, StreamingRecipe};
use crate::e2e::fixture::{Assertion, Fixture, FixtureGroup};
const MATRIX_FUNCTION_NAME: &str = "mock_matrix_call";
const MATRIX_STREAM_ITEM_TYPE: &str = "MockMatrixStreamItem";
use super::snippets;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Shape {
Plain,
Streaming,
Bytes,
MockOnly,
}
impl Shape {
const ALL: [Shape; 4] = [Shape::Plain, Shape::Streaming, Shape::Bytes, Shape::MockOnly];
fn label(self) -> &'static str {
match self {
Shape::Plain => "plain_typed_result",
Shape::Streaming => "streaming",
Shape::Bytes => "bytes_result",
Shape::MockOnly => "mock_only",
}
}
fn mock_assertion() -> Assertion {
Assertion {
assertion_type: "equals".to_string(),
field: Some("mock.requests.total".to_string()),
value: Some(serde_json::json!(1)),
..Default::default()
}
}
fn assertions(self) -> Vec<Assertion> {
match self {
Shape::Plain => vec![
Assertion {
assertion_type: "not_error".to_string(),
..Default::default()
},
Self::mock_assertion(),
],
Shape::Streaming | Shape::Bytes | Shape::MockOnly => vec![Self::mock_assertion()],
}
}
fn fixture(self, id: &str) -> Fixture {
Fixture {
id: id.to_string(),
description: format!("cross-backend mock-capture matrix fixture ({})", self.label()),
assertions: self.assertions(),
..Fixture::default()
}
}
fn e2e_config(self, language: &str) -> E2eConfig {
let mut call = CallConfig {
function: MATRIX_FUNCTION_NAME.to_string(),
returns_result: true,
..CallConfig::default()
};
match self {
Shape::Plain | Shape::MockOnly => {}
Shape::Streaming => {
call.streaming = Some(StreamingConfig::Recipe(StreamingRecipe {
enabled: Some(true),
item_type: Some(MATRIX_STREAM_ITEM_TYPE.to_string()),
}));
if language == "zig" {
call.overrides.insert(
"zig".to_string(),
CallOverride {
client_factory: Some("Client.create".to_string()),
..CallOverride::default()
},
);
}
}
Shape::Bytes => {
call.result_is_bytes = true;
if language == "zig" {
call.overrides.insert(
"zig".to_string(),
CallOverride {
result_is_bytes: true,
client_factory: Some("Client.create".to_string()),
..CallOverride::default()
},
);
}
}
}
E2eConfig {
call,
..E2eConfig::default()
}
}
fn resolved_crate_config(self) -> ResolvedCrateConfig {
match self {
Shape::Plain | Shape::MockOnly | Shape::Bytes => ResolvedCrateConfig::default(),
Shape::Streaming => ResolvedCrateConfig {
adapters: vec![AdapterConfig {
name: MATRIX_FUNCTION_NAME.to_string(),
pattern: AdapterPattern::Streaming,
core_path: format!("mock_matrix_core::{MATRIX_FUNCTION_NAME}"),
params: Vec::new(),
returns: None,
error_type: None,
owner_type: None,
item_type: Some(MATRIX_STREAM_ITEM_TYPE.to_string()),
gil_release: false,
trait_name: None,
trait_method: None,
detect_async: false,
request_type: None,
skip_languages: Vec::new(),
}],
..ResolvedCrateConfig::default()
},
}
}
}
struct Exemption {
language: &'static str,
shape: Shape,
reason: &'static str,
}
const EXEMPTIONS: &[Exemption] = &[];
const UNWIRED_LANGUAGES: &[&str] = &["brew", "homebrew", "php_ext"];
struct RenderedFile {
path: String,
content: String,
}
fn field_skip_names_a_mock_field(content: &str) -> Option<String> {
content.lines().find_map(|line| {
let (field, _variant) = FieldSkip::extract_classified(line)?;
super::is_mock_virtual_field(field).then(|| line.to_string())
})
}
const LITERAL_FALLTHROUGH_ARTIFACTS: &[&str] = &["result.mock", ".mock().requests()"];
fn assert_renders_mock_capture(language: &str, shape: Shape, files: &[RenderedFile]) {
assert!(
!files.is_empty(),
"{language}/{}: generate() returned no files at all -- nothing to examine",
shape.label()
);
let capture = snippets::mock_capture(language)
.unwrap_or_else(|_| panic!("{language} is in the matrix's wired set but snippets::mock_capture rejects it"));
let total_occurrences: usize = files
.iter()
.map(|file| file.content.matches(capture.helper_name).count())
.sum();
let per_file_hits: Vec<&str> = files
.iter()
.filter(|file| file.content.contains(capture.helper_name))
.map(|file| file.path.as_str())
.collect();
assert!(
total_occurrences >= 2,
"{language}/{}: expected the mock-capture helper name '{}' to appear at least twice \
across the emitted files (a definition and at least one call site), found {} \
occurrence(s) in file(s) {:?}. Emitted files: {:?}",
shape.label(),
capture.helper_name,
total_occurrences,
per_file_hits,
files.iter().map(|file| file.path.as_str()).collect::<Vec<_>>(),
);
for file in files {
if let Some(line) = field_skip_names_a_mock_field(&file.content) {
panic!(
"{language}/{}: file '{}' fell through to a generic field-skip marker instead of \
being captured -- got line: {line}",
shape.label(),
file.path,
);
}
for artifact in LITERAL_FALLTHROUGH_ARTIFACTS {
assert!(
!file.content.contains(artifact),
"{language}/{}: file '{}' contains the literal fallthrough artifact '{artifact}'",
shape.label(),
file.path,
);
}
}
}
fn render(generator: &dyn E2eCodegen, shape: Shape) -> anyhow::Result<Vec<RenderedFile>> {
let language = generator.language_name();
let e2e_config = shape.e2e_config(language);
let fixture = shape.fixture(&format!("mock_matrix_{}_{}", language, shape.label()));
let groups = [FixtureGroup {
category: "mock_matrix".to_string(),
fixtures: vec![fixture],
}];
let crate_config = shape.resolved_crate_config();
let files = generator.generate(&groups, &e2e_config, &crate_config, &[], &[], &[], &[])?;
Ok(files
.into_iter()
.map(|file| RenderedFile {
path: file.path.display().to_string(),
content: file.content,
})
.collect())
}
#[test]
fn unwired_languages_are_exactly_the_expected_three() {
let mut actually_unwired: Vec<&str> = all_generators()
.iter()
.map(|generator| generator.language_name())
.filter(|language| snippets::mock_capture(language).is_err())
.collect();
actually_unwired.sort_unstable();
let mut expected = UNWIRED_LANGUAGES.to_vec();
expected.sort_unstable();
assert_eq!(
actually_unwired, expected,
"the unwired-language allowlist has drifted from snippets::MOCK_CAPTURE_TABLE; update \
UNWIRED_LANGUAGES (and this matrix's understanding of which cells are structurally \
exempt) to match"
);
}
#[test]
fn every_wired_backend_emits_its_mock_capture_call_across_every_shape() {
let generators = all_generators();
let mut executed = 0usize;
let mut skipped_unwired = 0usize;
let mut skipped_exempt = 0usize;
let mut failures: Vec<String> = Vec::new();
for generator in &generators {
let language = generator.language_name();
if UNWIRED_LANGUAGES.contains(&language) {
assert!(
snippets::mock_capture(language).is_err(),
"'{language}' is listed in UNWIRED_LANGUAGES but snippets::mock_capture now \
resolves it -- move it into the executed set instead of skipping it"
);
skipped_unwired += Shape::ALL.len();
continue;
}
for shape in Shape::ALL {
if let Some(exemption) = EXEMPTIONS
.iter()
.find(|entry| entry.language == language && entry.shape == shape)
{
skipped_exempt += 1;
eprintln!(
"mock-capture matrix: skipping {language}/{} -- {}",
shape.label(),
exemption.reason
);
continue;
}
executed += 1;
let outcome = render(generator.as_ref(), shape);
match outcome {
Ok(output) => {
if let Err(panic_message) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
assert_renders_mock_capture(language, shape, &output);
})) {
let message = panic_message
.downcast_ref::<String>()
.cloned()
.or_else(|| panic_message.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_else(|| "<non-string panic payload>".to_string());
failures.push(message);
}
}
Err(error) => {
failures.push(format!("{language}/{}: generation failed: {error:#}", shape.label()));
}
}
}
}
let total_cells = generators.len() * Shape::ALL.len();
let summary = format!(
"mock-capture matrix: {executed} executed, {skipped_unwired} skipped (unwired language), \
{skipped_exempt} skipped (named exemption), {total_cells} total cells"
);
eprintln!("{summary}");
assert!(
failures.is_empty(),
"{summary}\n{} failure(s):\n{}",
failures.len(),
failures.join("\n\n")
);
}