diff options
| author | Loris Cro <kappaloris@gmail.com> | 2023-06-18 09:06:40 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-18 09:06:40 +0200 |
| commit | 216ef10dc471e4db60a30208be178d6c59efeaaf (patch) | |
| tree | 8c239dab283ae9cb3b7fe099bae240bcc53f894e /lib/std/json/static_test.zig | |
| parent | 0fc1d396495c1ab482197021dedac8bea3f9401c (diff) | |
| parent | 729a051e9e38674233190aea23c0ac8c134f2d67 (diff) | |
| download | zig-216ef10dc471e4db60a30208be178d6c59efeaaf.tar.gz zig-216ef10dc471e4db60a30208be178d6c59efeaaf.zip | |
Merge branch 'master' into autodoc-searchkey
Diffstat (limited to 'lib/std/json/static_test.zig')
| -rw-r--r-- | lib/std/json/static_test.zig | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/lib/std/json/static_test.zig b/lib/std/json/static_test.zig new file mode 100644 index 0000000000..b512f8a890 --- /dev/null +++ b/lib/std/json/static_test.zig @@ -0,0 +1,437 @@ +const std = @import("std"); +const testing = std.testing; + +const parseFromSlice = @import("./static.zig").parseFromSlice; +const parseFromTokenSource = @import("./static.zig").parseFromTokenSource; +const parseFree = @import("./static.zig").parseFree; +const ParseOptions = @import("./static.zig").ParseOptions; +const JsonScanner = @import("./scanner.zig").Scanner; +const jsonReader = @import("./scanner.zig").reader; + +test "parse" { + try testing.expectEqual(false, try parseFromSlice(bool, testing.allocator, "false", .{})); + try testing.expectEqual(true, try parseFromSlice(bool, testing.allocator, "true", .{})); + try testing.expectEqual(@as(u1, 1), try parseFromSlice(u1, testing.allocator, "1", .{})); + try testing.expectError(error.Overflow, parseFromSlice(u1, testing.allocator, "50", .{})); + try testing.expectEqual(@as(u64, 42), try parseFromSlice(u64, testing.allocator, "42", .{})); + try testing.expectEqual(@as(f64, 42), try parseFromSlice(f64, testing.allocator, "42.0", .{})); + try testing.expectEqual(@as(?bool, null), try parseFromSlice(?bool, testing.allocator, "null", .{})); + try testing.expectEqual(@as(?bool, true), try parseFromSlice(?bool, testing.allocator, "true", .{})); + + try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "\"foo\"", .{})); + try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSlice([3]u8, testing.allocator, "[102, 111, 111]", .{})); + try testing.expectEqual(@as([0]u8, undefined), try parseFromSlice([0]u8, testing.allocator, "[]", .{})); + + try testing.expectEqual(@as(u64, 12345678901234567890), try parseFromSlice(u64, testing.allocator, "\"12345678901234567890\"", .{})); + try testing.expectEqual(@as(f64, 123.456), try parseFromSlice(f64, testing.allocator, "\"123.456\"", .{})); +} + +test "parse into enum" { + const T = enum(u32) { + Foo = 42, + Bar, + @"with\\escape", + }; + try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "\"Foo\"", .{})); + try testing.expectEqual(@as(T, .Foo), try parseFromSlice(T, testing.allocator, "42", .{})); + try testing.expectEqual(@as(T, .@"with\\escape"), try parseFromSlice(T, testing.allocator, "\"with\\\\escape\"", .{})); + try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "5", .{})); + try testing.expectError(error.InvalidEnumTag, parseFromSlice(T, testing.allocator, "\"Qux\"", .{})); +} + +test "parse into that allocates a slice" { + { + // string as string + const r = try parseFromSlice([]u8, testing.allocator, "\"foo\"", .{}); + defer parseFree([]u8, testing.allocator, r); + try testing.expectEqualSlices(u8, "foo", r); + } + { + // string as array of u8 integers + const r = try parseFromSlice([]u8, testing.allocator, "[102, 111, 111]", .{}); + defer parseFree([]u8, testing.allocator, r); + try testing.expectEqualSlices(u8, "foo", r); + } + { + const r = try parseFromSlice([]u8, testing.allocator, "\"with\\\\escape\"", .{}); + defer parseFree([]u8, testing.allocator, r); + try testing.expectEqualSlices(u8, "with\\escape", r); + } +} + +test "parse into sentinel slice" { + const result = try parseFromSlice([:0]const u8, testing.allocator, "\"\\n\"", .{}); + defer parseFree([:0]const u8, testing.allocator, result); + try testing.expect(std.mem.eql(u8, result, "\n")); +} + +test "parse into tagged union" { + const T = union(enum) { + nothing, + int: i32, + float: f64, + string: []const u8, + }; + try testing.expectEqual(T{ .float = 1.5 }, try parseFromSlice(T, testing.allocator, "{\"float\":1.5}", .{})); + try testing.expectEqual(T{ .int = 1 }, try parseFromSlice(T, testing.allocator, "{\"int\":1}", .{})); + try testing.expectEqual(T{ .nothing = {} }, try parseFromSlice(T, testing.allocator, "{\"nothing\":{}}", .{})); +} + +test "parse into tagged union errors" { + const T = union(enum) { + nothing, + int: i32, + float: f64, + string: []const u8, + }; + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "42", .{})); + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{}", .{})); + try testing.expectError(error.UnknownField, parseFromSlice(T, testing.allocator, "{\"bogus\":1}", .{})); + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"int\":1", .{})); + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"int\":1, \"float\":1.0}", .{})); + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":null}", .{})); + try testing.expectError(error.UnexpectedToken, parseFromSlice(T, testing.allocator, "{\"nothing\":{\"no\":0}}", .{})); + + // Allocator failure + var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0); + const failing_allocator = fail_alloc.allocator(); + try testing.expectError(error.OutOfMemory, parseFromSlice(T, failing_allocator, "{\"string\"\"foo\"}", .{})); +} + +test "parseFree descends into tagged union" { + const T = union(enum) { + nothing, + int: i32, + float: f64, + string: []const u8, + }; + const r = try parseFromSlice(T, testing.allocator, "{\"string\":\"foo\"}", .{}); + try testing.expectEqualSlices(u8, "foo", r.string); + parseFree(T, testing.allocator, r); +} + +test "parse into struct with no fields" { + const T = struct {}; + try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{})); +} + +const test_const_value: usize = 123; + +test "parse into struct with default const pointer field" { + const T = struct { a: *const usize = &test_const_value }; + try testing.expectEqual(T{}, try parseFromSlice(T, testing.allocator, "{}", .{})); +} + +const test_default_usize: usize = 123; +const test_default_usize_ptr: *align(1) const usize = &test_default_usize; +const test_default_str: []const u8 = "test str"; +const test_default_str_slice: [2][]const u8 = [_][]const u8{ + "test1", + "test2", +}; + +test "freeing parsed structs with pointers to default values" { + const T = struct { + int: *const usize = &test_default_usize, + int_ptr: *allowzero align(1) const usize = test_default_usize_ptr, + str: []const u8 = test_default_str, + str_slice: []const []const u8 = &test_default_str_slice, + }; + + const parsed = try parseFromSlice(T, testing.allocator, "{}", .{}); + try testing.expectEqual(T{}, parsed); + // This will panic if it tries to free global constants: + parseFree(T, testing.allocator, parsed); +} + +test "parse into struct where destination and source lengths mismatch" { + const T = struct { a: [2]u8 }; + try testing.expectError(error.LengthMismatch, parseFromSlice(T, testing.allocator, "{\"a\": \"bbb\"}", .{})); +} + +test "parse into struct with misc fields" { + const T = struct { + int: i64, + float: f64, + @"with\\escape": bool, + @"withąunicode😂": bool, + language: []const u8, + optional: ?bool, + default_field: i32 = 42, + static_array: [3]f64, + dynamic_array: []f64, + + complex: struct { + nested: []const u8, + }, + + veryComplex: []struct { + foo: []const u8, + }, + + a_union: Union, + const Union = union(enum) { + x: u8, + float: f64, + string: []const u8, + }; + }; + var document_str = + \\{ + \\ "int": 420, + \\ "float": 3.14, + \\ "with\\escape": true, + \\ "with\u0105unicode\ud83d\ude02": false, + \\ "language": "zig", + \\ "optional": null, + \\ "static_array": [66.6, 420.420, 69.69], + \\ "dynamic_array": [66.6, 420.420, 69.69], + \\ "complex": { + \\ "nested": "zig" + \\ }, + \\ "veryComplex": [ + \\ { + \\ "foo": "zig" + \\ }, { + \\ "foo": "rocks" + \\ } + \\ ], + \\ "a_union": { + \\ "float": 100000 + \\ } + \\} + ; + const r = try parseFromSlice(T, testing.allocator, document_str, .{}); + defer parseFree(T, testing.allocator, r); + try testing.expectEqual(@as(i64, 420), r.int); + try testing.expectEqual(@as(f64, 3.14), r.float); + try testing.expectEqual(true, r.@"with\\escape"); + try testing.expectEqual(false, r.@"withąunicode😂"); + try testing.expectEqualSlices(u8, "zig", r.language); + try testing.expectEqual(@as(?bool, null), r.optional); + try testing.expectEqual(@as(i32, 42), r.default_field); + try testing.expectEqual(@as(f64, 66.6), r.static_array[0]); + try testing.expectEqual(@as(f64, 420.420), r.static_array[1]); + try testing.expectEqual(@as(f64, 69.69), r.static_array[2]); + try testing.expectEqual(@as(usize, 3), r.dynamic_array.len); + try testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]); + try testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]); + try testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]); + try testing.expectEqualSlices(u8, r.complex.nested, "zig"); + try testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo); + try testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo); + try testing.expectEqual(T.Union{ .float = 100000 }, r.a_union); +} + +test "parse into struct with strings and arrays with sentinels" { + const T = struct { + language: [:0]const u8, + language_without_sentinel: []const u8, + data: [:99]const i32, + simple_data: []const i32, + }; + var document_str = + \\{ + \\ "language": "zig", + \\ "language_without_sentinel": "zig again!", + \\ "data": [1, 2, 3], + \\ "simple_data": [4, 5, 6] + \\} + ; + const r = try parseFromSlice(T, testing.allocator, document_str, .{}); + defer parseFree(T, testing.allocator, r); + + try testing.expectEqualSentinel(u8, 0, "zig", r.language); + + const data = [_:99]i32{ 1, 2, 3 }; + try testing.expectEqualSentinel(i32, 99, data[0..data.len], r.data); + + // Make sure that arrays who aren't supposed to have a sentinel still parse without one. + try testing.expectEqual(@as(?i32, null), std.meta.sentinel(@TypeOf(r.simple_data))); + try testing.expectEqual(@as(?u8, null), std.meta.sentinel(@TypeOf(r.language_without_sentinel))); +} + +test "parse into struct with duplicate field" { + // allow allocator to detect double frees by keeping bucket in use + const ballast = try testing.allocator.alloc(u64, 1); + defer testing.allocator.free(ballast); + + const options_first = ParseOptions{ .duplicate_field_behavior = .use_first }; + const options_last = ParseOptions{ .duplicate_field_behavior = .use_last }; + + const str = "{ \"a\": 1, \"a\": 0.25 }"; + + const T1 = struct { a: *u64 }; + // both .use_first and .use_last should fail because second "a" value isn't a u64 + try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_first)); + try testing.expectError(error.InvalidNumber, parseFromSlice(T1, testing.allocator, str, options_last)); + + const T2 = struct { a: f64 }; + try testing.expectEqual(T2{ .a = 1.0 }, try parseFromSlice(T2, testing.allocator, str, options_first)); + try testing.expectEqual(T2{ .a = 0.25 }, try parseFromSlice(T2, testing.allocator, str, options_last)); +} + +test "parse into struct ignoring unknown fields" { + const T = struct { + int: i64, + language: []const u8, + }; + + var str = + \\{ + \\ "int": 420, + \\ "float": 3.14, + \\ "with\\escape": true, + \\ "with\u0105unicode\ud83d\ude02": false, + \\ "optional": null, + \\ "static_array": [66.6, 420.420, 69.69], + \\ "dynamic_array": [66.6, 420.420, 69.69], + \\ "complex": { + \\ "nested": "zig" + \\ }, + \\ "veryComplex": [ + \\ { + \\ "foo": "zig" + \\ }, { + \\ "foo": "rocks" + \\ } + \\ ], + \\ "a_union": { + \\ "float": 100000 + \\ }, + \\ "language": "zig" + \\} + ; + const r = try parseFromSlice(T, testing.allocator, str, .{ .ignore_unknown_fields = true }); + defer parseFree(T, testing.allocator, r); + + try testing.expectEqual(@as(i64, 420), r.int); + try testing.expectEqualSlices(u8, "zig", r.language); +} + +test "parse into tuple" { + const Union = union(enum) { + char: u8, + float: f64, + string: []const u8, + }; + const T = std.meta.Tuple(&.{ + i64, + f64, + bool, + []const u8, + ?bool, + struct { + foo: i32, + bar: []const u8, + }, + std.meta.Tuple(&.{ u8, []const u8, u8 }), + Union, + }); + var str = + \\[ + \\ 420, + \\ 3.14, + \\ true, + \\ "zig", + \\ null, + \\ { + \\ "foo": 1, + \\ "bar": "zero" + \\ }, + \\ [4, "två", 42], + \\ {"float": 12.34} + \\] + ; + const r = try parseFromSlice(T, testing.allocator, str, .{}); + defer parseFree(T, testing.allocator, r); + try testing.expectEqual(@as(i64, 420), r[0]); + try testing.expectEqual(@as(f64, 3.14), r[1]); + try testing.expectEqual(true, r[2]); + try testing.expectEqualSlices(u8, "zig", r[3]); + try testing.expectEqual(@as(?bool, null), r[4]); + try testing.expectEqual(@as(i32, 1), r[5].foo); + try testing.expectEqualSlices(u8, "zero", r[5].bar); + try testing.expectEqual(@as(u8, 4), r[6][0]); + try testing.expectEqualSlices(u8, "två", r[6][1]); + try testing.expectEqual(@as(u8, 42), r[6][2]); + try testing.expectEqual(Union{ .float = 12.34 }, r[7]); +} + +const ParseIntoRecursiveUnionDefinitionValue = union(enum) { + integer: i64, + array: []const ParseIntoRecursiveUnionDefinitionValue, +}; + +test "parse into recursive union definition" { + const T = struct { + values: ParseIntoRecursiveUnionDefinitionValue, + }; + + const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"integer\":58}]}}", .{}); + defer parseFree(T, testing.allocator, r); + + try testing.expectEqual(@as(i64, 58), r.values.array[0].integer); +} + +const ParseIntoDoubleRecursiveUnionValueFirst = union(enum) { + integer: i64, + array: []const ParseIntoDoubleRecursiveUnionValueSecond, +}; + +const ParseIntoDoubleRecursiveUnionValueSecond = union(enum) { + boolean: bool, + array: []const ParseIntoDoubleRecursiveUnionValueFirst, +}; + +test "parse into double recursive union definition" { + const T = struct { + values: ParseIntoDoubleRecursiveUnionValueFirst, + }; + + const r = try parseFromSlice(T, testing.allocator, "{\"values\":{\"array\":[{\"array\":[{\"integer\":58}]}]}}", .{}); + defer parseFree(T, testing.allocator, r); + + try testing.expectEqual(@as(i64, 58), r.values.array[0].array[0].integer); +} + +test "parse exponential into int" { + const T = struct { int: i64 }; + const r = try parseFromSlice(T, testing.allocator, "{ \"int\": 4.2e2 }", .{}); + try testing.expectEqual(@as(i64, 420), r.int); + try testing.expectError(error.InvalidNumber, parseFromSlice(T, testing.allocator, "{ \"int\": 0.042e2 }", .{})); + try testing.expectError(error.Overflow, parseFromSlice(T, testing.allocator, "{ \"int\": 18446744073709551616.0 }", .{})); +} + +test "parseFromTokenSource" { + var scanner = JsonScanner.initCompleteInput(testing.allocator, "123"); + defer scanner.deinit(); + try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &scanner, .{})); + + var stream = std.io.fixedBufferStream("123"); + var json_reader = jsonReader(std.testing.allocator, stream.reader()); + defer json_reader.deinit(); + try testing.expectEqual(@as(u32, 123), try parseFromTokenSource(u32, testing.allocator, &json_reader, .{})); +} + +test "max_value_len" { + try testing.expectError(error.ValueTooLong, parseFromSlice([]u8, testing.allocator, "\"0123456789\"", .{ .max_value_len = 5 })); +} + +test "parse into vector" { + const T = struct { + vec_i32: @Vector(4, i32), + vec_f32: @Vector(2, f32), + }; + var s = + \\{ + \\ "vec_f32": [1.5, 2.5], + \\ "vec_i32": [4, 5, 6, 7] + \\} + ; + const r = try parseFromSlice(T, testing.allocator, s, .{}); + defer parseFree(T, testing.allocator, r); + try testing.expectApproxEqAbs(@as(f32, 1.5), r.vec_f32[0], 0.0000001); + try testing.expectApproxEqAbs(@as(f32, 2.5), r.vec_f32[1], 0.0000001); + try testing.expectEqual(@Vector(4, i32){ 4, 5, 6, 7 }, r.vec_i32); +} |
