Add misc unit tests + harden the WAV chunk walk
Fills small coverage gaps: - shared_memory_test: SharedMemory create-or-open aliasing, move (steal + empty the source, no double-free), reset, open-missing. - wav_test: malformed input -- truncation, bad magic, missing data chunk, over-long data size (clamps), odd-sized chunk (word-align skip), and a corrupt ~4 GB chunk_size. The reader gains an advance guard so that last case can't wrap pos on a 32-bit size_t (x86) or spin the walk; it stops cleanly. - tool_paths_test: deployed_artifact_path resolution -- next-to-exe, one-dir-up, and the not-found fallback -- with real marker files. - audio_ring_test: an overrun-at-the-seam case (write head near the end: a wrapping push that fits vs. an over-capacity wrapping push dropped whole), exercising the wrap split + overrun together, not just at offset 0. The injector bitness check isn't added as a unit test: is_wow64_process is file-local and the real WOW64 path needs a 32-bit target, so it stays inspection-covered (and exercised by the x86 injection path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
160
tests/wav_test.cpp
Normal file
160
tests/wav_test.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user