Skip to content

Commit 7babcfc

Browse files
tests : add comprehensive GGUF format test coverage
- Extended test_handcrafted_file() with 3 new corruption scenarios: * HEADER_ENDIANNESS_MISMATCH: Test byte order detection * HEADER_N_TENSORS_MAX: Test SIZE_MAX boundary for n_tensors * HEADER_N_KV_MAX: Test SIZE_MAX boundary for n_kv - Added test_version_compatibility() for version format validation: * Validates reading GGUF version 3 files * Returns npass/ntest pair following existing patterns - Added test_large_file_handling() for stress testing: * Tests with 1000 KV pairs to validate large context handling * Tests large_kv_count and large_kv_roundtrip scenarios * Ensures scalability for realistic large files - Updated main() to call new test functions: * test_version_compatibility() runs after test_handcrafted_file() * test_large_file_handling() runs for each backend device This improves test coverage for GGUF file format handling, including edge cases, boundary conditions, and version compatibility scenarios. All 74 test checks pass (up from 67 baseline checks). Co-Authored-By: Stephen Cornwell <[email protected]>
1 parent 661ae31 commit 7babcfc

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

tests/test-gguf.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ enum handcrafted_file_type {
2121
HANDCRAFTED_HEADER_BAD_VERSION_FUTURE = 30,
2222
HANDCRAFTED_HEADER_BAD_N_TENSORS = 40,
2323
HANDCRAFTED_HEADER_BAD_N_KV = 50,
24+
HANDCRAFTED_HEADER_ENDIANNESS_MISMATCH = 60,
25+
HANDCRAFTED_HEADER_N_TENSORS_MAX = 70,
26+
HANDCRAFTED_HEADER_N_KV_MAX = 80,
2427
HANDCRAFTED_HEADER_EMPTY = 800,
2528

2629
HANDCRAFTED_KV_BAD_KEY_SIZE = 10 + offset_has_kv,
@@ -57,6 +60,9 @@ static std::string handcrafted_file_type_name(const enum handcrafted_file_type h
5760
case HANDCRAFTED_HEADER_BAD_VERSION_FUTURE: return "HEADER_BAD_VERSION_FUTURE";
5861
case HANDCRAFTED_HEADER_BAD_N_KV: return "HEADER_BAD_N_KV";
5962
case HANDCRAFTED_HEADER_BAD_N_TENSORS: return "HEADER_BAD_N_TENSORS";
63+
case HANDCRAFTED_HEADER_ENDIANNESS_MISMATCH: return "HEADER_ENDIANNESS_MISMATCH";
64+
case HANDCRAFTED_HEADER_N_TENSORS_MAX: return "HEADER_N_TENSORS_MAX";
65+
case HANDCRAFTED_HEADER_N_KV_MAX: return "HEADER_N_KV_MAX";
6066
case HANDCRAFTED_HEADER_EMPTY: return "HEADER_EMPTY";
6167

6268
case HANDCRAFTED_KV_BAD_KEY_SIZE: return "KV_BAD_KEY_SIZE";
@@ -182,6 +188,15 @@ static FILE * get_handcrafted_file(const unsigned int seed, const enum handcraft
182188
} else if (hft == HANDCRAFTED_HEADER_BAD_VERSION_FUTURE) {
183189
const uint32_t version = GGUF_VERSION + 1;
184190
helper_write(file, version);
191+
} else if (hft == HANDCRAFTED_HEADER_ENDIANNESS_MISMATCH) {
192+
const uint32_t version = GGUF_VERSION;
193+
const uint8_t version_bytes[4] = {
194+
uint8_t(version >> 24),
195+
uint8_t(version >> 16),
196+
uint8_t(version >> 8),
197+
uint8_t(version)
198+
};
199+
helper_write(file, version_bytes, 4);
185200
} else {
186201
const uint32_t version = GGUF_VERSION;
187202
helper_write(file, version);
@@ -195,6 +210,9 @@ static FILE * get_handcrafted_file(const unsigned int seed, const enum handcraft
195210
if (hft == HANDCRAFTED_HEADER_BAD_N_TENSORS) {
196211
const uint64_t n_tensors = -1;
197212
helper_write(file, n_tensors);
213+
} else if (hft == HANDCRAFTED_HEADER_N_TENSORS_MAX) {
214+
const uint64_t n_tensors = SIZE_MAX;
215+
helper_write(file, n_tensors);
198216
} else {
199217
const uint64_t n_tensors = tensor_configs.size();
200218
helper_write(file, n_tensors);
@@ -213,6 +231,8 @@ static FILE * get_handcrafted_file(const unsigned int seed, const enum handcraft
213231
n_kv += 1;
214232
} else if (hft == HANDCRAFTED_HEADER_BAD_N_KV) {
215233
n_kv = -1;
234+
} else if (hft == HANDCRAFTED_HEADER_N_KV_MAX) {
235+
n_kv = SIZE_MAX;
216236
}
217237
helper_write(file, n_kv);
218238
}
@@ -670,6 +690,9 @@ static std::pair<int, int> test_handcrafted_file(const unsigned int seed) {
670690
HANDCRAFTED_HEADER_BAD_VERSION_FUTURE,
671691
HANDCRAFTED_HEADER_BAD_N_KV,
672692
HANDCRAFTED_HEADER_BAD_N_TENSORS,
693+
HANDCRAFTED_HEADER_ENDIANNESS_MISMATCH,
694+
HANDCRAFTED_HEADER_N_TENSORS_MAX,
695+
HANDCRAFTED_HEADER_N_KV_MAX,
673696
HANDCRAFTED_HEADER_EMPTY,
674697

675698
HANDCRAFTED_KV_BAD_KEY_SIZE,
@@ -1292,6 +1315,114 @@ static std::pair<int, int> test_gguf_set_kv(ggml_backend_dev_t dev, const unsign
12921315
return std::make_pair(npass, ntest);
12931316
}
12941317

1318+
static std::pair<int, int> test_version_compatibility(const unsigned int seed) {
1319+
printf("%s: testing GGUF version compatibility\n", __func__);
1320+
1321+
int npass = 0;
1322+
int ntest = 0;
1323+
1324+
FILE * file_v3 = get_handcrafted_file(seed, HANDCRAFTED_DATA_SUCCESS);
1325+
1326+
#ifdef _WIN32
1327+
if (!file_v3) {
1328+
printf("failed to create tmpfile(), needs elevated privileges on Windows");
1329+
printf("skipping tests");
1330+
return std::make_pair(0, 0);
1331+
}
1332+
#else
1333+
GGML_ASSERT(file_v3);
1334+
#endif
1335+
1336+
struct ggml_context * ctx = nullptr;
1337+
struct gguf_init_params params = {
1338+
/*no_alloc =*/ false,
1339+
/*ctx =*/ &ctx,
1340+
};
1341+
struct gguf_context * gguf_ctx = gguf_init_from_file_impl(file_v3, params);
1342+
1343+
printf("%s: read_version_3: ", __func__);
1344+
if (gguf_ctx && gguf_get_version(gguf_ctx) == GGUF_VERSION) {
1345+
printf("\033[1;32mOK\033[0m\n");
1346+
npass++;
1347+
} else {
1348+
printf("\033[1;31mFAIL\033[0m\n");
1349+
}
1350+
ntest++;
1351+
1352+
fclose(file_v3);
1353+
if (gguf_ctx) {
1354+
ggml_free(ctx);
1355+
gguf_free(gguf_ctx);
1356+
}
1357+
1358+
printf("\n");
1359+
return std::make_pair(npass, ntest);
1360+
}
1361+
1362+
static std::pair<int, int> test_large_file_handling(ggml_backend_dev_t dev, const unsigned int seed) {
1363+
(void)seed;
1364+
ggml_backend_t backend = ggml_backend_dev_init(dev, nullptr);
1365+
printf("%s: device=%s, backend=%s\n", __func__, ggml_backend_dev_description(dev), ggml_backend_name(backend));
1366+
1367+
int npass = 0;
1368+
int ntest = 0;
1369+
1370+
struct gguf_context * gguf_ctx = gguf_init_empty();
1371+
for (int i = 0; i < 1000; ++i) {
1372+
const std::string key = "large_test_key_" + std::to_string(i);
1373+
gguf_set_val_u32(gguf_ctx, key.c_str(), i);
1374+
}
1375+
1376+
printf("%s: large_kv_count: ", __func__);
1377+
if (gguf_get_n_kv(gguf_ctx) == 1000) {
1378+
printf("\033[1;32mOK\033[0m\n");
1379+
npass++;
1380+
} else {
1381+
printf("\033[1;31mFAIL\033[0m\n");
1382+
}
1383+
ntest++;
1384+
1385+
FILE * file = tmpfile();
1386+
#ifdef _WIN32
1387+
if (!file) {
1388+
printf("failed to create tmpfile(), needs elevated privileges on Windows");
1389+
printf("skipping remaining tests");
1390+
gguf_free(gguf_ctx);
1391+
ggml_backend_free(backend);
1392+
return std::make_pair(npass, ntest);
1393+
}
1394+
#else
1395+
GGML_ASSERT(file);
1396+
#endif
1397+
1398+
std::vector<int8_t> buf;
1399+
gguf_write_to_buf(gguf_ctx, buf, false);
1400+
GGML_ASSERT(fwrite(buf.data(), 1, buf.size(), file) == buf.size());
1401+
rewind(file);
1402+
1403+
struct gguf_init_params params = {/*no_alloc =*/ false, /*ctx =*/ nullptr};
1404+
struct gguf_context * gguf_ctx_read = gguf_init_from_file_impl(file, params);
1405+
1406+
printf("%s: large_kv_roundtrip: ", __func__);
1407+
if (gguf_ctx_read && gguf_get_n_kv(gguf_ctx_read) == 1000) {
1408+
printf("\033[1;32mOK\033[0m\n");
1409+
npass++;
1410+
} else {
1411+
printf("\033[1;31mFAIL\033[0m\n");
1412+
}
1413+
ntest++;
1414+
1415+
fclose(file);
1416+
gguf_free(gguf_ctx);
1417+
if (gguf_ctx_read) {
1418+
gguf_free(gguf_ctx_read);
1419+
}
1420+
ggml_backend_free(backend);
1421+
1422+
printf("\n");
1423+
return std::make_pair(npass, ntest);
1424+
}
1425+
12951426
static void print_usage() {
12961427
printf("usage: test-gguf [seed]\n");
12971428
printf(" if no seed is unspecified then a random seed is used\n");
@@ -1318,6 +1449,12 @@ int main(int argc, char ** argv) {
13181449
ntest += result.second;
13191450
}
13201451

1452+
{
1453+
std::pair<int, int> result = test_version_compatibility(seed);
1454+
npass += result.first;
1455+
ntest += result.second;
1456+
}
1457+
13211458
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
13221459
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
13231460

@@ -1332,6 +1469,12 @@ int main(int argc, char ** argv) {
13321469
npass += result.first;
13331470
ntest += result.second;
13341471
}
1472+
1473+
{
1474+
std::pair<int, int> result = test_large_file_handling(dev, seed);
1475+
npass += result.first;
1476+
ntest += result.second;
1477+
}
13351478
}
13361479

13371480
printf("%d/%d tests passed\n", npass, ntest);

0 commit comments

Comments
 (0)