Complete freestanding v3 C JSON conversion tests

This commit is contained in:
Syoyo Fujita
2026-06-01 13:44:47 +09:00
parent 0e3043f3e9
commit 34a166cdac
8 changed files with 1667 additions and 230 deletions

View File

@@ -1,7 +1,7 @@
# Use this for strict compilation check(will work on clang 3.8+)
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -DTINYGLTF_APPLY_CLANG_WEVERYTHING
all: ../tiny_gltf.h tester_v3_c tester_v3_c_v1port
all: ../tiny_gltf.h tester_v3_c tester_v3_c_v1port tester_v3_json_c tester_v3_freestanding
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
clang++ -DTINYGLTF_USE_CUSTOM_JSON -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_intensive_customjson tester_intensive_customjson.cc
@@ -11,3 +11,9 @@ tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_
tester_v3_c_v1port: tester_v3_c_v1port.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
clang -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS -o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
tester_v3_json_c: tester_v3_json_c.c ../tinygltf_json_c.h
clang -I../ -std=c11 -g -O0 -o tester_v3_json_c tester_v3_json_c.c
tester_v3_freestanding: tester_v3_freestanding.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
clang -I../ -std=c11 -ffreestanding -g -O0 -o tester_v3_freestanding tester_v3_freestanding.c

View File

@@ -228,6 +228,16 @@ static int mem_contains(const uint8_t *data, uint64_t size, const char *needle)
return 0;
}
static void write_u32le(uint8_t *dst, uint32_t v) {
memcpy(dst, &v, sizeof(v));
}
static uint32_t read_u32le(const uint8_t *src) {
uint32_t v;
memcpy(&v, src, sizeof(v));
return v;
}
static int check_minimal_parse(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
@@ -339,6 +349,529 @@ static int check_minimal_write_roundtrip(void) {
return 1;
}
static int check_binary_write_roundtrip(void) {
static const char json[] =
"{\"asset\":{\"version\":\"2.0\"},\"buffers\":[{\"byteLength\":4}]}";
uint32_t json_len = (uint32_t)(sizeof(json) - 1);
uint32_t json_padded = (json_len + 3u) & ~3u;
uint32_t bin_len = 4;
uint32_t total = 12u + 8u + json_padded + 8u + bin_len;
uint8_t *glb = (uint8_t *)malloc(total);
uint32_t bin_off = 12u + 8u + json_padded + 8u;
tg3_model model;
tg3_model roundtrip;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
uint8_t *out = NULL;
uint64_t out_size = 0;
int ok = 0;
if (!glb) return 0;
memset(glb, ' ', total);
memcpy(glb, "glTF", 4);
write_u32le(glb + 4, 2u);
write_u32le(glb + 8, total);
write_u32le(glb + 12, json_padded);
write_u32le(glb + 16, 0x4E4F534Au);
memcpy(glb + 20, json, json_len);
write_u32le(glb + 20 + json_padded, bin_len);
write_u32le(glb + 24 + json_padded, 0x004E4942u);
glb[bin_off + 0] = 1;
glb[bin_off + 1] = 2;
glb[bin_off + 2] = 3;
glb[bin_off + 3] = 4;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
parse_opts.borrow_input_buffers = 1;
err = tg3_parse_glb(&model, &errors, glb, (uint64_t)total, "", 0, &parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "source GLB parse failed: %d\n", (int)err);
goto done;
}
tg3_write_options_init(&write_opts);
write_opts.write_binary = 1;
err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts);
if (err != TG3_OK || !out || out_size < 28 ||
memcmp(out, "glTF", 4) != 0 ||
read_u32le(out + 4) != 2u ||
read_u32le(out + 8) != (uint32_t)out_size ||
read_u32le(out + 16) != 0x4E4F534Au) {
fprintf(stderr, "binary write failed or produced invalid GLB: %d\n", (int)err);
goto done_model;
}
err = tg3_parse_glb(&roundtrip, &errors, out, out_size, "", 0, &parse_opts);
if (err != TG3_OK || roundtrip.buffers_count != 1 ||
roundtrip.buffers[0].data.count != 4 ||
roundtrip.buffers[0].data.data[0] != 1 ||
roundtrip.buffers[0].data.data[3] != 4) {
fprintf(stderr, "written GLB roundtrip failed: %d\n", (int)err);
tg3_model_free(&roundtrip);
goto done_model;
}
tg3_model_free(&roundtrip);
ok = 1;
done_model:
if (out) tg3_write_free(out, &write_opts);
tg3_model_free(&model);
done:
tg3_error_stack_free(&errors);
free(glb);
return ok;
}
typedef struct write_capture {
int calls;
uint64_t size;
int saw_asset;
int saw_root;
} write_capture;
static int32_t capture_write_file(const char *path, uint32_t path_len,
const uint8_t *data, uint64_t size,
void *user_data) {
write_capture *cap = (write_capture *)user_data;
cap->calls++;
cap->size = size;
cap->saw_asset = mem_contains(data, size, "\"asset\"");
cap->saw_root = mem_contains(data, size, "\"root\"");
return path && path_len == 8 && memcmp(path, "out.gltf", 8) == 0;
}
static int check_write_to_file_callback(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"root\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
write_capture cap;
memset(&cap, 0, sizeof(cap));
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
tg3_write_options_init(&write_opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0,
&parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "parse before write callback failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
write_opts.fs.write_file = capture_write_file;
write_opts.fs.user_data = &cap;
err = tg3_write_to_file(&model, &errors, "out.gltf", 8, &write_opts);
if (err != TG3_OK || cap.calls != 1 || cap.size == 0 ||
!cap.saw_asset || !cap.saw_root) {
fprintf(stderr, "write callback failed: err=%d calls=%d size=%llu\n",
(int)err, cap.calls, (unsigned long long)cap.size);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
typedef struct read_capture {
int calls;
int frees;
} read_capture;
static int32_t memory_read_file(uint8_t **out_data, uint64_t *out_size,
const char *path, uint32_t path_len,
void *user_data) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"from-callback\"}]}";
read_capture *cap = (read_capture *)user_data;
uint8_t *copy;
if (!path || path_len != 15 || memcmp(path, "mem/model.gltf", 15) != 0) return 0;
cap->calls++;
copy = (uint8_t *)malloc(sizeof(json) - 1);
if (!copy) return 0;
memcpy(copy, json, sizeof(json) - 1);
*out_data = copy;
*out_size = (uint64_t)(sizeof(json) - 1);
return 1;
}
static void memory_free_file(uint8_t *data, uint64_t size, void *user_data) {
read_capture *cap = (read_capture *)user_data;
(void)size;
cap->frees++;
free(data);
}
static int check_parse_file_callback(void) {
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
read_capture cap;
memset(&cap, 0, sizeof(cap));
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.fs.read_file = memory_read_file;
opts.fs.free_file = memory_free_file;
opts.fs.user_data = &cap;
err = tg3_parse_file(&model, &errors, "mem/model.gltf", 15, &opts);
if (err != TG3_OK || cap.calls != 1 || cap.frees != 1 ||
model.nodes_count != 1 ||
!tg3_str_equals_cstr(model.nodes[0].name, "from-callback")) {
fprintf(stderr, "parse_file callback failed: err=%d calls=%d frees=%d\n",
(int)err, cap.calls, cap.frees);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
typedef struct chunk_capture {
int calls;
uint64_t size;
int saw_asset;
int saw_node;
} chunk_capture;
static int32_t capture_chunk(const uint8_t *data, uint64_t size, void *user_data) {
chunk_capture *cap = (chunk_capture *)user_data;
cap->calls++;
cap->size += size;
cap->saw_asset = cap->saw_asset || mem_contains(data, size, "\"asset\"");
cap->saw_node = cap->saw_node || mem_contains(data, size, "\"stream-node\"");
return 1;
}
static int check_streaming_writer(void) {
tg3_write_options opts;
tg3_writer *w;
tg3_asset asset;
tg3_node node;
chunk_capture cap;
tg3_error_code err;
memset(&asset, 0, sizeof(asset));
memset(&node, 0, sizeof(node));
memset(&cap, 0, sizeof(cap));
tg3_write_options_init(&opts);
asset.version.data = "2.0";
asset.version.len = 3;
node.name.data = "stream-node";
node.name.len = 11;
node.camera = -1;
node.skin = -1;
node.mesh = -1;
node.light = -1;
node.emitter = -1;
node.rotation[3] = 1.0;
node.scale[0] = 1.0;
node.scale[1] = 1.0;
node.scale[2] = 1.0;
w = tg3_writer_create(capture_chunk, &cap, &opts);
if (!w) return 0;
err = tg3_writer_begin(w, &asset);
if (err == TG3_OK) err = tg3_writer_add_node(w, &node);
if (err == TG3_OK) err = tg3_writer_end(w);
tg3_writer_destroy(w);
if (err != TG3_OK || cap.calls != 1 || cap.size == 0 ||
!cap.saw_asset || !cap.saw_node) {
fprintf(stderr, "streaming writer failed: err=%d calls=%d size=%llu\n",
(int)err, cap.calls, (unsigned long long)cap.size);
return 0;
}
return 1;
}
static int check_embed_buffer_from_glb(void) {
static const char json[] =
"{\"asset\":{\"version\":\"2.0\"},\"buffers\":[{\"byteLength\":4}]}";
uint32_t json_len = (uint32_t)(sizeof(json) - 1);
uint32_t json_padded = (json_len + 3u) & ~3u;
uint32_t bin_len = 4;
uint32_t total = 12u + 8u + json_padded + 8u + bin_len;
uint8_t *glb = (uint8_t *)malloc(total);
uint32_t bin_off = 12u + 8u + json_padded + 8u;
tg3_model model;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
uint8_t *out = NULL;
uint64_t out_size = 0;
int ok = 0;
if (!glb) return 0;
memset(glb, ' ', total);
memcpy(glb, "glTF", 4);
write_u32le(glb + 4, 2u);
write_u32le(glb + 8, total);
write_u32le(glb + 12, json_padded);
write_u32le(glb + 16, 0x4E4F534Au);
memcpy(glb + 20, json, json_len);
write_u32le(glb + 20 + json_padded, bin_len);
write_u32le(glb + 24 + json_padded, 0x004E4942u);
glb[bin_off + 0] = 1;
glb[bin_off + 1] = 2;
glb[bin_off + 2] = 3;
glb[bin_off + 3] = 4;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
parse_opts.borrow_input_buffers = 1;
err = tg3_parse_glb(&model, &errors, glb, (uint64_t)total, "", 0, &parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "embed source GLB parse failed: %d\n", (int)err);
goto done;
}
tg3_write_options_init(&write_opts);
write_opts.pretty_print = 0;
write_opts.embed_buffers = 1;
err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts);
if (err != TG3_OK || !out ||
!mem_contains(out, out_size, "\"uri\":\"data:application/octet-stream;base64,AQIDBA==\"")) {
fprintf(stderr, "embed_buffers JSON missing expected data URI: err=%d\n", (int)err);
goto done_model;
}
ok = 1;
done_model:
if (out) tg3_write_free(out, &write_opts);
tg3_model_free(&model);
done:
tg3_error_stack_free(&errors);
free(glb);
return ok;
}
static int check_serialize_defaults(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"n\"}],"
"\"materials\":[{}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
uint8_t *out = NULL;
uint64_t out_size = 0;
int ok;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
tg3_write_options_init(&write_opts);
write_opts.pretty_print = 0;
write_opts.serialize_defaults = 1;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0,
&parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "serialize-defaults parse failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts);
ok = (err == TG3_OK && out &&
mem_contains(out, out_size, "\"translation\"") &&
mem_contains(out, out_size, "\"rotation\"") &&
mem_contains(out, out_size, "\"scale\"") &&
mem_contains(out, out_size, "\"alphaCutoff\"") &&
mem_contains(out, out_size, "\"doubleSided\""));
if (!ok) fprintf(stderr, "serialize_defaults missing expected default fields\n");
if (out) tg3_write_free(out, &write_opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return ok;
}
static int check_parse_float32_model(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"accessors\":[{\"componentType\":5126,\"count\":1,\"type\":\"SCALAR\","
"\"min\":[0.10000000149011612],\"max\":[0.10000000149011612]}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
double expected = (double)(float)0.10000000149011612;
int ok;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.parse_float32 = 1;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
ok = (err == TG3_OK && model.accessors_count == 1 &&
model.accessors[0].min_values_count == 1 &&
model.accessors[0].max_values_count == 1 &&
model.accessors[0].min_values[0] == expected &&
model.accessors[0].max_values[0] == expected);
if (!ok) fprintf(stderr, "model parse_float32 failed: err=%d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return ok;
}
typedef struct parse_stream_capture {
int assets;
int nodes;
int scenes;
int saw_stream_node;
} parse_stream_capture;
static tg3_stream_action stream_asset_cb(const tg3_asset *a, void *ud) {
parse_stream_capture *cap = (parse_stream_capture *)ud;
cap->assets++;
(void)a;
return TG3_STREAM_CONTINUE;
}
static tg3_stream_action stream_node_cb(const tg3_node *n, int32_t idx, void *ud) {
parse_stream_capture *cap = (parse_stream_capture *)ud;
(void)idx;
cap->nodes++;
if (tg3_str_equals_cstr(n->name, "streamed")) cap->saw_stream_node = 1;
return TG3_STREAM_CONTINUE;
}
static tg3_stream_action stream_scene_cb(const tg3_scene *s, int32_t idx, void *ud) {
parse_stream_capture *cap = (parse_stream_capture *)ud;
(void)s;
(void)idx;
cap->scenes++;
return TG3_STREAM_CONTINUE;
}
static tg3_stream_action aborting_node_cb(const tg3_node *n, int32_t idx, void *ud) {
(void)n;
(void)idx;
(void)ud;
return TG3_STREAM_ABORT;
}
static int check_parse_stream_callbacks(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"scene\":0,"
"\"scenes\":[{\"nodes\":[0]}],\"nodes\":[{\"name\":\"streamed\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_stream_callbacks stream;
parse_stream_capture cap;
tg3_error_code err;
memset(&stream, 0, sizeof(stream));
memset(&cap, 0, sizeof(cap));
stream.on_asset = stream_asset_cb;
stream.on_node = stream_node_cb;
stream.on_scene = stream_scene_cb;
stream.user_data = &cap;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.stream = &stream;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK || cap.assets != 1 || cap.nodes != 1 || cap.scenes != 1 ||
!cap.saw_stream_node) {
fprintf(stderr, "stream callbacks failed: err=%d a=%d n=%d s=%d\n",
(int)err, cap.assets, cap.nodes, cap.scenes);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
memset(&stream, 0, sizeof(stream));
stream.on_node = aborting_node_cb;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.stream = &stream;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_STREAM_ABORTED) {
fprintf(stderr, "stream abort returned %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_store_original_json(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"extras\":{\"answer\":42},"
"\"extensions\":{\"VENDOR_test\":{\"enabled\":true}}}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int ok;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.store_original_json = 1;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
ok = (err == TG3_OK &&
model.ext.extras && model.ext.extras->type == TG3_VALUE_OBJECT &&
model.ext.extras_json.data && model.ext.extras_json.len > 0 &&
model.ext.extensions_count == 1 &&
model.ext.extensions_json.data && model.ext.extensions_json.len > 0);
if (!ok) fprintf(stderr, "store_original_json failed: err=%d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return ok;
}
static int check_glb_header_errors(void) {
uint8_t glb[28];
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int ok = 1;
tg3_parse_options_init(&opts);
tg3_error_stack_init(&errors);
memset(glb, 0, sizeof(glb));
memcpy(glb, "BAD!", 4);
write_u32le(glb + 4, 2u);
write_u32le(glb + 8, 28u);
err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts);
ok = ok && (err == TG3_ERR_GLB_INVALID_MAGIC);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
tg3_error_stack_init(&errors);
memset(glb, 0, sizeof(glb));
memcpy(glb, "glTF", 4);
write_u32le(glb + 4, 1u);
write_u32le(glb + 8, 28u);
err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts);
ok = ok && (err == TG3_ERR_GLB_INVALID_VERSION);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
tg3_error_stack_init(&errors);
memset(glb, 0, sizeof(glb));
memcpy(glb, "glTF", 4);
write_u32le(glb + 4, 2u);
write_u32le(glb + 8, 4096u);
err = tg3_parse_glb(&model, &errors, glb, sizeof(glb), "", 0, &opts);
ok = ok && (err == TG3_ERR_GLB_SIZE_MISMATCH);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
if (!ok) fprintf(stderr, "GLB header error coverage failed\n");
return ok;
}
static int check_parse_file_failure_initializes_model(void) {
tg3_model model;
tg3_error_stack errors;
@@ -928,6 +1461,36 @@ int main(int argc, char **argv) {
if (!check_minimal_write_roundtrip()) {
return 1;
}
if (!check_binary_write_roundtrip()) {
return 1;
}
if (!check_embed_buffer_from_glb()) {
return 1;
}
if (!check_serialize_defaults()) {
return 1;
}
if (!check_parse_float32_model()) {
return 1;
}
if (!check_write_to_file_callback()) {
return 1;
}
if (!check_parse_file_callback()) {
return 1;
}
if (!check_streaming_writer()) {
return 1;
}
if (!check_parse_stream_callbacks()) {
return 1;
}
if (!check_store_original_json()) {
return 1;
}
if (!check_glb_header_errors()) {
return 1;
}
if (!check_parse_file_failure_initializes_model()) {
return 1;
}

View File

@@ -0,0 +1,75 @@
#include <stddef.h>
#include <stdint.h>
static union {
uint64_t align;
unsigned char bytes[512 * 1024];
} test_heap;
static size_t test_heap_used;
static void *test_malloc(size_t size) {
size_t total = (size + sizeof(size_t) + 7u) & ~(size_t)7u;
if (test_heap_used + total > sizeof(test_heap.bytes)) return 0;
{
unsigned char *base = test_heap.bytes + test_heap_used;
*((size_t *)base) = size;
test_heap_used += total;
return base + sizeof(size_t);
}
}
static void *test_realloc(void *ptr, size_t size) {
unsigned char *old_base;
size_t old_size;
unsigned char *new_ptr;
size_t n;
size_t i;
if (!ptr) return test_malloc(size);
old_base = (unsigned char *)ptr - sizeof(size_t);
old_size = *((size_t *)old_base);
new_ptr = (unsigned char *)test_malloc(size);
if (!new_ptr) return 0;
n = old_size < size ? old_size : size;
for (i = 0; i < n; ++i) new_ptr[i] = ((unsigned char *)ptr)[i];
return new_ptr;
}
static void test_free(void *ptr) {
(void)ptr;
}
#define TINYGLTF3_NO_STDLIB
#define TINYGLTF3_MALLOC(sz) test_malloc(sz)
#define TINYGLTF3_REALLOC(ptr, sz) test_realloc((ptr), (sz))
#define TINYGLTF3_FREE(ptr) test_free(ptr)
#define TINYGLTF3_IMPLEMENTATION
#include "tiny_gltf_v3.h"
static int streq(tg3_str s, const char *lit, uint32_t len) {
uint32_t i;
if (!s.data || s.len != len) return 0;
for (i = 0; i < len; ++i) {
if (s.data[i] != lit[i]) return 0;
}
return 1;
}
int main(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"free\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"", 0, &opts);
if (err != TG3_OK) return 1;
if (model.nodes_count != 1) return 3;
if (!streq(model.nodes[0].name, "free", 4)) return 4;
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}

165
tests/tester_v3_json_c.c Normal file
View File

@@ -0,0 +1,165 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define TINYGLTF_JSON_C_IMPLEMENTATION
#include "tinygltf_json_c.h"
static uint64_t dbl_bits(double v) {
uint64_t bits;
memcpy(&bits, &v, sizeof(bits));
return bits;
}
static double dbl_from_bits(uint64_t bits) {
double v;
memcpy(&v, &bits, sizeof(v));
return v;
}
static int check_stringify(const char *json, const char *expected) {
tg3json_value v;
const char *err = NULL;
char *out;
size_t out_len = 0;
int ok = 1;
if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1);
return 0;
}
out = tg3json_stringify(&v, &out_len);
if (!out || strcmp(out, expected) != 0) {
fprintf(stderr, "stringify(%s) = %s, expected %s\n",
json, out ? out : "(null)", expected);
ok = 0;
}
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
return ok;
}
static int check_parse_rejects(const char *json) {
tg3json_value v;
const char *err = NULL;
if (tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse unexpectedly accepted %s\n", json);
tg3json_value_free(&v);
return 0;
}
return 1;
}
static int check_roundtrip(const char *json) {
tg3json_value a;
tg3json_value b;
const char *err = NULL;
char *out;
size_t out_len = 0;
int ok = 1;
if (!tg3json_parse(json, json + strlen(json), 128, &a, &err)) return 0;
out = tg3json_stringify(&a, &out_len);
if (!out || !tg3json_parse(out, out + out_len, 128, &b, &err)) {
ok = 0;
} else if (a.type != TG3JSON_REAL ||
!((b.type == TG3JSON_REAL && dbl_bits(a.u.real) == dbl_bits(b.u.real)) ||
(b.type == TG3JSON_INT && dbl_bits(a.u.real) == dbl_bits((double)b.u.integer)))) {
fprintf(stderr, "roundtrip changed bits: %s -> %s\n", json, out);
ok = 0;
}
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&a);
tg3json_value_free(&b);
return ok;
}
static int check_parse_bits(const char *json, uint64_t expected_bits) {
tg3json_value v;
const char *err = NULL;
if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1);
return 0;
}
if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != expected_bits) {
fprintf(stderr, "parse bits mismatch: %s -> 0x%llx, expected 0x%llx\n",
json, (unsigned long long)(v.type == TG3JSON_REAL ? dbl_bits(v.u.real) : 0),
(unsigned long long)expected_bits);
tg3json_value_free(&v);
return 0;
}
tg3json_value_free(&v);
return 1;
}
static int check_parse_float32(void) {
static const char json[] = "0.10000000149011612";
tg3json_parse_options opts;
tg3json_value v;
const char *err = NULL;
double expected = (double)(float)0.10000000149011612;
memset(&opts, 0, sizeof(opts));
opts.parse_float32 = 1;
if (!tg3json_parse_n_opts(json, strlen(json), &opts, &v, &err)) {
fprintf(stderr, "parse_float32 parse failed\n");
return 0;
}
if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != dbl_bits(expected)) {
fprintf(stderr, "parse_float32 did not round through float\n");
tg3json_value_free(&v);
return 0;
}
tg3json_value_free(&v);
return 1;
}
static int check_nonfinite_stringifies_to_null(void) {
tg3json_value v;
char *out;
size_t len = 0;
int ok;
tg3json_value_init_real(&v, dbl_from_bits(0x7ff0000000000000ULL));
out = tg3json_stringify(&v, &len);
ok = out && strcmp(out, "null") == 0;
if (!ok) fprintf(stderr, "inf stringify = %s\n", out ? out : "(null)");
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
if (!ok) return 0;
tg3json_value_init_real(&v, dbl_from_bits(0x7ff8000000000001ULL));
out = tg3json_stringify(&v, &len);
ok = out && strcmp(out, "null") == 0;
if (!ok) fprintf(stderr, "nan stringify = %s\n", out ? out : "(null)");
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
return ok;
}
int main(void) {
int ok = 1;
ok = check_stringify("1.0", "1") && ok;
ok = check_stringify("-1.0", "-1") && ok;
ok = check_stringify("0.1", "0.1") && ok;
ok = check_stringify("0.0001", "0.0001") && ok;
ok = check_stringify("0.00001", "1e-5") && ok;
ok = check_stringify("1000000000000000.0", "1000000000000000") && ok;
ok = check_stringify("10000000000000000.0", "1e16") && ok;
ok = check_roundtrip("1.2345678901234567") && ok;
ok = check_roundtrip("2.2250738585072014e-308") && ok;
ok = check_roundtrip("5e-324") && ok;
ok = check_roundtrip("-5e-324") && ok;
ok = check_roundtrip("9007199254740993.0") && ok;
ok = check_parse_bits("1.23456789012345678901", 0x3ff3c0ca428c59fbULL) && ok;
ok = check_parse_bits("1.234567890123456789012345678901234567890e-100",
0x2b31482fe620c5d2ULL) && ok;
ok = check_parse_bits("1.7976931348623157e308", 0x7fefffffffffffffULL) && ok;
ok = check_parse_float32() && ok;
ok = check_nonfinite_stringifies_to_null() && ok;
ok = check_parse_rejects("+1") && ok;
ok = check_parse_rejects("01") && ok;
ok = check_parse_rejects("1.") && ok;
ok = check_parse_rejects("1e") && ok;
ok = check_parse_rejects("1e400") && ok;
ok = check_parse_rejects("-1e400") && ok;
ok = check_parse_rejects("1.7976931348623159e308") && ok;
ok = check_parse_rejects("[1,]") && ok;
return ok ? 0 : 1;
}