@@ -331,6 +331,104 @@ START_TEST(test_bar_state_drain_playlist) {
331331}
332332END_TEST
333333
334+ START_TEST (test_bar_state_advance_playlist ) {
335+ BarApp_t app ;
336+ PianoSong_t song1 , song2 ;
337+ memset (& song1 , 0 , sizeof (song1 ));
338+ memset (& song2 , 0 , sizeof (song2 ));
339+ song1 .head .next = & song2 .head ;
340+ song1 .title = (char * )"first" ;
341+ song2 .title = (char * )"second" ;
342+ bar_state_test_setup (& app , BAR_UI_MODE_WEB );
343+
344+ BarStateSetPlaylist (& app , & song1 );
345+
346+ PianoSong_t * finished = BarStateAdvancePlaylist (& app );
347+ ck_assert_ptr_eq (finished , & song1 );
348+ ck_assert_ptr_null (finished -> head .next );
349+ ck_assert_ptr_eq (BarStateGetPlaylist (& app ), & song2 );
350+
351+ finished = BarStateAdvancePlaylist (& app );
352+ ck_assert_ptr_eq (finished , & song2 );
353+ ck_assert_ptr_null (BarStateGetPlaylist (& app ));
354+
355+ finished = BarStateAdvancePlaylist (& app );
356+ ck_assert_ptr_null (finished );
357+
358+ bar_state_test_teardown (& app );
359+ }
360+ END_TEST
361+
362+ START_TEST (test_bar_state_advance_playlist_both ) {
363+ BarApp_t app ;
364+ PianoSong_t song ;
365+ memset (& song , 0 , sizeof (song ));
366+ song .title = (char * )"both-mode" ;
367+ bar_state_test_setup (& app , BAR_UI_MODE_BOTH );
368+
369+ BarStateSetPlaylist (& app , & song );
370+ PianoSong_t * finished = BarStateAdvancePlaylist (& app );
371+ ck_assert_ptr_eq (finished , & song );
372+ ck_assert_ptr_null (BarStateGetPlaylist (& app ));
373+
374+ bar_state_test_teardown (& app );
375+ }
376+ END_TEST
377+
378+ typedef struct {
379+ BarApp_t * app ;
380+ int iterations ;
381+ } bar_state_playlist_thread_args_t ;
382+
383+ static void * advance_playlist_worker (void * arg ) {
384+ const bar_state_playlist_thread_args_t * args = arg ;
385+ for (int i = 0 ; i < args -> iterations ; ++ i ) {
386+ PianoSong_t * finished = BarStateAdvancePlaylist (args -> app );
387+ (void ) finished ;
388+ }
389+ return NULL ;
390+ }
391+
392+ static void * drain_playlist_worker (void * arg ) {
393+ const bar_state_playlist_thread_args_t * args = arg ;
394+ for (int i = 0 ; i < args -> iterations ; ++ i ) {
395+ BarStateDrainPlaylist (args -> app );
396+ }
397+ return NULL ;
398+ }
399+
400+ /* Regression: reconnect/disconnect DrainPlaylist vs playback-manager advance. */
401+ START_TEST (test_bar_state_advance_playlist_race_with_drain ) {
402+ BarApp_t app ;
403+ bar_state_test_setup (& app , BAR_UI_MODE_WEB );
404+
405+ for (int round = 0 ; round < 50 ; ++ round ) {
406+ PianoSong_t * s1 = calloc (1 , sizeof (* s1 ));
407+ PianoSong_t * s2 = calloc (1 , sizeof (* s2 ));
408+ ck_assert_ptr_nonnull (s1 );
409+ ck_assert_ptr_nonnull (s2 );
410+ s1 -> head .next = & s2 -> head ;
411+ BarStateSetPlaylist (& app , s1 );
412+
413+ bar_state_playlist_thread_args_t args = { .app = & app , .iterations = 200 };
414+ pthread_t advance_tid ;
415+ pthread_t drain_tid ;
416+ ck_assert_int_eq (pthread_create (& advance_tid , NULL ,
417+ advance_playlist_worker , & args ), 0 );
418+ ck_assert_int_eq (pthread_create (& drain_tid , NULL ,
419+ drain_playlist_worker , & args ), 0 );
420+ ck_assert_int_eq (pthread_join (advance_tid , NULL ), 0 );
421+ ck_assert_int_eq (pthread_join (drain_tid , NULL ), 0 );
422+
423+ if (BarStateGetPlaylist (& app ) != NULL ) {
424+ BarStateDrainPlaylist (& app );
425+ }
426+ }
427+
428+ bar_state_test_teardown (& app );
429+ }
430+ END_TEST
431+
334432/* SwitchStation: drains playlist and sets nextStation (test with playlist NULL) */
335433START_TEST (test_bar_state_switch_station ) {
336434 BarApp_t app ;
@@ -657,6 +755,9 @@ Suite *bar_state_suite(void) {
657755 tcase_add_test (tc_playlist , test_bar_state_playlist_get_set );
658756 tcase_add_test (tc_playlist , test_bar_state_playlist_get_set_web );
659757 tcase_add_test (tc_playlist , test_bar_state_drain_playlist );
758+ tcase_add_test (tc_playlist , test_bar_state_advance_playlist );
759+ tcase_add_test (tc_playlist , test_bar_state_advance_playlist_both );
760+ tcase_add_test (tc_playlist , test_bar_state_advance_playlist_race_with_drain );
660761 tcase_add_test (tc_playlist , test_bar_state_switch_station );
661762 suite_add_tcase (s , tc_playlist );
662763
0 commit comments