mirror of
https://github.com/izzy2lost/libultraship.git
synced 2026-10-26 11:44:27 -07:00
* test: add comprehensive test coverage for utility modules Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/a89625bd-3a8f-4c51-b418-4258f9c6c44a Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * chore: plan ResourceManager/ArchiveManager test additions Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/a89625bd-3a8f-4c51-b418-4258f9c6c44a Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * chore: plan maximum coverage test additions Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/a89625bd-3a8f-4c51-b418-4258f9c6c44a Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * test: add comprehensive coverage — glob, splitText, Path/DiskFile, resource types/factories, Archive self tests, ResourceLoader, ResourceManager helpers Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/a89625bd-3a8f-4c51-b418-4258f9c6c44a Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * test: address PR review suggestions and splitText leak Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/44c1e76b-581e-4593-b8bc-a60be4c0dd9f Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * Changes before error encountered Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/44c1e76b-581e-4593-b8bc-a60be4c0dd9f Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> * style: apply clang-format to remaining test files Agent-Logs-Url: https://github.com/Kenix3/libultraship/sessions/1a9e1a9f-2efb-449b-91a3-8c8372053c64 Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Kenix3 <18299654+Kenix3@users.noreply.github.com>
165 lines
6.3 KiB
C++
165 lines
6.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <tuple>
|
|
#include "ship/audio/SoundMatrixDecoder.h"
|
|
|
|
// Helper: build a stereo int16 buffer from pairs
|
|
static std::vector<uint8_t> MakeStereoBuffer(const std::vector<std::pair<int16_t, int16_t>>& pairs) {
|
|
std::vector<uint8_t> buf(pairs.size() * 2 * sizeof(int16_t));
|
|
for (size_t i = 0; i < pairs.size(); i++) {
|
|
std::memcpy(&buf[i * 4 + 0], &pairs[i].first, sizeof(int16_t));
|
|
std::memcpy(&buf[i * 4 + 2], &pairs[i].second, sizeof(int16_t));
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
// ============================================================
|
|
// Construction
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, ConstructWithStandardSampleRate) {
|
|
// Should not throw at typical sample rates
|
|
EXPECT_NO_THROW(Ship::SoundMatrixDecoder dec(44100));
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, ConstructWith48kHz) {
|
|
EXPECT_NO_THROW(Ship::SoundMatrixDecoder dec(48000));
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, ConstructWith22kHz) {
|
|
EXPECT_NO_THROW(Ship::SoundMatrixDecoder dec(22050));
|
|
}
|
|
|
|
// ============================================================
|
|
// ResetState
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, ResetStateDoesNotCrash) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
// Process some samples to dirty the filter state
|
|
auto buf = MakeStereoBuffer({ { 10000, -10000 }, { 5000, -5000 } });
|
|
dec.Process(buf.data(), buf.size());
|
|
// Then reset — should succeed without throwing
|
|
EXPECT_NO_THROW(dec.ResetState());
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, AfterResetStateOutputMatchesFreshDecoder) {
|
|
// Two decoders at the same rate should give identical output after one is reset
|
|
Ship::SoundMatrixDecoder dec1(44100);
|
|
Ship::SoundMatrixDecoder dec2(44100);
|
|
|
|
// Dirty dec1 with some samples
|
|
auto noise = MakeStereoBuffer({ { 32767, -32768 }, { 1000, -1000 }, { 500, 200 } });
|
|
dec1.Process(noise.data(), noise.size());
|
|
|
|
// Reset dec1 to match the fresh state of dec2
|
|
dec1.ResetState();
|
|
|
|
// Both should now produce identical output for the same input
|
|
auto input = MakeStereoBuffer({ { 8000, -8000 }, { 4000, 4000 } });
|
|
auto [ptr1, len1] = dec1.Process(input.data(), input.size());
|
|
auto [ptr2, len2] = dec2.Process(input.data(), input.size());
|
|
|
|
ASSERT_EQ(len1, len2);
|
|
EXPECT_EQ(std::memcmp(ptr1, ptr2, len1), 0);
|
|
}
|
|
|
|
// ============================================================
|
|
// Process — output size
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, ProcessOutputSizeIs6ChannelsPerStereopair) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
// 1 stereo pair → 6 int16 samples = 12 bytes
|
|
auto buf = MakeStereoBuffer({ { 0, 0 } });
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
EXPECT_EQ(len, 1 * 6 * static_cast<int>(sizeof(int16_t)));
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, ProcessOutputSizeScalesWithInput) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
const int pairs = 64;
|
|
auto buf = MakeStereoBuffer(std::vector<std::pair<int16_t, int16_t>>(pairs, { 100, -100 }));
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
EXPECT_EQ(len, pairs * 6 * static_cast<int>(sizeof(int16_t)));
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, ProcessEmptyBufferReturnsZeroLength) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
auto [ptr, len] = dec.Process(nullptr, 0);
|
|
EXPECT_EQ(len, 0);
|
|
}
|
|
|
|
// ============================================================
|
|
// Process — output validity
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, ProcessReturnNonNullPointerForNonEmptyInput) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
auto buf = MakeStereoBuffer({ { 1000, -1000 } });
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
EXPECT_NE(ptr, nullptr);
|
|
EXPECT_GT(len, 0);
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, SilentInputProducesFiniteOutput) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
// Feed silence; output should be all zeros (filters start at zero)
|
|
auto buf = MakeStereoBuffer(std::vector<std::pair<int16_t, int16_t>>(8, { 0, 0 }));
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
const int16_t* out = reinterpret_cast<const int16_t*>(ptr);
|
|
int samples = len / static_cast<int>(sizeof(int16_t));
|
|
for (int i = 0; i < samples; i++) {
|
|
EXPECT_EQ(out[i], 0) << "sample " << i << " should be zero for silent input";
|
|
}
|
|
}
|
|
|
|
TEST(SoundMatrixDecoder, OutputIsSymmetricForSymmetricInput) {
|
|
// For L = R, FL should equal FR
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
auto buf = MakeStereoBuffer({ { 10000, 10000 } });
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
const int16_t* out = reinterpret_cast<const int16_t*>(ptr);
|
|
// out[0]=FL, out[1]=FR
|
|
EXPECT_EQ(out[0], out[1]) << "FL and FR should be equal for symmetric stereo input";
|
|
}
|
|
|
|
// ============================================================
|
|
// Saturation clamping
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, ExtremeInputIsClamped) {
|
|
Ship::SoundMatrixDecoder dec(44100);
|
|
// Use maximum-magnitude values; output must not exceed int16 range
|
|
auto buf = MakeStereoBuffer(std::vector<std::pair<int16_t, int16_t>>(
|
|
128, { std::numeric_limits<int16_t>::max(), std::numeric_limits<int16_t>::min() }));
|
|
auto [ptr, len] = dec.Process(buf.data(), buf.size());
|
|
const int16_t* out = reinterpret_cast<const int16_t*>(ptr);
|
|
int samples = len / static_cast<int>(sizeof(int16_t));
|
|
for (int i = 0; i < samples; i++) {
|
|
EXPECT_GE(out[i], std::numeric_limits<int16_t>::min());
|
|
EXPECT_LE(out[i], std::numeric_limits<int16_t>::max());
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Determinism
|
|
// ============================================================
|
|
|
|
TEST(SoundMatrixDecoder, SameInputProducesSameOutput) {
|
|
auto input = MakeStereoBuffer({ { 1000, -500 }, { 2000, -1500 }, { -300, 800 } });
|
|
|
|
Ship::SoundMatrixDecoder dec1(44100);
|
|
auto [ptr1, len1] = dec1.Process(input.data(), input.size());
|
|
std::vector<uint8_t> out1(ptr1, ptr1 + len1);
|
|
|
|
Ship::SoundMatrixDecoder dec2(44100);
|
|
auto [ptr2, len2] = dec2.Process(input.data(), input.size());
|
|
std::vector<uint8_t> out2(ptr2, ptr2 + len2);
|
|
|
|
ASSERT_EQ(len1, len2);
|
|
EXPECT_EQ(out1, out2);
|
|
}
|