mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-08 03:03:50 +00:00
Complete freestanding v3 C JSON conversion tests
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 = ∩
|
||||
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 = ∩
|
||||
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 = ∩
|
||||
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;
|
||||
}
|
||||
|
||||
75
tests/tester_v3_freestanding.c
Normal file
75
tests/tester_v3_freestanding.c
Normal 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
165
tests/tester_v3_json_c.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user