Files
CoopAllTheThings/tests/wav_test.cpp
BlackMark 30eccf749d Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs,
Allman functions) over every source file so the tree is formatter-clean.
Whitespace only -- no behavior change; full x64 + x86 suites pass.

Also set SortIncludes: false in .clang-format. Windows include order is
load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h /
dinput.h; winsock2.h must precede windows.h), and the default
alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build
break. Leaving order alone keeps the manual, correct grouping.
2026-07-12 11:52:53 +02:00

154 lines
5.4 KiB
C++

// Unit test for the WAV reader's robustness on malformed input (common/include/coop/wav.hpp).
// tone_analysis_test already round-trips the writer's own output; this feeds hand-built byte streams:
// truncated headers, bad magic, missing chunks, an over-long `data` size (must clamp), an odd-sized
// chunk before data (word-align skip), and a corrupt huge chunk_size (must not hang/overflow).
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include <windows.h>
#include "coop/wav.hpp"
using namespace coop;
namespace {
int g_failures = 0;
int g_counter = 0;
void check(bool ok, const char* what)
{
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
if (!ok) {
++g_failures;
}
}
std::wstring write_temp(const std::vector<std::uint8_t>& bytes)
{
wchar_t dir[MAX_PATH] = {};
GetTempPathW(MAX_PATH, dir);
std::wstring path = std::wstring(dir) + L"coop_wavtest_" + std::to_wstring(GetCurrentProcessId()) + L"_"
+ std::to_wstring(g_counter++) + L".wav";
FILE* f = nullptr;
if (_wfopen_s(&f, path.c_str(), L"wb") == 0 && f != nullptr) {
if (!bytes.empty()) {
std::fwrite(bytes.data(), 1, bytes.size(), f);
}
std::fclose(f);
}
return path;
}
void put4(std::vector<std::uint8_t>& b, const char* s)
{
b.insert(b.end(), s, s + 4);
}
// Build a WAV where the `data` chunk's declared size can differ from the bytes actually appended
// (declared_data_size < 0 means "use the real size"), and an optional junk chunk can be inserted
// before `data` with a chosen size field (to exercise the chunk walk).
std::vector<std::uint8_t> build_wav(std::uint16_t tag, std::uint16_t channels, std::uint32_t rate, std::uint16_t bits,
const std::vector<std::uint8_t>& data, long long declared_data_size = -1,
const char* junk_id = nullptr, std::uint32_t junk_size = 0)
{
std::vector<std::uint8_t> b;
put4(b, "RIFF");
detail::wav_put_u32(b, 0); // riff size (reader ignores it)
put4(b, "WAVE");
put4(b, "fmt ");
detail::wav_put_u32(b, 16);
detail::wav_put_u16(b, tag);
detail::wav_put_u16(b, channels);
detail::wav_put_u32(b, rate);
detail::wav_put_u32(b, rate * channels * (bits / 8));
detail::wav_put_u16(b, static_cast<std::uint16_t>(channels * (bits / 8)));
detail::wav_put_u16(b, bits);
if (junk_id != nullptr) {
put4(b, junk_id);
detail::wav_put_u32(b, junk_size);
b.insert(b.end(), junk_size, 0xAB); // junk body
if (junk_size & 1) {
b.push_back(0); // RIFF pads odd chunks to an even boundary (what the reader's & 1 skips)
}
}
put4(b, "data");
detail::wav_put_u32(b, declared_data_size < 0 ? static_cast<std::uint32_t>(data.size())
: static_cast<std::uint32_t>(declared_data_size));
b.insert(b.end(), data.begin(), data.end());
return b;
}
} // namespace
int main()
{
WavData out;
// Truncated (< 44 bytes).
check(!wav_read(write_temp({'R', 'I', 'F', 'F', 0, 0, 0, 0}), out), "truncated file (<44B) rejected");
// Right size but wrong magic.
{
std::vector<std::uint8_t> b(64, 0);
b[0] = 'R'; // not "RIFF"...."WAVE"
check(!wav_read(write_temp(b), out), "bad RIFF/WAVE magic rejected");
}
// Valid 16-bit PCM round-trips.
{
const std::vector<std::uint8_t> data(800, 0x42);
out = WavData{};
check(wav_read(write_temp(build_wav(1, 2, 44100, 16, data)), out), "valid PCM parses");
check(out.format_tag == 1 && out.channels == 2 && out.sample_rate == 44100 && out.bits == 16,
"valid PCM: format fields correct");
check(out.pcm.size() == 800, "valid PCM: full data recovered");
}
// fmt present but no data chunk -> rejected (have_data == false).
{
std::vector<std::uint8_t> b;
put4(b, "RIFF");
detail::wav_put_u32(b, 0);
put4(b, "WAVE");
put4(b, "fmt ");
detail::wav_put_u32(b, 16);
detail::wav_put_u16(b, 1);
detail::wav_put_u16(b, 2);
detail::wav_put_u32(b, 48000);
detail::wav_put_u32(b, 48000 * 4);
detail::wav_put_u16(b, 4);
detail::wav_put_u16(b, 16);
check(!wav_read(write_temp(b), out), "fmt-only (no data chunk) rejected");
}
// data chunk declares MORE than the file holds -> clamp to what's there, still parse.
{
const std::vector<std::uint8_t> data(100, 0x7F);
out = WavData{};
check(wav_read(write_temp(build_wav(3, 1, 48000, 32, data, /*declared=*/1000000)), out),
"over-long data size still parses");
check(out.pcm.size() == 100, "over-long data size clamps to available bytes");
}
// An odd-sized junk chunk before data -> the word-align skip must still find data.
{
const std::vector<std::uint8_t> data(40, 0x11);
out = WavData{};
check(wav_read(write_temp(build_wav(1, 2, 44100, 16, data, -1, "LIST", /*odd*/ 3)), out),
"odd-sized chunk before data: word-align skip finds data");
check(out.pcm.size() == 40, "data after an odd chunk recovered");
}
// A corrupt, huge chunk_size before data must not hang or overflow -- the walk stops.
{
const std::vector<std::uint8_t> data(40, 0x22);
out = WavData{};
// junk chunk claims ~4 GB; the guard breaks before reaching the real data chunk -> no data found.
const bool parsed = wav_read(write_temp(build_wav(1, 2, 44100, 16, data, -1, "junk", 0xFFFFFFF0u)), out);
check(!parsed, "corrupt huge chunk_size: walk stops cleanly (no hang/overflow), data not reached");
}
std::printf(g_failures == 0 ? "PASS wav_test\n" : "FAILED wav_test (%d)\n", g_failures);
return g_failures == 0 ? 0 : 1;
}