2020
2121#include < QtTest>
2222#include < mutex>
23+ #include < condition_variable>
2324
2425namespace noteahead {
2526
@@ -28,6 +29,7 @@ class MockAudioFileIO : public AudioFileReader
2829public:
2930 bool open (const std::string &, Mode mode, Info & info) override
3031 {
32+ std::lock_guard<std::mutex> lock (m_mutex);
3133 m_mode = mode;
3234 if (mode == Mode::Read) {
3335 info = m_info;
@@ -37,10 +39,16 @@ class MockAudioFileIO : public AudioFileReader
3739 }
3840 m_isOpen = true ;
3941 m_pos = 0 ;
42+ m_cv.notify_all ();
4043 return true ;
4144 }
4245
43- void close () override { m_isOpen = false ; }
46+ void close () override
47+ {
48+ std::lock_guard<std::mutex> lock (m_mutex);
49+ m_isOpen = false ;
50+ m_cv.notify_all ();
51+ }
4452
4553 int64_t readFloat (std::span<float >) override { return 0 ; }
4654 int64_t readDouble (std::span<double >) override { return 0 ; }
@@ -53,6 +61,8 @@ class MockAudioFileIO : public AudioFileReader
5361 if (toRead == 0 ) return 0 ;
5462 std::copy (m_data.begin () + m_pos, m_data.begin () + m_pos + toRead, data.begin ());
5563 m_pos += toRead;
64+ m_readCount++;
65+ m_cv.notify_all ();
5666 return static_cast <int64_t >(toRead / static_cast <size_t >(m_info.channels ));
5767 }
5868
@@ -64,13 +74,17 @@ class MockAudioFileIO : public AudioFileReader
6474 if (!m_isOpen || m_mode != Mode::Write) return 0 ;
6575 m_data.insert (m_data.end (), data.begin (), data.end ());
6676 m_info.frames += static_cast <int64_t >(data.size () / static_cast <size_t >(m_info.channels ));
77+ m_writeCount++;
78+ m_cv.notify_all ();
6779 return static_cast <int64_t >(data.size () / static_cast <size_t >(m_info.channels ));
6880 }
6981
7082 bool seek (int64_t frames, int ) override
7183 {
7284 std::lock_guard<std::mutex> lock (m_mutex);
7385 m_pos = static_cast <size_t >(frames * m_info.channels );
86+ m_seekCount++;
87+ m_cv.notify_all ();
7488 return true ;
7589 }
7690
@@ -80,18 +94,46 @@ class MockAudioFileIO : public AudioFileReader
8094 // Helper for testing
8195 void setData (const std::vector<int32_t > & data, const Info & info)
8296 {
97+ std::lock_guard<std::mutex> lock (m_mutex);
8398 m_data = data;
8499 m_info = info;
85100 }
86- const std::vector<int32_t > & data () const { return m_data; }
101+
102+ const std::vector<int32_t > & data () const
103+ {
104+ std::lock_guard<std::mutex> lock (m_mutex);
105+ return m_data;
106+ }
107+
108+ bool waitForWriteSize (size_t expectedSize, std::chrono::milliseconds timeout)
109+ {
110+ std::unique_lock<std::mutex> lock (m_mutex);
111+ return m_cv.wait_for (lock, timeout, [this , expectedSize] { return m_data.size () >= expectedSize; });
112+ }
113+
114+ bool waitForReadCount (int expectedCount, std::chrono::milliseconds timeout)
115+ {
116+ std::unique_lock<std::mutex> lock (m_mutex);
117+ return m_cv.wait_for (lock, timeout, [this , expectedCount] { return m_readCount >= expectedCount; });
118+ }
119+
120+ bool waitForSeekCount (int expectedCount, std::chrono::milliseconds timeout)
121+ {
122+ std::unique_lock<std::mutex> lock (m_mutex);
123+ return m_cv.wait_for (lock, timeout, [this , expectedCount] { return m_seekCount >= expectedCount; });
124+ }
87125
88126private:
89127 std::vector<int32_t > m_data;
90128 Info m_info;
91129 bool m_isOpen = false ;
92130 Mode m_mode = Mode::Read;
93131 size_t m_pos = 0 ;
132+ int m_readCount = 0 ;
133+ int m_writeCount = 0 ;
134+ int m_seekCount = 0 ;
94135 mutable std::mutex m_mutex;
136+ std::condition_variable m_cv;
95137};
96138
97139void AudioFileIoTest::testRecordingAndStreaming ()
@@ -114,15 +156,10 @@ void AudioFileIoTest::testRecordingAndStreaming()
114156 testData[i] = static_cast <int32_t >(i);
115157 }
116158
117- // Push in smaller chunks to ensure ring buffer doesn't overflow
118159 QVERIFY (recorder.push (testData.data (), testData.size ()));
119160
120161 // Wait for background thread to process the ring buffer
121- int attempts = 0 ;
122- while (mockWriterPtr->data ().size () < 1000 && attempts < 100 ) {
123- QThread::msleep (10 );
124- attempts++;
125- }
162+ QVERIFY (mockWriterPtr->waitForWriteSize (1000 , std::chrono::milliseconds (1000 )));
126163
127164 recorder.stop ();
128165 recordedData = mockWriterPtr->data ();
@@ -132,6 +169,7 @@ void AudioFileIoTest::testRecordingAndStreaming()
132169
133170 // 2. Stream it back and verify using Mock
134171 auto mockReader = std::make_unique<MockAudioFileIO>();
172+ auto mockReaderPtr = mockReader.get ();
135173 AudioFileReader::Info info;
136174 info.samplerate = sampleRate;
137175 info.channels = channels;
@@ -145,16 +183,17 @@ void AudioFileIoTest::testRecordingAndStreaming()
145183 QCOMPARE (streamer.sampleRate (), static_cast <int >(sampleRate));
146184 QCOMPARE (streamer.channels (), static_cast <int >(channels));
147185
148- // Wait for disk read thread to fill ring buffer
149- QThread::msleep ( 100 );
186+ // Wait for disk read thread to fill ring buffer (at least one read operation)
187+ QVERIFY (mockReaderPtr-> waitForReadCount ( 1 , std::chrono::milliseconds ( 1000 )) );
150188
151189 std::vector<int32_t > readData (1000 );
152190 size_t totalRead = 0 ;
153191 int attempts = 0 ;
154192 while (totalRead < 1000 && attempts < 100 ) {
155193 size_t read = streamer.pop (readData.data () + totalRead, readData.size () - totalRead);
156194 if (read == 0 ) {
157- QThread::msleep (10 );
195+ // If the ring buffer was empty, we might need to wait for another read from mock
196+ mockReaderPtr->waitForReadCount (attempts + 2 , std::chrono::milliseconds (100 ));
158197 attempts++;
159198 continue ;
160199 }
@@ -176,6 +215,7 @@ void AudioFileIoTest::testPosition()
176215 const size_t bufferSize = 4096 ;
177216
178217 auto mockReader = std::make_unique<MockAudioFileIO>();
218+ auto mockReaderPtr = mockReader.get ();
179219 AudioFileReader::Info info;
180220 info.samplerate = sampleRate;
181221 info.channels = channels;
@@ -186,13 +226,16 @@ void AudioFileIoTest::testPosition()
186226 {
187227 AudioFileStreamer streamer (std::move (mockReader));
188228 streamer.start (" dummy" , bufferSize);
189- QThread::msleep (100 );
229+
230+ // Wait for initial fill
231+ QVERIFY (mockReaderPtr->waitForReadCount (1 , std::chrono::milliseconds (1000 )));
190232
191233 streamer.setPosition (0.5 );
192234 QCOMPARE (streamer.position (), 0.5 );
235+ QVERIFY (mockReaderPtr->waitForSeekCount (1 , std::chrono::milliseconds (1000 )));
193236
194237 // Wait for background thread to fill ring buffer from new position
195- QThread::msleep ( 100 );
238+ QVERIFY (mockReaderPtr-> waitForReadCount ( 2 , std::chrono::milliseconds ( 1000 )) );
196239
197240 std::vector<int32_t > dummy (100 );
198241 size_t read = streamer.pop (dummy.data (), dummy.size ());
0 commit comments