Repository: beeware/briefcase
Pull request #1697 (2024-03-14 16:18:42)

Exclude logging output from emulator list
## Changes
- Recently, `emulator -list-avds` started including logging output in the list of AVDs...so, it's ignored now

```
>>> Running Command:
>>>     /opt/sdks/Android/sdk/emulator/emulator -list-avds
>>> Working Directory:
>>>     /home/russell/tmp/beeware/helloworld
>>> Command Output:
>>>     INFO    | Storing crashdata in: /tmp/android-russell/emu-crash-34.1.19.db, detection is enabled for process: 2413840
>>>     Pixel_3a_API_31
>>>     Pixel_5_API_31
>>>     Pixel_6a_API_31
>>>     Pixel_7a_API_34
>>>     Pixel_8_Pro_API_31
>>>     beePhone
>>> Return code: 0

Select device:

  1) Unknown device (offline) (emulator-5554)
  2) @INFO    | Storing crashdata in: /tmp/android-russell/emu-crash-34.1.19.db, detection is enabled for process: 2413840 (emulator)
  3) @Pixel_3a_API_31 (emulator)
  4) @Pixel_5_API_31 (emulator)
  5) @Pixel_6a_API_31 (emulator)
  6) @Pixel_7a_API_34 (emulator)
  7) @Pixel_8_Pro_API_31 (emulator)
  8) @beePhone (emulator)
  9) Create a new Android emulator

>
```
Other [mayhem](https://www.google.com/search?q=emulator+list-avds+%22INFO++++%7C+Storing+crashdata+in%22&sca_esv=941af24518045317&sxsrf=ACQVn09ejTmdHe31ARSr4fK7_DdAm8JHHQ%3A1710433939367&source=hp&ei=kybzZZeCFOGsur8P79OjoAs&iflsig=ANes7DEAAAAAZfM0oz8dWdGcNrA2xkBX2HKbd01kBRNB&ved=0ahUKEwjXl_b6lvSEAxVhlu4BHe_pCLQQ4dUDCBc&uact=5&oq=emulator+list-avds+%22INFO++++%7C+Storing+crashdata+in%22&gs_lp=Egdnd3Mtd2l6IjNlbXVsYXRvciBsaXN0LWF2ZHMgIklORk8gICAgfCBTdG9yaW5nIGNyYXNoZGF0YSBpbiIyBxAhGAoYoAEyBxAhGAoYoAEyBxAhGAoYoAEyBxAhGAoYoAFImi1Q0QVY1ytwAXgAkAEAmAGaAaABxA-qAQQxNS42uAEDyAEA-AEB-AECmAIWoAKAEKgCCsICBxAjGOoCGCfCAgoQIxiABBiKBRgnwgIEECMYJ8ICERAuGIAEGLEDGIMBGMcBGNEDwgILEAAYgAQYsQMYgwHCAgsQLhiABBixAxiDAcICDhAuGIAEGLEDGMcBGNEDwgIFEAAYgATCAg4QLhiABBiKBRixAxiDAcICCBAuGIAEGLEDwgIFEC4YgATCAgsQLhiABBixAxjUAsICCBAAGIAEGLEDwgIOEAAYgAQYigUYsQMYgwHCAgsQABiABBiKBRiGA8ICBhAAGBYYHpgDBZIHBDE1LjegB4qEAQ&sclient=gws-wiz) caused by this logging.

## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [X] All new features have been tested
- [X] All new features have been documented
- [X] I have read the **CONTRIBUTING.md** file
- [X] I will abide by the code of conduct


Patch:
diff --git a/changes/1697.bugfix.rst b/changes/1697.bugfix.rst
new file mode 100644
index 00000000..2db576c1
--- /dev/null
+++ b/changes/1697.bugfix.rst
@@ -0,0 +1,1 @@
+A spurious Android emulator named ``@INFO`` will no longer be included in the list of available emulators.
diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py
index 40ff8ca4..40802db9 100644
--- a/src/briefcase/integrations/android_sdk.py
+++ b/src/briefcase/integrations/android_sdk.py
@@ -814,10 +814,10 @@ connection.
                     extract_dir=skin_path,
                     **({"filter": "data"} if sys.version_info >= (3, 12) else {}),
                 )
-            except (shutil.ReadError, EOFError) as err:
+            except (shutil.ReadError, EOFError) as e:
                 raise BriefcaseCommandError(
                     f"Unable to unpack {skin} device skin."
-                ) from err
+                ) from e
 
             # Delete the downloaded file.
             skin_tgz_path.unlink()
@@ -825,17 +825,18 @@ connection.
     def emulators(self) -> list[str]:
         """Find the list of emulators that are available."""
         try:
-            # Capture `stderr` so that if the process exits with failure, the
-            # stderr data is in `e.output`.
-            output = self.tools.subprocess.check_output(
+            emulators = self.tools.subprocess.check_output(
                 [self.emulator_path, "-list-avds"]
             ).strip()
-            # AVD names are returned one per line.
-            if len(output) == 0:
-                return []
-            return output.split("\n")
         except subprocess.CalledProcessError as e:
             raise BriefcaseCommandError("Unable to obtain Android emulator list") from e
+        else:
+            return [
+                emu
+                for emu in emulators.split("\n")
+                # ignore any logging output included in output list
+                if emu and not emu.startswith(("INFO    |", "WARNING |", "ERROR   |"))
+            ]
 
     def devices(self) -> dict[str, dict[str, str | bool]]:
         """Find the devices that are attached and available to ADB."""


Test patch:
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_emulators.py b/tests/integrations/android_sdk/AndroidSDK/test_emulators.py
index 1619345c..7a9fb001 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_emulators.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_emulators.py
@@ -5,32 +5,35 @@ import pytest
 from briefcase.exceptions import BriefcaseCommandError
 
 
-def test_no_emulators(mock_tools, android_sdk):
-    """If there are no emulators, an empty list is returned."""
-    mock_tools.subprocess.check_output.return_value = ""
-
-    assert android_sdk.emulators() == []
-
-
-def test_one_emulator(mock_tools, android_sdk):
-    """If there is a single emulator, it is returned."""
-    mock_tools.subprocess.check_output.return_value = "first\n"
-
-    assert android_sdk.emulators() == ["first"]
-
-
-def test_multiple_emulators(mock_tools, android_sdk):
-    """If there are multiple emulators, they are all returned."""
-    mock_tools.subprocess.check_output.return_value = "first\nsecond\nthird\n"
-
-    assert android_sdk.emulators() == ["first", "second", "third"]
-
-
-def test_adb_error(mock_tools, android_sdk):
-    """If there is a problem invoking adb, an error is returned."""
+@pytest.mark.parametrize(
+    "output, expected_list",
+    [
+        ("", []),
+        ("first\n", ["first"]),
+        ("first\nsecond\nthird\n", ["first", "second", "third"]),
+        ("first\n\nsecond", ["first", "second"]),
+        (
+            "first\nINFO    | Storing crashdata in\nsecond\nWARNING | nothing to see\n"
+            "third\nERROR   | lot to see here",
+            ["first", "second", "third"],
+        ),
+    ],
+)
+def test_no_emulators(mock_tools, android_sdk, output, expected_list):
+    """The returned list of emulators is properly parsed."""
+    mock_tools.subprocess.check_output.return_value = output
+
+    assert android_sdk.emulators() == expected_list
+
+
+def test_emulator_error(mock_tools, android_sdk):
+    """If there is a problem invoking emulator, an error is returned."""
     mock_tools.subprocess.check_output.side_effect = subprocess.CalledProcessError(
-        returncode=69, cmd="adb devices -l"
+        returncode=69, cmd="emulator -list-avd"
     )
 
-    with pytest.raises(BriefcaseCommandError):
+    with pytest.raises(
+        BriefcaseCommandError,
+        match=r"Unable to obtain Android emulator list",
+    ):
         android_sdk.emulators()