local createLibTest = require('/apis/libtest'); local createMusic = require('/apis/libmusic'); local testlib = createLibTest({ ... }); local function fakeFs(dir, entries) return { isDir = function(path) return path == dir; end, list = function(path) if path ~= dir then error('unexpected list: ' .. tostring(path)); end return entries; end, }; end testlib.test('listTracks filters dfpwm, strips extension and sorts', function() local music = createMusic({ dir = '/data/musics', fs = fakeFs('/data/musics', { 'Zebra.dfpwm', 'notes.txt', 'Apple.dfpwm' }), }); local tracks = music.listTracks(); testlib.assertEquals(#tracks, 2); testlib.assertEquals(tracks[1].name, 'Apple'); testlib.assertEquals(tracks[1].path, '/data/musics/Apple.dfpwm'); testlib.assertEquals(tracks[2].name, 'Zebra'); testlib.assertEquals(tracks[2].path, '/data/musics/Zebra.dfpwm'); end); testlib.test('listTracks returns empty when directory is missing', function() local music = createMusic({ dir = '/data/musics', fs = { isDir = function() return false; end }, }); testlib.assertEquals(#music.listTracks(), 0); end); -- A speaker whose queue accepts `capacity` buffers before refusing. Tests -- simulate a drained queue by resetting `queued` before firing the captured -- `speaker_audio_empty` handler. local function fakeSpeaker(capacity) local s = { played = {}, stops = 0, queued = 0, capacity = capacity or 1 }; s.playAudio = function(buffer) if s.queued >= s.capacity then return false; end s.queued = s.queued + 1; s.played[#s.played + 1] = buffer; return true; end; s.stop = function() s.stops = s.stops + 1; end; return s; end local function fakeHandle(chunks) local index = 0; return { closed = false, read = function(_, _) index = index + 1; return chunks[index]; end, close = function(self) self.closed = true; end, }; end -- identity decoder: the played buffers equal the raw chunks for easy asserts local fakeDfpwm = { make_decoder = function() return function(chunk) return chunk; end; end }; local function makePlayer(opts) local captured = {}; local eventloop = { register = function(event, handler) captured[event] = handler; return function() end; end, }; local music = createMusic({ peripheral = { find = function(name) return name == 'speaker' and opts.speaker or nil; end }, io = { open = function(path) return opts.handles[path]; end }, dfpwm = fakeDfpwm, }); local player = music.createPlayer(eventloop); return player, captured, eventloop; end testlib.test('play opens the file, primes the speaker and reports playing', function() local speaker = fakeSpeaker(1); local handle = fakeHandle({ 'a', 'b' }); local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } }); player.play('/song.dfpwm'); testlib.assertEquals(player.getStatus(), 'playing'); testlib.assertEquals(player.getCurrentPath(), '/song.dfpwm'); testlib.assertEquals(#speaker.played, 1); testlib.assertEquals(speaker.played[1], 'a'); end); testlib.test('back-pressure resumes feeding on speaker_audio_empty', function() local speaker = fakeSpeaker(1); local handle = fakeHandle({ 'a', 'b' }); local player, captured = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } }); player.play('/song.dfpwm'); -- 'a' queued, 'b' refused (pending) testlib.assertEquals(#speaker.played, 1); speaker.queued = 0; -- simulate the queue draining captured.speaker_audio_empty(); testlib.assertEquals(speaker.played[2], 'b'); -- pending buffer replayed testlib.assertEquals(player.getStatus(), 'stopped'); -- then EOF end); testlib.test('pause stops feeding and resume loses no buffered chunk', function() local speaker = fakeSpeaker(1); local handle = fakeHandle({ 'a', 'b', 'c' }); local player, captured = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } }); player.play('/song.dfpwm'); -- 'a' queued, 'b' pending player.pause(); testlib.assertEquals(player.getStatus(), 'paused'); speaker.queued = 0; captured.speaker_audio_empty(); -- no-op while paused testlib.assertEquals(#speaker.played, 1); player.resume(); testlib.assertEquals(player.getStatus(), 'playing'); testlib.assertEquals(speaker.played[2], 'b'); -- pending 'b' fed, nothing dropped end); testlib.test('stop clears the speaker, closes the handle and resets state', function() local speaker = fakeSpeaker(1); local handle = fakeHandle({ 'a', 'b' }); local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } }); player.play('/song.dfpwm'); player.stop(); testlib.assertEquals(speaker.stops, 1); testlib.assertTrue(handle.closed, 'handle should be closed'); testlib.assertEquals(player.getStatus(), 'stopped'); testlib.assertEquals(player.getCurrentPath(), nil); end); testlib.test('reaching the end fires onFinish once and stays stopped', function() local speaker = fakeSpeaker(10); -- large queue: whole track flows in one pump local handle = fakeHandle({ 'a', 'b' }); local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } }); local finishes = 0; player.onFinish(function() finishes = finishes + 1; end); player.play('/song.dfpwm'); testlib.assertEquals(finishes, 1); testlib.assertEquals(player.getStatus(), 'stopped'); testlib.assertEquals(#speaker.played, 2); end); testlib.run();