update tests for zig 0.15.1
Some checks failed
AWS-Zig Build / build-zig-amd64-host (push) Failing after 1m41s

This removes the need to spin up a web server for each test, instead,
mocking the necessary methods to do everything in line. This will make
the tests much more resilient, and with the remaining WriterGate changes
expected in zig 0.16, I suspect the mocking will be unnecessary in the
next release.

There are several test issues that remain:

* Two skipped tests in signature verification. This is the most
  concerning of the remaining issues
* Serialization of [][]const u8 was probably broken in zig 0.14.1, but
  the new version has surfaced this issue. Warning messages are being
  sent, and this needs to be tracked down
* One of the tests is failing as S3 storage tier extra header is not
  being offered. I'm not sure what in the upgrade might have changed
  this behavior, but this needs to be investigated
This commit is contained in:
Emil Lerch 2025-08-24 15:56:36 -07:00
parent 1e8756cc9a
commit 74704506d8
Signed by: lobo
GPG key ID: A7B62D657EF764F8
4 changed files with 478 additions and 480 deletions

View file

@ -85,9 +85,9 @@ pub const Options = struct {
success_http_code: i64 = 200, success_http_code: i64 = 200,
client: Client, client: Client,
/// Used for testing to provide consistent signing. If null, will use current time
signing_time: ?i64 = null,
diagnostics: ?*Diagnostics = null, diagnostics: ?*Diagnostics = null,
mock: ?awshttp.Mock = null,
}; };
pub const Diagnostics = struct { pub const Diagnostics = struct {
@ -298,8 +298,8 @@ pub fn Request(comptime request_action: anytype) type {
.region = options.region, .region = options.region,
.dualstack = options.dualstack, .dualstack = options.dualstack,
.client = options.client, .client = options.client,
.signing_time = options.signing_time,
.diagnostics = options.diagnostics, .diagnostics = options.diagnostics,
.mock = options.mock,
}); });
} }
@ -395,7 +395,7 @@ pub fn Request(comptime request_action: anytype) type {
.region = options.region, .region = options.region,
.dualstack = options.dualstack, .dualstack = options.dualstack,
.sigv4_service_name = Self.service_meta.sigv4_name, .sigv4_service_name = Self.service_meta.sigv4_name,
.signing_time = options.signing_time, .mock = options.mock,
}, },
); );
defer response.deinit(); defer response.deinit();

View file

@ -90,8 +90,37 @@ pub const Options = struct {
dualstack: bool = false, dualstack: bool = false,
sigv4_service_name: ?[]const u8 = null, sigv4_service_name: ?[]const u8 = null,
/// Used for testing to provide consistent signing. If null, will use current time mock: ?Mock = null,
signing_time: ?i64 = null, };
/// mocking methods for isolated testing
pub const Mock = struct {
/// Used to provide consistent signing
signing_time: ?i64,
/// context is desiged to be type-erased pointer (@intFromPtr)
context: usize = 0,
request_fn: *const fn (
usize,
std.http.Method,
std.Uri,
std.http.Client.RequestOptions,
) std.http.Client.RequestError!std.http.Client.Request,
send_body_complete: *const fn (usize, []u8) std.Io.Writer.Error!void,
receive_head: *const fn (usize) std.http.Client.Request.ReceiveHeadError!std.http.Client.Response,
reader_decompressing: *const fn (usize) *std.Io.Reader,
fn request(m: Mock, method: std.http.Method, uri: std.Uri, options: std.http.Client.RequestOptions) std.http.Client.RequestError!std.http.Client.Request {
return m.request_fn(m.context, method, uri, options);
}
fn sendBodyComplete(m: Mock, body: []u8) std.Io.Writer.Error!void {
return m.send_body_complete(m.context, body);
}
fn receiveHead(m: Mock) std.http.Client.Request.ReceiveHeadError!std.http.Client.Response {
return m.receive_head(m.context);
}
fn readerDecompressing(m: Mock) *std.Io.Reader {
return m.reader_decompressing(m.context);
}
}; };
pub const Header = std.http.Header; pub const Header = std.http.Header;
@ -163,9 +192,9 @@ pub const AwsHttp = struct {
.region = getRegion(service, options.region), .region = getRegion(service, options.region),
.service = options.sigv4_service_name orelse service, .service = options.sigv4_service_name orelse service,
.credentials = creds, .credentials = creds,
.signing_time = options.signing_time, .signing_time = if (options.mock) |m| m.signing_time else null,
}; };
return try self.makeRequest(endpoint, request, signing_config); return try self.makeRequest(endpoint, request, signing_config, options);
} }
/// makeRequest is a low level http/https function that can be used inside /// makeRequest is a low level http/https function that can be used inside
@ -184,7 +213,13 @@ pub const AwsHttp = struct {
/// Content-Length: (length of body) /// Content-Length: (length of body)
/// ///
/// Return value is an HttpResult, which will need the caller to deinit(). /// Return value is an HttpResult, which will need the caller to deinit().
pub fn makeRequest(self: Self, endpoint: EndPoint, request: HttpRequest, signing_config: ?signing.Config) !HttpResult { pub fn makeRequest(
self: Self,
endpoint: EndPoint,
request: HttpRequest,
signing_config: ?signing.Config,
options: Options,
) !HttpResult {
var request_cp = request; var request_cp = request;
log.debug("Request Path: {s}", .{request_cp.path}); log.debug("Request Path: {s}", .{request_cp.path});
@ -227,13 +262,16 @@ pub const AwsHttp = struct {
log.debug("Request url: {s}", .{url}); log.debug("Request url: {s}", .{url});
// TODO: Fix this proxy stuff. This is all a kludge just to compile, but std.http.Client has it all built in now // TODO: Fix this proxy stuff. This is all a kludge just to compile, but std.http.Client has it all built in now
var cl = std.http.Client{ .allocator = self.allocator, .https_proxy = if (self.proxy) |*p| @constCast(p) else null }; var cl = std.http.Client{ .allocator = self.allocator, .https_proxy = if (self.proxy) |*p| @constCast(p) else null };
// Not sure this if statement is correct here. deinit seems to assume
// that client.request was called at least once, but we don't do that
// if we're in a test harness
defer cl.deinit(); // TODO: Connection pooling defer cl.deinit(); // TODO: Connection pooling
const method = std.meta.stringToEnum(std.http.Method, request_cp.method).?; const method = std.meta.stringToEnum(std.http.Method, request_cp.method).?;
// Fetch API in 0.15.1 is insufficient as it does not provide // Fetch API in 0.15.1 is insufficient as it does not provide
// server headers. We'll construct and send the request ourselves // server headers. We'll construct and send the request ourselves
var req = try cl.request(method, try std.Uri.parse(url), .{ const uri = try std.Uri.parse(url);
const req_options: std.http.Client.RequestOptions = .{
// we need full control over most headers. I wish libraries would do a // we need full control over most headers. I wish libraries would do a
// better job of having default headers as an opt-in... // better job of having default headers as an opt-in...
.headers = .{ .headers = .{
@ -245,7 +283,13 @@ pub const AwsHttp = struct {
.content_type = .omit, .content_type = .omit,
}, },
.extra_headers = headers.items, .extra_headers = headers.items,
}); };
var req = if (options.mock) |m|
try m.request(method, uri, req_options) // This will call the test harness
else
try cl.request(method, uri, req_options);
// TODO: Need to test for payloads > 2^14. I believe one of our tests does this, but not sure // TODO: Need to test for payloads > 2^14. I believe one of our tests does this, but not sure
// if (request_cp.body.len > 0) { // if (request_cp.body.len > 0) {
// // Workaround for https://github.com/ziglang/zig/issues/15626 // // Workaround for https://github.com/ziglang/zig/issues/15626
@ -267,10 +311,14 @@ pub const AwsHttp = struct {
// in the chain then does actually modify the body of the request // in the chain then does actually modify the body of the request
// so we'll need to duplicate it here // so we'll need to duplicate it here
const req_body = try self.allocator.dupe(u8, request_cp.body); const req_body = try self.allocator.dupe(u8, request_cp.body);
try req.sendBodyComplete(req_body); defer self.allocator.free(req_body); // docs for sendBodyComplete say it flushes, so no need to outlive this
} else try req.sendBodiless(); if (options.mock) |m|
try m.sendBodyComplete(req_body)
else
try req.sendBodyComplete(req_body);
} else if (options.mock == null) try req.sendBodiless();
var response = try req.receiveHead(&.{}); var response = if (options.mock) |m| try m.receiveHead() else try req.receiveHead(&.{});
// TODO: Timeout - is this now above us? // TODO: Timeout - is this now above us?
log.debug( log.debug(

File diff suppressed because it is too large Load diff

View file

@ -413,7 +413,8 @@ test "stringify basic types" {
{ {
const result = try stringifyAlloc(allocator, 3.14, .{}); const result = try stringifyAlloc(allocator, 3.14, .{});
defer allocator.free(result); defer allocator.free(result);
try testing.expectEqualStrings("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>3.14e0</root>", result); // zig 0.14.x outputs 3.14e0, but zig 0.15.1 outputs 3.14. Either *should* be acceptable
try testing.expectEqualStrings("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>3.14</root>", result);
} }
// Test string // Test string