scripts/cake/xharness-android.cake (170 lines of code) (raw):

#addin nuget:?package=Cake.Android.Adb&version=3.2.0 #addin nuget:?package=Cake.Android.AvdManager&version=2.2.0 DirectoryPath ROOT_PATH = MakeAbsolute(Directory("../..")); #load "shared.cake" var TEST_APP = Argument("app", EnvironmentVariable("ANDROID_TEST_APP") ?? ""); var TEST_RESULTS = Argument("results", EnvironmentVariable("ANDROID_TEST_RESULTS") ?? ""); var TEST_DEVICE = Argument("device", EnvironmentVariable("ANDROID_TEST_DEVICE") ?? "android-emulator-64"); var TEST_VERSION = Argument("deviceVersion", EnvironmentVariable("ANDROID_TEST_DEVICE_VERSION") ?? "34"); var TEST_APP_PACKAGE_NAME = Argument("package", EnvironmentVariable("ANDROID_TEST_APP_PACKAGE_NAME") ?? ""); var TEST_APP_INSTRUMENTATION = Argument("instrumentation", EnvironmentVariable("ANDROID_TEST_APP_INSTRUMENTATION") ?? "devicerunners.xharness.maui.XHarnessInstrumentation"); // other var ANDROID_AVD = "DEVICE_TESTS_EMULATOR"; var DEVICE_NAME = Argument("skin", EnvironmentVariable("ANDROID_TEST_SKIN") ?? "Nexus 5X"); var DEVICE_ID = ""; var DEVICE_ARCH = ""; if (string.IsNullOrEmpty(TEST_APP)) { throw new Exception("A path to a test app is required."); } if (string.IsNullOrEmpty(TEST_RESULTS)) { TEST_RESULTS = TEST_APP + "-results"; } Information("Test Results Directory: {0}", TEST_RESULTS); CleanDir(TEST_RESULTS); // set up env var ANDROID_SDK_ROOT = Argument("android", EnvironmentVariable("ANDROID_SDK_ROOT") ?? EnvironmentVariable("ANDROID_SDK_HOME")); if (string.IsNullOrEmpty(ANDROID_SDK_ROOT)) { throw new Exception("Environment variable 'ANDROID_SDK_ROOT' must be set to the Android SDK root."); } System.Environment.SetEnvironmentVariable("PATH", $"{ANDROID_SDK_ROOT}/cmdline-tools/latest/bin" + System.IO.Path.PathSeparator + $"{ANDROID_SDK_ROOT}/platform-tools" + System.IO.Path.PathSeparator + $"{ANDROID_SDK_ROOT}/emulator" + System.IO.Path.PathSeparator + $"{ANDROID_SDK_ROOT}/tools/bin" + System.IO.Path.PathSeparator + EnvironmentVariable("PATH")); Information("Android SDK Root: {0}", ANDROID_SDK_ROOT); Information("PATH: {0}", EnvironmentVariable("PATH")); var bat = IsRunningOnWindows() ? ".bat" : ""; var exe = IsRunningOnWindows() ? ".exe" : ""; var avdSettings = new AndroidAvdManagerToolSettings { SdkRoot = ANDROID_SDK_ROOT, ToolPath = $"{ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/avdmanager{bat}" }; var adbSettings = new AdbToolSettings { SdkRoot = ANDROID_SDK_ROOT, ToolPath = $"{ANDROID_SDK_ROOT}/platform-tools/adb{exe}" }; var emuSettings = new AndroidEmulatorToolSettings { SdkRoot = ANDROID_SDK_ROOT, ToolPath = $"{ANDROID_SDK_ROOT}/emulator/emulator{exe}", Verbose = true, ArgumentCustomization = args => args.Append( "-no-boot-anim " + "-no-snapshot " + "-gpu host " + "-no-audio " + "-camera-back none " + "-camera-front none " + "-qemu -m 2048") }; AndroidEmulatorProcess emulatorProcess = null; Setup(context => { if (!string.IsNullOrEmpty(TEST_VERSION) && TEST_VERSION != "latest") TEST_DEVICE = $"{TEST_DEVICE}_{TEST_VERSION}"; Information("Test App: {0}", TEST_APP); Information("Test Device: {0}", TEST_DEVICE); Information("Test Results Directory: {0}", TEST_RESULTS); // determine the device characteristics { var working = TEST_DEVICE.Trim().ToLower(); var emulator = true; var api = 34; // version if (working.IndexOf("_") is int idx && idx > 0) { api = int.Parse(working.Substring(idx + 1)); working = working.Substring(0, idx); } var parts = working.Split('-'); // os if (parts[0] != "android") throw new Exception("Unexpected platform (expected: android) in device: " + TEST_DEVICE); // device/emulator if (parts[1] == "device") emulator = false; else if (parts[1] != "emulator" && parts[1] != "simulator") throw new Exception("Unexpected device type (expected: device|emulator) in device: " + TEST_DEVICE); // arch/bits if (parts[2] == "32") { if (emulator) DEVICE_ARCH = "x86"; else DEVICE_ARCH = "armeabi-v7a"; } else if (parts[2] == "64") { if (RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64) DEVICE_ARCH = "arm64-v8a"; else if (emulator) DEVICE_ARCH = "x86_64"; else DEVICE_ARCH = "arm64-v8a"; } DEVICE_ID = $"system-images;android-{api};google_apis;{DEVICE_ARCH}"; // we are not using a virtual device, so quit if (!emulator) return; } Information("Test Device ID: {0}", DEVICE_ID); // delete the AVD first, if it exists Information("Deleting AVD if exists: {0}...", ANDROID_AVD); try { AndroidAvdDelete(ANDROID_AVD, avdSettings); } catch { } // create the new AVD Information("Creating AVD: {0}...", ANDROID_AVD); AndroidAvdCreate(ANDROID_AVD, DEVICE_ID, DEVICE_NAME, force: true, settings: avdSettings); // start the emulator Information("Starting Emulator: {0}...", ANDROID_AVD); emulatorProcess = AndroidEmulatorStart(ANDROID_AVD, emuSettings); // wait for it to finish booting (4 mins) var waited = 0; var interval = 10; var totalMins = 4; var total = 60 * totalMins / interval; while (AdbShell("getprop sys.boot_completed", adbSettings).FirstOrDefault() != "1") { TakeSnapshot(TEST_RESULTS, $"boot-{waited:000}"); System.Threading.Thread.Sleep(interval * 1000); Information("Wating {0}/{1} seconds for the emulator to boot up.", waited * interval, total); if (waited++ > total) break; } TakeSnapshot(TEST_RESULTS, "boot-complete"); Information("Waited {0} seconds for the emulator to boot up.", waited); }); Teardown(context => { TakeSnapshot(TEST_RESULTS, "teardown"); // no virtual device was used if (emulatorProcess == null) return; // stop and cleanup the emulator AdbEmuKill(adbSettings); System.Threading.Thread.Sleep(5000); // kill the process if it has not already exited try { emulatorProcess.Kill(); } catch { } // delete the AVD try { AndroidAvdDelete(ANDROID_AVD, avdSettings); } catch { } }); Task("Default") .Does(() => { if (string.IsNullOrEmpty(TEST_APP_PACKAGE_NAME)) { var appFile = (FilePath)TEST_APP; appFile = appFile.GetFilenameWithoutExtension(); TEST_APP_PACKAGE_NAME = appFile.FullPath.Replace("-Signed", ""); } if (string.IsNullOrEmpty(TEST_APP_INSTRUMENTATION)) { TEST_APP_INSTRUMENTATION = TEST_APP_PACKAGE_NAME + ".TestInstrumentation"; } Information("Test App: {0}", TEST_APP); Information("Test App Package Name: {0}", TEST_APP_PACKAGE_NAME); Information("Test App Instrumentation: {0}", TEST_APP_INSTRUMENTATION); Information("Test Results Directory: {0}", TEST_RESULTS); TakeSnapshot(TEST_RESULTS, "starting-tests"); var complete = false; System.Threading.Tasks.Task.Run(() => { while (!complete) { TakeSnapshot(TEST_RESULTS, "running-tests"); System.Threading.Thread.Sleep(5000); } }); DotNetTool("xharness android test " + $"--app=\"{TEST_APP}\" " + $"--package-name=\"{TEST_APP_PACKAGE_NAME}\" " + $"--instrumentation=\"{TEST_APP_INSTRUMENTATION}\" " + $"--output-directory=\"{TEST_RESULTS}\" " + $"--timeout=00:15:00 " + $"--launch-timeout=00:05:00 " + $"--verbosity=\"Debug\" "); complete = true; TakeSnapshot(TEST_RESULTS, "finished-tests"); var failed = XmlPeek($"{TEST_RESULTS}/TestResults.xml", "/assemblies/assembly[@failed > 0 or @errors > 0]/@failed"); if (!string.IsNullOrEmpty(failed)) { throw new Exception($"At least {failed} test(s) failed."); } }); RunTarget(TARGET);