vlib/json2/json5_allocations_test.c.v
Rama master · réplica de lectura del repositorio original.
// vtest vflags: -gc none -d track_heap -nocache
module json2
import os
#include "@VMODROOT/vlib/json2/testdata/json5_heap_tracking.h"
fn C.json5_heap_live_bytes() u64
struct Json5PortOnly {
port int
}
struct Json5KeyEncodingRecord {
enabled bool
unicode bool @[json: 'clé']
quoted bool @[json: 'with space']
}
@[manualfree]
fn assert_json5_unquoted_encoding_releases_temporaries[T](value T, expected string) {
// Reuse caller-owned storage so the measurement isolates per-call scratch.
mut output := []u8{cap: 4096}
encode_append(value, mut output, json5: true, unquoted_keys: true)
first := output.bytestr()
assert first == expected
unsafe { first.free() }
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
output.clear()
encode_append(value, mut output, json5: true, unquoted_keys: true)
result := output.bytestr()
assert result == expected
unsafe { result.free() }
}
assert C.json5_heap_live_bytes() == before
unsafe { output.free() }
}
@[manualfree]
fn test_json5_unquoted_map_keys_release_encoding_temporaries() {
value := {
'$key0': true
'a\u0903': true
'a\u203f': false
'a\u00b2': false
'clé': true
'with space': false
'\U00010341': true
}
assert_json5_unquoted_encoding_releases_temporaries(value,
'{$key0:true,"a\u00b2":false,a\u0903:true,a\u203f:false,clé:true,"with space":false,\U00010341:true}')
}
@[manualfree]
fn test_json5_unquoted_struct_keys_release_encoding_temporaries() {
value := Json5KeyEncodingRecord{
enabled: true
quoted: true
}
assert_json5_unquoted_encoding_releases_temporaries(value,
'{enabled:true,clé:false,"with space":true}')
}
@[manualfree]
fn test_json5_integer_decoding_releases_temporary_storage() {
for input in ['123', "'123'", '0x7b', '+123', '-123'] {
expected := if input == '-123' { -123 } else { 123 }
assert decode[int](input, json5: true)! == expected
before := C.json5_heap_live_bytes()
for _ in 0 .. 1000 {
assert decode[int](input, json5: true)! == expected
}
assert C.json5_heap_live_bytes() == before
}
}
@[manualfree]
fn test_json5_large_hexadecimal_float_decoding_releases_temporary_storage() {
for input in ['0x10000000000000000', '-0x10000000000000000',
'0x' + '0'.repeat(1024) + '10000000000000000'] {
expected := if input.starts_with('-') {
-18446744073709551616.0
} else {
18446744073709551616.0
}
assert decode[f64](input, json5: true)! == expected
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
assert decode[f64](input, json5: true)! == expected
}
assert C.json5_heap_live_bytes() == before
}
}
@[manualfree]
fn test_json5_reuse_releases_normalized_text_of_ignored_fields() {
for prefix in ["{port:123,ignored:'", r"{\u0070ort:123,ig\u006eored:'"] {
input := prefix + 'x'.repeat(8192) + "'}"
mut buffer := DecodeBuffer{}
assert decode_reuse[Json5PortOnly](input, mut buffer, json5: true)!.port == 123
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
assert decode_reuse[Json5PortOnly](input, mut buffer, json5: true)!.port == 123
}
assert C.json5_heap_live_bytes() == before
unsafe { buffer.values_info.free() }
}
}
@[manualfree]
fn test_json5_string_results_own_only_their_returned_text() {
input := "'" + 'x'.repeat(8192) + "'"
first := decode[string](input, json5: true)!
unsafe { first.free() }
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
decoded := decode[string](input, json5: true)!
assert decoded.len == 8192
unsafe { decoded.free() }
}
assert C.json5_heap_live_bytes() == before
}
@[manualfree]
fn test_decode_any_releases_serialization_scratch_for_numbers() {
for json5 in [false, true] {
for number in [0, 1, 123] {
value := Any(number)
input := number.str()
assert decode_any[int](value, json5: json5)! == number
// Plain JSON validation allocates EOF error sentinels even on success.
// Measure direct decoding so the assertion isolates wrapper scratch storage.
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
assert decode[int](input, json5: json5)! == number
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
assert decode_any[int](value, json5: json5)! == number
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}
}
@[manualfree]
fn test_decode_any_string_results_own_only_their_returned_text() {
text := 'x'.repeat(8192)
value := Any(text)
for json5 in [false, true] {
first := decode_any[string](value, json5: json5)!
unsafe { first.free() }
input := '"${text}"'
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
decoded := decode[string](input, json5: json5)!
unsafe { decoded.free() }
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
decoded := decode_any[string](value, json5: json5)!
assert decoded == text
unsafe { decoded.free() }
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}
@[manualfree]
fn test_decode_any_escaped_strings_release_encoding_temporaries() {
text := '\x00\x01\n\r\t\b\f\\"' + rune(0x2028).str() + rune(0x2029).str() + 'é😀'
value := Any(text)
for json5 in [false, true] {
input := encode(text, json5: json5)
first := decode_any[string](value, json5: json5)!
unsafe { first.free() }
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
decoded := decode[string](input, json5: json5)!
unsafe { decoded.free() }
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
decoded := decode_any[string](value, json5: json5)!
assert decoded == text
unsafe { decoded.free() }
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}
@[manualfree]
fn test_decode_file_releases_file_contents_for_numeric_results() {
path := os.join_path(os.temp_dir(), 'json2_file_allocations_${os.getpid()}.json')
defer { os.rm(path) or {} }
input := ' '.repeat(8192) + '123'
os.write_file(path, input)!
for json5 in [false, true] {
assert decode_file[int](path, json5: json5)! == 123
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
assert decode[int](input, json5: json5)! == 123
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
assert decode_file[int](path, json5: json5)! == 123
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}
@[manualfree]
fn test_doc_decode_and_reflect_release_serialization_scratch() {
for json5 in [false, true] {
doc := new_doc(Any(123), json5: json5)
assert doc.decode[int]()! == 123
assert doc.reflect[int]() == 123
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 200 {
assert decode[int]('123', json5: json5)! == 123
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 100 {
assert doc.decode[int]()! == 123
assert doc.reflect[int]() == 123
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}
@[manualfree]
fn test_doc_decode_releases_scratch_for_large_ignored_fields() {
value := Any({
'port': Any(123)
'ignored': Any('x'.repeat(8192))
})
for json5 in [false, true] {
doc := new_doc(value, json5: json5)
input := encode(value, json5: json5)
assert doc.decode[Json5PortOnly]()!.port == 123
baseline := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
assert decode[Json5PortOnly](input, json5: json5)!.port == 123
}
decoder_allocations := C.json5_heap_live_bytes() - baseline
before := C.json5_heap_live_bytes()
for _ in 0 .. 10 {
assert doc.decode[Json5PortOnly]()!.port == 123
}
assert C.json5_heap_live_bytes() == before + decoder_allocations
}
}