Skip to content

Commit 07ee890

Browse files
committed
feat(test name): add test name to parsed output
1 parent bc59417 commit 07ee890

File tree

3 files changed

+90
-43
lines changed

3 files changed

+90
-43
lines changed

.github/workflows/release.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ jobs:
1616
- name: Install Zig
1717
uses: mlugg/setup-zig@v2
1818
with:
19-
version: 0.15.1
19+
version: 0.15.2
2020

21-
- name: Build Windows
21+
- name: Build Windows
2222
run: "zig build -Dtarget=x86_64-windows -Doptimize=ReleaseSafe -Dexe_name=httpspec_windows_x86"
2323

24-
- name: Build Mac_x86
24+
- name: Build Mac_x86
2525
run: "zig build -Dtarget=x86_64-macos -Doptimize=ReleaseSafe -Dexe_name=httpspec_mac_x86"
2626

27-
- name: Build Mac_ARM
27+
- name: Build Mac_ARM
2828
run: "zig build -Dtarget=aarch64-macos -Doptimize=ReleaseSafe -Dexe_name=httpspec_mac_arm"
2929

30-
- name: Build Linux_x86
30+
- name: Build Linux_x86
3131
run: "zig build -Dtarget=x86_64-linux -Doptimize=ReleaseSafe -Dexe_name=httpspec_linux_x86"
3232

3333
- name: Build Linux_ARM
@@ -50,4 +50,3 @@ jobs:
5050
./zig-out/bin/httpspec_linux_arm
5151
./zig-out/bin/checksums.txt
5252
LICENSE
53-

src/httpfile/assertion_checker.zig

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
2124
pub 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

146180
fn 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

Comments
 (0)