@@ -18,6 +18,9 @@ pub const FailureReason = enum {
1818 status_format_error ,
1919};
2020
21+ // Represents a test failure due to assertion checking.
22+ // This struct needs to own its own memory. We should adapt it to use a .init pattern
23+ // to make that more obvious.
2124pub const AssertionFailure = struct {
2225 assertion_key : []const u8 ,
2326 assertion_value : []const u8 ,
@@ -26,8 +29,32 @@ pub const AssertionFailure = struct {
2629 actual : []const u8 ,
2730 reason : FailureReason ,
2831 source_file : ? []const u8 = null ,
32+ test_name : ? []const u8 = null ,
2933 assertion_index : usize = 0 ,
3034
35+ pub fn init (
36+ allocator : std.mem.Allocator ,
37+ assertion : HttpParser.Assertion ,
38+ expected : []const u8 ,
39+ actual : []const u8 ,
40+ reason : FailureReason ,
41+ source_file : ? []const u8 ,
42+ test_name : ? []const u8 ,
43+ assertion_index : usize ,
44+ ) ! AssertionFailure {
45+ return AssertionFailure {
46+ .assertion_key = try allocator .dupe (u8 , assertion .key ),
47+ .assertion_value = try allocator .dupe (u8 , assertion .value ),
48+ .assertion_type = assertion .assertion_type ,
49+ .expected = try allocator .dupe (u8 , expected ),
50+ .actual = try allocator .dupe (u8 , actual ),
51+ .reason = reason ,
52+ .assertion_index = assertion_index ,
53+ .source_file = if (source_file ) | file | try allocator .dupe (u8 , file ) else null ,
54+ .test_name = if (test_name ) | name | try allocator .dupe (u8 , name ) else null ,
55+ };
56+ }
57+
3158 pub fn deinit (self : * AssertionFailure , allocator : Allocator ) void {
3259 allocator .free (self .assertion_key );
3360 allocator .free (self .assertion_value );
@@ -36,6 +63,9 @@ pub const AssertionFailure = struct {
3663 if (self .source_file ) | file | {
3764 allocator .free (file );
3865 }
66+ if (self .test_name ) | name | {
67+ allocator .free (name );
68+ }
3969 }
4070};
4171
@@ -65,16 +95,19 @@ pub const AssertionDiagnostic = struct {
6595 reason : FailureReason ,
6696 assertion_index : usize ,
6797 source_file : ? []const u8 ,
98+ test_name : ? []const u8 ,
6899 ) ! void {
69- const failure = AssertionFailure {
70- .assertion_key = try self .allocator .dupe (u8 , assertion .key ),
71- .assertion_value = try self .allocator .dupe (u8 , assertion .value ),
72- .assertion_type = assertion .assertion_type ,
73- .expected = try self .allocator .dupe (u8 , expected ),
74- .actual = try self .allocator .dupe (u8 , actual ),
75- .reason = reason ,
76- .assertion_index = assertion_index ,
77- .source_file = if (source_file ) | file | try self .allocator .dupe (u8 , file ) else null ,
100+ const failure = AssertionFailure .init (
101+ self .allocator ,
102+ assertion ,
103+ expected ,
104+ actual ,
105+ reason ,
106+ source_file ,
107+ test_name ,
108+ assertion_index ,
109+ ) catch {
110+ return error .UnableToAllocateAssertionFailure ;
78111 };
79112 try self .failures .append (self .allocator , failure );
80113 }
@@ -130,21 +163,23 @@ pub fn check(
130163 source_file : ? []const u8 ,
131164) void {
132165 for (request .assertions .items , 0.. ) | assertion , index | {
133- checkAssertion (assertion , response , diagnostic , index , source_file ) catch | err | {
166+ checkAssertion (assertion , request , response , diagnostic , index , source_file ) catch | err | {
134167 diagnostic .addFailure (
135168 assertion ,
136169 "N/A" ,
137170 @errorName (err ),
138171 .status_format_error ,
139172 index ,
140173 source_file ,
174+ request .name ,
141175 ) catch {};
142176 };
143177 }
144178}
145179
146180fn checkAssertion (
147181 assertion : HttpParser.Assertion ,
182+ request : * HttpParser.HttpRequest ,
148183 response : Client.HttpResponse ,
149184 diagnostic : * AssertionDiagnostic ,
150185 assertion_index : usize ,
@@ -158,22 +193,22 @@ fn checkAssertion(
158193 if (response .status != expected_status ) {
159194 const actual_str = try std .fmt .allocPrint (diagnostic .allocator , "{d}" , .{@intFromEnum (response .status .? )});
160195 defer diagnostic .allocator .free (actual_str );
161- try diagnostic .addFailure (assertion , assertion .value , actual_str , .status_mismatch , assertion_index , source_file );
196+ try diagnostic .addFailure (assertion , assertion .value , actual_str , .status_mismatch , assertion_index , source_file , request . name );
162197 }
163198 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
164199 if (! std .mem .eql (u8 , response .body , assertion .value )) {
165- try diagnostic .addFailure (assertion , assertion .value , response .body , .body_mismatch , assertion_index , source_file );
200+ try diagnostic .addFailure (assertion , assertion .value , response .body , .body_mismatch , assertion_index , source_file , request . name );
166201 }
167202 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
168203 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
169204 const actual_value = response .headers .get (header_name );
170205 if (actual_value == null ) {
171- try diagnostic .addFailure (assertion , assertion .value , "null" , .header_missing , assertion_index , source_file );
206+ try diagnostic .addFailure (assertion , assertion .value , "null" , .header_missing , assertion_index , source_file , request . name );
172207 } else if (! std .ascii .eqlIgnoreCase (actual_value .? , assertion .value )) {
173- try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .header_mismatch , assertion_index , source_file );
208+ try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .header_mismatch , assertion_index , source_file , request . name );
174209 }
175210 } else {
176- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
211+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
177212 }
178213 },
179214 .not_equal = > {
@@ -183,20 +218,20 @@ fn checkAssertion(
183218 if (response .status == expected_status ) {
184219 const actual_str = try std .fmt .allocPrint (diagnostic .allocator , "{d}" , .{@intFromEnum (response .status .? )});
185220 defer diagnostic .allocator .free (actual_str );
186- try diagnostic .addFailure (assertion , assertion .value , actual_str , .status_mismatch , assertion_index , source_file );
221+ try diagnostic .addFailure (assertion , assertion .value , actual_str , .status_mismatch , assertion_index , source_file , request . name );
187222 }
188223 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
189224 if (std .mem .eql (u8 , response .body , assertion .value )) {
190- try diagnostic .addFailure (assertion , assertion .value , response .body , .body_mismatch , assertion_index , source_file );
225+ try diagnostic .addFailure (assertion , assertion .value , response .body , .body_mismatch , assertion_index , source_file , request . name );
191226 }
192227 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
193228 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
194229 const actual_value = response .headers .get (header_name );
195230 if (actual_value != null and std .ascii .eqlIgnoreCase (actual_value .? , assertion .value )) {
196- try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .header_mismatch , assertion_index , source_file );
231+ try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .header_mismatch , assertion_index , source_file , request . name );
197232 }
198233 } else {
199- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
234+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
200235 }
201236 },
202237 .contains = > {
@@ -205,20 +240,20 @@ fn checkAssertion(
205240 const status_code = @intFromEnum (response .status .? );
206241 const status_str = try std .fmt .bufPrint (& status_buf , "{}" , .{status_code });
207242 if (std .mem .indexOf (u8 , status_str , assertion .value ) == null ) {
208- try diagnostic .addFailure (assertion , assertion .value , status_str , .contains_failed , assertion_index , source_file );
243+ try diagnostic .addFailure (assertion , assertion .value , status_str , .contains_failed , assertion_index , source_file , request . name );
209244 }
210245 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
211246 if (std .mem .indexOf (u8 , response .body , assertion .value ) == null ) {
212- try diagnostic .addFailure (assertion , assertion .value , response .body , .contains_failed , assertion_index , source_file );
247+ try diagnostic .addFailure (assertion , assertion .value , response .body , .contains_failed , assertion_index , source_file , request . name );
213248 }
214249 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
215250 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
216251 const actual_value = response .headers .get (header_name );
217252 if (actual_value == null or std .mem .indexOf (u8 , actual_value .? , assertion .value ) == null ) {
218- try diagnostic .addFailure (assertion , assertion .value , actual_value orelse "null" , .contains_failed , assertion_index , source_file );
253+ try diagnostic .addFailure (assertion , assertion .value , actual_value orelse "null" , .contains_failed , assertion_index , source_file , request . name );
219254 }
220255 } else {
221- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
256+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
222257 }
223258 },
224259 .not_contains = > {
@@ -227,20 +262,20 @@ fn checkAssertion(
227262 const status_code = @intFromEnum (response .status .? );
228263 const status_str = try std .fmt .bufPrint (& status_buf , "{}" , .{status_code });
229264 if (std .mem .indexOf (u8 , status_str , assertion .value ) != null ) {
230- try diagnostic .addFailure (assertion , assertion .value , status_str , .not_contains_failed , assertion_index , source_file );
265+ try diagnostic .addFailure (assertion , assertion .value , status_str , .not_contains_failed , assertion_index , source_file , request . name );
231266 }
232267 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
233268 if (std .mem .indexOf (u8 , response .body , assertion .value ) != null ) {
234- try diagnostic .addFailure (assertion , assertion .value , response .body , .not_contains_failed , assertion_index , source_file );
269+ try diagnostic .addFailure (assertion , assertion .value , response .body , .not_contains_failed , assertion_index , source_file , request . name );
235270 }
236271 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
237272 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
238273 const actual_value = response .headers .get (header_name );
239274 if (actual_value != null and std .mem .indexOf (u8 , actual_value .? , assertion .value ) != null ) {
240- try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .not_contains_failed , assertion_index , source_file );
275+ try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .not_contains_failed , assertion_index , source_file , request . name );
241276 }
242277 } else {
243- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
278+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
244279 }
245280 },
246281 .matches_regex = > {
@@ -249,20 +284,20 @@ fn checkAssertion(
249284 const status_code = @intFromEnum (response .status .? );
250285 const status_str = try std .fmt .bufPrint (& status_buf , "{}" , .{status_code });
251286 if (! matchesRegex (status_str , assertion .value )) {
252- try diagnostic .addFailure (assertion , assertion .value , status_str , .contains_failed , assertion_index , source_file );
287+ try diagnostic .addFailure (assertion , assertion .value , status_str , .contains_failed , assertion_index , source_file , request . name );
253288 }
254289 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
255290 if (! matchesRegex (response .body , assertion .value )) {
256- try diagnostic .addFailure (assertion , assertion .value , response .body , .contains_failed , assertion_index , source_file );
291+ try diagnostic .addFailure (assertion , assertion .value , response .body , .contains_failed , assertion_index , source_file , request . name );
257292 }
258293 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
259294 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
260295 const actual_value = response .headers .get (header_name );
261296 if (actual_value == null or ! matchesRegex (actual_value .? , assertion .value )) {
262- try diagnostic .addFailure (assertion , assertion .value , actual_value orelse "null" , .contains_failed , assertion_index , source_file );
297+ try diagnostic .addFailure (assertion , assertion .value , actual_value orelse "null" , .contains_failed , assertion_index , source_file , request . name );
263298 }
264299 } else {
265- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
300+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
266301 }
267302 },
268303 .not_matches_regex = > {
@@ -271,20 +306,20 @@ fn checkAssertion(
271306 const status_code = @intFromEnum (response .status .? );
272307 const status_str = try std .fmt .bufPrint (& status_buf , "{}" , .{status_code });
273308 if (matchesRegex (status_str , assertion .value )) {
274- try diagnostic .addFailure (assertion , assertion .value , status_str , .not_contains_failed , assertion_index , source_file );
309+ try diagnostic .addFailure (assertion , assertion .value , status_str , .not_contains_failed , assertion_index , source_file , request . name );
275310 }
276311 } else if (std .ascii .eqlIgnoreCase (assertion .key , "body" )) {
277312 if (matchesRegex (response .body , assertion .value )) {
278- try diagnostic .addFailure (assertion , assertion .value , response .body , .not_contains_failed , assertion_index , source_file );
313+ try diagnostic .addFailure (assertion , assertion .value , response .body , .not_contains_failed , assertion_index , source_file , request . name );
279314 }
280315 } else if (std .mem .startsWith (u8 , assertion .key , "header[\" " )) {
281316 const header_name = assertion .key [8 .. assertion .key .len - 2 ];
282317 const actual_value = response .headers .get (header_name );
283318 if (actual_value != null and matchesRegex (actual_value .? , assertion .value )) {
284- try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .not_contains_failed , assertion_index , source_file );
319+ try diagnostic .addFailure (assertion , assertion .value , actual_value .? , .not_contains_failed , assertion_index , source_file , request . name );
285320 }
286321 } else {
287- try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file );
322+ try diagnostic .addFailure (assertion , assertion .value , "N/A" , .invalid_assertion_key , assertion_index , source_file , request . name );
288323 }
289324 },
290325 else = > {},
@@ -324,6 +359,7 @@ test "Assertion checker with diagnostics - all pass" {
324359 var request = HttpParser.HttpRequest {
325360 .method = .GET ,
326361 .url = "https://api.example.com" ,
362+ .name = "test name" ,
327363 .headers = .empty ,
328364 .assertions = assertions ,
329365 .body = null ,
@@ -382,6 +418,7 @@ test "Assertion checker with not_equal - all pass" {
382418 .headers = .empty ,
383419 .assertions = assertions ,
384420 .body = null ,
421+ .name = "test name" ,
385422 .version = .@"HTTP/1.1" ,
386423 };
387424
@@ -437,6 +474,7 @@ test "Assertion checker with failures - collects all failures" {
437474 .headers = .empty ,
438475 .assertions = assertions ,
439476 .body = null ,
477+ .name = "test name" ,
440478 .version = .@"HTTP/1.1" ,
441479 };
442480
@@ -495,6 +533,7 @@ test "HttpParser supports starts_with for status, body, and header" {
495533 .url = "https://api.example.com" ,
496534 .headers = .empty ,
497535 .assertions = assertions ,
536+ .name = "test name" ,
498537 .body = null ,
499538 .version = .@"HTTP/1.1" ,
500539 };
@@ -560,6 +599,7 @@ test "HttpParser supports matches_regex and not_matches_regex for status, body,
560599 .headers = .empty ,
561600 .assertions = assertions ,
562601 .body = null ,
602+ .name = "TEST NAME" ,
563603 .version = .@"HTTP/1.1" ,
564604 };
565605
@@ -610,6 +650,7 @@ test "HttpParser supports contains and not_contains for headers" {
610650 .headers = .empty ,
611651 .assertions = assertions ,
612652 .body = null ,
653+ .name = "test name" ,
613654 .version = .@"HTTP/1.1" ,
614655 };
615656
0 commit comments