#!/bin/bash set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PYTHONPATH="$ROOT/lib" python3 - <<'PY' from classroom_audio import ( ANALOG_ONLY, AUTO, HDMI, candidates, choose, profile_for, settled, ) passed = failed = 0 def check(label, condition, detail=""): global passed, failed if condition: print(f" ok {label}") passed += 1 else: print(f" FAIL {label}") if detail: print(f" {detail}") failed += 1 # The shapes here are what `pactl -f json list cards` really emits, taken # from PipeWire 1.4.2 and WirePlumber 0.5.8 running in the lab. Two things # the first set of fixtures here got wrong, and both of them mattered: # # * a port carries no "direction" key at all, so nothing can be filtered # on it and the port names are doing all of the work; and # * an unknown availability is the phrase "availability unknown", not an # empty string. # # Fixtures that describe a machine nobody has cannot catch a bug on a # machine somebody does have, which is most of why 0.2.2 shipped. UNKNOWN_AVAILABILITY = "availability unknown" # Every profile a real Intel HDA card lists against these ports, trimmed # to the ones that matter here: each output appears both on its own and # paired with the analog input. ANALOG_PROFILES = [ "output:analog-stereo", "output:analog-stereo+input:analog-stereo", "output:analog-surround-40", "output:analog-surround-40+input:analog-stereo", ] HDMI_PROFILES = [ "output:hdmi-stereo", "output:hdmi-stereo+input:analog-stereo", "output:hdmi-surround", "output:hdmi-surround+input:analog-stereo", ] def card(name, ports, active_profile="output:analog-stereo+input:analog-stereo"): return { "name": name, "index": 0, "active_profile": active_profile, "ports": { port: { "availability": availability, "priority": priority, "profiles": profiles, } for port, availability, priority, profiles in ports }, } # The room this policy was written for. A Samsung television on HDMI with # working PCM audio, and an onboard analog jack with nothing in it that # reports itself unavailable. WirePlumber chose analog. THE_ROOM = [card("alsa_card.pci-0000_00_1f.3", [ ("analog-output-lineout", "not available", 9900, ANALOG_PROFILES), ("hdmi-output-0", "available", 5900, HDMI_PROFILES), ])] print("1. the classroom that went to analog") chosen = choose(THE_ROOM, AUTO) check("auto picks the television, not the empty jack", chosen and chosen["port"] == "hdmi-output-0", f"chose {chosen and chosen['port']}") check("and the stereo television profile that keeps the input", profile_for(chosen) == "output:hdmi-stereo+input:analog-stereo", f"chose {profile_for(chosen)}") check("which is the profile an administrator reaches for by hand", profile_for(chosen) == "output:hdmi-stereo+input:analog-stereo") print("2. an unavailable digital port is not a candidate") TV_OFF = [card("alsa_card.pci", [ ("analog-output-lineout", "available", 9900, ANALOG_PROFILES), ("hdmi-output-0", "not available", 5900, HDMI_PROFILES), ])] chosen = choose(TV_OFF, AUTO) check("auto falls back to analog with the television off", chosen and chosen["port"] == "analog-output-lineout", f"chose {chosen and chosen['port']}") print("3. availability is not always reported") UNKNOWN = [card("alsa_card.pci", [ ("analog-output-lineout", UNKNOWN_AVAILABILITY, 9900, ANALOG_PROFILES), ("hdmi-output-0", UNKNOWN_AVAILABILITY, 5900, HDMI_PROFILES), ])] chosen = choose(UNKNOWN, AUTO) check("a silent card still prefers the digital output", chosen and chosen["port"] == "hdmi-output-0", f"chose {chosen and chosen['port']}") check("the real phrase for unknown is read as unknown, not as absent", [item for item in candidates(UNKNOWN) if item["availability"] == "unknown"], "'availability unknown' was not understood") print("4. an available analog jack does not outrank a live television") BOTH = [card("alsa_card.pci", [ ("analog-output-lineout", "available", 9900, ANALOG_PROFILES), ("hdmi-output-0", "available", 5900, HDMI_PROFILES), ])] chosen = choose(BOTH, AUTO) check("auto still picks HDMI when both are connected", chosen and chosen["port"] == "hdmi-output-0", f"chose {chosen and chosen['port']}") print("5. explicit overrides") check("analog forces the jack even with a live television", choose(BOTH, ANALOG_ONLY)["port"] == "analog-output-lineout") check("hdmi forces the television even when it is off", choose(TV_OFF, HDMI)["port"] == "hdmi-output-0") check("hdmi still falls back rather than leaving the room silent", choose([card("alsa_card.pci", [ ("analog-output-lineout", "available", 9900, ANALOG_PROFILES), ])], HDMI)["port"] == "analog-output-lineout") print("6. two displays") TWO = [card("alsa_card.pci", [ ("hdmi-output-0", "not available", 5900, HDMI_PROFILES), ("hdmi-output-1", "available", 5800, ["output:hdmi-stereo-extra1", "output:hdmi-stereo-extra1+input:analog-stereo"]), ])] chosen = choose(TWO, AUTO) check("the one that is switched on wins, whatever its priority", chosen and chosen["port"] == "hdmi-output-1", f"chose {chosen and chosen['port']}") print("7. nothing at all") check("a machine with no sound card decides nothing rather than crashing", choose([], AUTO) is None) check("and a card with only inputs is not an output", choose([{"name": "c", "index": 0, "ports": { "analog-input-mic": {"availability": UNKNOWN_AVAILABILITY, "priority": 100, "profiles": ["input:analog-stereo"]}}}], AUTO) is None) print("8. ports that are not outputs of interest are ignored") check("a bluetooth headset port is not mistaken for a display", not [item for item in candidates([card("bt", [ ("headset-output", "available", 100, ["headset-head-unit"])])]) if item["kind"] == HDMI]) print("9. the profile chosen is always a real one on the port") for fixture in (THE_ROOM, TV_OFF, UNKNOWN, BOTH, TWO): pick = choose(fixture, AUTO) check(f"{pick['port']}: profile is one the port actually offers", profile_for(pick) in pick["profiles"], f"{profile_for(pick)} not in {pick['profiles']}") print("10. stereo is preferred over surround for a television") check("a card offering only surround still yields an output profile", profile_for({"profiles": ["output:hdmi-surround71", "output:hdmi-surround71+input:analog-stereo"]}) == "output:hdmi-surround71+input:analog-stereo") check("a port with no profiles at all decides nothing", profile_for({"profiles": []}) is None) print("11. knowing whether there is anything left to wait for") # This is what stops the room polling when it is already right, and what # keeps it looking when a television might still be switched on. check("a room already on its television is settled", settled(THE_ROOM, AUTO, choose(THE_ROOM, AUTO))) check("a room on analog with an HDMI port that is off is NOT settled", not settled(TV_OFF, AUTO, choose(TV_OFF, AUTO)), "a television switched on later would never be looked for") check("a machine with no digital output at all is settled on analog", settled([card("alsa_card.pci", [ ("analog-output-lineout", "available", 9900, ANALOG_PROFILES), ])], AUTO, choose([card("alsa_card.pci", [ ("analog-output-lineout", "available", 9900, ANALOG_PROFILES), ])], AUTO)), "a laptop with only a jack would poll for a television forever") check("an analog preference is settled once it has the jack", settled(BOTH, ANALOG_ONLY, choose(BOTH, ANALOG_ONLY))) check("a machine with no sound card at all is settled", settled([], AUTO, None)) print() print(f"{passed} passed, {failed} failed") raise SystemExit(1 if failed else 0) PY