summaryrefslogtreecommitdiff
path: root/docs/handbook/libnbtplusplus/testing.md
blob: 807f0fbaa8bcf0975c6422b8b8a81ca02626f883 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# Testing

## Overview

libnbt++ uses the **CxxTest** testing framework. Tests are defined as C++ header files with test classes inheriting from `CxxTest::TestSuite`. The test suite covers all tag types, I/O operations, endian conversion, zlib streams, and value semantics.

Build configuration is in `test/CMakeLists.txt`.

---

## Build Configuration

```cmake
# test/CMakeLists.txt
if(NOT (UNIX AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86")))
    message(WARNING "Tests are only supported on Linux x86/x86_64")
    return()
endif()

find_package(CxxTest REQUIRED)
```

Tests are **Linux x86/x86_64 only** due to the use of `objcopy` for embedding binary test data.

### CMake Options

Tests are controlled by the `NBT_BUILD_TESTS` option:

```cmake
option(NBT_BUILD_TESTS "Build libnbt++ tests" ON)

if(NBT_BUILD_TESTS)
    enable_testing()
    add_subdirectory(test)
endif()
```

### Binary Test Data Embedding

Test data files (e.g., `bigtest.nbt`, `bigtest_uncompr`, `littletest.nbt`) are converted to object files via `objcopy` and linked directly into test executables:

```cmake
set(BINARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/")

function(add_nbt_test name)
    # ...
    foreach(binfile ${ARGN})
        set(obj "${CMAKE_CURRENT_BINARY_DIR}/${binfile}.o")
        add_custom_command(
            OUTPUT ${obj}
            COMMAND objcopy -I binary -O elf64-x86-64
                -B i386:x86-64 ${binfile} ${obj}
            WORKING_DIRECTORY ${BINARY_DIR}
            DEPENDS ${BINARY_DIR}/${binfile}
        )
        target_sources(${name} PRIVATE ${obj})
    endforeach()
endfunction()
```

The embedded data is accessed via extern symbols declared in `test/data.h`:

```cpp
// test/data.h
#define DECLARE_BINARY(name) \
    extern "C" const char _binary_##name##_start[]; \
    extern "C" const char _binary_##name##_end[];

DECLARE_BINARY(bigtest_nbt)
DECLARE_BINARY(bigtest_uncompr)
DECLARE_BINARY(littletest_nbt)
DECLARE_BINARY(littletest_uncompr)
DECLARE_BINARY(errortest_eof_nbt)
DECLARE_BINARY(errortest_negative_length_nbt)
DECLARE_BINARY(errortest_excessive_depth_nbt)
```

---

## Test Targets

| Target | Source | Tests |
|--------|--------|-------|
| `nbttest` | `test/nbttest.h` | Core tag types, value, compound, list, visitor |
| `endian_str_test` | `test/endian_str_test.h` | Endian read/write roundtrips |
| `read_test` | `test/read_test.h` | stream_reader, big/little endian, errors |
| `write_test` | `test/write_test.h` | stream_writer, payload writing, roundtrips |
| `zlibstream_test` | `test/zlibstream_test.h` | izlibstream, ozlibstream, compression roundtrip |
| `format_test` | `test/format_test.cpp` | JSON text formatting |
| `test_value` | `test/test_value.h` | Value numeric assignment and conversion |

Test registration in CMake:

```cmake
add_nbt_test(nbttest nbttest.h)
add_nbt_test(endian_str_test endian_str_test.h)
add_nbt_test(read_test read_test.h
    bigtest.nbt bigtest_uncompr littletest.nbt littletest_uncompr
    errortest_eof.nbt errortest_negative_length.nbt
    errortest_excessive_depth.nbt)
add_nbt_test(write_test write_test.h
    bigtest_uncompr littletest_uncompr)
if(NBT_HAVE_ZLIB)
    add_nbt_test(zlibstream_test zlibstream_test.h
        bigtest.nbt bigtest_uncompr)
endif()
add_nbt_test(test_value test_value.h)
```

`zlibstream_test` is only built when `NBT_HAVE_ZLIB` is defined.

---

## Test Details

### nbttest.h — Core Functionality

Tests the fundamental tag and value operations:

```cpp
class NbtTest : public CxxTest::TestSuite
{
public:
    void test_tag_primitive();     // Constructors, get/set, implicit conversion
    void test_tag_string();        // String constructors, conversion operators
    void test_tag_compound();      // Insertion, lookup, iteration, has_key()
    void test_tag_list();          // Type enforcement, push_back, of<T>()
    void test_tag_array();         // Vector access, constructors
    void test_value();             // Type erasure, numeric assignment
    void test_visitor();           // Double dispatch, visitor invocation
    void test_equality();          // operator==, operator!= for all types
    void test_clone();             // clone() and move_clone() correctness
    void test_as();                // tag::as<T>() casting, bad_cast
};
```

### read_test.h — Deserialization

Tests `stream_reader` against known binary data:

```cpp
class ReadTest : public CxxTest::TestSuite
{
public:
    void test_read_bigtest();       // Verifies full bigtest.nbt structure
    void test_read_littletest();    // Little-endian variant
    void test_read_bigtest_uncompr(); // Uncompressed big-endian
    void test_read_littletest_uncompr(); // Uncompressed little-endian
    void test_read_eof();           // Truncated data → input_error
    void test_read_negative_length(); // Negative array length → input_error
    void test_read_excessive_depth(); // >1024 nesting → input_error
};
```

The "bigtest" validates a complex nested compound with all tag types — the standard NBT test file from the Minecraft community. Fields verified include:

- `"byteTest"`: `tag_byte` with value 127
- `"shortTest"`: `tag_short` with value 32767
- `"intTest"`: `tag_int` with value 2147483647
- `"longTest"`: `tag_long` with value 9223372036854775807
- `"floatTest"`: `tag_float` with value ~0.498...
- `"doubleTest"`: `tag_double` with value ~0.493...
- `"stringTest"`: UTF-8 string "HELLO WORLD THIS IS A TEST STRING ÅÄÖ!"
- `"byteArrayTest"`: 1000-element byte array
- `"listTest (long)"`: List of 5 longs
- `"listTest (compound)"`: List of compounds
- Nested compound within compound

### write_test.h — Serialization

Tests `stream_writer` and write-then-read roundtrips:

```cpp
class WriteTest : public CxxTest::TestSuite
{
public:
    void test_write_bigtest();      // Write and compare against reference
    void test_write_littletest();   // Little-endian write
    void test_write_payload();      // Individual payload writing
    void test_roundtrip();          // Write → read → compare equality
};
```

### endian_str_test.h — Byte Order

Tests all numeric types through read/write roundtrips in both endiannesses:

```cpp
class EndianStrTest : public CxxTest::TestSuite
{
public:
    void test_read_big();
    void test_read_little();
    void test_write_big();
    void test_write_little();
    void test_roundtrip_big();
    void test_roundtrip_little();
    void test_float_big();
    void test_float_little();
    void test_double_big();
    void test_double_little();
};
```

### zlibstream_test.h — Compression

Tests zlib stream wrappers:

```cpp
class ZlibstreamTest : public CxxTest::TestSuite
{
public:
    void test_inflate_gzip();       // Decompress gzip data
    void test_inflate_zlib();       // Decompress zlib data
    void test_inflate_corrupt();    // Corrupt data → zlib_error
    void test_inflate_eof();        // Truncated data → EOF
    void test_inflate_trailing();   // Data after compressed stream
    void test_deflate_roundtrip();  // Compress → decompress → compare
    void test_deflate_gzip();       // Gzip format output
};
```

### test_value.h — Value Semantics

Tests the `value` class's numeric assignment and conversion:

```cpp
class TestValue : public CxxTest::TestSuite
{
public:
    void test_assign_byte();
    void test_assign_short();
    void test_assign_int();
    void test_assign_long();
    void test_assign_float();
    void test_assign_double();
    void test_assign_widening();    // int8 → int16 → int32 → int64
    void test_assign_narrowing();   // Narrowing disallowed
    void test_assign_string();
};
```

### format_test.cpp — Text Output

A standalone test (not CxxTest) that constructs a compound, serializes it, reads it back, and prints JSON:

```cpp
// Constructs a compound with nested types
// Writes to stringstream, reads back
// Outputs via operator<< (json_formatter)
```

---

## Running Tests

```bash
mkdir build && cd build
cmake .. -DNBT_BUILD_TESTS=ON
cmake --build .
ctest
```

Or run individual tests:

```bash
./test/nbttest
./test/read_test
./test/write_test
./test/endian_str_test
./test/zlibstream_test
./test/test_value
```

---

## Test Data Files

Located in `test/`:

| File | Description |
|------|-------------|
| `bigtest.nbt` | Standard NBT test file, gzip-compressed, big-endian |
| `bigtest_uncompr` | Same as bigtest but uncompressed |
| `littletest.nbt` | Little-endian NBT test file, compressed |
| `littletest_uncompr` | Little-endian, uncompressed |
| `errortest_eof.nbt` | Truncated NBT for error testing |
| `errortest_negative_length.nbt` | NBT with negative array length |
| `errortest_excessive_depth.nbt` | NBT with >1024 nesting levels |

These files are embedded into test binaries via `objcopy` and accessed through the `DECLARE_BINARY` macros in `data.h`.