From 5d2189d6294b5f8a17b23a40ac6abe69e8d0fead Mon Sep 17 00:00:00 2001 From: Marcus Holland-Moritz Date: Thu, 11 Jan 2024 19:08:17 +0100 Subject: [PATCH] test(pcmaudio): add format error tests --- test/pcmaudio_categorizer_test.cpp | 898 +++++++++++++++++++++++++++++ 1 file changed, 898 insertions(+) diff --git a/test/pcmaudio_categorizer_test.cpp b/test/pcmaudio_categorizer_test.cpp index cf24ade1..49609932 100644 --- a/test/pcmaudio_categorizer_test.cpp +++ b/test/pcmaudio_categorizer_test.cpp @@ -27,7 +27,9 @@ #include +#include #include +#include #include "dwarfs/categorizer.h" #include "dwarfs/mmap.h" @@ -39,8 +41,174 @@ using testing::MatchesRegex; namespace fs = std::filesystem; +namespace { + auto test_dir = fs::path(TEST_DATA_DIR); +FOLLY_PACK_PUSH + +struct aiff_file_hdr_t { + uint8_t id[4]; + uint32_t size; + uint8_t form[4]; +} FOLLY_PACK_ATTR; + +aiff_file_hdr_t as_big_endian(aiff_file_hdr_t const& hdr) { + aiff_file_hdr_t result{hdr}; + result.size = folly::Endian::big(result.size); + return result; +} + +struct aiff_chunk_hdr_t { + uint8_t id[4]; + uint32_t size; +} FOLLY_PACK_ATTR; + +aiff_chunk_hdr_t as_big_endian(aiff_chunk_hdr_t const& hdr) { + aiff_chunk_hdr_t result{hdr}; + result.size = folly::Endian::big(result.size); + return result; +} + +struct aiff_comm_chk_t { + uint16_t num_chan; + uint32_t num_sample_frames; + uint16_t sample_size; + uint8_t sample_rate[10]; // long double, but we can't pack that +} FOLLY_PACK_ATTR; + +aiff_comm_chk_t as_big_endian(aiff_comm_chk_t const& chk) { + aiff_comm_chk_t result{chk}; + result.num_chan = folly::Endian::big(result.num_chan); + result.num_sample_frames = folly::Endian::big(result.num_sample_frames); + result.sample_size = folly::Endian::big(result.sample_size); + return result; +} + +struct aiff_ssnd_chk_t { + uint32_t offset; + uint32_t block_size; +} FOLLY_PACK_ATTR; + +aiff_ssnd_chk_t as_big_endian(aiff_ssnd_chk_t const& chk) { + aiff_ssnd_chk_t result{chk}; + result.offset = folly::Endian::big(result.offset); + result.block_size = folly::Endian::big(result.block_size); + return result; +} + +struct caff_file_hdr_t { + uint8_t id[4]; + uint16_t version; + uint16_t flags; +} FOLLY_PACK_ATTR; + +caff_file_hdr_t as_big_endian(caff_file_hdr_t const& hdr) { + caff_file_hdr_t result{hdr}; + result.version = folly::Endian::big(result.version); + result.flags = folly::Endian::big(result.flags); + return result; +} + +struct caff_chunk_hdr_t { + uint8_t id[4]; + uint64_t size; +} FOLLY_PACK_ATTR; + +caff_chunk_hdr_t as_big_endian(caff_chunk_hdr_t const& hdr) { + caff_chunk_hdr_t result{hdr}; + result.size = folly::Endian::big(result.size); + return result; +} + +struct caff_format_chk_t { + double sample_rate; + uint8_t format_id[4]; + uint32_t format_flags; + uint32_t bytes_per_packet; + uint32_t frames_per_packet; + uint32_t channels_per_frame; + uint32_t bits_per_channel; +} FOLLY_PACK_ATTR; + +caff_format_chk_t as_big_endian(caff_format_chk_t const& chk) { + caff_format_chk_t result{chk}; + result.format_flags = folly::Endian::big(result.format_flags); + result.bytes_per_packet = folly::Endian::big(result.bytes_per_packet); + result.frames_per_packet = folly::Endian::big(result.frames_per_packet); + result.channels_per_frame = folly::Endian::big(result.channels_per_frame); + result.bits_per_channel = folly::Endian::big(result.bits_per_channel); + return result; +} + +struct caff_data_chk_t { + uint32_t edit_count; +} FOLLY_PACK_ATTR; + +caff_data_chk_t as_big_endian(caff_data_chk_t const& chk) { + caff_data_chk_t result{chk}; + result.edit_count = folly::Endian::big(result.edit_count); + return result; +} + +struct wav_file_hdr_t { + uint8_t id[4]; + uint32_t size; + uint8_t form[4]; +} FOLLY_PACK_ATTR; + +struct wav_chunk_hdr_t { + uint8_t id[4]; + uint32_t size; +} FOLLY_PACK_ATTR; + +struct wav64_file_hdr_t { + uint8_t id[16]; + uint64_t size; + uint8_t form[16]; +} FOLLY_PACK_ATTR; + +struct wav64_chunk_hdr_t { + uint8_t id[16]; + uint64_t size; +} FOLLY_PACK_ATTR; + +struct wav_fmt_chunk_t { + uint16_t format_code; + uint16_t num_channels; + uint32_t samples_per_sec; + uint32_t avg_bytes_per_sec; + uint16_t block_align; + uint16_t bits_per_sample; + uint16_t ext_size{0}; + uint16_t valid_bits_per_sample{0}; + uint32_t channel_mask{0}; + uint16_t sub_format_code{0}; + uint8_t guid_remainder[14]{0}; +} FOLLY_PACK_ATTR; + +FOLLY_PACK_POP + +struct pcmfile_builder { + template + void add(T const& t, size_t size = sizeof(T)) { + auto const* p = reinterpret_cast(&t); + data.insert(data.end(), p, p + size); + } + + void add_bytes(size_t count, uint8_t value) { + data.insert(data.end(), count, value); + } + + std::span span() const { return data; } + + size_t size() const { return data.size(); } + + std::vector data; +}; + +} // namespace + TEST(pcmaudio_categorizer, requirements) { test::test_logger logger(logger::INFO); boost::program_options::variables_map vm; @@ -120,3 +288,733 @@ TEST(pcmaudio_categorizer, requirements) { EXPECT_EQ(mm.size(), first.size() + second.size()); } } + +class pcmaudio_error_test : public testing::Test { + public: + test::test_logger logger{logger::VERBOSE}; + categorizer_manager catmgr{logger}; + + auto categorize(pcmfile_builder const& builder) { + // std::cout << folly::hexDump(builder.data.data(), builder.data.size()); + auto job = catmgr.job(filename()); + job.set_total_size(builder.size()); + job.categorize_random_access(builder.span()); + return job.result(); + } + + void SetUp() override { + boost::program_options::variables_map vm; + auto& catreg = categorizer_registry::instance(); + catmgr.add(catreg.create(logger, "pcmaudio", vm)); + + catmgr.set_metadata_requirements( + catmgr.category_value("pcmaudio/waveform").value(), + R"({"endianness": ["set", ["big", "little"]], "bytes_per_sample": ["range", 1, 4]})"); + } + + virtual std::string_view filename() const = 0; +}; + +class pcmaudio_error_test_aiff : public pcmaudio_error_test { + public: + aiff_file_hdr_t aiff_file_hdr{{'F', 'O', 'R', 'M'}, 62, {'A', 'I', 'F', 'F'}}; + aiff_chunk_hdr_t aiff_comm_chunk_hdr{{'C', 'O', 'M', 'M'}, 18}; + aiff_comm_chk_t aiff_comm_chunk{1, 8, 16, {0}}; + aiff_chunk_hdr_t aiff_ssnd_chunk_hdr{{'S', 'S', 'N', 'D'}, 24}; + aiff_ssnd_chk_t aiff_ssnd_chunk{0, 0}; + + pcmfile_builder build_file() { + pcmfile_builder builder; + builder.add(as_big_endian(aiff_file_hdr)); + builder.add(as_big_endian(aiff_comm_chunk_hdr)); + builder.add(as_big_endian(aiff_comm_chunk)); + builder.add(as_big_endian(aiff_ssnd_chunk_hdr)); + builder.add(as_big_endian(aiff_ssnd_chunk)); + builder.add_bytes(16, 42); + return builder; + } + + std::string_view filename() const override { return "test.aiff"; } +}; + +class pcmaudio_error_test_caf : public pcmaudio_error_test { + public: + caff_file_hdr_t caff_file_hdr{{'c', 'a', 'f', 'f'}, 1, 0}; + caff_chunk_hdr_t caff_format_chunk_hdr{{'d', 'e', 's', 'c'}, 32}; + caff_format_chk_t caff_format_chunk{44100, {'l', 'p', 'c', 'm'}, 0, 2, 1, 1, + 16}; + caff_chunk_hdr_t caff_data_chunk_hdr{{'d', 'a', 't', 'a'}, 20}; + caff_data_chk_t caff_data_chunk{0}; + + pcmfile_builder build_file() { + pcmfile_builder builder; + builder.add(as_big_endian(caff_file_hdr)); + builder.add(as_big_endian(caff_format_chunk_hdr)); + builder.add(as_big_endian(caff_format_chunk)); + builder.add(as_big_endian(caff_data_chunk_hdr)); + builder.add(as_big_endian(caff_data_chunk)); + builder.add_bytes(16, 42); + return builder; + } + + std::string_view filename() const override { return "test.caf"; } +}; + +class pcmaudio_error_test_wav : public pcmaudio_error_test { + public: + wav_file_hdr_t wav_file_hdr{{'R', 'I', 'F', 'F'}, 52, {'W', 'A', 'V', 'E'}}; + wav_chunk_hdr_t wav_fmt_chunk_hdr{{'f', 'm', 't', ' '}, 16}; + wav_fmt_chunk_t wav_fmt_chunk{.format_code = 1, + .num_channels = 1, + .samples_per_sec = 44100, + .avg_bytes_per_sec = 44100 * 2, + .block_align = 2, + .bits_per_sample = 16}; + wav_chunk_hdr_t wav_data_chunk_hdr{{'d', 'a', 't', 'a'}, 16}; + + pcmfile_builder build_file() { + pcmfile_builder builder; + builder.add(wav_file_hdr); + builder.add(wav_fmt_chunk_hdr); + builder.add(wav_fmt_chunk, 16); + builder.add(wav_data_chunk_hdr); + builder.add_bytes(16, 42); + return builder; + } + + std::string_view filename() const override { return "test.wav"; } +}; + +class pcmaudio_error_test_wav64 : public pcmaudio_error_test { + public: + wav64_file_hdr_t wav_file_hdr{ + {'r', 'i', 'f', 'f', 0x2e, 0x91, 0xcf, 0x11, 0xa5, 0xd6, 0x28, 0xdb, 0x04, + 0xc1, 0x00, 0x00}, + 120, + {'w', 'a', 'v', 'e', 0xf3, 0xac, 0xd3, 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, + 0x8e, 0xdb, 0x8a}}; + wav64_chunk_hdr_t wav_fmt_chunk_hdr{{'f', 'm', 't', ' ', 0xf3, 0xac, 0xd3, + 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, 0x8e, + 0xdb, 0x8a}, + 40}; + wav_fmt_chunk_t wav_fmt_chunk{.format_code = 1, + .num_channels = 1, + .samples_per_sec = 44100, + .avg_bytes_per_sec = 44100 * 2, + .block_align = 2, + .bits_per_sample = 16}; + wav64_chunk_hdr_t wav_data_chunk_hdr{{'d', 'a', 't', 'a', 0xf3, 0xac, 0xd3, + 0x11, 0x8c, 0xd1, 0x00, 0xc0, 0x4f, + 0x8e, 0xdb, 0x8a}, + 40}; + + pcmfile_builder build_file() { + pcmfile_builder builder; + builder.add(wav_file_hdr); + builder.add(wav_fmt_chunk_hdr); + builder.add(wav_fmt_chunk, 16); + builder.add(wav_data_chunk_hdr); + builder.add_bytes(16, 42); + return builder; + } + + std::string_view filename() const override { return "test.w64"; } +}; + +TEST_F(pcmaudio_error_test_wav, no_error) { + auto builder = build_file(); + auto frag = categorize(builder); + + EXPECT_TRUE(logger.empty()); + + ASSERT_EQ(2, frag.size()); + + auto const& first = frag.span()[0]; + auto const& second = frag.span()[1]; + EXPECT_EQ("pcmaudio/metadata", + catmgr.category_name(first.category().value())); + EXPECT_EQ(44, first.size()); + EXPECT_EQ("pcmaudio/waveform", + catmgr.category_name(second.category().value())); + EXPECT_EQ(16, second.size()); + EXPECT_EQ(builder.size(), first.size() + second.size()); +} + +TEST_F(pcmaudio_error_test_wav, missing_fmt_chunk) { + wav_file_hdr.size -= 24; + + pcmfile_builder builder; + builder.add(wav_file_hdr); + builder.add(wav_data_chunk_hdr); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[WAV] \"test.wav\": got `data` chunk without `fmt ` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, unknown_format_code) { + wav_file_hdr.form[0] = 'F'; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + // not a WAVE file, so we don't expect any warnings + EXPECT_TRUE(log.empty()); + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, unexpected_fmt_chunk_size) { + wav_file_hdr.size += 4; + wav_fmt_chunk_hdr.size += 4; + + pcmfile_builder builder; + builder.add(wav_file_hdr); + builder.add(wav_fmt_chunk_hdr); + builder.add(wav_fmt_chunk, 20); + builder.add(wav_data_chunk_hdr); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[WAV] \"test.wav\": unexpected size for `fmt " + "` chunk: 20 (expected 16, 18, 40)")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, unexpected_second_fmt_chunk) { + wav_file_hdr.size += 24; + + pcmfile_builder builder; + builder.add(wav_file_hdr); + builder.add(wav_fmt_chunk_hdr); + builder.add(wav_fmt_chunk, 16); + builder.add(wav_fmt_chunk_hdr); + builder.add(wav_fmt_chunk, 16); + builder.add(wav_data_chunk_hdr); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[WAV] \"test.wav\": unexpected second `fmt ` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, unsupported_format_code) { + wav_fmt_chunk.format_code = 2; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[WAV] \"test.wav\": unsupported format: 2/0")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, metadata_check_failed) { + wav_fmt_chunk.bits_per_sample = 13; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[WAV] \"test.wav\": metadata check failed")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav, chunk_size_mismatch) { + wav_fmt_chunk.bits_per_sample = 24; + wav_fmt_chunk.avg_bytes_per_sec = 44100 * 3; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[WAV] \"test.wav\": `data` chunk size mismatch")); + + ASSERT_EQ(3, frag.size()); + + auto f1 = frag.span()[0]; + auto f2 = frag.span()[1]; + auto f3 = frag.span()[2]; + + EXPECT_EQ("pcmaudio/metadata", catmgr.category_name(f1.category().value())); + EXPECT_EQ("pcmaudio/waveform", catmgr.category_name(f2.category().value())); + EXPECT_EQ(15, f2.size()); + EXPECT_EQ("pcmaudio/metadata", catmgr.category_name(f3.category().value())); + EXPECT_EQ(1, f3.size()); +} + +TEST_F(pcmaudio_error_test_wav, unexpected_file_size) { + wav_file_hdr.size -= 4; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr( + "[WAV] \"test.wav\": unexpected file size: 48 (expected 52)")); + + ASSERT_EQ(2, frag.size()); + + auto f1 = frag.span()[0]; + auto f2 = frag.span()[1]; + + EXPECT_EQ("pcmaudio/metadata", catmgr.category_name(f1.category().value())); + EXPECT_EQ(44, f1.size()); + EXPECT_EQ("pcmaudio/waveform", catmgr.category_name(f2.category().value())); + EXPECT_EQ(16, f2.size()); +} + +TEST_F(pcmaudio_error_test_wav64, no_error) { + auto builder = build_file(); + auto frag = categorize(builder); + + EXPECT_TRUE(logger.empty()); + + ASSERT_EQ(2, frag.size()); + + auto const& first = frag.span()[0]; + auto const& second = frag.span()[1]; + EXPECT_EQ("pcmaudio/metadata", + catmgr.category_name(first.category().value())); + EXPECT_EQ(104, first.size()); + EXPECT_EQ("pcmaudio/waveform", + catmgr.category_name(second.category().value())); + EXPECT_EQ(16, second.size()); + EXPECT_EQ(builder.size(), first.size() + second.size()); +} + +TEST_F(pcmaudio_error_test_wav64, truncated_file) { + auto builder = build_file(); + builder.data.resize(builder.data.size() - 41); + + auto frag = categorize(builder); + + ASSERT_EQ(2, logger.get_log().size()); + + EXPECT_THAT( + logger.get_log()[0].output, + testing::HasSubstr("[WAV64] \"test.w64\": unexpected file size: 120 " + "(expected 79)")); + + EXPECT_THAT( + logger.get_log()[1].output, + testing::HasSubstr("[WAV64] \"test.w64\": unexpected end of file")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_wav64, invalid_chunk_size) { + wav_fmt_chunk_hdr.size = 8; + + auto builder = build_file(); + auto frag = categorize(builder); + + ASSERT_EQ(1, logger.get_log().size()); + + EXPECT_THAT( + logger.get_log()[0].output, + testing::HasSubstr("[WAV64] \"test.w64\": invalid chunk size: 8")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_aiff, no_error) { + auto builder = build_file(); + auto frag = categorize(builder); + + EXPECT_TRUE(logger.empty()); + + ASSERT_EQ(2, frag.size()); + + auto const& first = frag.span()[0]; + auto const& second = frag.span()[1]; + EXPECT_EQ("pcmaudio/metadata", + catmgr.category_name(first.category().value())); + EXPECT_EQ(54, first.size()); + EXPECT_EQ("pcmaudio/waveform", + catmgr.category_name(second.category().value())); + EXPECT_EQ(16, second.size()); + EXPECT_EQ(builder.size(), first.size() + second.size()); +} + +TEST_F(pcmaudio_error_test_aiff, unexpected_second_comm_chunk) { + aiff_file_hdr.size += 26; + + pcmfile_builder builder; + builder.add(as_big_endian(aiff_file_hdr)); + builder.add(as_big_endian(aiff_comm_chunk_hdr)); + builder.add(as_big_endian(aiff_comm_chunk)); + builder.add(as_big_endian(aiff_comm_chunk_hdr)); + builder.add(as_big_endian(aiff_comm_chunk)); + builder.add(as_big_endian(aiff_ssnd_chunk_hdr)); + builder.add(as_big_endian(aiff_ssnd_chunk)); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[AIFF] \"test.aiff\": unexpected second `COMM` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_aiff, missing_comm_chunk) { + aiff_file_hdr.size -= 26; + + pcmfile_builder builder; + builder.add(as_big_endian(aiff_file_hdr)); + builder.add(as_big_endian(aiff_ssnd_chunk_hdr)); + builder.add(as_big_endian(aiff_ssnd_chunk)); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr( + "[AIFF] \"test.aiff\": got `SSND` chunk without `COMM` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_aiff, ssnd_invalid_chunk_size) { + aiff_ssnd_chunk_hdr.size -= 1; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[AIFF] \"test.aiff\": `SSND` invalid chunk size: 23")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, no_error) { + auto builder = build_file(); + auto frag = categorize(builder); + + EXPECT_TRUE(logger.empty()); + + ASSERT_EQ(2, frag.size()); + + auto const& first = frag.span()[0]; + auto const& second = frag.span()[1]; + EXPECT_EQ("pcmaudio/metadata", + catmgr.category_name(first.category().value())); + EXPECT_EQ(68, first.size()); + EXPECT_EQ("pcmaudio/waveform", + catmgr.category_name(second.category().value())); + EXPECT_EQ(16, second.size()); + EXPECT_EQ(builder.size(), first.size() + second.size()); +} + +TEST_F(pcmaudio_error_test_caf, no_error_unkown_data_size) { + caff_data_chunk_hdr.size = -1; + + auto builder = build_file(); + auto frag = categorize(builder); + + EXPECT_TRUE(logger.empty()); + + ASSERT_EQ(2, frag.size()); + + auto const& first = frag.span()[0]; + auto const& second = frag.span()[1]; + EXPECT_EQ("pcmaudio/metadata", + catmgr.category_name(first.category().value())); + EXPECT_EQ(68, first.size()); + EXPECT_EQ("pcmaudio/waveform", + catmgr.category_name(second.category().value())); + EXPECT_EQ(16, second.size()); + EXPECT_EQ(builder.size(), first.size() + second.size()); +} + +TEST_F(pcmaudio_error_test_caf, unsupported_version_or_flags) { + caff_file_hdr.version = 2; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": unsupported file version/flags: 2/0")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unexpected_second_desc_chunk) { + pcmfile_builder builder; + builder.add(as_big_endian(caff_file_hdr)); + builder.add(as_big_endian(caff_format_chunk_hdr)); + builder.add(as_big_endian(caff_format_chunk)); + builder.add(as_big_endian(caff_format_chunk_hdr)); + builder.add(as_big_endian(caff_format_chunk)); + builder.add(as_big_endian(caff_data_chunk_hdr)); + builder.add(as_big_endian(caff_data_chunk)); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": unexpected second `desc` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, missing_desc_chunk) { + pcmfile_builder builder; + builder.add(as_big_endian(caff_file_hdr)); + builder.add(as_big_endian(caff_data_chunk_hdr)); + builder.add(as_big_endian(caff_data_chunk)); + builder.add_bytes(16, 42); + + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": got `data` chunk without `desc` chunk")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unexpected_desc_chunk_size) { + caff_format_chunk_hdr.size += 1; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": unexpected size for `desc` " + "chunk: 33 (expected 32)")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unsupported_format) { + caff_format_chunk.format_id[0] = 'y'; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": unsupported `ypcm` format")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unsupported_floating_point) { + caff_format_chunk.format_flags = 1; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": floating point format not supported")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unsupported_frames_per_packet) { + caff_format_chunk.frames_per_packet = 2; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": unsupported frames per packet: 2")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, bytes_per_packet_zero) { + caff_format_chunk.bytes_per_packet = 0; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": bytes per packet must not be zero")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, bytes_per_packet_out_of_range) { + caff_format_chunk.bytes_per_packet = 5; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": bytes per packet out of range: 5")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, unsupported_packet_size) { + caff_format_chunk.channels_per_frame = 4; + caff_format_chunk.bytes_per_packet = 10; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT( + log.front().output, + testing::HasSubstr( + "[CAF] \"test.caf\": unsupported packet size: 10 (4 channels)")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, metadata_check_failed1) { + caff_format_chunk.channels_per_frame = 1; + caff_format_chunk.channels_per_frame = 4; + caff_format_chunk.bytes_per_packet = 4; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": metadata check failed")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, metadata_check_failed2) { + caff_format_chunk.channels_per_frame = 2; + caff_format_chunk.bits_per_channel = 8; + caff_format_chunk.bytes_per_packet = 4; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": metadata check failed")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, metadata_check_failed3) { + caff_format_chunk.channels_per_frame = 1; + caff_format_chunk.bits_per_channel = 24; + caff_format_chunk.bytes_per_packet = 2; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": metadata check failed")); + + EXPECT_EQ(0, frag.size()); +} + +TEST_F(pcmaudio_error_test_caf, metadata_check_failed4) { + caff_format_chunk.channels_per_frame = 1; + caff_format_chunk.bits_per_channel = 32; + caff_format_chunk.bytes_per_packet = 3; + + auto builder = build_file(); + auto frag = categorize(builder); + auto const& log = logger.get_log(); + + ASSERT_EQ(1, log.size()); + + EXPECT_THAT(log.front().output, + testing::HasSubstr("[CAF] \"test.caf\": metadata check failed")); + + EXPECT_EQ(0, frag.size()); +}