From 74704506d85fd468687996d54160afe9ccd24582 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sun, 24 Aug 2025 15:56:36 -0700 Subject: [PATCH] update tests for zig 0.15.1 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 --- src/aws.zig | 8 +- src/aws_http.zig | 70 +++- src/aws_test.zig | 877 +++++++++++++++++++---------------------- src/xml_serializer.zig | 3 +- 4 files changed, 478 insertions(+), 480 deletions(-) diff --git a/src/aws.zig b/src/aws.zig index e9b64de..14e5760 100644 --- a/src/aws.zig +++ b/src/aws.zig @@ -85,9 +85,9 @@ pub const Options = struct { success_http_code: i64 = 200, client: Client, - /// Used for testing to provide consistent signing. If null, will use current time - signing_time: ?i64 = null, diagnostics: ?*Diagnostics = null, + + mock: ?awshttp.Mock = null, }; pub const Diagnostics = struct { @@ -298,8 +298,8 @@ pub fn Request(comptime request_action: anytype) type { .region = options.region, .dualstack = options.dualstack, .client = options.client, - .signing_time = options.signing_time, .diagnostics = options.diagnostics, + .mock = options.mock, }); } @@ -395,7 +395,7 @@ pub fn Request(comptime request_action: anytype) type { .region = options.region, .dualstack = options.dualstack, .sigv4_service_name = Self.service_meta.sigv4_name, - .signing_time = options.signing_time, + .mock = options.mock, }, ); defer response.deinit(); diff --git a/src/aws_http.zig b/src/aws_http.zig index bd7f46f..891db54 100644 --- a/src/aws_http.zig +++ b/src/aws_http.zig @@ -90,8 +90,37 @@ pub const Options = struct { dualstack: bool = false, sigv4_service_name: ?[]const u8 = null, - /// Used for testing to provide consistent signing. If null, will use current time - signing_time: ?i64 = null, + mock: ?Mock = 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; @@ -163,9 +192,9 @@ pub const AwsHttp = struct { .region = getRegion(service, options.region), .service = options.sigv4_service_name orelse service, .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 @@ -184,7 +213,13 @@ pub const AwsHttp = struct { /// Content-Length: (length of body) /// /// 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; log.debug("Request Path: {s}", .{request_cp.path}); @@ -227,13 +262,16 @@ pub const AwsHttp = struct { 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 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 - const method = std.meta.stringToEnum(std.http.Method, request_cp.method).?; // Fetch API in 0.15.1 is insufficient as it does not provide // 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 // better job of having default headers as an opt-in... .headers = .{ @@ -245,7 +283,13 @@ pub const AwsHttp = struct { .content_type = .omit, }, .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 // if (request_cp.body.len > 0) { // // 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 // so we'll need to duplicate it here const req_body = try self.allocator.dupe(u8, request_cp.body); - try req.sendBodyComplete(req_body); - } else try req.sendBodiless(); + defer self.allocator.free(req_body); // docs for sendBodyComplete say it flushes, so no need to outlive this + 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? log.debug( diff --git a/src/aws_test.zig b/src/aws_test.zig index a7b9bea..d57e641 100644 --- a/src/aws_test.zig +++ b/src/aws_test.zig @@ -11,6 +11,8 @@ const Services = aws.Services; const log = std.log.scoped(.aws_test); +var test_error_log_enabled = true; + // TODO: Where does this belong really? fn typeForField(comptime T: type, comptime field_name: []const u8) !type { const ti = @typeInfo(T); @@ -25,6 +27,22 @@ fn typeForField(comptime T: type, comptime field_name: []const u8) !type { } return error.FieldNotFound; } +pub fn StringCaseInsensitiveHashMap(comptime V: type) type { + return std.HashMap([]const u8, V, StringInsensitiveContext, std.hash_map.default_max_load_percentage); +} +pub const StringInsensitiveContext = struct { + pub fn hash(self: @This(), s: []const u8) u64 { + _ = self; + var buf: [1024]u8 = undefined; + if (s.len > buf.len) unreachable; // tolower has a debug assert, but we want non-debug check too + const lower_s = std.ascii.lowerString(buf[0..], s); + return std.hash.Wyhash.hash(0, lower_s); + } + pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { + _ = self; + return std.ascii.eqlIgnoreCase(a, b); + } +}; test "custom serialization for map objects" { const allocator = std.testing.allocator; @@ -46,12 +64,12 @@ test "custom serialization for map objects" { Foo: []const u8, Baz: []const u8, }, - }, testing.allocator, buffer.written(), .{}); + }, std.testing.allocator, buffer.written(), .{}); defer parsed_body.deinit(); - try testing.expectEqualStrings("hello", parsed_body.value.Resource); - try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo); - try testing.expectEqualStrings("Qux", parsed_body.value.Tags.Baz); + try std.testing.expectEqualStrings("hello", parsed_body.value.Resource); + try std.testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo); + try std.testing.expectEqualStrings("Qux", parsed_body.value.Tags.Baz); } test "proper serialization for kms" { @@ -81,14 +99,14 @@ test "proper serialization for kms" { GrantTokens: [][]const u8, EncryptionAlgorithm: []const u8, DryRun: bool, - }, testing.allocator, buffer.written(), .{}); + }, std.testing.allocator, buffer.written(), .{}); defer parsed_body.deinit(); - try testing.expectEqualStrings("42", parsed_body.value.KeyId); - try testing.expectEqualStrings("foo", parsed_body.value.Plaintext); - try testing.expectEqual(0, parsed_body.value.GrantTokens.len); - try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm); - try testing.expectEqual(false, parsed_body.value.DryRun); + try std.testing.expectEqualStrings("42", parsed_body.value.KeyId); + try std.testing.expectEqualStrings("foo", parsed_body.value.Plaintext); + try std.testing.expectEqual(0, parsed_body.value.GrantTokens.len); + try std.testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm); + try std.testing.expectEqual(false, parsed_body.value.DryRun); } var buffer_null = std.Io.Writer.Allocating.init(allocator); @@ -113,15 +131,15 @@ test "proper serialization for kms" { GrantTokens: [][]const u8, EncryptionAlgorithm: []const u8, DryRun: bool, - }, testing.allocator, buffer_null.written(), .{}); + }, std.testing.allocator, buffer_null.written(), .{}); defer parsed_body.deinit(); - try testing.expectEqualStrings("42", parsed_body.value.KeyId); - try testing.expectEqualStrings("foo", parsed_body.value.Plaintext); - try testing.expectEqual(null, parsed_body.value.EncryptionContext); - try testing.expectEqual(0, parsed_body.value.GrantTokens.len); - try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm); - try testing.expectEqual(false, parsed_body.value.DryRun); + try std.testing.expectEqualStrings("42", parsed_body.value.KeyId); + try std.testing.expectEqualStrings("foo", parsed_body.value.Plaintext); + try std.testing.expectEqual(null, parsed_body.value.EncryptionContext); + try std.testing.expectEqual(0, parsed_body.value.GrantTokens.len); + try std.testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm); + try std.testing.expectEqual(false, parsed_body.value.DryRun); } } @@ -224,280 +242,246 @@ test { std.testing.refAllDecls(@import("servicemodel.zig")); std.testing.refAllDecls(@import("xml_shaper.zig")); } + const TestOptions = struct { allocator: std.mem.Allocator, - arena: ?*std.heap.ArenaAllocator = null, - server_port: ?u16 = null, - server_remaining_requests: usize = 1, server_response: []const u8 = "unset", server_response_status: std.http.Status = .ok, server_response_headers: []const std.http.Header = &.{}, server_response_transfer_encoding: ?std.http.TransferEncoding = null, - request_body: []u8 = "", - request_method: std.http.Method = undefined, - request_target: []const u8 = undefined, - request_headers: []std.http.Header = undefined, - test_server_runtime_uri: ?[]u8 = null, - server_ready: std.Thread.Semaphore = .{}, - requests_processed: usize = 0, +}; +const TestSetup = struct { + allocator: std.mem.Allocator, + options: TestOptions, + creds: aws_auth.Credentials, + client: aws.Client, + call_options: *aws.Options, - const Self = @This(); + request_actuals: ?*RequestActuals = null, + response_actuals: ?*ResponseActuals = null, - /// Builtin hashmap for strings as keys. - /// Key memory is managed by the caller. Keys and values - /// will not automatically be freed. - pub fn StringCaseInsensitiveHashMap(comptime V: type) type { - return std.HashMap([]const u8, V, StringInsensitiveContext, std.hash_map.default_max_load_percentage); - } + pub const RequestActuals = struct { + request: *std.http.Client.Request, + trace: []const u8, - pub const StringInsensitiveContext = struct { - pub fn hash(self: @This(), s: []const u8) u64 { - _ = self; - return hashString(s); + // Looks like uri might be getting trounced before deinit + request_uri: []const u8, + + /// Body only exists if and when sendBodyComplete is called + body: ?[]u8 = null, + + /// extra_headers are copied from request call + extra_headers: []const std.http.Header, + + fn expectHeader(self: RequestActuals, name: []const u8, value: []const u8) !void { + for (self.extra_headers) |h| + if (std.ascii.eqlIgnoreCase(name, h.name) and + std.mem.eql(u8, value, h.value)) return; + return error.HeaderOrValueNotFound; } - pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { - _ = self; - return eqlString(a, b); + fn expectNoDuplicateHeaders(self: RequestActuals, allocator: std.mem.Allocator) !void { + // As header keys are + var hm = StringCaseInsensitiveHashMap(void).init(allocator); + try hm.ensureTotalCapacity(@intCast(self.request.extra_headers.len)); + defer hm.deinit(); + // TODO: How should we deal with the standard headers? + for (self.extra_headers) |h| { + if (hm.getKey(h.name)) |_| { + log.err("Duplicate key detected. Key name: {s}", .{h.name}); + return error.duplicateKeyDetected; + } + try hm.put(h.name, {}); + } + } + fn deinit(self: *RequestActuals, allocator: std.mem.Allocator) void { + if (self.body) |b| allocator.free(b); + for (self.extra_headers) |h| { + allocator.free(h.name); + allocator.free(h.value); + } + allocator.free(self.extra_headers); + + allocator.free(self.trace); + allocator.free(self.request_uri); + allocator.destroy(self.request.reader.in); + allocator.destroy(self.request); } }; - pub fn eqlString(a: []const u8, b: []const u8) bool { - return std.ascii.eqlIgnoreCase(a, b); - } - - pub fn hashString(s: []const u8) u64 { - var buf: [1024]u8 = undefined; - if (s.len > buf.len) unreachable; // tolower has a debug assert, but we want non-debug check too - const lower_s = std.ascii.lowerString(buf[0..], s); - return std.hash.Wyhash.hash(0, lower_s); - } - - fn expectNoDuplicateHeaders(self: *Self) !void { - // As header keys are - var hm = StringCaseInsensitiveHashMap(void).init(self.allocator); - try hm.ensureTotalCapacity(@intCast(self.request_headers.len)); - defer hm.deinit(); - for (self.request_headers) |h| { - if (hm.getKey(h.name)) |_| { - log.err("Duplicate key detected. Key name: {s}", .{h.name}); - return error.duplicateKeyDetected; - } - try hm.put(h.name, {}); - } - } - - fn expectHeader(self: *Self, name: []const u8, value: []const u8) !void { - for (self.request_headers) |h| - if (std.ascii.eqlIgnoreCase(name, h.name) and - std.mem.eql(u8, value, h.value)) return; - return error.HeaderOrValueNotFound; - } - fn waitForReady(self: *Self) !void { - // Set 10s timeout...this is way longer than necessary - log.debug("waiting for ready", .{}); - try self.server_ready.timedWait(1000 * std.time.ns_per_ms); - // var deadline = std.Thread.Futex.Deadline.init(1000 * std.time.ns_per_ms); - // if (self.futex_word.load(.acquire) != 0) return; - // log.debug("futex zero", .{}); - // // note that this seems backwards from the documentation... - // deadline.wait(self.futex_word, 1) catch { - // log.err("futex value {d}", .{self.futex_word.load(.acquire)}); - // return error.TestServerTimeoutWaitingForReady; - // }; - log.debug("the wait is over!", .{}); - } -}; - -/// This starts a test server. We're not testing the server itself, -/// so the main tests will start this thing up and create an arena around the -/// whole thing so we can just deallocate everything at once at the end, -/// leaks be damned -fn threadMain(options: *TestOptions) !void { - // https://github.com/ziglang/zig/blob/d2be725e4b14c33dbd39054e33d926913eee3cd4/lib/compiler/std-docs.zig#L22-L54 - - options.arena = try options.allocator.create(std.heap.ArenaAllocator); - options.arena.?.* = std.heap.ArenaAllocator.init(options.allocator); - const allocator = options.arena.?.allocator(); - options.allocator = allocator; - - const address = try std.net.Address.parseIp("127.0.0.1", 0); - var http_server = try address.listen(.{}); - options.server_port = http_server.listen_address.in.getPort(); - // TODO: remove - options.test_server_runtime_uri = try std.fmt.allocPrint(options.allocator, "http://127.0.0.1:{d}", .{options.server_port.?}); - log.debug("server listening at {s}", .{options.test_server_runtime_uri.?}); - log.info("starting server thread, tid {d}", .{std.Thread.getCurrentId()}); - // var arena = std.heap.ArenaAllocator.init(options.allocator); - // defer arena.deinit(); - // var aa = arena.allocator(); - // We're in control of all requests/responses, so this flag will tell us - // when it's time to shut down - if (options.server_remaining_requests == 0) - options.server_ready.post(); // This will cause the wait for server to return - while (options.server_remaining_requests > 0) : (options.server_remaining_requests -= 1) { - processRequest(options, &http_server) catch |e| { - log.err("Unexpected error processing request: {any}", .{e}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - }; - } -} - -fn processRequest(options: *TestOptions, net_server: *std.net.Server) !void { - log.debug( - "tid {d} (server): server waiting to accept. requests remaining: {d}", - .{ std.Thread.getCurrentId(), options.server_remaining_requests }, - ); - // options.futex_word.store(1, .release); - // errdefer options.futex_word.store(0, .release); - options.server_ready.post(); - var connection = try net_server.accept(); - defer connection.stream.close(); - var read_buffer: [1024 * 16]u8 = undefined; - var http_server = std.http.Server.init(connection, &read_buffer); - while (http_server.state == .ready) { - var request = http_server.receiveHead() catch |err| switch (err) { - error.HttpConnectionClosing => return, - else => { - std.log.err("closing http connection: {s}", .{@errorName(err)}); - std.log.debug("Error occurred from this request: \n{s}", .{read_buffer[0..http_server.read_buffer_len]}); - return; - }, - }; - try serveRequest(options, &request); - } -} - -fn serveRequest(options: *TestOptions, request: *std.http.Server.Request) !void { - options.requests_processed += 1; - options.request_body = try (try request.reader()).readAllAlloc(options.allocator, std.math.maxInt(usize)); - options.request_method = request.head.method; - options.request_target = try options.allocator.dupe(u8, request.head.target); - var req_headers = std.ArrayList(std.http.Header).init(options.allocator); - defer req_headers.deinit(); - var it = request.iterateHeaders(); - while (it.next()) |f| { - const h = try options.allocator.create(std.http.Header); - h.* = .{ .name = try options.allocator.dupe(u8, f.name), .value = try options.allocator.dupe(u8, f.value) }; - try req_headers.append(h.*); - } - options.request_headers = try req_headers.toOwnedSlice(); - log.debug( - "tid {d} (server): {d} bytes read from request", - .{ std.Thread.getCurrentId(), options.request_body.len }, - ); - - // try response.headers.append("content-type", "text/plain"); - try request.respond(options.server_response, .{ - .status = options.server_response_status, - .extra_headers = options.server_response_headers, - }); - - log.debug( - "tid {d} (server): sent response", - .{std.Thread.getCurrentId()}, - ); -} - -//////////////////////////////////////////////////////////////////////// -// These will replicate the tests that were in src/main.zig -// The server_response and server_response_headers come from logs of -// a previous run of src/main.zig, with redactions -//////////////////////////////////////////////////////////////////////// - -const TestSetup = struct { - allocator: std.mem.Allocator, - request_options: TestOptions, - server_thread: std.Thread = undefined, - creds: aws_auth.Credentials = undefined, - client: aws.Client = undefined, - started: bool = false, - + pub const ResponseActuals = struct { + body: []const u8, + response: std.http.Client.Response, + }; const Self = @This(); const aws_creds = @import("aws_credentials.zig"); const aws_auth = @import("aws_authentication.zig"); const signing_time = - date.dateTimeToTimestamp(date.parseIso8601ToDateTime("20230908T170252Z") catch @compileError("Cannot parse date")) catch @compileError("Cannot parse date"); + date.dateTimeToTimestamp( + date.parseIso8601ToDateTime("20230908T170252Z") catch @compileError("Cannot parse date"), + ) catch @compileError("Cannot parse date"); - fn init(options: TestOptions) Self { - return .{ - .request_options = options, - .allocator = options.allocator, + fn request( + self_ptr: usize, + method: std.http.Method, + uri: std.Uri, + options: std.http.Client.RequestOptions, + ) std.http.Client.RequestError!std.http.Client.Request { + const self: *Self = @ptrFromInt(self_ptr); + if (self.request_actuals) |r| { + std.debug.print("request has been called twice. Previous stack trace:\n", .{}); + var stderr = std.fs.File.stderr().writer(&.{}); + stderr.interface.writeAll(r.trace) catch @panic("could not write to stderr"); + std.debug.print("Current stack trace:\n", .{}); + std.debug.dumpCurrentStackTrace(null); + return error.ConnectionRefused; // we should not be called twice + } + const acts = try self.allocator.create(RequestActuals); + errdefer self.allocator.destroy(acts); + var aw = std.Io.Writer.Allocating.init(self.allocator); + defer aw.deinit(); + std.debug.dumpCurrentStackTraceToWriter(null, &aw.writer) catch return error.OutOfMemory; + const req = try self.allocator.create(std.http.Client.Request); + errdefer self.allocator.destroy(req); + const reader = try self.allocator.create(std.Io.Reader); + errdefer self.allocator.destroy(reader); + reader.* = .fixed(self.options.server_response); + req.* = .{ + .uri = uri, + .client = undefined, + .connection = options.connection, + .reader = .{ + .in = reader, + .interface = reader.*, + .state = .ready, + .max_head_len = 1024, + }, + .keep_alive = true, + .method = method, + .transfer_encoding = .none, + .redirect_behavior = options.redirect_behavior, + .handle_continue = options.handle_continue, + .headers = options.headers, + .extra_headers = options.extra_headers, + .privileged_headers = options.privileged_headers, }; - } + var al = try std.ArrayList(std.http.Header).initCapacity(self.allocator, options.extra_headers.len); + defer al.deinit(self.allocator); + for (options.extra_headers) |h| + al.appendAssumeCapacity(.{ + .name = try self.allocator.dupe(u8, h.name), + .value = try self.allocator.dupe(u8, h.value), + }); - fn start(self: *Self) !aws.Options { - self.server_thread = try std.Thread.spawn( - .{}, - threadMain, - .{&self.request_options}, - ); - self.started = true; - try self.request_options.waitForReady(); - // Not sure why we're getting sprayed here, but we have an arena allocator, and this - // is testing, so yolo - awshttp.endpoint_override = self.request_options.test_server_runtime_uri; - if (awshttp.endpoint_override == null) return error.TestSetupStartFailure; - std.log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override}); - self.creds = aws_auth.Credentials.init( - self.allocator, - try self.allocator.dupe(u8, "ACCESS"), - try self.allocator.dupe(u8, "SECRET"), - null, - ); - aws_creds.static_credentials = self.creds; - const client = aws.Client.init(self.allocator, .{}); - self.client = client; - return .{ + acts.* = .{ + .trace = try self.allocator.dupe(u8, aw.written()), + .request = req, + .request_uri = try std.fmt.allocPrint(self.allocator, "{f}", .{uri}), + .extra_headers = try al.toOwnedSlice(self.allocator), + }; + self.request_actuals = acts; + return acts.request.*; + } + fn sendBodyComplete(self_ptr: usize, body: []u8) std.io.Writer.Error!void { + const self: *Self = @ptrFromInt(self_ptr); + if (self.request_actuals == null) return error.WriteFailed; // invalid state - must be called after request + self.request_actuals.?.body = self.allocator.dupe(u8, body) catch return error.WriteFailed; + } + fn receiveHead(self_ptr: usize) std.http.Client.Request.ReceiveHeadError!std.http.Client.Response { + const self: *Self = @ptrFromInt(self_ptr); + if (self.request_actuals == null) return error.WriteFailed; // invalid state - must be called after request + const req = self.request_actuals.?.request; + + var response_body = try std.Io.Writer.Allocating.initCapacity(self.allocator, 256); + defer response_body.deinit(); + const writer = &response_body.writer; + + try writer.print("HTTP/1.1 {d} {?s}\r\n", .{ @intFromEnum(self.options.server_response_status), self.options.server_response_status.phrase() }); + for (self.options.server_response_headers) |header| + try writer.print("{s}: {s}\r\n", .{ header.name, header.value }); + try writer.print("\r\n", .{}); + try writer.print("{s}", .{self.options.server_response}); + // server_response_transfer_encoding: ?std.http.TransferEncoding = null, + // This is the real work here - now we have to do the server side of this and end up with a raw response + // + // const acts = self.request_actuals.?; + // acts.response = .{ + // .request = acts.request, + // .head = std.http.Client.Response.Head.parse() catch return error.HttpHeadersInvalid, + // }; + const body = try response_body.toOwnedSlice(); + errdefer self.allocator.free(body); + + const actual_response = try self.allocator.create(ResponseActuals); + errdefer self.allocator.destroy(actual_response); + actual_response.* = .{ + .body = body, + .response = .{ + .request = req, + .head = std.http.Client.Response.Head.parse(body) catch return error.HttpHeadersInvalid, + }, + }; + req.reader.state = .received_head; + self.response_actuals = actual_response; + return actual_response.response; + } + pub fn readerDecompressing(self_ptr: usize) *std.Io.Reader { + // At the end, this has to provide a reader that supports streamRemaining + const self: *Self = @ptrFromInt(self_ptr); + std.debug.assert(self.request_actuals != null); // invalid state - must be called after request + std.debug.assert(self.response_actuals != null); // invalid state - must be called after receiveHead + return self.request_actuals.?.request.reader.in; + } + fn init(options: TestOptions) !*Self { + const client = aws.Client.init(options.allocator, .{}); + const call_options = try options.allocator.create(aws.Options); + const self = try options.allocator.create(Self); + call_options.* = .{ .region = "us-west-2", .client = client, - .signing_time = signing_time, + + // Test specific stuff + .mock = .{ + .signing_time = signing_time, + .request_fn = request, + .send_body_complete = sendBodyComplete, + .receive_head = receiveHead, + .reader_decompressing = readerDecompressing, + .context = @intFromPtr(self), + }, }; + self.* = .{ + .options = options, + .allocator = options.allocator, + .creds = aws_auth.Credentials.init( + options.allocator, + try options.allocator.dupe(u8, "ACCESS"), + try options.allocator.dupe(u8, "SECRET"), + null, + ), + .client = client, + .call_options = call_options, + }; + aws_creds.static_credentials = self.creds; + return self; } - - fn stop(self: *Self) void { - if (self.request_options.server_remaining_requests > 0) - if (test_error_log_enabled) - std.log.err( - "Test server has {d} request(s) remaining to issue! Draining", - .{self.request_options.server_remaining_requests}, - ) - else - std.log.info( - "Test server has {d} request(s) remaining to issue! Draining", - .{self.request_options.server_remaining_requests}, - ); - - var rr = self.request_options.server_remaining_requests; - while (rr > 0) : (rr -= 1) { - std.log.debug("rr: {d}", .{self.request_options.server_remaining_requests}); - // We need to drain all remaining requests, otherwise the server - // will hang indefinitely - var client = std.http.Client{ .allocator = self.allocator }; - defer client.deinit(); - _ = client.fetch(.{ .location = .{ .url = self.request_options.test_server_runtime_uri.? } }) catch unreachable; - } - self.server_thread.join(); - } - fn deinit(self: *Self) void { - if (self.request_options.arena) |a| { - a.deinit(); - self.allocator.destroy(a); + if (self.response_actuals) |r| { + self.allocator.free(r.body); + self.allocator.destroy(r); } - if (!self.started) return; - awshttp.endpoint_override = null; - // creds.deinit(); Creds will get deinited in the course of the call. We don't want to do it twice - aws_creds.static_credentials = null; // we do need to reset the static creds for the next user though - self.client.deinit(); + if (self.request_actuals) |r| { + r.deinit(self.allocator); + self.allocator.destroy(r); + } + self.allocator.destroy(self.call_options); + self.call_options = undefined; + self.allocator.destroy(self); } }; - test "query_no_input: sts getCallerIdentity comptime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + const test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"GetCallerIdentityResponse":{"GetCallerIdentityResult":{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/admin","UserId":"AIDAYAM4POHXHRVANDQBQ"},"ResponseMetadata":{"RequestId":"8f0d54da-1230-40f7-b4ac-95015c4b84cd"}}} @@ -508,18 +492,18 @@ test "query_no_input: sts getCallerIdentity comptime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const sts = (Services(.{.sts}){}).sts; - const call = try aws.Request(sts.get_caller_identity).call(.{}, options); - // const call = try client.call(services.sts.get_caller_identity.Request{}, options); + const call = try aws.Request(sts.get_caller_identity).call(.{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); + // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://sts.us-west-2.amazonaws.com/", req_actuals.request_uri); try std.testing.expectEqualStrings( \\Action=GetCallerIdentity&Version=2011-06-15 - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings( "arn:aws:iam::123456789012:user/admin", @@ -529,10 +513,11 @@ test "query_no_input: sts getCallerIdentity comptime" { try std.testing.expectEqualStrings("123456789012", call.response.account.?); try std.testing.expectEqualStrings("8f0d54da-1230-40f7-b4ac-95015c4b84cd", call.response_metadata.request_id); } + test "query_with_input: iam getRole runtime" { // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\ @@ -564,19 +549,19 @@ test "query_with_input: iam getRole runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const iam = (Services(.{.iam}){}).iam; const call = try test_harness.client.call(iam.get_role.Request{ .role_name = "S3Access", - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://iam.amazonaws.com/", req_actuals.request_uri); try std.testing.expectEqualStrings( \\Action=GetRole&Version=2010-05-08&RoleName=S3Access - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings("arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access", call.response.role.arn); try std.testing.expectEqualStrings("df37e965-9967-11e1-a4c3-270EXAMPLE04", call.response_metadata.request_id); @@ -584,7 +569,7 @@ test "query_with_input: iam getRole runtime" { test "query_with_input: sts getAccessKeyInfo runtime" { // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\ @@ -602,19 +587,19 @@ test "query_with_input: sts getAccessKeyInfo runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const sts = (Services(.{.sts}){}).sts; const call = try test_harness.client.call(sts.get_access_key_info.Request{ .access_key_id = "ASIAYAM4POHXJNKTYFUN", - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://sts.us-west-2.amazonaws.com/", req_actuals.request_uri); try std.testing.expectEqualStrings( \\Action=GetAccessKeyInfo&Version=2011-06-15&AccessKeyId=ASIAYAM4POHXJNKTYFUN - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expect(call.response.account != null); try std.testing.expectEqualStrings("123456789012", call.response.account.?); @@ -622,7 +607,7 @@ test "query_with_input: sts getAccessKeyInfo runtime" { } test "json_1_0_query_with_input: dynamodb listTables runtime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"LastEvaluatedTableName":"Customer","TableNames":["Customer"]} @@ -633,26 +618,26 @@ test "json_1_0_query_with_input: dynamodb listTables runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const dynamo_db = (Services(.{.dynamo_db}){}).dynamo_db; const call = try test_harness.client.call(dynamo_db.list_tables.Request{ .limit = 1, - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); - try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables"); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com/", req_actuals.request_uri); + try req_actuals.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables"); const parsed_body = try std.json.parseFromSlice(struct { ExclusiveStartTableName: ?[]const u8, Limit: u8, - }, testing.allocator, test_harness.request_options.request_body, .{}); + }, std.testing.allocator, req_actuals.body.?, .{}); defer parsed_body.deinit(); - try testing.expectEqual(null, parsed_body.value.ExclusiveStartTableName); - try testing.expectEqual(1, parsed_body.value.Limit); + try std.testing.expectEqual(null, parsed_body.value.ExclusiveStartTableName); + try std.testing.expectEqual(1, parsed_body.value.Limit); // Response expectations try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id); @@ -662,7 +647,7 @@ test "json_1_0_query_with_input: dynamodb listTables runtime" { test "json_1_0_query_no_input: dynamodb listTables runtime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"AccountMaxReadCapacityUnits":80000,"AccountMaxWriteCapacityUnits":80000,"TableMaxReadCapacityUnits":40000,"TableMaxWriteCapacityUnits":40000} @@ -673,25 +658,25 @@ test "json_1_0_query_no_input: dynamodb listTables runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const dynamo_db = (Services(.{.dynamo_db}){}).dynamo_db; - const call = try test_harness.client.call(dynamo_db.describe_limits.Request{}, options); + const call = try test_harness.client.call(dynamo_db.describe_limits.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); - try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.DescribeLimits"); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com/", req_actuals.request_uri); + try req_actuals.expectHeader("X-Amz-Target", "DynamoDB_20120810.DescribeLimits"); try std.testing.expectEqualStrings( \\{} - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id); try std.testing.expectEqual(@as(i64, 80000), call.response.account_max_read_capacity_units.?); } test "json_1_1_query_with_input: ecs listClusters runtime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="} @@ -702,26 +687,26 @@ test "json_1_1_query_with_input: ecs listClusters runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const ecs = (Services(.{.ecs}){}).ecs; const call = try test_harness.client.call(ecs.list_clusters.Request{ .max_results = 1, - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); - try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters"); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://ecs.us-west-2.amazonaws.com/", req_actuals.request_uri); + try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters"); const parsed_body = try std.json.parseFromSlice(struct { nextToken: ?[]const u8, maxResults: u8, - }, testing.allocator, test_harness.request_options.request_body, .{}); + }, std.testing.allocator, req_actuals.body.?, .{}); defer parsed_body.deinit(); - try testing.expectEqual(null, parsed_body.value.nextToken); - try testing.expectEqual(1, parsed_body.value.maxResults); + try std.testing.expectEqual(null, parsed_body.value.nextToken); + try std.testing.expectEqual(1, parsed_body.value.maxResults); // Response expectations try std.testing.expectEqualStrings("b2420066-ff67-4237-b782-721c4df60744", call.response_metadata.request_id); @@ -733,7 +718,7 @@ test "json_1_1_query_no_input: ecs listClusters runtime" { // defer std.testing.log_level = old; // std.testing.log_level = .debug; const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="} @@ -744,24 +729,24 @@ test "json_1_1_query_no_input: ecs listClusters runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const ecs = (Services(.{.ecs}){}).ecs; - const call = try test_harness.client.call(ecs.list_clusters.Request{}, options); + const call = try test_harness.client.call(ecs.list_clusters.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); - try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters"); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://ecs.us-west-2.amazonaws.com/", req_actuals.request_uri); + try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters"); const parsed_body = try std.json.parseFromSlice(struct { nextToken: ?[]const u8, maxResults: ?u8, - }, testing.allocator, test_harness.request_options.request_body, .{}); + }, std.testing.allocator, req_actuals.body.?, .{}); defer parsed_body.deinit(); - try testing.expectEqual(null, parsed_body.value.nextToken); - try testing.expectEqual(null, parsed_body.value.maxResults); + try std.testing.expectEqual(null, parsed_body.value.nextToken); + try std.testing.expectEqual(null, parsed_body.value.maxResults); // Response expectations try std.testing.expectEqualStrings("e65322b2-0065-45f2-ba37-f822bb5ce395", call.response_metadata.request_id); @@ -770,7 +755,7 @@ test "json_1_1_query_no_input: ecs listClusters runtime" { } test "rest_json_1_query_with_input: lambda listFunctions runtime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"Functions":[{"Description":"AWS CDK resource provider framework - onEvent (DevelopmentFrontendStack-g650u/com.amazonaws.cdk.custom-resources.amplify-asset-deployment-provider/amplify-asset-deployment-handler-provider)","TracingConfig":{"Mode":"PassThrough"},"VpcConfig":null,"SigningJobArn":null,"SnapStart":{"OptimizationStatus":"Off","ApplyOn":"None"},"RevisionId":"0c62fc74-a692-403d-9206-5fcbad406424","LastModified":"2023-03-01T18:13:15.704+0000","FileSystemConfigs":null,"FunctionName":"DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","Runtime":"nodejs14.x","Version":"$LATEST","PackageType":"Zip","LastUpdateStatus":null,"Layers":null,"FunctionArn":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","KMSKeyArn":null,"MemorySize":128,"ImageConfigResponse":null,"LastUpdateStatusReason":null,"DeadLetterConfig":null,"Timeout":900,"Handler":"framework.onEvent","CodeSha256":"m4tt+M0l3p8bZvxIDj83dwGrwRW6atCfS/q8AiXCD3o=","Role":"arn:aws:iam::550620852718:role/DevelopmentFrontendStack-amplifyassetdeploymentha-1782JF7WAPXZ3","SigningProfileVersionArn":null,"MasterArn":null,"RuntimeVersionConfig":null,"CodeSize":4307,"State":null,"StateReason":null,"Environment":{"Variables":{"USER_ON_EVENT_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymenton-X9iZJSCSPYDH","WAITER_STATE_MACHINE_ARN":"arn:aws:states:us-west-2:550620852718:stateMachine:amplifyassetdeploymenthandlerproviderwaiterstatemachineB3C2FCBE-Ltggp5wBcHWO","USER_IS_COMPLETE_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentis-jaHopLrSSARV"},"Error":null},"EphemeralStorage":{"Size":512},"StateReasonCode":null,"LastUpdateStatusReasonCode":null,"Architectures":["x86_64"]}],"NextMarker":"lslTXFcbLQKkb0vP9Kgh5hUL7C3VghELNGbWgZfxrRCk3eiDRMkct7D8EmptWfHSXssPdS7Bo66iQPTMpVOHZgANewpgGgFGGr4pVjd6VgLUO6qPe2EMAuNDBjUTxm8z6N28yhlUwEmKbrAV/m0k5qVzizwoxFwvyruMbuMx9kADFACSslcabxXl3/jDI4rfFnIsUVdzTLBgPF1hzwrE1f3lcdkBvUp+QgY+Pn3w5QuJmwsp/di8COzFemY89GgOHbLNqsrBsgR/ee2eXoJp0ZkKM4EcBK3HokqBzefLfgR02PnfNOdXwqTlhkSPW0TKiKGIYu3Bw7lSNrLd+q3+wEr7ZakqOQf0BVo3FMRhMHlVYgwUJzwi3ActyH2q6fuqGG1sS0B8Oa/prUpe5fmp3VaA3WpazioeHtrKF78JwCi6/nfQsrj/8ZtXGQOxlwEgvT1CIUaF+CdHY3biezrK0tRZNpkCtHnkPtF9lq2U7+UiKXSW9yzxT8P2b0M/Qh4IVdnw4rncQK/doYriAeOdrs1wjMEJnHWq9lAaEyipoxYcVr/z5+yaC6Gwxdg45p9X1vIAaYMf6IZxyFuua43SYi0Ls+IBk4VvpR2io7T0dCxHAr3WAo3D2dm0y8OsbM59"} @@ -781,19 +766,17 @@ test "rest_json_1_query_with_input: lambda listFunctions runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const lambda = (Services(.{.lambda}){}).lambda; const call = try test_harness.client.call(lambda.list_functions.Request{ .max_items = 1, - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/2015-03-31/functions?MaxItems=1", test_harness.request_options.request_target); - try std.testing.expectEqualStrings( - \\ - , test_harness.request_options.request_body); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.GET, req_actuals.request.method); + try std.testing.expectEqualStrings("https://lambda.us-west-2.amazonaws.com/2015-03-31/functions?MaxItems=1", req_actuals.request_uri); + try std.testing.expect(req_actuals.body == null); // should be sent bodiless, so harness will not even trigger // Response expectations try std.testing.expectEqualStrings("c4025199-226f-4a16-bb1f-48618e9d2ea6", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 1), call.response.functions.?.len); @@ -804,7 +787,7 @@ test "rest_json_1_query_with_input: lambda listFunctions runtime" { } test "rest_json_1_query_no_input: lambda listFunctions runtime" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = @embedFile("test_rest_json_1_query_no_input.response"), .server_response_headers = &.{ @@ -813,17 +796,15 @@ test "rest_json_1_query_no_input: lambda listFunctions runtime" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const lambda = (Services(.{.lambda}){}).lambda; - const call = try test_harness.client.call(lambda.list_functions.Request{}, options); + const call = try test_harness.client.call(lambda.list_functions.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/2015-03-31/functions", test_harness.request_options.request_target); - try std.testing.expectEqualStrings( - \\ - , test_harness.request_options.request_body); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.GET, req_actuals.request.method); + try std.testing.expectEqualStrings("https://lambda.us-west-2.amazonaws.com/2015-03-31/functions", req_actuals.request_uri); + try std.testing.expect(req_actuals.body == null); // should be sent bodiless, so harness will not even trigger // Response expectations try std.testing.expectEqualStrings("b2aad11f-36fc-4d0d-ae92-fe0167fb0f40", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 24), call.response.functions.?.len); @@ -838,7 +819,7 @@ test "rest_json_1_query_no_input: lambda listFunctions runtime" { } test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig issue 17015" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = "", .server_response_status = .no_content, @@ -848,35 +829,35 @@ test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const lambda = (Services(.{.lambda}){}).lambda; var tags = try std.ArrayList(@typeInfo(try typeForField(lambda.tag_resource.Request, "tags")).pointer.child).initCapacity(allocator, 1); defer tags.deinit(allocator); tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" }); const req = lambda.tag_resource.Request{ .resource = "arn:aws:lambda:us-west-2:550620852718:function:awsome-lambda-LambdaStackawsomeLambda", .tags = tags.items }; - const call = try aws.Request(lambda.tag_resource).call(req, options); + const call = try aws.Request(lambda.tag_resource).call(req, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); const parsed_body = try std.json.parseFromSlice(struct { Tags: struct { Foo: []const u8, }, - }, testing.allocator, test_harness.request_options.request_body, .{ .ignore_unknown_fields = true }); + }, std.testing.allocator, req_actuals.body.?, .{ .ignore_unknown_fields = true }); defer parsed_body.deinit(); - try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo); + try std.testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo); // Due to 17015, we see %253A instead of %3A - try std.testing.expectEqualStrings("/2017-03-31/tags/arn%3Aaws%3Alambda%3Aus-west-2%3A550620852718%3Afunction%3Aawsome-lambda-LambdaStackawsomeLambda", test_harness.request_options.request_target); + try std.testing.expectEqualStrings("https://lambda.us-west-2.amazonaws.com/2017-03-31/tags/arn%3Aaws%3Alambda%3Aus-west-2%3A550620852718%3Afunction%3Aawsome-lambda-LambdaStackawsomeLambda", req_actuals.request_uri); // Response expectations try std.testing.expectEqualStrings("a521e152-6e32-4e67-9fb3-abc94e34551b", call.response_metadata.request_id); } test "rest_json_1_url_parameters_not_in_request: lambda update_function_code" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = "{\"CodeSize\": 42}", .server_response_status = .ok, @@ -886,7 +867,6 @@ test "rest_json_1_url_parameters_not_in_request: lambda update_function_code" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const lambda = (Services(.{.lambda}){}).lambda; const architectures = [_][]const u8{"x86_64"}; const arches: [][]const u8 = @constCast(architectures[0..]); @@ -895,32 +875,33 @@ test "rest_json_1_url_parameters_not_in_request: lambda update_function_code" { .architectures = arches, .zip_file = "zipfile", }; - const call = try aws.Request(lambda.update_function_code).call(req, options); + const call = try aws.Request(lambda.update_function_code).call(req, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.PUT, req_actuals.request.method); const parsed_body = try std.json.parseFromSlice(struct { ZipFile: []const u8, Architectures: [][]const u8, - }, testing.allocator, test_harness.request_options.request_body, .{ + }, std.testing.allocator, req_actuals.body.?, .{ .ignore_unknown_fields = true, }); defer parsed_body.deinit(); - try testing.expectEqualStrings("zipfile", parsed_body.value.ZipFile); - try testing.expectEqual(1, parsed_body.value.Architectures.len); - try testing.expectEqualStrings("x86_64", parsed_body.value.Architectures[0]); + try std.testing.expectEqualStrings("zipfile", parsed_body.value.ZipFile); + try std.testing.expectEqual(1, parsed_body.value.Architectures.len); + try std.testing.expectEqualStrings("x86_64", parsed_body.value.Architectures[0]); // Due to 17015, we see %253A instead of %3A - try std.testing.expectEqualStrings("/2015-03-31/functions/functionname/code", test_harness.request_options.request_target); + try std.testing.expectEqualStrings("https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/functionname/code", req_actuals.request_uri); // Response expectations try std.testing.expectEqualStrings("a521e152-6e32-4e67-9fb3-abc94e34551b", call.response_metadata.request_id); } test "ec2_query_no_input: EC2 describe regions" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = @embedFile("test_ec2_query_no_input.response"), .server_response_headers = &.{ @@ -930,17 +911,17 @@ test "ec2_query_no_input: EC2 describe regions" { .server_response_transfer_encoding = .chunked, }); defer test_harness.deinit(); - const options = try test_harness.start(); const ec2 = (Services(.{.ec2}){}).ec2; - const call = try test_harness.client.call(ec2.describe_regions.Request{}, options); + const call = try test_harness.client.call(ec2.describe_regions.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/?Action=DescribeRegions&Version=2016-11-15", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://ec2.us-west-2.amazonaws.com/?Action=DescribeRegions&Version=2016-11-15", req_actuals.request_uri); try std.testing.expectEqualStrings( \\Action=DescribeRegions&Version=2016-11-15 - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings("4cdbdd69-800c-49b5-8474-ae4c17709782", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 17), call.response.regions.?.len); @@ -952,7 +933,7 @@ test "ec2_query_no_input: EC2 describe regions" { test "ec2_query_with_input: EC2 describe instances" { if (builtin.cpu.arch == .riscv64 and builtin.os.tag == .linux) return error.SkipZigTest; const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = @embedFile("test_ec2_query_with_input.response"), .server_response_headers = &.{ @@ -961,19 +942,19 @@ test "ec2_query_with_input: EC2 describe instances" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const ec2 = (Services(.{.ec2}){}).ec2; const call = try test_harness.client.call(ec2.describe_instances.Request{ .max_results = 6, - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/?Action=DescribeInstances&Version=2016-11-15", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://ec2.us-west-2.amazonaws.com/?Action=DescribeInstances&Version=2016-11-15", req_actuals.request_uri); try std.testing.expectEqualStrings( \\Action=DescribeInstances&Version=2016-11-15&MaxResults=6 - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings("150a14cc-785d-476f-a4c9-2aa4d03b14e2", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 6), call.response.reservations.?.len); @@ -982,7 +963,7 @@ test "ec2_query_with_input: EC2 describe instances" { } test "rest_xml_with_input_s3: S3 create bucket" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\ @@ -994,24 +975,24 @@ test "rest_xml_with_input_s3: S3 create bucket" { }); defer test_harness.deinit(); errdefer test_harness.creds.deinit(); - const options = try test_harness.start(); const s3 = (Services(.{.s3}){}).s3; const call = try test_harness.client.call(s3.create_bucket.Request{ .bucket = "", .create_bucket_configuration = .{ .location_constraint = "us-west-2", }, - }, options); + }, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.PUT, req_actuals.request.method); + try std.testing.expectEqualStrings("https://s3.us-west-2.amazonaws.com/", req_actuals.request_uri); try std.testing.expectEqualStrings( \\ \\ us-west-2 \\ - , test_harness.request_options.request_body); + , req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings( "9PEYBAZ9J7TPRX43, host_id: u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=", @@ -1020,7 +1001,7 @@ test "rest_xml_with_input_s3: S3 create bucket" { } test "rest_xml_no_input: S3 list buckets" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\3367189aa775bd98da38e55093705f2051443c1e775fc0971d6d77387a47c8d0emilerch+sub1550620852718-backup2020-06-17T16:26:51.000Zamplify-letmework-staging-185741-deployment2023-03-10T18:57:49.000Zaws-cloudtrail-logs-550620852718-224022a72021-06-21T18:32:44.000Zaws-sam-cli-managed-default-samclisourcebucket-1gy0z00mj47xe2021-10-05T16:38:07.000Zawsomeprojectstack-pipelineartifactsbucketaea9a05-1uzwo6c86ecr2021-10-05T22:55:09.000Zcdk-hnb659fds-assets-550620852718-us-west-22023-02-28T21:49:36.000Zcf-templates-12iy6putgdxtk-us-west-22020-06-26T02:31:59.000Zcodepipeline-us-west-2-467140836372021-09-14T18:43:07.000Zelasticbeanstalk-us-west-2-5506208527182022-04-15T16:22:42.000Zlobo-west2021-06-21T17:17:22.000Zlobo-west-22021-11-19T20:12:31.000Zlogging-backup-550620852718-us-east-22022-05-29T21:55:16.000Zmysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr02023-03-01T04:53:55.000Z @@ -1031,27 +1012,27 @@ test "rest_xml_no_input: S3 list buckets" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const s3 = (Services(.{.s3}){}).s3; - const call = try test_harness.client.call(s3.list_buckets.Request{}, options); + const call = try test_harness.client.call(s3.list_buckets.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.GET, req_actuals.request.method); // This changed in rev 830202d722c904c7e3da40e8dde7b9338d08752c of the go sdk, and // contrary to the documentation, a query string argument was added. My guess is that // there is no functional reason, and that this is strictly for some AWS reporting function. // Alternatively, it could be to support some customization mechanism, as the commit // title of that commit is "Merge customizations for S3" - try std.testing.expectEqualStrings("/?x-id=ListBuckets", test_harness.request_options.request_target); - try std.testing.expectEqualStrings("", test_harness.request_options.request_body); + try std.testing.expectEqualStrings("https://s3.us-west-2.amazonaws.com/?x-id=ListBuckets", req_actuals.request_uri); + try std.testing.expect(req_actuals.body == null); // should be sent bodiless, so harness will not even trigger // Response expectations try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 13), call.response.buckets.?.len); } test "rest_xml_anything_but_s3: CloudFront list key groups" { const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"Items":null,"MaxItems":100,"NextMarker":null,"Quantity":0} @@ -1062,15 +1043,15 @@ test "rest_xml_anything_but_s3: CloudFront list key groups" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const cloudfront = (Services(.{.cloudfront}){}).cloudfront; - const call = try test_harness.client.call(cloudfront.list_key_groups.Request{}, options); + const call = try test_harness.client.call(cloudfront.list_key_groups.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/2020-05-31/key-group", test_harness.request_options.request_target); - try std.testing.expectEqualStrings("", test_harness.request_options.request_body); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.GET, req_actuals.request.method); + try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com/2020-05-31/key-group", req_actuals.request_uri); + try std.testing.expect(req_actuals.body == null); // should be sent bodiless, so harness will not even trigger // Response expectations try std.testing.expectEqualStrings("d3382082-5291-47a9-876b-8df3accbb7ea", call.response_metadata.request_id); try std.testing.expectEqual(@as(i64, 100), call.response.key_group_list.?.max_items); @@ -1080,7 +1061,7 @@ test "rest_xml_with_input: S3 put object" { // defer std.testing.log_level = old; // std.testing.log_level = .debug; const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = "", .server_response_headers = &.{ @@ -1092,11 +1073,10 @@ test "rest_xml_with_input: S3 put object" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const s3opts = aws.Options{ .region = "us-west-2", - .client = options.client, - .signing_time = TestSetup.signing_time, + .client = test_harness.call_options.client, + .mock = test_harness.call_options.mock, }; const s3 = (Services(.{.s3}){}).s3; const result = try aws.Request(s3.put_object).call(.{ @@ -1107,21 +1087,22 @@ test "rest_xml_with_input: S3 put object" { .storage_class = "STANDARD", }, s3opts); defer result.deinit(); - for (test_harness.request_options.request_headers) |header| { - std.log.info("Request header: {s}: {s}", .{ header.name, header.value }); - } - try test_harness.request_options.expectNoDuplicateHeaders(); - std.log.info("PutObject Request id: {s}", .{result.response_metadata.request_id}); - std.log.info("PutObject etag: {s}", .{result.response.e_tag.?}); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + // for (test_harness.request_options.request_headers) |header| { + // std.log.info("Request header: {s}: {s}", .{ header.name, header.value }); + // } + try req_actuals.expectNoDuplicateHeaders(std.testing.allocator); + // std.log.info("PutObject Request id: {s}", .{result.response_metadata.request_id}); + // std.log.info("PutObject etag: {s}", .{result.response.e_tag.?}); //mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method); + try std.testing.expectEqual(std.http.Method.PUT, req_actuals.request.method); // I don't think this will work since we're overriding the url - // try test_harness.request_options.expectHeader("Host", "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com"); - try test_harness.request_options.expectHeader("x-amz-storage-class", "STANDARD"); - try std.testing.expectEqualStrings("/mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject", test_harness.request_options.request_target); - try std.testing.expectEqualStrings("bar", test_harness.request_options.request_body); + // try req_actuals.expectHeader("Host", "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com"); + try req_actuals.expectHeader("x-amz-storage-class", "STANDARD"); + try std.testing.expectEqualStrings("/mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject", req_actuals.request_uri); + try std.testing.expectEqualStrings("bar", req_actuals.body.?); // Response expectations try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43, host_id: jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=", result.response_metadata.request_id); try std.testing.expectEqualStrings("AES256", result.response.server_side_encryption.?); @@ -1154,7 +1135,7 @@ test "json_1_1: ECR timestamps" { // std.testing.log_level = .debug; const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ + var test_harness = try TestSetup.init(.{ .allocator = allocator, .server_response = \\{"authorizationData":[{"authorizationToken":"***","expiresAt":"2022-05-17T06:56:13.652000+00:00","proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]} @@ -1166,16 +1147,16 @@ test "json_1_1: ECR timestamps" { }, }); defer test_harness.deinit(); - const options = try test_harness.start(); const ecr = (Services(.{.ecr}){}).ecr; std.log.debug("Typeof response {}", .{@TypeOf(ecr.get_authorization_token.Response{})}); - const call = try test_harness.client.call(ecr.get_authorization_token.Request{}, options); + const call = try test_harness.client.call(ecr.get_authorization_token.Request{}, test_harness.call_options.*); defer call.deinit(); - test_harness.stop(); // Request expectations - try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method); - try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); - try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerRegistry_V20150921.GetAuthorizationToken"); + if (test_harness.request_actuals == null) return error.NoCallMade; + const req_actuals = test_harness.request_actuals.?; + try std.testing.expectEqual(std.http.Method.POST, req_actuals.request.method); + try std.testing.expectEqualStrings("https://api.ecr.us-west-2.amazonaws.com/", req_actuals.request_uri); + try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerRegistry_V20150921.GetAuthorizationToken"); // Response expectations try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id); try std.testing.expectEqual(@as(usize, 1), call.response.authorization_data.?.len); @@ -1191,38 +1172,6 @@ test "json_1_1: ECR timestamps" { try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?); } -var test_error_log_enabled = true; -test "test server timeout works" { - // const old = std.testing.log_level; - // defer std.testing.log_level = old; - // std.testing.log_level = .debug; - // defer std.testing.log_level = old; - // std.testing.log_level = .debug; - test_error_log_enabled = false; - defer test_error_log_enabled = true; - std.log.debug("test start", .{}); - const allocator = std.testing.allocator; - var test_harness = TestSetup.init(.{ - .allocator = allocator, - .server_response = - \\{} - , - .server_response_headers = &.{ - .{ .name = "Content-Type", .value = "application/json" }, - .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" }, - }, - }); - defer test_harness.deinit(); - defer test_harness.creds.deinit(); // Usually this gets done during the call, - // but we're purposely not making a call - // here, so we have to deinit() manually - _ = try test_harness.start(); - std.log.debug("harness started", .{}); - test_harness.stop(); - std.log.debug("test complete", .{}); -} - -const testing = std.testing; test "jsonStringify: structure + enums" { const media_convert = (Services(.{.media_convert}){}).media_convert; @@ -1234,7 +1183,7 @@ test "jsonStringify: structure + enums" { }, }; - var arena = std.heap.ArenaAllocator.init(testing.allocator); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})}); @@ -1246,12 +1195,12 @@ test "jsonStringify: structure + enums" { httpsInputs: []const u8, s3Inputs: []const u8, }, - }, testing.allocator, request_json, .{}); + }, std.testing.allocator, request_json, .{}); defer parsed.deinit(); - try testing.expectEqualStrings("foo", parsed.value.policy.httpInputs); - try testing.expectEqualStrings("bar", parsed.value.policy.httpsInputs); - try testing.expectEqualStrings("baz", parsed.value.policy.s3Inputs); + try std.testing.expectEqualStrings("foo", parsed.value.policy.httpInputs); + try std.testing.expectEqualStrings("bar", parsed.value.policy.httpsInputs); + try std.testing.expectEqualStrings("baz", parsed.value.policy.s3Inputs); } test "jsonStringify: strings" { @@ -1260,13 +1209,13 @@ test "jsonStringify: strings" { .arn = "1234", }; - var arena = std.heap.ArenaAllocator.init(testing.allocator); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})}); defer std.testing.allocator.free(request_json); - try testing.expectEqualStrings("{\"arn\":\"1234\"}", request_json); + try std.testing.expectEqualStrings("{\"arn\":\"1234\"}", request_json); } test "jsonStringify" { @@ -1283,7 +1232,7 @@ test "jsonStringify" { .tags = &tags, }; - var arena = std.heap.ArenaAllocator.init(testing.allocator); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})}); @@ -1294,11 +1243,11 @@ test "jsonStringify" { tags: struct { foo: []const u8, }, - }, testing.allocator, request_json, .{}); + }, std.testing.allocator, request_json, .{}); defer json_parsed.deinit(); - try testing.expectEqualStrings("1234", json_parsed.value.arn); - try testing.expectEqualStrings("bar", json_parsed.value.tags.foo); + try std.testing.expectEqualStrings("1234", json_parsed.value.arn); + try std.testing.expectEqualStrings("bar", json_parsed.value.tags.foo); } test "jsonStringify nullable object" { @@ -1324,13 +1273,13 @@ test "jsonStringify nullable object" { RoutingConfig: struct { AdditionalVersionWeights: ?struct {}, }, - }, testing.allocator, request_json, .{ .ignore_unknown_fields = true }); + }, std.testing.allocator, request_json, .{ .ignore_unknown_fields = true }); defer json_parsed.deinit(); - try testing.expectEqualStrings("foo", json_parsed.value.FunctionName); - try testing.expectEqualStrings("bar", json_parsed.value.FunctionVersion); - try testing.expectEqualStrings("baz", json_parsed.value.Name); - try testing.expectEqual(null, json_parsed.value.RoutingConfig.AdditionalVersionWeights); + try std.testing.expectEqualStrings("foo", json_parsed.value.FunctionName); + try std.testing.expectEqualStrings("bar", json_parsed.value.FunctionVersion); + try std.testing.expectEqualStrings("baz", json_parsed.value.Name); + try std.testing.expectEqual(null, json_parsed.value.RoutingConfig.AdditionalVersionWeights); } // structure is null @@ -1347,10 +1296,10 @@ test "jsonStringify nullable object" { const json_parsed = try std.json.parseFromSlice(struct { KeyId: []const u8, CiphertextBlob: []const u8, - }, testing.allocator, request_json, .{ .ignore_unknown_fields = true }); + }, std.testing.allocator, request_json, .{ .ignore_unknown_fields = true }); defer json_parsed.deinit(); - try testing.expectEqualStrings("foo", json_parsed.value.KeyId); - try testing.expectEqualStrings("bar", json_parsed.value.CiphertextBlob); + try std.testing.expectEqualStrings("foo", json_parsed.value.KeyId); + try std.testing.expectEqualStrings("bar", json_parsed.value.CiphertextBlob); } } diff --git a/src/xml_serializer.zig b/src/xml_serializer.zig index 35fa500..f422ad2 100644 --- a/src/xml_serializer.zig +++ b/src/xml_serializer.zig @@ -413,7 +413,8 @@ test "stringify basic types" { { const result = try stringifyAlloc(allocator, 3.14, .{}); defer allocator.free(result); - try testing.expectEqualStrings("\n3.14e0", result); + // zig 0.14.x outputs 3.14e0, but zig 0.15.1 outputs 3.14. Either *should* be acceptable + try testing.expectEqualStrings("\n3.14", result); } // Test string