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:
2026-06-24 02:05:35 +02:00
parent 9cc69d1dcd
commit 2802fbddf6
7 changed files with 393 additions and 3 deletions

160
tests/wav_test.cpp Normal file
View 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;
}