Skip to content

Commit c47c736

Browse files
authored
Merge pull request #13 from bradcypert/bradcypert/add-thread-pool
Add threadpool, break out reporter
2 parents 9a6b619 + 3aa392f commit c47c736

File tree

3 files changed

+123
-43
lines changed

3 files changed

+123
-43
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,13 @@ Fail: 1
4141
There are some gaps in this implementation at this point (these are things I plan to address):
4242

4343
1. There is not currently a way to pipe the response from a previous request into the next request. This is limiting as you often may need an ID from a previous request to make the next request.
44-
2. Currently, everything runs serially. The plan is to run each file in isolation against a thread pool. This will happen eventually, so if you start writing tests today, write them with this in mind (dont try to get fancy with sequencing of test files).
45-
3. Not all of the assertion types specified in the [specification](./HTTPSpec.md) are implemented yet.
44+
2. Not all of the assertion types specified in the [specification](./HTTPSpec.md) are implemented yet.
45+
46+
# Configuration
47+
48+
Do you have the need for speed? Tests are ran against a thread-pool and you can configure the number of jobs in said pool! Use the `HTTP_THREAD_COUNT` env var to specify the number of jobs in the pool.
49+
50+
Example:
51+
```bash
52+
HTTP_THREAD_COUNT=4 httpspec ./my_project/httptests/
53+
```

src/main.zig

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
const std = @import("std");
2+
const clap = @import("clap");
3+
24
const HttpParser = @import("./httpfile/parser.zig");
35
const Client = @import("./httpfile/http_client.zig");
46
const AssertionChecker = @import("./httpfile/assertion_checker.zig");
5-
const clap = @import("clap");
7+
const TestReporter = @import("./reporters/test_reporter.zig");
68

79
pub fn main() !void {
810
var debug = std.heap.DebugAllocator(.{}){};
911
defer _ = debug.deinit();
1012

13+
const threads = std.process.parseEnvVarInt("HTTP_THREAD_COUNT", usize, 10) catch 1;
14+
1115
const allocator = debug.allocator();
1216

1317
const params = comptime clap.parseParamsComptime(
@@ -66,51 +70,65 @@ pub fn main() !void {
6670
}
6771
}
6872

69-
var test_count: usize = 0;
70-
var test_pass: usize = 0;
71-
var test_fail: usize = 0;
7273
// TODO: This is simple, but completely serial. Ideally, we'd span this across multiple threads.
73-
for (files.items) |pos| {
74-
test_count += 1;
75-
var has_failure = false;
74+
75+
var pool: std.Thread.Pool = undefined;
76+
try pool.init(.{
77+
.allocator = allocator,
78+
.n_jobs = threads,
79+
});
80+
defer pool.deinit();
81+
82+
var wg: std.Thread.WaitGroup = .{};
83+
var reporter = TestReporter.BasicReporter.init();
84+
85+
for (files.items) |path| {
7686
// TODO: Each one gets its own areana?
77-
std.io.getStdOut().writer().print("Running test {d}: {s}\n", .{ test_count, pos }) catch |err| {
78-
std.debug.print("Error writing to stdout: {}\n", .{err});
79-
return err;
80-
};
81-
var items = try HttpParser.parseFile(allocator, pos);
82-
const owned_items = try items.toOwnedSlice();
83-
defer allocator.free(owned_items);
84-
var client = Client.HttpClient.init(allocator);
85-
defer client.deinit();
86-
for (owned_items) |*owned_item| {
87-
defer owned_item.deinit(allocator);
88-
var responses = try client.execute(owned_item);
89-
defer responses.deinit();
90-
// Check assertions
91-
AssertionChecker.check(owned_item, responses) catch {
92-
has_failure = true;
93-
break;
94-
};
95-
}
96-
if (!has_failure) {
97-
test_pass += 1;
98-
} else {
99-
test_fail += 1;
100-
}
87+
pool.spawnWg(&wg, runTest, .{ allocator, &reporter, path });
10188
}
10289

103-
std.io.getStdOut().writer().print(
104-
\\
105-
\\All {d} tests ran successfully!
106-
\\
107-
\\Pass: {d}
108-
\\Fail: {d}
109-
\\
110-
, .{ test_count, test_pass, test_fail }) catch |err| {
111-
std.debug.print("Error writing to stdout: {}\n", .{err});
112-
return err;
90+
wg.wait();
91+
reporter.report(std.io.getStdOut().writer());
92+
}
93+
94+
fn runTest(allocator: std.mem.Allocator, reporter: *TestReporter.BasicReporter, path: []const u8) void {
95+
var has_failure = false;
96+
97+
reporter.incTestCount();
98+
var items = HttpParser.parseFile(allocator, path) catch |err| {
99+
reporter.incTestInvalid();
100+
std.debug.print("Failed to parse file {s}: {s}\n", .{ path, @errorName(err) });
101+
return;
102+
};
103+
const owned_items = items.toOwnedSlice() catch |err| {
104+
// TODO: This seems like an US error, not an invalid test error.
105+
reporter.incTestInvalid();
106+
std.debug.print("Failed to convert items to owned slice in file {s}: {s}\n", .{ path, @errorName(err) });
107+
return;
113108
};
109+
defer allocator.free(owned_items);
110+
111+
var client = Client.HttpClient.init(allocator);
112+
defer client.deinit();
113+
for (owned_items) |*owned_item| {
114+
defer owned_item.deinit(allocator);
115+
var responses = client.execute(owned_item) catch |err| {
116+
reporter.incTestInvalid();
117+
std.debug.print("Failed to execute request in file {s}: {s}\n", .{ path, @errorName(err) });
118+
return;
119+
};
120+
defer responses.deinit();
121+
// Check assertions
122+
AssertionChecker.check(owned_item, responses) catch {
123+
has_failure = true;
124+
break;
125+
};
126+
}
127+
if (!has_failure) {
128+
reporter.incTestPass();
129+
} else {
130+
reporter.incTestFail();
131+
}
114132
}
115133

116134
// List all HTTP files in the given directory and its subdirectories

src/reporters/test_reporter.zig

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const std = @import("std");
2+
3+
pub const BasicReporter = struct {
4+
test_count: usize,
5+
test_pass: usize,
6+
test_fail: usize,
7+
test_invalid: usize,
8+
m: std.Thread.Mutex,
9+
10+
pub fn init() BasicReporter {
11+
return .{
12+
.test_count = 0,
13+
.test_pass = 0,
14+
.test_fail = 0,
15+
.test_invalid = 0,
16+
.m = std.Thread.Mutex{},
17+
};
18+
}
19+
20+
pub fn report(self: *BasicReporter, writer: anytype) void {
21+
writer.print(
22+
\\
23+
\\All {d} tests ran successfully!
24+
\\
25+
\\Pass: {d}
26+
\\Fail: {d}
27+
\\Invalid: {d}
28+
\\
29+
, .{ self.test_count, self.test_pass, self.test_fail, self.test_invalid }) catch |err| {
30+
std.debug.print("Error writing to stdout: {}\n", .{err});
31+
};
32+
}
33+
34+
pub fn incTestCount(self: *BasicReporter) void {
35+
self.m.lock();
36+
defer self.m.unlock();
37+
self.test_count += 1;
38+
}
39+
pub fn incTestPass(self: *BasicReporter) void {
40+
self.m.lock();
41+
defer self.m.unlock();
42+
self.test_pass += 1;
43+
}
44+
pub fn incTestFail(self: *BasicReporter) void {
45+
self.m.lock();
46+
defer self.m.unlock();
47+
self.test_fail += 1;
48+
}
49+
pub fn incTestInvalid(self: *BasicReporter) void {
50+
self.m.lock();
51+
defer self.m.unlock();
52+
self.test_invalid += 1;
53+
}
54+
};

0 commit comments

Comments
 (0)