266 lines
8.6 KiB
Python
Executable File
266 lines
8.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Run automatic Maestro groups and guarded destructive manual targets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from collections.abc import Sequence
|
|
|
|
import run_maestro
|
|
|
|
|
|
ANDROID_15_INSTRUMENTATION_CLASSES = (
|
|
"androidx.media3.decoder.ffmpeg.PlezyFfmpegPlaybackTest,"
|
|
"com.edde746.plezy.exoplayer.PlezyAudioModePlaybackTest"
|
|
)
|
|
ANDROID_15_INSTRUMENTATION_TARGET = "android-15-instrumentation"
|
|
# Separate R8 reachability from instrumentation: these suites exercise APIs the app
|
|
# does not call directly, so shrinking them is expected. Covers name-based reachability (#1703).
|
|
ANDROID_R8_REACHABILITY_CLASSES = "androidx.media3.decoder.ffmpeg.FfmpegDecoderReachabilityTest"
|
|
ANDROID_R8_REACHABILITY_TARGET = "android-r8-reachability"
|
|
|
|
|
|
GROUPS: dict[str, tuple[tuple[str, ...], ...]] = {
|
|
"android-15": (
|
|
("basic",),
|
|
("catalog",),
|
|
("media",),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/03_tv_library_focus.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-tv/library-focus.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-tv/library-focus-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/04_tv_player_keys.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-tv/player-keys.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-tv/player-keys-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/05_tv_next_episode_back.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-tv/next-episode.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-tv/next-episode-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/07_sheet_back_dismiss.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-sheets/back-dismiss.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-sheets/back-dismiss-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/08_track_choice_survives_pending_pass.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-tracks/pending-pass.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-tracks/pending-pass-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/09_language_picker_locales.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-i18n/language-picker.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-i18n/language-picker-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--fault",
|
|
"music-failure",
|
|
"--flow",
|
|
".maestro/real_flows/02_music_browse.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-recovery/music-jellyfin.log",
|
|
"--proxy-journal",
|
|
"build/maestro-recovery/music-proxy-journal.jsonl",
|
|
"--diagnostics-dir",
|
|
"build/maestro-recovery/music-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--fault",
|
|
"offline",
|
|
"--flow",
|
|
".maestro/flows/09_download_offline_playback.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-offline/jellyfin.log",
|
|
"--proxy-log",
|
|
"build/maestro-offline/jellyfin-proxy.log",
|
|
"--proxy-journal",
|
|
"build/maestro-offline/proxy-journal.jsonl",
|
|
"--diagnostics-dir",
|
|
"build/maestro-offline/diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--fault",
|
|
"recovery",
|
|
"--flow",
|
|
".maestro/regression_flows/06_playback_recovery.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-recovery/jellyfin.log",
|
|
"--proxy-journal",
|
|
"build/maestro-recovery/proxy-journal.jsonl",
|
|
"--diagnostics-dir",
|
|
"build/maestro-recovery/diagnostics",
|
|
),
|
|
),
|
|
"android-9": (
|
|
(
|
|
"basic",
|
|
# API 28 uses adb reverse because 10.0.2.2 routing is unreliable here.
|
|
"--adb-reverse",
|
|
"--flow",
|
|
".maestro/flows/05_playback.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-legacy/jellyfin.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-legacy/diagnostics",
|
|
),
|
|
),
|
|
# Manual Android TV hardware target; workflow runs phone-emulator TV regressions.
|
|
"android-tv-device": (
|
|
(
|
|
"basic",
|
|
"--adb-reverse",
|
|
"--flow",
|
|
".maestro/regression_flows/10_tv_settings_navigation.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-tv-device/settings-navigation.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-tv-device/settings-navigation-diagnostics",
|
|
),
|
|
),
|
|
}
|
|
|
|
|
|
DESTRUCTIVE_MANUAL_TARGETS: dict[str, tuple[tuple[str, ...], ...]] = {
|
|
"profile-regressions": (
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/01_profile_switch_isolation.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-profile-regressions/profile-switch-isolation-jellyfin.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-profile-regressions/profile-switch-isolation-diagnostics",
|
|
),
|
|
(
|
|
"basic",
|
|
"--flow",
|
|
".maestro/regression_flows/02_profile_teardown.yaml",
|
|
"--jellyfin-log",
|
|
"build/maestro-profile-regressions/profile-teardown-jellyfin.log",
|
|
"--diagnostics-dir",
|
|
"build/maestro-profile-regressions/profile-teardown-diagnostics",
|
|
),
|
|
),
|
|
}
|
|
|
|
|
|
def run_android_15_instrumentation() -> None:
|
|
print("==> Android 15 filtered instrumentation", flush=True)
|
|
run_maestro._run_checked(
|
|
(
|
|
"android/gradlew",
|
|
"-p",
|
|
"android",
|
|
":app:connectedDebugAndroidTest",
|
|
"-x",
|
|
":app:compileFlutterBuildDebug",
|
|
f"-Pandroid.testInstrumentationRunnerArguments.class={ANDROID_15_INSTRUMENTATION_CLASSES}",
|
|
)
|
|
)
|
|
|
|
|
|
def run_android_r8_reachability() -> None:
|
|
print("==> Android R8 reachability", flush=True)
|
|
# Run minified instrumentation to catch reflective, JNI, and native-load keep
|
|
# regressions. Do not exclude the Flutter build; CI only prebuilds debug.
|
|
run_maestro._run_checked(
|
|
(
|
|
"android/gradlew",
|
|
"-p",
|
|
"android",
|
|
":app:connectedMinifiedAndroidTest",
|
|
"-Pplezy.testBuildType=minified",
|
|
f"-Pandroid.testInstrumentationRunnerArguments.class={ANDROID_R8_REACHABILITY_CLASSES}",
|
|
)
|
|
)
|
|
|
|
|
|
def run_recipes(recipes: tuple[tuple[str, ...], ...]) -> int:
|
|
failed = False
|
|
for arguments in recipes:
|
|
print(f"==> Maestro {' '.join(arguments)}", flush=True)
|
|
exit_status = run_maestro.main(arguments)
|
|
if exit_status >= 128:
|
|
return exit_status
|
|
failed = failed or exit_status != 0
|
|
return 1 if failed else 0
|
|
|
|
|
|
def run_group(name: str) -> int:
|
|
return run_recipes(GROUPS[name])
|
|
|
|
|
|
def run_target(name: str, *, disposable_emulator: bool = False) -> int:
|
|
if name in DESTRUCTIVE_MANUAL_TARGETS:
|
|
if not disposable_emulator:
|
|
print(
|
|
f"Refusing destructive manual target {name!r}: "
|
|
"re-run with --disposable-emulator only on a disposable emulator.",
|
|
file=sys.stderr,
|
|
)
|
|
return 2
|
|
print(f"==> DESTRUCTIVE manual Maestro target: {name}", flush=True)
|
|
return run_recipes(DESTRUCTIVE_MANUAL_TARGETS[name])
|
|
if name == ANDROID_15_INSTRUMENTATION_TARGET:
|
|
run_android_15_instrumentation()
|
|
return 0
|
|
if name == ANDROID_R8_REACHABILITY_TARGET:
|
|
run_android_r8_reachability()
|
|
return 0
|
|
return run_group(name)
|
|
|
|
|
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"target",
|
|
choices=(
|
|
*GROUPS,
|
|
ANDROID_15_INSTRUMENTATION_TARGET,
|
|
ANDROID_R8_REACHABILITY_TARGET,
|
|
*DESTRUCTIVE_MANUAL_TARGETS,
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--disposable-emulator",
|
|
action="store_true",
|
|
help="opt in to a destructive manual target on a disposable emulator",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
return run_target(args.target, disposable_emulator=args.disposable_emulator)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|