xref: /aosp_15_r20/external/flatbuffers/dart/test/flat_buffers_test.dart (revision 890232f25432b36107d06881e0a25aaa6b473652)
1import 'dart:typed_data';
2import 'dart:io' as io;
3
4import 'package:path/path.dart' as path;
5
6import 'package:flat_buffers/flat_buffers.dart';
7import 'package:test/test.dart';
8import 'package:test_reflective_loader/test_reflective_loader.dart';
9
10import './monster_test_my_game.example_generated.dart' as example;
11import './monster_test_my_game.example2_generated.dart' as example2;
12import './list_of_enums_generated.dart' as example3;
13import './bool_structs_generated.dart' as example4;
14import './keyword_test_keyword_test_generated.dart' as keyword_test;
15
16main() {
17  defineReflectiveSuite(() {
18    defineReflectiveTests(BuilderTest);
19    defineReflectiveTests(ObjectAPITest);
20    defineReflectiveTests(CheckOtherLangaugesData);
21    defineReflectiveTests(GeneratorTest);
22    defineReflectiveTests(ListOfEnumsTest);
23  });
24}
25
26int indexToField(int index) {
27  return (1 + 1 + index) * 2;
28}
29
30@reflectiveTest
31class CheckOtherLangaugesData {
32  test_cppData() async {
33    List<int> data = await io.File(path.join(
34      path.context.current,
35      'test',
36      'monsterdata_test.mon',
37    )).readAsBytes();
38    example.Monster mon = example.Monster(data);
39    expect(mon.hp, 80);
40    expect(mon.mana, 150);
41    expect(mon.name, 'MyMonster');
42    expect(mon.pos!.x, 1.0);
43    expect(mon.pos!.y, 2.0);
44    expect(mon.pos!.z, 3.0);
45    expect(mon.pos!.test1, 3.0);
46    expect(mon.pos!.test2.value, 2.0);
47    expect(mon.pos!.test3.a, 5);
48    expect(mon.pos!.test3.b, 6);
49    expect(mon.testType!.value, example.AnyTypeId.Monster.value);
50    expect(mon.test is example.Monster, true);
51    final monster2 = mon.test as example.Monster;
52    expect(monster2.name, "Fred");
53
54    expect(mon.inventory!.length, 5);
55    expect(mon.inventory!.reduce((cur, next) => cur + next), 10);
56    final test4 = mon.test4!;
57    expect(test4.length, 2);
58    expect(test4[0].a + test4[0].b + test4[1].a + test4[1].b, 100);
59    expect(mon.testarrayofstring!.length, 2);
60    expect(mon.testarrayofstring![0], "test1");
61    expect(mon.testarrayofstring![1], "test2");
62
63    // this will fail if accessing any field fails.
64    expect(
65      mon.toString(),
66      'Monster{'
67      'pos: Vec3{x: 1.0, y: 2.0, z: 3.0, test1: 3.0, test2: Color{value: 2}, test3: Test{a: 5, b: 6}}, '
68      'mana: 150, hp: 80, name: MyMonster, inventory: [0, 1, 2, 3, 4], '
69      'color: Color{value: 8}, testType: AnyTypeId{value: 1}, '
70      'test: Monster{pos: null, mana: 150, hp: 100, name: Fred, '
71      'inventory: null, color: Color{value: 8}, testType: null, '
72      'test: null, test4: null, testarrayofstring: null, '
73      'testarrayoftables: null, enemy: null, testnestedflatbuffer: null, '
74      'testempty: null, testbool: false, testhashs32Fnv1: 0, '
75      'testhashu32Fnv1: 0, testhashs64Fnv1: 0, testhashu64Fnv1: 0, '
76      'testhashs32Fnv1a: 0, testhashu32Fnv1a: 0, testhashs64Fnv1a: 0, '
77      'testhashu64Fnv1a: 0, testarrayofbools: null, testf: 3.14159, '
78      'testf2: 3.0, testf3: 0.0, testarrayofstring2: null, '
79      'testarrayofsortedstruct: null, flex: null, test5: null, '
80      'vectorOfLongs: null, vectorOfDoubles: null, parentNamespaceTest: null, '
81      'vectorOfReferrables: null, singleWeakReference: 0, '
82      'vectorOfWeakReferences: null, vectorOfStrongReferrables: null, '
83      'coOwningReference: 0, vectorOfCoOwningReferences: null, '
84      'nonOwningReference: 0, vectorOfNonOwningReferences: null, '
85      'anyUniqueType: null, anyUnique: null, anyAmbiguousType: null, '
86      'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race{value: -1}, '
87      'testrequirednestedflatbuffer: null, scalarKeySortedTables: null, '
88      'nativeInline: null, '
89      'longEnumNonEnumDefault: LongEnum{value: 0}, '
90      'longEnumNormalDefault: LongEnum{value: 2}}, '
91      'test4: [Test{a: 10, b: 20}, Test{a: 30, b: 40}], '
92      'testarrayofstring: [test1, test2], testarrayoftables: null, '
93      'enemy: Monster{pos: null, mana: 150, hp: 100, name: Fred, '
94      'inventory: null, color: Color{value: 8}, testType: null, '
95      'test: null, test4: null, testarrayofstring: null, '
96      'testarrayoftables: null, enemy: null, testnestedflatbuffer: null, '
97      'testempty: null, testbool: false, testhashs32Fnv1: 0, '
98      'testhashu32Fnv1: 0, testhashs64Fnv1: 0, testhashu64Fnv1: 0, '
99      'testhashs32Fnv1a: 0, testhashu32Fnv1a: 0, testhashs64Fnv1a: 0, '
100      'testhashu64Fnv1a: 0, testarrayofbools: null, testf: 3.14159, '
101      'testf2: 3.0, testf3: 0.0, testarrayofstring2: null, '
102      'testarrayofsortedstruct: null, flex: null, test5: null, '
103      'vectorOfLongs: null, vectorOfDoubles: null, parentNamespaceTest: null, '
104      'vectorOfReferrables: null, singleWeakReference: 0, '
105      'vectorOfWeakReferences: null, vectorOfStrongReferrables: null, '
106      'coOwningReference: 0, vectorOfCoOwningReferences: null, '
107      'nonOwningReference: 0, vectorOfNonOwningReferences: null, '
108      'anyUniqueType: null, anyUnique: null, anyAmbiguousType: null, '
109      'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race{value: -1}, '
110      'testrequirednestedflatbuffer: null, scalarKeySortedTables: null, '
111      'nativeInline: null, '
112      'longEnumNonEnumDefault: LongEnum{value: 0}, '
113      'longEnumNormalDefault: LongEnum{value: 2}}, '
114      'testnestedflatbuffer: null, testempty: null, testbool: true, '
115      'testhashs32Fnv1: -579221183, testhashu32Fnv1: 3715746113, '
116      'testhashs64Fnv1: 7930699090847568257, '
117      'testhashu64Fnv1: 7930699090847568257, '
118      'testhashs32Fnv1a: -1904106383, testhashu32Fnv1a: 2390860913, '
119      'testhashs64Fnv1a: 4898026182817603057, '
120      'testhashu64Fnv1a: 4898026182817603057, '
121      'testarrayofbools: [true, false, true], testf: 3.14159, testf2: 3.0, '
122      'testf3: 0.0, testarrayofstring2: null, testarrayofsortedstruct: ['
123      'Ability{id: 0, distance: 45}, Ability{id: 1, distance: 21}, '
124      'Ability{id: 5, distance: 12}], '
125      'flex: null, test5: [Test{a: 10, b: 20}, Test{a: 30, b: 40}], '
126      'vectorOfLongs: [1, 100, 10000, 1000000, 100000000], '
127      'vectorOfDoubles: [-1.7976931348623157e+308, 0.0, 1.7976931348623157e+308], '
128      'parentNamespaceTest: null, vectorOfReferrables: null, '
129      'singleWeakReference: 0, vectorOfWeakReferences: null, '
130      'vectorOfStrongReferrables: null, coOwningReference: 0, '
131      'vectorOfCoOwningReferences: null, nonOwningReference: 0, '
132      'vectorOfNonOwningReferences: null, '
133      'anyUniqueType: null, anyUnique: null, '
134      'anyAmbiguousType: null, '
135      'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race{value: -1}, '
136      'testrequirednestedflatbuffer: null, scalarKeySortedTables: [Stat{id: '
137      'miss, val: 0, count: 0}, Stat{id: hit, val: 10, count: 1}], '
138      'nativeInline: Test{a: 1, b: 2}, '
139      'longEnumNonEnumDefault: LongEnum{value: 0}, '
140      'longEnumNormalDefault: LongEnum{value: 2}}',
141    );
142  }
143}
144
145/// Test a custom, fixed-memory allocator (no actual allocations performed)
146class CustomAllocator extends Allocator {
147  final _memory = ByteData(10 * 1024);
148  int _used = 0;
149
150  Uint8List buffer(int size) => _memory.buffer.asUint8List(_used - size, size);
151
152  @override
153  ByteData allocate(int size) {
154    if (size > _memory.lengthInBytes) {
155      throw UnsupportedError('Trying to allocate too much');
156    }
157    _used = size;
158    return ByteData.sublistView(_memory, 0, size);
159  }
160
161  @override
162  void deallocate(ByteData _) {}
163}
164
165@reflectiveTest
166class BuilderTest {
167  void test_monsterBuilder([Builder? builder]) {
168    final fbBuilder = builder ?? Builder();
169    final str = fbBuilder.writeString('MyMonster');
170
171    fbBuilder.writeString('test1');
172    fbBuilder.writeString('test2', asciiOptimization: true);
173    final testArrayOfString = fbBuilder.endStructVector(2);
174
175    final fred = fbBuilder.writeString('Fred');
176
177    final List<int> treasure = [0, 1, 2, 3, 4];
178    final inventory = fbBuilder.writeListUint8(treasure);
179
180    final monBuilder = example.MonsterBuilder(fbBuilder)
181      ..begin()
182      ..addNameOffset(fred);
183    final mon2 = monBuilder.finish();
184
185    final testBuilder = example.TestBuilder(fbBuilder);
186    testBuilder.finish(10, 20);
187    testBuilder.finish(30, 40);
188    final test4 = fbBuilder.endStructVector(2);
189
190    monBuilder
191      ..begin()
192      ..addPos(
193        example.Vec3Builder(fbBuilder).finish(
194          1.0,
195          2.0,
196          3.0,
197          3.0,
198          example.Color.Green,
199          () => testBuilder.finish(5, 6),
200        ),
201      )
202      ..addHp(80)
203      ..addNameOffset(str)
204      ..addInventoryOffset(inventory)
205      ..addTestType(example.AnyTypeId.Monster)
206      ..addTestOffset(mon2)
207      ..addTest4Offset(test4)
208      ..addTestarrayofstringOffset(testArrayOfString);
209    final mon = monBuilder.finish();
210    fbBuilder.finish(mon);
211  }
212
213  void test_error_addInt32_withoutStartTable([Builder? builder]) {
214    builder ??= Builder();
215    expect(() {
216      builder!.addInt32(0, 0);
217    }, throwsA(isA<AssertionError>()));
218  }
219
220  void test_error_addOffset_withoutStartTable() {
221    Builder builder = Builder();
222    expect(() {
223      builder.addOffset(0, 0);
224    }, throwsA(isA<AssertionError>()));
225  }
226
227  void test_error_endTable_withoutStartTable() {
228    Builder builder = Builder();
229    expect(() {
230      builder.endTable();
231    }, throwsA(isA<AssertionError>()));
232  }
233
234  void test_error_startTable_duringTable() {
235    Builder builder = Builder();
236    builder.startTable(0);
237    expect(() {
238      builder.startTable(0);
239    }, throwsA(isA<AssertionError>()));
240  }
241
242  void test_error_writeString_duringTable() {
243    Builder builder = Builder();
244    builder.startTable(1);
245    expect(() {
246      builder.writeString('12345');
247    }, throwsA(isA<AssertionError>()));
248  }
249
250  void test_file_identifier() {
251    Uint8List byteList;
252    {
253      Builder builder = Builder(initialSize: 0);
254      builder.startTable(0);
255      int offset = builder.endTable();
256      builder.finish(offset, 'Az~ÿ');
257      byteList = builder.buffer;
258    }
259    // Convert byteList to a ByteData so that we can read data from it.
260    ByteData byteData = byteList.buffer.asByteData(byteList.offsetInBytes);
261    // First 4 bytes are an offset to the table data.
262    int tableDataLoc = byteData.getUint32(0, Endian.little);
263    // Next 4 bytes are the file identifier.
264    expect(byteData.getUint8(4), 65); // 'a'
265    expect(byteData.getUint8(5), 122); // 'z'
266    expect(byteData.getUint8(6), 126); // '~'
267    expect(byteData.getUint8(7), 255); // 'ÿ'
268    // First 4 bytes of the table data are a backwards offset to the vtable.
269    int vTableLoc =
270        tableDataLoc - byteData.getInt32(tableDataLoc, Endian.little);
271    // First 2 bytes of the vtable are the size of the vtable in bytes, which
272    // should be 4.
273    expect(byteData.getUint16(vTableLoc, Endian.little), 4);
274    // Next 2 bytes are the size of the object in bytes (including the vtable
275    // pointer), which should be 4.
276    expect(byteData.getUint16(vTableLoc + 2, Endian.little), 4);
277  }
278
279  void test_low() {
280    final allocator = CustomAllocator();
281    final builder = Builder(initialSize: 0, allocator: allocator);
282
283    builder.putUint8(1);
284    expect(allocator.buffer(builder.size()), [1]);
285
286    builder.putUint32(2);
287    expect(allocator.buffer(builder.size()), [2, 0, 0, 0, 0, 0, 0, 1]);
288
289    builder.putUint8(3);
290    expect(
291        allocator.buffer(builder.size()), [0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 1]);
292
293    builder.putUint8(4);
294    expect(
295        allocator.buffer(builder.size()), [0, 0, 4, 3, 2, 0, 0, 0, 0, 0, 0, 1]);
296
297    builder.putUint8(5);
298    expect(
299        allocator.buffer(builder.size()), [0, 5, 4, 3, 2, 0, 0, 0, 0, 0, 0, 1]);
300
301    builder.putUint32(6);
302    expect(allocator.buffer(builder.size()),
303        [6, 0, 0, 0, 0, 5, 4, 3, 2, 0, 0, 0, 0, 0, 0, 1]);
304  }
305
306  void test_table_default() {
307    List<int> byteList;
308    {
309      final builder = Builder(initialSize: 0, allocator: CustomAllocator());
310      builder.startTable(2);
311      builder.addInt32(0, 10, 10);
312      builder.addInt32(1, 20, 10);
313      int offset = builder.endTable();
314      builder.finish(offset);
315      byteList = builder.buffer;
316      expect(builder.size(), byteList.length);
317    }
318    // read and verify
319    BufferContext buffer = BufferContext.fromBytes(byteList);
320    int objectOffset = buffer.derefObject(0);
321    // was not written, so uses the new default value
322    expect(
323        const Int32Reader()
324            .vTableGet(buffer, objectOffset, indexToField(0), 15),
325        15);
326    // has the written value
327    expect(
328        const Int32Reader()
329            .vTableGet(buffer, objectOffset, indexToField(1), 15),
330        20);
331  }
332
333  void test_table_format([Builder? builder]) {
334    Uint8List byteList;
335    {
336      builder ??= Builder(initialSize: 0);
337      builder.startTable(3);
338      builder.addInt32(0, 10);
339      builder.addInt32(1, 20);
340      builder.addInt32(2, 30);
341      builder.finish(builder.endTable());
342      byteList = builder.buffer;
343    }
344    // Convert byteList to a ByteData so that we can read data from it.
345    ByteData byteData = byteList.buffer.asByteData(byteList.offsetInBytes);
346    // First 4 bytes are an offset to the table data.
347    int tableDataLoc = byteData.getUint32(0, Endian.little);
348    // First 4 bytes of the table data are a backwards offset to the vtable.
349    int vTableLoc =
350        tableDataLoc - byteData.getInt32(tableDataLoc, Endian.little);
351    // First 2 bytes of the vtable are the size of the vtable in bytes, which
352    // should be 10.
353    expect(byteData.getUint16(vTableLoc, Endian.little), 10);
354    // Next 2 bytes are the size of the object in bytes (including the vtable
355    // pointer), which should be 16.
356    expect(byteData.getUint16(vTableLoc + 2, Endian.little), 16);
357    // Remaining 6 bytes are the offsets within the object where the ints are
358    // located.
359    for (int i = 0; i < 3; i++) {
360      int offset = byteData.getUint16(vTableLoc + 4 + 2 * i, Endian.little);
361      expect(
362          byteData.getInt32(tableDataLoc + offset, Endian.little), 10 + 10 * i);
363    }
364  }
365
366  void test_table_string() {
367    String latinString = 'test';
368    String unicodeString = 'Проба пера';
369    List<int> byteList;
370    {
371      Builder builder = Builder(initialSize: 0);
372      int? latinStringOffset =
373          builder.writeString(latinString, asciiOptimization: true);
374      int? unicodeStringOffset =
375          builder.writeString(unicodeString, asciiOptimization: true);
376      builder.startTable(2);
377      builder.addOffset(0, latinStringOffset);
378      builder.addOffset(1, unicodeStringOffset);
379      int offset = builder.endTable();
380      builder.finish(offset);
381      byteList = builder.buffer;
382    }
383    // read and verify
384    BufferContext buf = BufferContext.fromBytes(byteList);
385    int objectOffset = buf.derefObject(0);
386    expect(
387        const StringReader()
388            .vTableGetNullable(buf, objectOffset, indexToField(0)),
389        latinString);
390    expect(
391        const StringReader(asciiOptimization: true)
392            .vTableGetNullable(buf, objectOffset, indexToField(1)),
393        unicodeString);
394  }
395
396  void test_table_types([Builder? builder]) {
397    List<int> byteList;
398    {
399      builder ??= Builder(initialSize: 0);
400      int? stringOffset = builder.writeString('12345');
401      builder.startTable(7);
402      builder.addBool(0, true);
403      builder.addInt8(1, 10);
404      builder.addInt32(2, 20);
405      builder.addOffset(3, stringOffset);
406      builder.addInt32(4, 40);
407      builder.addUint32(5, 0x9ABCDEF0);
408      builder.addUint8(6, 0x9A);
409      int offset = builder.endTable();
410      builder.finish(offset);
411      byteList = builder.buffer;
412    }
413    // read and verify
414    BufferContext buf = BufferContext.fromBytes(byteList);
415    int objectOffset = buf.derefObject(0);
416    expect(
417        const BoolReader()
418            .vTableGetNullable(buf, objectOffset, indexToField(0)),
419        true);
420    expect(
421        const Int8Reader()
422            .vTableGetNullable(buf, objectOffset, indexToField(1)),
423        10);
424    expect(
425        const Int32Reader()
426            .vTableGetNullable(buf, objectOffset, indexToField(2)),
427        20);
428    expect(
429        const StringReader()
430            .vTableGetNullable(buf, objectOffset, indexToField(3)),
431        '12345');
432    expect(
433        const Int32Reader()
434            .vTableGetNullable(buf, objectOffset, indexToField(4)),
435        40);
436    expect(
437        const Uint32Reader()
438            .vTableGetNullable(buf, objectOffset, indexToField(5)),
439        0x9ABCDEF0);
440    expect(
441        const Uint8Reader()
442            .vTableGetNullable(buf, objectOffset, indexToField(6)),
443        0x9A);
444  }
445
446  void test_writeList_of_Uint32() {
447    List<int> values = <int>[10, 100, 12345, 0x9abcdef0];
448    // write
449    List<int> byteList;
450    {
451      Builder builder = Builder(initialSize: 0);
452      int offset = builder.writeListUint32(values);
453      builder.finish(offset);
454      byteList = builder.buffer;
455    }
456    // read and verify
457    BufferContext buf = BufferContext.fromBytes(byteList);
458    List<int> items = const Uint32ListReader().read(buf, 0);
459    expect(items, hasLength(4));
460    expect(items, orderedEquals(values));
461  }
462
463  void test_writeList_ofBool() {
464    void verifyListBooleans(int len, List<int> trueBits) {
465      // write
466      List<int> byteList;
467      {
468        Builder builder = Builder(initialSize: 0);
469        List<bool> values = List<bool>.filled(len, false);
470        for (int bit in trueBits) {
471          values[bit] = true;
472        }
473        int offset = builder.writeListBool(values);
474        builder.finish(offset);
475        byteList = builder.buffer;
476      }
477      // read and verify
478      BufferContext buf = BufferContext.fromBytes(byteList);
479      List<bool> items = const BoolListReader().read(buf, 0);
480      expect(items, hasLength(len));
481      for (int i = 0; i < items.length; i++) {
482        expect(items[i], trueBits.contains(i), reason: 'bit $i of $len');
483      }
484    }
485
486    verifyListBooleans(0, <int>[]);
487    verifyListBooleans(1, <int>[]);
488    verifyListBooleans(1, <int>[0]);
489    verifyListBooleans(31, <int>[0, 1]);
490    verifyListBooleans(31, <int>[1, 2, 24, 25, 30]);
491    verifyListBooleans(31, <int>[0, 30]);
492    verifyListBooleans(32, <int>[1, 2, 24, 25, 31]);
493    verifyListBooleans(33, <int>[1, 2, 24, 25, 32]);
494    verifyListBooleans(33, <int>[1, 2, 24, 25, 31, 32]);
495    verifyListBooleans(63, <int>[]);
496    verifyListBooleans(63, <int>[0, 1, 2, 61, 62]);
497    verifyListBooleans(63, List<int>.generate(63, (i) => i));
498    verifyListBooleans(64, <int>[]);
499    verifyListBooleans(64, <int>[0, 1, 2, 61, 62, 63]);
500    verifyListBooleans(64, <int>[1, 2, 62]);
501    verifyListBooleans(64, <int>[0, 1, 2, 63]);
502    verifyListBooleans(64, List<int>.generate(64, (i) => i));
503    verifyListBooleans(100, <int>[0, 3, 30, 60, 90, 99]);
504  }
505
506  void test_writeList_ofInt32() {
507    List<int> byteList;
508    {
509      Builder builder = Builder(initialSize: 0);
510      int offset = builder.writeListInt32(<int>[1, 2, 3, 4, 5]);
511      builder.finish(offset);
512      byteList = builder.buffer;
513    }
514    // read and verify
515    BufferContext buf = BufferContext.fromBytes(byteList);
516    List<int> items = const ListReader<int>(Int32Reader()).read(buf, 0);
517    expect(items, hasLength(5));
518    expect(items, orderedEquals(<int>[1, 2, 3, 4, 5]));
519  }
520
521  void test_writeList_ofFloat64() {
522    List<double> values = <double>[-1.234567, 3.4E+9, -5.6E-13, 7.8, 12.13];
523    // write
524    List<int> byteList;
525    {
526      Builder builder = Builder(initialSize: 0);
527      int offset = builder.writeListFloat64(values);
528      builder.finish(offset);
529      byteList = builder.buffer;
530    }
531
532    // read and verify
533    BufferContext buf = BufferContext.fromBytes(byteList);
534    List<double> items = const Float64ListReader().read(buf, 0);
535
536    expect(items, hasLength(values.length));
537    for (int i = 0; i < values.length; i++) {
538      expect(values[i], closeTo(items[i], .001));
539    }
540  }
541
542  void test_writeList_ofFloat32() {
543    List<double> values = [1.0, 2.23, -3.213, 7.8, 12.13];
544    // write
545    List<int> byteList;
546    {
547      Builder builder = Builder(initialSize: 0);
548      int offset = builder.writeListFloat32(values);
549      builder.finish(offset);
550      byteList = builder.buffer;
551    }
552    // read and verify
553    BufferContext buf = BufferContext.fromBytes(byteList);
554    List<double> items = const Float32ListReader().read(buf, 0);
555    expect(items, hasLength(5));
556    for (int i = 0; i < values.length; i++) {
557      expect(values[i], closeTo(items[i], .001));
558    }
559  }
560
561  void test_writeList_ofObjects([Builder? builder]) {
562    List<int> byteList;
563    {
564      builder ??= Builder(initialSize: 0);
565      // write the object #1
566      int object1;
567      {
568        builder.startTable(2);
569        builder.addInt32(0, 10);
570        builder.addInt32(1, 20);
571        object1 = builder.endTable();
572      }
573      // write the object #1
574      int object2;
575      {
576        builder.startTable(2);
577        builder.addInt32(0, 100);
578        builder.addInt32(1, 200);
579        object2 = builder.endTable();
580      }
581      // write the list
582      int offset = builder.writeList([object1, object2]);
583      builder.finish(offset);
584      byteList = builder.buffer;
585    }
586    // read and verify
587    BufferContext buf = BufferContext.fromBytes(byteList);
588    List<TestPointImpl> items =
589        const ListReader<TestPointImpl>(TestPointReader()).read(buf, 0);
590    expect(items, hasLength(2));
591    expect(items[0].x, 10);
592    expect(items[0].y, 20);
593    expect(items[1].x, 100);
594    expect(items[1].y, 200);
595  }
596
597  void test_writeList_ofStrings_asRoot() {
598    List<int> byteList;
599    {
600      Builder builder = Builder(initialSize: 0);
601      int? str1 = builder.writeString('12345');
602      int? str2 = builder.writeString('ABC');
603      int offset = builder.writeList([str1, str2]);
604      builder.finish(offset);
605      byteList = builder.buffer;
606    }
607    // read and verify
608    BufferContext buf = BufferContext.fromBytes(byteList);
609    List<String> items = const ListReader<String>(StringReader()).read(buf, 0);
610    expect(items, hasLength(2));
611    expect(items, contains('12345'));
612    expect(items, contains('ABC'));
613  }
614
615  void test_writeList_ofStrings_inObject([Builder? builder]) {
616    List<int> byteList;
617    {
618      builder ??= Builder(initialSize: 0);
619      int listOffset = builder.writeList(
620          [builder.writeString('12345'), builder.writeString('ABC')]);
621      builder.startTable(1);
622      builder.addOffset(0, listOffset);
623      int offset = builder.endTable();
624      builder.finish(offset);
625      byteList = builder.buffer;
626    }
627    // read and verify
628    BufferContext buf = BufferContext.fromBytes(byteList);
629    StringListWrapperImpl reader = StringListWrapperReader().read(buf, 0);
630    List<String>? items = reader.items;
631    expect(items, hasLength(2));
632    expect(items, contains('12345'));
633    expect(items, contains('ABC'));
634  }
635
636  void test_writeList_ofUint32() {
637    List<int> byteList;
638    {
639      Builder builder = Builder(initialSize: 0);
640      int offset = builder.writeListUint32(<int>[1, 2, 0x9ABCDEF0]);
641      builder.finish(offset);
642      byteList = builder.buffer;
643    }
644    // read and verify
645    BufferContext buf = BufferContext.fromBytes(byteList);
646    List<int> items = const Uint32ListReader().read(buf, 0);
647    expect(items, hasLength(3));
648    expect(items, orderedEquals(<int>[1, 2, 0x9ABCDEF0]));
649  }
650
651  void test_writeList_ofUint16() {
652    List<int> byteList;
653    {
654      Builder builder = Builder(initialSize: 0);
655      int offset = builder.writeListUint16(<int>[1, 2, 60000]);
656      builder.finish(offset);
657      byteList = builder.buffer;
658    }
659    // read and verify
660    BufferContext buf = BufferContext.fromBytes(byteList);
661    List<int> items = const Uint16ListReader().read(buf, 0);
662    expect(items, hasLength(3));
663    expect(items, orderedEquals(<int>[1, 2, 60000]));
664  }
665
666  void test_writeList_ofUint8() {
667    List<int> byteList;
668    {
669      Builder builder = Builder(initialSize: 0);
670      int offset = builder.writeListUint8(<int>[1, 2, 3, 4, 0x9A, 0xFA]);
671      builder.finish(offset);
672      byteList = builder.buffer;
673    }
674    // read and verify
675    BufferContext buf = BufferContext.fromBytes(byteList);
676    const buffOffset = 8; // 32-bit offset to the list, + 32-bit length
677    for (final lazy in [true, false]) {
678      List<int> items = Uint8ListReader(lazy: lazy).read(buf, 0);
679      expect(items, hasLength(6));
680      expect(items, orderedEquals(<int>[1, 2, 3, 4, 0x9A, 0xFA]));
681
682      // overwrite the buffer to verify the laziness
683      buf.buffer.setUint8(buffOffset + 1, 99);
684      expect(items, orderedEquals(<int>[1, lazy ? 99 : 2, 3, 4, 0x9A, 0xFA]));
685
686      // restore the previous value for the next loop
687      buf.buffer.setUint8(buffOffset + 1, 2);
688    }
689  }
690
691  void test_reset() {
692    // We'll run a selection of tests , reusing the builder between them.
693    final testCases = <void Function(Builder?)>[
694      test_monsterBuilder,
695      test_error_addInt32_withoutStartTable,
696      test_table_format,
697      test_table_types,
698      test_writeList_ofObjects,
699      test_writeList_ofStrings_inObject
700    ];
701
702    // Execute all test cases in all permutations of their order.
703    // To do that, we generate permutations of test case indexes.
704    final testCasesPermutations =
705        _permutationsOf(List.generate(testCases.length, (index) => index));
706    expect(testCasesPermutations.length, _factorial(testCases.length));
707
708    for (var indexes in testCasesPermutations) {
709      // print the order so failures are reproducible
710      printOnFailure('Running reset() test cases in order: $indexes');
711
712      Builder? builder;
713      for (var index in indexes) {
714        if (builder == null) {
715          // Initial size small enough so at least one test case increases it.
716          // On the other hand, it's large enough so that some test cases don't.
717          builder = Builder(initialSize: 32);
718        } else {
719          builder.reset();
720        }
721        testCases[index](builder);
722      }
723    }
724  }
725
726  // Generate permutations of the given list
727  List<List<T>> _permutationsOf<T>(List<T> source) {
728    final result = <List<T>>[];
729
730    void permutate(List<T> items, int startAt) {
731      for (var i = startAt; i < items.length; i++) {
732        List<T> permutation = items.toList(growable: false);
733        permutation[i] = items[startAt];
734        permutation[startAt] = items[i];
735
736        // add the current list upon reaching the end
737        if (startAt == items.length - 1) {
738          result.add(items);
739        } else {
740          permutate(permutation, startAt + 1);
741        }
742      }
743    }
744
745    permutate(source, 0);
746    return result;
747  }
748
749  // a very simple implementation of n!
750  int _factorial(int n) {
751    var result = 1;
752    for (var i = 2; i <= n; i++) {
753      result *= i;
754    }
755    return result;
756  }
757}
758
759@reflectiveTest
760class ObjectAPITest {
761  void test_tableStat() {
762    final object1 = example.StatT(count: 3, id: "foo", val: 4);
763    expect(object1 is Packable, isTrue);
764    final fbb = Builder();
765    fbb.finish(object1.pack(fbb));
766    final object2 = example.Stat(fbb.buffer).unpack();
767    expect(object2.count, object1.count);
768    expect(object2.id, object1.id);
769    expect(object2.val, object1.val);
770    expect(object2.toString(), object1.toString());
771  }
772
773  void test_tableMonster() {
774    final monster = example.MonsterT()
775      ..pos = example.Vec3T(
776          x: 1,
777          y: 2,
778          z: 3,
779          test1: 4.0,
780          test2: example.Color.Red,
781          test3: example.TestT(a: 1, b: 2))
782      ..mana = 2
783      ..name = 'Monstrous'
784      ..inventory = [24, 42]
785      ..color = example.Color.Green
786      // TODO be smarter for unions and automatically set the `type` field?
787      ..testType = example.AnyTypeId.MyGame_Example2_Monster
788      ..test = example2.MonsterT()
789      ..test4 = [example.TestT(a: 3, b: 4), example.TestT(a: 5, b: 6)]
790      ..testarrayofstring = ["foo", "bar"]
791      ..testarrayoftables = [example.MonsterT(name: 'Oof')]
792      ..enemy = example.MonsterT(name: 'Enemy')
793      ..testarrayofbools = [false, true, false]
794      ..testf = 42.24
795      ..testarrayofsortedstruct = [
796        example.AbilityT(id: 1, distance: 5),
797        example.AbilityT(id: 3, distance: 7)
798      ]
799      ..vectorOfLongs = [5, 6, 7]
800      ..vectorOfDoubles = [8.9, 9.0, 10.1, 11.2]
801      ..anyAmbiguousType = example.AnyAmbiguousAliasesTypeId.M2
802      ..anyAmbiguous = null
803      ..vectorOfEnums = [example.Color.Blue, example.Color.Green]
804      ..signedEnum = example.Race.None;
805
806    final fbBuilder = Builder();
807    final offset = monster.pack(fbBuilder);
808    expect(offset, isNonZero);
809    fbBuilder.finish(offset);
810    final data = fbBuilder.buffer;
811
812    // TODO currently broken because of struct builder issue, see #6688
813    // final monster2 = example.Monster(data); // Monster (reader)
814    // expect(
815    //     // map Monster => MonsterT, Vec3 => Vec3T, ...
816    //     monster2.toString().replaceAllMapped(
817    //         RegExp('([a-zA-z0-9]+){'), (match) => match.group(1) + 'T{'),
818    //     monster.toString());
819    //
820    // final monster3 = monster2.unpack(); // MonsterT
821    // expect(monster3.toString(), monster.toString());
822  }
823
824  void test_Lists() {
825    // Ensure unpack() reads lists eagerly by reusing the same builder and
826    // overwriting data. Why: because standard reader reads lists lazily...
827    final fbb = Builder();
828
829    final object1 = example.TypeAliasesT(v8: [1, 2, 3], vf64: [5, 6]);
830    fbb.finish(object1.pack(fbb));
831    final object1Read = example.TypeAliases(fbb.buffer).unpack();
832
833    // overwrite the original buffer by writing to the same builder
834    fbb.reset();
835    final object2 = example.TypeAliasesT(v8: [7, 8, 9], vf64: [10, 11]);
836    fbb.finish(object2.pack(fbb));
837    final object2Read = example.TypeAliases(fbb.buffer).unpack();
838
839    // this is fine even with lazy lists:
840    expect(object2.toString(), object2Read.toString());
841
842    // this fails with lazy lists:
843    expect(object1.toString(), object1Read.toString());
844
845    // empty list must be serialized as such (were stored NULL before v2.0)
846    fbb.reset();
847    final object3 = example.TypeAliasesT(v8: [], vf64: null);
848    fbb.finish(object3.pack(fbb));
849    final object3Read = example.TypeAliases(fbb.buffer).unpack();
850    expect(object3.toString(), object3Read.toString());
851  }
852}
853
854class StringListWrapperImpl {
855  final BufferContext bp;
856  final int offset;
857
858  StringListWrapperImpl(this.bp, this.offset);
859
860  List<String>? get items => const ListReader<String>(StringReader())
861      .vTableGetNullable(bp, offset, indexToField(0));
862}
863
864class StringListWrapperReader extends TableReader<StringListWrapperImpl> {
865  const StringListWrapperReader();
866
867  @override
868  StringListWrapperImpl createObject(BufferContext object, int offset) {
869    return StringListWrapperImpl(object, offset);
870  }
871}
872
873class TestPointImpl {
874  final BufferContext bp;
875  final int offset;
876
877  TestPointImpl(this.bp, this.offset);
878
879  int get x => const Int32Reader().vTableGet(bp, offset, indexToField(0), 0);
880
881  int get y => const Int32Reader().vTableGet(bp, offset, indexToField(1), 0);
882}
883
884class TestPointReader extends TableReader<TestPointImpl> {
885  const TestPointReader();
886
887  @override
888  TestPointImpl createObject(BufferContext object, int offset) {
889    return TestPointImpl(object, offset);
890  }
891}
892
893@reflectiveTest
894class GeneratorTest {
895  void test_constantEnumValues() async {
896    expect(example.Color.values, same(example.Color.values));
897    expect(example.Race.values, same(example.Race.values));
898    expect(example.AnyTypeId.values, same(example.AnyTypeId.values));
899    expect(example.AnyUniqueAliasesTypeId.values,
900        same(example.AnyUniqueAliasesTypeId.values));
901    expect(example.AnyAmbiguousAliasesTypeId.values,
902        same(example.AnyAmbiguousAliasesTypeId.values));
903  }
904}
905
906// See #6869
907@reflectiveTest
908class ListOfEnumsTest {
909  void test_listOfEnums() async {
910    var mytable = example3.MyTableObjectBuilder(options: [
911      example3.OptionsEnum.A,
912      example3.OptionsEnum.B,
913      example3.OptionsEnum.C
914    ]);
915    var bytes = mytable.toBytes();
916    var mytable_read = example3.MyTable(bytes);
917    expect(mytable_read.options![0].value, example3.OptionsEnum.A.value);
918    expect(mytable_read.options![1].value, example3.OptionsEnum.B.value);
919    expect(mytable_read.options![2].value, example3.OptionsEnum.C.value);
920  }
921}
922
923@reflectiveTest
924class BoolInStructTest {
925  void test_boolInStruct() async {
926    var mystruct = example4.FooObjectBuilder(
927        myFoo: example4.FooPropertiesObjectBuilder(a: true, b: false));
928    var bytes = mystruct.toBytes();
929    var mystruct_read = example4.Foo(bytes);
930    expect(mystruct_read.myFoo!.a, true);
931    expect(mystruct_read.myFoo!.b, false);
932  }
933}
934