1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! Streaming virtual field assertion rendering for Rust e2e tests.
//!
//! Split out of `assertions.rs`: streaming virtual fields (e.g. `chunks`, `imports`,
//! `structure`) resolve against the collected `chunks` local rather than a struct field, which
//! makes their assertion rendering a self-contained concern distinct from the synthetic derived
//! fields in `assertion_synthetic.rs` (e.g. `chunks_have_content`).
use std::fmt::Write as FmtWrite;
use crate::e2e::codegen::streaming_assertions::StreamingFieldResolver;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;
/// Renders an assertion against a streaming virtual field into `out` and returns `true` when
/// `assertion.field` names one (and the assertion was handled), signaling the caller to return
/// immediately rather than fall through to struct-field assertion handling.
///
/// Streaming virtual fields: intercept before is_valid_for_result so they are never skipped.
/// These fields resolve against the `chunks` collected-list variable.
///
/// For streaming fixtures, `chunks` is bound by the collect snippet emitted in
/// `render_test_function`. For non-streaming fixtures whose result struct has a literal field
/// whose name collides with a streaming-virtual name (e.g. `chunks`, `imports`, `structure`),
/// `render_test_function` emits `let {f} = &result.{f};` before assertions, so the hardcoded
/// `chunks` identifier used below still resolves.
pub(super) fn try_render_streaming_virtual_field_assertion(
out: &mut String,
assertion: &Assertion,
dep_name: &str,
field_resolver: &FieldResolver,
streaming_item_type: Option<&str>,
) -> bool {
let Some(f) = &assertion.field else { return false };
if f.is_empty() || !crate::e2e::codegen::streaming_assertions::is_streaming_virtual_field(f) {
return false;
}
if let Some(expr) = StreamingFieldResolver::accessor_with_streaming_context(
f,
"rust",
"chunks",
Some(dep_name),
streaming_item_type,
) {
// `field_resolver.is_optional` answers for the *declared* IR field, but the expression
// actually emitted is whatever the streaming resolver built — and most of its accessors
// flatten the declared `Option` away (`.iter().flatten()`, `.unwrap_or_default()`) while
// pinning a concrete type. Ask the accessor owner which shape it produced instead of
// assuming the declared wrapper survived: appending `.as_ref()` to a `Vec`- or
// `String`-typed chain is an unannotatable `AsRef<T>` call (E0282), not a no-op. ~keep
let option_wrapper_survives = field_resolver.is_optional(f)
&& StreamingFieldResolver::accessor_is_collected_var_passthrough(f, "rust", "chunks");
match assertion.assertion_type.as_str() {
"count_min" => {
if let Some(val) = &assertion.value
&& let Some(n) = val.as_u64()
{
let expr_for_len = if option_wrapper_survives {
format!("{expr}.as_ref().map_or(0, |v| v.len())")
} else {
format!("{expr}.len()")
};
let _ = writeln!(
out,
" assert!({expr_for_len} >= {n} as usize, \"expected >= {n} chunks\");"
);
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'count_min' requires a numeric value in the fixture, got {:?}",
assertion.value
);
}
}
"count_equals" => {
if let Some(val) = &assertion.value
&& let Some(n) = val.as_u64()
{
let expr_for_len = if option_wrapper_survives {
format!("{expr}.as_ref().map_or(0, |v| v.len())")
} else {
format!("{expr}.len()")
};
let _ = writeln!(
out,
" assert_eq!({expr_for_len}, {n} as usize, \"expected exactly {n} chunks\");"
);
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'count_equals' requires a numeric value in the fixture, got {:?}",
assertion.value
);
}
}
"equals" => {
if let Some(serde_json::Value::String(s)) = &assertion.value {
let escaped = crate::e2e::escape::escape_rust(s);
let _ = writeln!(out, " assert_eq!({expr}, \"{escaped}\");");
} else if let Some(val) = &assertion.value {
let lit = super::assertion_synthetic::numeric_literal(val);
let _ = writeln!(out, " assert_eq!({expr}, {lit});");
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'equals' requires a string or numeric value in the fixture, got {:?}",
assertion.value
);
}
}
"not_empty" => {
let check_expr = if option_wrapper_survives {
format!("{expr}.as_ref().is_some_and(|v| !v.is_empty())")
} else {
format!("!{expr}.is_empty()")
};
let _ = writeln!(out, " assert!({check_expr}, \"expected non-empty\");");
}
"is_empty" => {
let check_expr = if option_wrapper_survives {
format!("{expr}.as_ref().is_none_or(|v| v.is_empty())")
} else {
format!("{expr}.is_empty()")
};
let _ = writeln!(out, " assert!({check_expr}, \"expected empty\");");
}
"is_true" => {
let _ = writeln!(out, " assert!({expr}, \"expected true\");");
}
"is_false" => {
let _ = writeln!(out, " assert!(!{expr}, \"expected false\");");
}
"greater_than" => {
if let Some(val) = &assertion.value {
let lit = super::assertion_synthetic::numeric_literal(val);
let _ = writeln!(out, " assert!({expr} > {lit}, \"expected > {lit}\");");
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'greater_than' requires a numeric value in the fixture, got {:?}",
assertion.value
);
}
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let lit = super::assertion_synthetic::numeric_literal(val);
let _ = writeln!(out, " assert!({expr} >= {lit}, \"expected >= {lit}\");");
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'greater_than_or_equal' requires a numeric value in the fixture, got {:?}",
assertion.value
);
}
}
"contains" => {
if let Some(serde_json::Value::String(s)) = &assertion.value {
let escaped = crate::e2e::escape::escape_rust(s);
let _ = writeln!(
out,
" assert!({expr}.contains(\"{escaped}\"), \"expected to contain: {escaped}\");"
);
} else {
panic!(
"Rust e2e generator: streaming field '{f}' assertion 'contains' requires a string value in the fixture, got {:?}",
assertion.value
);
}
}
other => {
panic!("Rust e2e generator: unsupported assertion type '{other}' on streaming field '{f}'");
}
}
} else {
panic!(
"Rust e2e generator: streaming field '{f}' has no accessor for context (streaming_item_type={streaming_item_type:?}); check the streaming adapter configuration"
);
}
true
}