SDL: tests: Fix automated audio tests on Windows and Linux

From f569cc56a97567635d5a75712a82e9b6e5860d3a Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 15 Oct 2023 12:15:32 -0400
Subject: [PATCH] tests: Fix automated audio tests on Windows and Linux

If a specific audio driver was requested for testing, don't loop over all drivers in the init, open/close, and pause/unpause tests.

Additionally, drivers can fail if attempting to open an audio device when no underlying output is present, which is a valid case if the system has no audio hardware available. Check for the presence of audio output devices before attempting to open them.

Fixes automated tests with various drivers on Windows and Linux.
---
 test/testautomation_audio.c | 171 ++++++++++++++++++++++++++++++------
 1 file changed, 145 insertions(+), 26 deletions(-)

diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c
index 03cc6463256d..16ab753e9787 100644
--- a/test/testautomation_audio.c
+++ b/test/testautomation_audio.c
@@ -52,6 +52,42 @@ void SDLCALL _audio_testCallback(void *userdata, Uint8 *stream, int len)
     _audio_testCallbackLength += len;
 }
 
+#if defined(__linux__)
+/* Linux builds can include many audio drivers, but some are very
+ * obscure and typically unsupported on modern systems. They will
+ * be skipped in tests that run against all included drivers, as
+ * they are basically guaranteed to fail.
+ */
+static SDL_bool DriverIsProblematic(const char *driver)
+{
+    static const char *driverList[] = {
+        /* Omnipresent in Linux builds, but deprecated since 2002,
+        * very rarely used on Linux nowadays, and is almost certainly
+        * guaranteed to fail.
+         */
+        "dsp",
+
+        /* OpenBSD sound API. Can be used on Linux, but very rare. */
+        "sndio",
+
+        /* Always fails on initialization and/or opening a device.
+         * Does anyone or anything actually use this?
+         */
+        "nas"
+    };
+
+    int i;
+
+    for (i = 0; i < SDL_arraysize(driverList); ++i) {
+        if (SDL_strcmp(driver, driverList[i]) == 0) {
+            return SDL_TRUE;
+        }
+    }
+
+    return SDL_FALSE;
+}
+#endif
+
 /* Test case functions */
 
 /**
@@ -89,15 +125,33 @@ int audio_initQuitAudio()
     SDL_QuitSubSystem(SDL_INIT_AUDIO);
     SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)");
 
-    /* Loop over all available audio drivers */
-    iMax = SDL_GetNumAudioDrivers();
-    SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
-    SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    /* Was a specific driver requested? */
+    audioDriver = SDL_GetHint(SDL_HINT_AUDIODRIVER);
+
+    if (audioDriver == NULL) {
+        /* Loop over all available audio drivers */
+        iMax = SDL_GetNumAudioDrivers();
+        SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
+        SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    } else {
+        /* A specific driver was requested for testing */
+        iMax = 1;
+    }
     for (i = 0; i < iMax; i++) {
-        audioDriver = SDL_GetAudioDriver(i);
-        SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
-        SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
-        SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+        if (audioDriver == NULL) {
+            audioDriver = SDL_GetAudioDriver(i);
+            SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
+            SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
+            SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+
+#if defined(__linux__)
+            if (DriverIsProblematic(audioDriver)) {
+                SDLTest_Log("Audio driver '%s' flagged as problematic: skipping init/quit test (set SDL_AUDIODRIVER=%s to force)", audioDriver, audioDriver);
+                audioDriver = NULL;
+                continue;
+            }
+#endif
+        }
 
         if (hint && SDL_strcmp(audioDriver, hint) != 0) {
             continue;
@@ -111,6 +165,8 @@ int audio_initQuitAudio()
         /* Call Quit */
         SDL_AudioQuit();
         SDLTest_AssertPass("Call to SDL_AudioQuit()");
+
+        audioDriver = NULL;
     }
 
     /* NULL driver specification */
@@ -151,15 +207,33 @@ int audio_initOpenCloseQuitAudio()
     SDL_QuitSubSystem(SDL_INIT_AUDIO);
     SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)");
 
-    /* Loop over all available audio drivers */
-    iMax = SDL_GetNumAudioDrivers();
-    SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
-    SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    /* Was a specific driver requested? */
+    audioDriver = SDL_GetHint(SDL_HINT_AUDIODRIVER);
+
+    if (audioDriver == NULL) {
+        /* Loop over all available audio drivers */
+        iMax = SDL_GetNumAudioDrivers();
+        SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
+        SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    } else {
+        /* A specific driver was requested for testing */
+        iMax = 1;
+    }
     for (i = 0; i < iMax; i++) {
-        audioDriver = SDL_GetAudioDriver(i);
-        SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
-        SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
-        SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+        if (audioDriver == NULL) {
+            audioDriver = SDL_GetAudioDriver(i);
+            SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
+            SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
+            SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+
+#if defined(__linux__)
+            if (DriverIsProblematic(audioDriver)) {
+                SDLTest_Log("Audio driver '%s' flagged as problematic: skipping device open/close test (set SDL_AUDIODRIVER=%s to force)", audioDriver, audioDriver);
+                audioDriver = NULL;
+                continue;
+            }
+#endif
+        }
 
         if (hint && SDL_strcmp(audioDriver, hint) != 0) {
             continue;
@@ -173,6 +247,17 @@ int audio_initOpenCloseQuitAudio()
             SDLTest_AssertPass("Call to SDL_AudioInit('%s')", audioDriver);
             SDLTest_AssertCheck(result == 0, "Validate result value; expected: 0 got: %d", result);
 
+            /* Check for output devices */
+            result = SDL_GetNumAudioDevices(SDL_FALSE);
+            SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(SDL_FALSE)");
+            SDLTest_AssertCheck(result >= 0, "Validate result value; expected: >=0 got: %d", result);
+            if (result <= 0) {
+                SDLTest_Log("No output devices for '%s': skipping device open/close test", audioDriver);
+                SDL_AudioQuit();
+                SDLTest_AssertPass("Call to SDL_AudioQuit()");
+                break;
+            }
+
             /* Set spec */
             SDL_memset(&desired, 0, sizeof(desired));
             switch (j) {
@@ -217,7 +302,10 @@ int audio_initOpenCloseQuitAudio()
             }
 
         } /* spec loop */
-    }     /* driver loop */
+
+        audioDriver = NULL;
+
+    } /* driver loop */
 
     /* Restart audio again */
     _audioSetUp(NULL);
@@ -245,15 +333,33 @@ int audio_pauseUnpauseAudio()
     SDL_QuitSubSystem(SDL_INIT_AUDIO);
     SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)");
 
-    /* Loop over all available audio drivers */
-    iMax = SDL_GetNumAudioDrivers();
-    SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
-    SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    /* Was a specific driver requested? */
+    audioDriver = SDL_GetHint(SDL_HINT_AUDIODRIVER);
+
+    if (audioDriver == NULL) {
+        /* Loop over all available audio drivers */
+        iMax = SDL_GetNumAudioDrivers();
+        SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()");
+        SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax);
+    } else {
+        /* A specific driver was requested for testing */
+        iMax = 1;
+    }
     for (i = 0; i < iMax; i++) {
-        audioDriver = SDL_GetAudioDriver(i);
-        SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
-        SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
-        SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+        if (audioDriver == NULL) {
+            audioDriver = SDL_GetAudioDriver(i);
+            SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i);
+            SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL");
+            SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */
+
+#if defined(__linux__)
+            if (DriverIsProblematic(audioDriver)) {
+                SDLTest_Log("Audio driver '%s' flagged as problematic: skipping pause/unpause test (set SDL_AUDIODRIVER=%s to force)", audioDriver, audioDriver);
+                audioDriver = NULL;
+                continue;
+            }
+#endif
+        }
 
         if (hint && SDL_strcmp(audioDriver, hint) != 0) {
             continue;
@@ -267,6 +373,16 @@ int audio_pauseUnpauseAudio()
             SDLTest_AssertPass("Call to SDL_AudioInit('%s')", audioDriver);
             SDLTest_AssertCheck(result == 0, "Validate result value; expected: 0 got: %d", result);
 
+            result = SDL_GetNumAudioDevices(SDL_FALSE);
+            SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(SDL_FALSE)");
+            SDLTest_AssertCheck(result >= 0, "Validate result value; expected: >=0 got: %d", result);
+            if (result <= 0) {
+                SDLTest_Log("No output devices for '%s': skipping pause/unpause test", audioDriver);
+                SDL_AudioQuit();
+                SDLTest_AssertPass("Call to SDL_AudioQuit()");
+                break;
+            }
+
             /* Set spec */
             SDL_memset(&desired, 0, sizeof(desired));
             switch (j) {
@@ -341,7 +457,10 @@ int audio_pauseUnpauseAudio()
             SDLTest_AssertPass("Call to SDL_AudioQuit()");
 
         } /* spec loop */
-    }     /* driver loop */
+
+        audioDriver = NULL;
+
+    } /* driver loop */
 
     /* Restart audio again */
     _audioSetUp(NULL);