Skip to content

Commit 7fdf57b

Browse files
CodeRabbit Generated Unit Tests: Add GoogleTest ADLv3 and IMidiChannelVoice tests, test main, docs
1 parent a049e63 commit 7fdf57b

File tree

4 files changed

+270
-0
lines changed

4 files changed

+270
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// NOTE: Test framework and library:
2+
// - Preferred: GoogleTest (gtest). This repository's detected setup should be used.
3+
// - If the project uses Catch2/doctest instead, replace the gtest includes/assertions accordingly while keeping the same test logic.
4+
//
5+
// These tests focus on behaviors exposed/altered in the PR diff for IMidiChannelVoice:
6+
// - getChannelNum()
7+
// - setVolumes() which updates m_volume and recomputes real volume via calcVolume_().
8+
// The internal calcVolume_() is validated indirectly by observing effective volume changes via public accessors.
9+
// If a direct getter for "real volume" does not exist, we provide a minimal test double deriving from IMidiChannelVoice
10+
// with accessors solely for test visibility, without altering production code paths.
11+
12+
#include <cstdint>
13+
#include <limits>
14+
#include <type_traits>
15+
16+
// Try to include GoogleTest; adjust if your project uses a different framework.
17+
#include <gtest/gtest.h>
18+
19+
#include <HyperSonicDrivers/drivers/midi/IMidiChannelVoice.hpp>
20+
#include <HyperSonicDrivers/drivers/midi/IMidiChannel.hpp>
21+
22+
using HyperSonicDrivers::drivers::midi::IMidiChannelVoice;
23+
using HyperSonicDrivers::drivers::midi::IMidiChannel;
24+
25+
namespace {
26+
27+
// Minimal stub for IMidiChannel if it's an interface; if IMidiChannel is a struct with fields `channel` and `volume`,
28+
// include and use the real one. Where necessary, we define only what tests need and rely on compilation to signal mismatches.
29+
struct TestMidiChannel : public IMidiChannel {
30+
// We assume IMidiChannel has public members `uint8_t channel; uint8_t volume;`
31+
// If they are protected/private, adjust to use provided public setters in IMidiChannel.
32+
// Initialize with defaults for safety.
33+
TestMidiChannel(uint8_t ch, uint8_t vol) {
34+
this->channel = ch;
35+
this->volume = vol;
36+
}
37+
};
38+
39+
// A thin concrete test double for IMidiChannelVoice to expose volume state for assertions.
40+
// If IMidiChannelVoice is abstract with other pure virtuals, implement them as no-ops for test purposes.
41+
class TestMidiChannelVoice : public IMidiChannelVoice {
42+
public:
43+
explicit TestMidiChannelVoice(IMidiChannel* ch) {
44+
m_channel = ch; // accessing protected member if allowed; otherwise use constructor of real class if available.
45+
m_volume = 127;
46+
m_real_volume = calcVolume_();
47+
}
48+
49+
// Expose state for tests
50+
uint8_t exposedVolume() const { return m_volume; }
51+
uint8_t exposedRealVolume() const { return m_real_volume; }
52+
53+
// IMidiChannelVoice may declare additional pure virtuals; provide trivial implementations as needed.
54+
protected:
55+
// If IMidiChannelVoice has protected ctor/dtor semantics, ensure this class matches requirements.
56+
// Any audio rendering or hardware interactions are intentionally omitted in tests.
57+
using IMidiChannelVoice::calcVolume_; // grant access within this test double
58+
};
59+
60+
} // namespace
61+
62+
// ------------------------------ Tests ------------------------------
63+
64+
TEST(IMidiChannelVoiceBehavior, GetChannelNumReflectsUnderlyingChannel) {
65+
// Arrange
66+
TestMidiChannel channel(/*ch*/5, /*vol*/100);
67+
TestMidiChannelVoice voice(&channel);
68+
69+
// Act
70+
auto num = voice.getChannelNum();
71+
72+
// Assert
73+
EXPECT_EQ(num, 5u) << "getChannelNum should return IMidiChannel::channel";
74+
}
75+
76+
TEST(IMidiChannelVoiceBehavior, SetVolumesUpdatesStoredVolumeAndRecalculatesRealVolumeWithChannelVolumeScaling) {
77+
// Arrange
78+
TestMidiChannel channel(/*ch*/2, /*vol*/64); // 50% channel volume (approx, 64/127)
79+
TestMidiChannelVoice voice(&channel);
80+
81+
// Precondition: max volume => real volume equals channel scaling
82+
// With initial m_volume=127, expected real = min(127 * 64 / 127, 127) = 64
83+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(64));
84+
85+
// Act
86+
// Set voice volume to 100, expected real = min(100 * 64 / 127, 127)
87+
voice.setVolumes(100);
88+
const uint8_t expected = static_cast<uint8_t>(std::min<int>(100 * 64 / 127, 127));
89+
90+
// Assert
91+
EXPECT_EQ(voice.exposedVolume(), static_cast<uint8_t>(100));
92+
EXPECT_EQ(voice.exposedRealVolume(), expected);
93+
}
94+
95+
TEST(IMidiChannelVoiceBehavior, RealVolumeIsClampedTo127OnOverflow) {
96+
// Arrange: Set channel volume to maximum; set voice volume to maximum to test clamp.
97+
TestMidiChannel channel(/*ch*/3, /*vol*/127);
98+
TestMidiChannelVoice voice(&channel);
99+
100+
// Act
101+
voice.setVolumes(200); // Although API takes uint8_t, values beyond 127 will wrap; ensure test uses capped within 0..255
102+
// After implicit cast to uint8_t, 200 -> 200-256=-56 -> 200 mod 256 = 200; semantics depend on implementation.
103+
// For robustness, test with 255 as extreme inside uint8_t range to verify clamp behavior.
104+
voice.setVolumes(255);
105+
106+
// Expected: min(255 * 127 / 127, 127) = min(255, 127) = 127
107+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(127));
108+
}
109+
110+
TEST(IMidiChannelVoiceBehavior, ZeroChannelVolumeForcesRealVolumeZeroRegardlessOfVoiceVolume) {
111+
// Arrange
112+
TestMidiChannel channel(/*ch*/9, /*vol*/0);
113+
TestMidiChannelVoice voice(&channel);
114+
115+
// Act
116+
voice.setVolumes(0);
117+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(0));
118+
119+
voice.setVolumes(127);
120+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(0));
121+
122+
voice.setVolumes(64);
123+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(0));
124+
}
125+
126+
TEST(IMidiChannelVoiceBehavior, ZeroVoiceVolumeProducesZeroRealVolumeForAnyChannelVolume) {
127+
// Arrange
128+
TestMidiChannel channel(/*ch*/10, /*vol*/127);
129+
TestMidiChannelVoice voice(&channel);
130+
131+
// Act
132+
voice.setVolumes(0);
133+
134+
// Assert
135+
EXPECT_EQ(voice.exposedVolume(), static_cast<uint8_t>(0));
136+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(0));
137+
}
138+
139+
TEST(IMidiChannelVoiceBehavior, MidpointScalingMatchesIntegerDivisionRoundingDown) {
140+
// Arrange: Channel volume 127 (max), voice 63 should yield 63.
141+
TestMidiChannel channel(/*ch*/4, /*vol*/127);
142+
TestMidiChannelVoice voice(&channel);
143+
144+
// Act
145+
voice.setVolumes(63);
146+
147+
// Assert
148+
EXPECT_EQ(voice.exposedRealVolume(), static_cast<uint8_t>(63));
149+
150+
// Another case: channel=64 (approx 50%), voice=63 -> floor(63*64/127)=31
151+
TestMidiChannel channel2(/*ch*/4, /*vol*/64);
152+
TestMidiChannelVoice voice2(&channel2);
153+
voice2.setVolumes(63);
154+
EXPECT_EQ(voice2.exposedRealVolume(), static_cast<uint8_t>( (63 * 64) / 127 ));
155+
}

sdl2-hyper-sonic-drivers/test/HyperSonicDrivers/files/westwood/TestADLFile.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,96 @@ int main(int argc, char** argv)
129129

130130
return RUN_ALL_TESTS();
131131
}
132+
// -----------------------------------------------------------------------------
133+
// Additional ADL v3 tests (GoogleTest/GoogleMock)
134+
// These tests expand coverage for the LOREINTR.ADL fixture introduced in ADLv3,
135+
// focusing on bounds checking, exception behavior, and offset consistency.
136+
// -----------------------------------------------------------------------------
137+
namespace HyperSonicDrivers::files::westwood
138+
{
139+
// Verify that every declared track/instrument offset lies within the data buffer.
140+
TEST(ADLFile, ADLv3_OffsetsWithinBounds_AllEntries)
141+
{
142+
ADLFile f("../fixtures/LOREINTR.ADL");
143+
const auto dataSize = f.getDataSize();
144+
145+
for (int idx = 0; idx < f.getNumTrackOffsets(); ++idx)
146+
{
147+
const auto off = f.getTrackOffset(idx);
148+
EXPECT_LT(off, dataSize) << "track offset idx=" << idx;
149+
}
150+
151+
for (int idx = 0; idx < f.getNumInstrumentOffsets(); ++idx)
152+
{
153+
const auto off = f.getInstrumentOffset(idx);
154+
EXPECT_LT(off, dataSize) << "instrument offset idx=" << idx;
155+
}
156+
}
157+
158+
// Validate exception behavior on out-of-range indices for v3.
159+
TEST(ADLFile, ADLv3_InvalidIndicesThrow)
160+
{
161+
ADLFile f("../fixtures/LOREINTR.ADL");
162+
163+
// Index equal to size should be invalid for all tables.
164+
EXPECT_THROW(f.getTrack(f.getNumTracks()), std::out_of_range);
165+
EXPECT_THROW(f.getTrackOffset(f.getNumTrackOffsets()), std::out_of_range);
166+
EXPECT_THROW(f.getInstrumentOffset(f.getNumInstrumentOffsets()), std::out_of_range);
167+
}
168+
169+
// Track table entries should either be a valid index into the offset tables or the 0xFF sentinel.
170+
// For valid entries, program offsets must resolve to the corresponding offsets.
171+
TEST(ADLFile, ADLv3_TrackTableIndicesOrSentinel)
172+
{
173+
ADLFile f("../fixtures/LOREINTR.ADL");
174+
175+
// Probe a spread of entries (covers early, mid, and later indices).
176+
const int probes[] = {0, 1, 2, 5, 10, 20, 50, 100, 200};
177+
for (int probe : probes)
178+
{
179+
if (probe >= f.getNumTracks())
180+
break;
181+
182+
const int trackIndex = f.getTrack(probe);
183+
if (trackIndex != 0xFF)
184+
{
185+
EXPECT_LT(trackIndex, f.getNumTrackOffsets())
186+
<< "trackIndex " << trackIndex << " not < numTrackOffsets";
187+
188+
EXPECT_EQ(
189+
f.getProgramOffset(trackIndex, ADLFile::PROG_TYPE::Track),
190+
f.getTrackOffset(trackIndex)
191+
);
192+
EXPECT_EQ(
193+
f.getProgramOffset(trackIndex, ADLFile::PROG_TYPE::Instrument),
194+
f.getInstrumentOffset(trackIndex)
195+
);
196+
}
197+
else
198+
{
199+
// Sentinel entries are allowed; presence itself is acceptable.
200+
SUCCEED();
201+
}
202+
}
203+
}
204+
205+
// Parent buffer size should not be smaller than the data section size.
206+
TEST(ADLFile, ADLv3_ParentSizeAtLeastDataSize)
207+
{
208+
ADLFileMock f("../fixtures/LOREINTR.ADL");
209+
EXPECT_GE(f.parentSize(), f.getDataSize());
210+
}
211+
212+
// The first track is used elsewhere; ensure it's not a sentinel and the first byte
213+
// at the resolved track offset looks like a valid channel nibble (0..15).
214+
TEST(ADLFile, ADLv3_FirstTrackNotSentinelAndChannelNibbleRange)
215+
{
216+
ADLFile f("../fixtures/LOREINTR.ADL");
217+
const auto t0 = f.getTrack(0);
218+
ASSERT_NE(t0, 0xFF) << "First track must not be sentinel for this validation";
219+
220+
const auto off0 = f.getTrackOffset(t0);
221+
const unsigned char first = static_cast<unsigned char>(f.getData()[off0]);
222+
EXPECT_LE(static_cast<int>(first), 15) << "First data byte expected within 0..15";
223+
}
224+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
This test folder assumes GoogleTest (gtest) as the unit testing framework.
2+
If the repository instead uses Catch2 or doctest, replace the gtest includes in:
3+
- `HyperSonicDrivers/drivers/midi/IMidiChannelVoice_behavior_test.cpp`
4+
with the project's framework headers and macros, preserving the same test logic and cases.
5+
6+
Coverage focus (based on the PR diff):
7+
- `IMidiChannelVoice::getChannelNum`
8+
- `IMidiChannelVoice::setVolumes` (and its effect on real volume via `calcVolume_`)
9+
10+
These tests validate:
11+
- Correct channel number retrieval
12+
- Volume scaling by channel volume
13+
- Clamping at 127
14+
- Zero behaviors (voice zero, channel zero)
15+
- Integer division rounding behavior at midpoints
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Fallback test main if the project doesn't already define one.
2+
// Testing library: GoogleTest
3+
#include <gtest/gtest.h>
4+
int main(int argc, char** argv) {
5+
::testing::InitGoogleTest(&argc, argv);
6+
return RUN_ALL_TESTS();
7+
}

0 commit comments

Comments
 (0)