import 'dart:typed_data'; import 'package:pure_zstd/pure_zstd.dart'; import 'package:test/test.dart'; import 'corpus.dart'; /// The committed corpus, which exists because `hgtz_block_test.dart` decodes /// one container from one producer at one level and so proves the sequence /// loop and Huffman literals on level-19 terrain and nothing else. /// /// The frames here reach the parts of RFC 8878 that 16,490 real `.hgtz` /// blocks never touch: raw blocks, RLE blocks, RLE literals, a frame with no /// content size, a frame with a checksum, the eight-byte content size, and /// the direct-nibble Huffman weight form. /// /// Nothing below asserts what this decoder happens to do. A legal frame is /// compared against what pyzstd decompressed it to; a refused frame is one /// pyzstd also refuses. `tool/make_corpus.py` records both, along with the /// exact command behind each frame, in `test/fixtures/corpus/manifest.json`. void main() { final corpus = loadCorpus(); group('a legal frame decodes to the bytes pyzstd gives', () { for (final entry in corpus.where( (entry) => entry.legal && entry.trailing == null, )) { test('${entry.name}: ${entry.command}', () { final out = ZstdDecoder().decode( entry.frame, expectedSize: entry.expectedSize, ); expect(out.length, entry.size); expect(out, orderedEquals(entry.plain)); }); } }); group('a malformed frame is refused, never crashed on', () { for (final entry in corpus.where((entry) => !entry.legal)) { // A `ZstdException` and nothing else. `RangeError`, `OutOfMemoryError` // and `IndexError` are all `Error` rather than `Exception`, so a caller // catching `ZstdException` does not catch them and the isolate dies. test('${entry.name}: ${entry.why}', () { expect( () => ZstdDecoder().decode(entry.frame), throwsA(isA()), ); }); } }); group('two frames in one buffer', () { final entry = corpus.singleWhere((entry) => entry.name == 'twoframes'); final first = 5000; test('${entry.command}: decodes to both or refuses, never to the first ' 'alone', () { Uint8List? out; try { out = ZstdDecoder().decode(entry.frame); } on ZstdException { return; // Refusing is the other acceptable answer. } expect( out.length, isNot(first), reason: 'returned the first frame and stopped, raising nothing', ); expect(out, orderedEquals(entry.plain)); }); }); group('a frame whose declared size is not the one asked for', () { // A guard callers rest on, and it had no test: // deleting the whole check left every other case green. A caller that // knows the size, as the .hgtz reader does, gets an exact comparison // against the frame's own figure rather than only the ceiling. Left to // the end, a frame declaring less decoded its own content happily and a // frame declaring more allocated whatever it asked for first. for (final entry in corpus.where( // Not a buffer holding more than one frame: this decoder refuses // those whatever size it is told to expect. (entry) => entry.legal && entry.expectedSize == null && entry.trailing == null && entry.size! > 1, )) { test('${entry.name} is refused when asked for one byte more', () { expect( () => ZstdDecoder().decode(entry.frame, expectedSize: entry.size! + 1), throwsA(isA()), ); }); test('${entry.name} still decodes when asked for the size it has', () { expect( ZstdDecoder().decode(entry.frame, expectedSize: entry.size), orderedEquals(entry.plain), ); }); } }); group('a frame declaring no content size', () { final entry = corpus.singleWhere((entry) => entry.name == 'nofcs_windowed'); final size = entry.expectedSize!; test('decodes when given the size it really has', () { final out = ZstdDecoder().decode(entry.frame, expectedSize: size); expect(out, orderedEquals(entry.plain)); }); for (final wrong in [size - 1, size + 1, 0]) { test('is refused when given $wrong instead of $size', () { expect( () => ZstdDecoder().decode(entry.frame, expectedSize: wrong), throwsA(isA()), ); }); } // Streaming encoders write frames like this one, and their callers often // can't know the size: an Anki package's collection, for one. test('decodes without being told its size, to exactly what it holds', () { final out = ZstdDecoder().decode(entry.frame); expect(out.length, size); expect(out, orderedEquals(entry.plain)); }); test('is refused, not crashed on, when cut short', () { for (final cut in [1, 3, entry.frame.length ~/ 2]) { expect( () => ZstdDecoder().decode( Uint8List.sublistView(entry.frame, 0, entry.frame.length - cut), ), throwsA(isA()), ); } }); test('is refused when its blocks could hold more than allowed', () { expect( () => ZstdDecoder(maxOutputSize: size).decode(entry.frame), throwsA(isA()), ); }); }); }