protected override Task <ExitCode> InvokeInternal(ILogger logger) { logger.LogDebug($"Android Test command called: App = {_arguments.AppPackagePath}{Environment.NewLine}Instrumentation Name = {_arguments.InstrumentationName}"); logger.LogDebug($"Output Directory:{_arguments.OutputDirectory}{Environment.NewLine}Timeout = {_arguments.Timeout.TotalSeconds} seconds."); logger.LogDebug("Arguments to instrumentation:"); if (!File.Exists(_arguments.AppPackagePath)) { logger.LogCritical($"Couldn't find {_arguments.AppPackagePath}!"); return(Task.FromResult(ExitCode.PACKAGE_NOT_FOUND)); } var runner = new AdbRunner(logger); // Assumption: APKs we test will only have one arch for now string apkRequiredArchitecture; if (!string.IsNullOrEmpty(_arguments.DeviceArchitecture)) { apkRequiredArchitecture = _arguments.DeviceArchitecture; logger.LogInformation($"Will attempt to run device on specified architecture: '{apkRequiredArchitecture}'"); } else { apkRequiredArchitecture = ApkHelper.GetApkSupportedArchitectures(_arguments.AppPackagePath).First(); logger.LogInformation($"Will attempt to run device on detected architecture: '{apkRequiredArchitecture}'"); } // Package Name is not guaranteed to match file name, so it needs to be mandatory. string apkPackageName = _arguments.PackageName; int instrumentationExitCode = (int)ExitCode.GENERAL_FAILURE; try { using (logger.BeginScope("Initialization and setup of APK on device")) { // Make sure the adb server is freshly started runner.KillAdbServer(); runner.StartAdbServer(); // Wait til at least device(s) are ready runner.WaitForDevice(); // enumerate the devices attached and their architectures // Tell ADB to only use that one (will always use the present one for systems w/ only 1 machine) runner.SetActiveDevice(GetDeviceToUse(logger, runner, apkRequiredArchitecture)); // Empty log as we'll be uploading the full logcat for this execution runner.ClearAdbLog(); logger.LogDebug($"Working with {runner.GetAdbVersion()}"); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) // This is where mismatched architecture APKs fail. runner.UninstallApk(apkPackageName); if (runner.InstallApk(_arguments.AppPackagePath) != 0) { logger.LogCritical("Install failure: Test command cannot continue"); return(Task.FromResult(ExitCode.PACKAGE_INSTALLATION_FAILURE)); } runner.KillApk(apkPackageName); } // No class name = default Instrumentation (string stdOut, _, int exitCode) = runner.RunApkInstrumentation(apkPackageName, _arguments.InstrumentationName, _arguments.InstrumentationArguments, _arguments.Timeout); using (logger.BeginScope("Post-test copy and cleanup")) { if (exitCode == (int)ExitCode.SUCCESS) { (var resultValues, var instrExitCode) = ParseInstrumentationOutputs(logger, stdOut); instrumentationExitCode = instrExitCode; foreach (string possibleResultKey in _xmlOutputVariableNames) { if (resultValues.ContainsKey(possibleResultKey)) { logger.LogInformation($"Found XML result file: '{resultValues[possibleResultKey]}'(key: {possibleResultKey})"); runner.PullFiles(resultValues[possibleResultKey], _arguments.OutputDirectory); } } } // Optionally copy off an entire folder if (!string.IsNullOrEmpty(_arguments.DeviceOutputFolder)) { var logs = runner.PullFiles(_arguments.DeviceOutputFolder, _arguments.OutputDirectory); foreach (string log in logs) { logger.LogDebug($"Found output file: {log}"); } } runner.DumpAdbLog(Path.Combine(_arguments.OutputDirectory, $"adb-logcat-{_arguments.PackageName}.log")); runner.UninstallApk(apkPackageName); } if (instrumentationExitCode != (int)ExitCode.SUCCESS) { logger.LogError($"Non-success instrumentation exit code: {instrumentationExitCode}"); } else { return(Task.FromResult(ExitCode.SUCCESS)); } } catch (Exception toLog) { logger.LogCritical(toLog, $"Failure to run test package: {toLog.Message}"); } finally { runner.KillAdbServer(); } return(Task.FromResult(ExitCode.GENERAL_FAILURE)); }
protected override Task <ExitCode> InvokeInternal(ILogger logger) { logger.LogDebug($"Android Test command called: App = {_arguments.AppPackagePath}{Environment.NewLine}Instrumentation Name = {_arguments.InstrumentationName}"); logger.LogDebug($"Output Directory:{_arguments.OutputDirectory}{Environment.NewLine}Timeout = {_arguments.Timeout.TotalSeconds} seconds."); logger.LogDebug("Arguments to instrumentation:"); if (!File.Exists(_arguments.AppPackagePath)) { logger.LogCritical($"Couldn't find {_arguments.AppPackagePath}!"); return(Task.FromResult(ExitCode.PACKAGE_NOT_FOUND)); } var runner = new AdbRunner(logger); // Package Name is not guaranteed to match file name, so it needs to be mandatory. string apkPackageName = _arguments.PackageName; int instrumentationExitCode = (int)ExitCode.GENERAL_FAILURE; try { using (logger.BeginScope("Initialization and setup of APK on device")) { runner.KillAdbServer(); runner.StartAdbServer(); runner.WaitForDevice(); runner.ClearAdbLog(); logger.LogDebug($"Working with {runner.GetAdbVersion()}"); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) runner.UninstallApk(apkPackageName); if (runner.InstallApk(_arguments.AppPackagePath) != 0) { logger.LogCritical("Install failure: Test command cannot continue"); return(Task.FromResult(ExitCode.PACKAGE_INSTALLATION_FAILURE)); } runner.KillApk(apkPackageName); } // No class name = default Instrumentation (string stdOut, _, int exitCode) = runner.RunApkInstrumentation(apkPackageName, _arguments.InstrumentationName, _arguments.InstrumentationArguments, _arguments.Timeout); using (logger.BeginScope("Post-test copy and cleanup")) { if (exitCode == (int)ExitCode.SUCCESS) { (var resultValues, var instrExitCode) = ParseInstrumentationOutputs(logger, stdOut); instrumentationExitCode = instrExitCode; foreach (string possibleResultKey in _xmlOutputVariableNames) { if (resultValues.ContainsKey(possibleResultKey)) { logger.LogInformation($"Found XML result file: '{resultValues[possibleResultKey]}'(key: {possibleResultKey})"); runner.PullFiles(resultValues[possibleResultKey], _arguments.OutputDirectory); } } } // Optionally copy off an entire folder if (!string.IsNullOrEmpty(_arguments.DeviceOutputFolder)) { var logs = runner.PullFiles(_arguments.DeviceOutputFolder, _arguments.OutputDirectory); foreach (string log in logs) { logger.LogDebug($"Detected output file: {log}"); } } runner.DumpAdbLog(Path.Combine(_arguments.OutputDirectory, $"adb-logcat-{_arguments.PackageName}.log")); runner.UninstallApk(apkPackageName); } if (instrumentationExitCode != (int)ExitCode.SUCCESS) { logger.LogError($"Non-success instrumentation exit code: {instrumentationExitCode}"); } else { return(Task.FromResult(ExitCode.SUCCESS)); } } catch (Exception toLog) { logger.LogCritical(toLog, $"Failure to run test package: {toLog.Message}"); } finally { runner.KillAdbServer(); } return(Task.FromResult(ExitCode.GENERAL_FAILURE)); }
protected override Task <int> InvokeInternal() { _log.LogDebug($"Android Test command called: App = {_arguments.AppPackagePath}{Environment.NewLine}Instrumentation Name = {_arguments.InstrumentationName}"); _log.LogDebug($"Output Directory:{_arguments.OutputDirectory}{Environment.NewLine}Working Directory = {_arguments.WorkingDirectory}{Environment.NewLine}Timeout = {_arguments.Timeout.TotalSeconds} seconds."); _log.LogDebug("Arguments to instrumentation:"); if (!File.Exists(_arguments.AppPackagePath)) { _log.LogCritical($"Couldn't find {_arguments.AppPackagePath}!"); return(Task.FromResult((int)ExitCodes.PACKAGE_NOT_FOUND)); } var runner = new AdbRunner(_log); // Package Name is not guaranteed to match file name, so it needs to be mandatory. string apkPackageName = _arguments.PackageName; try { using (_log.BeginScope("Initialization and setup of APK on device")) { runner.KillAdbServer(); runner.StartAdbServer(); runner.ClearAdbLog(); _log.LogDebug($"Working with {runner.GetAdbVersion()}"); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) runner.UninstallApk(apkPackageName); if (runner.InstallApk(_arguments.AppPackagePath) != 0) { _log.LogCritical("Install failure: Test command cannot continue"); return(Task.FromResult((int)ExitCodes.PACKAGE_INSTALLATION_FAILURE)); } runner.KillApk(apkPackageName); } // No class name = default Instrumentation runner.RunApkInstrumentation(apkPackageName, _arguments.InstrumentationName, _arguments.InstrumentationArguments, _arguments.Timeout); using (_log.BeginScope("Post-test copy and cleanup")) { if (!string.IsNullOrEmpty(_arguments.DeviceOutputFolder)) { var logs = runner.PullFiles(_arguments.DeviceOutputFolder, _arguments.OutputDirectory); foreach (string log in logs) { _log.LogDebug($"Detected output file: {log}"); } } runner.DumpAdbLog(Path.Combine(_arguments.OutputDirectory, $"adb-logcat-{_arguments.PackageName}.log")); runner.UninstallApk(apkPackageName); } return(Task.FromResult((int)ExitCodes.SUCCESS)); } catch (Exception toLog) { _log.LogCritical(toLog, $"Failure to run test package: {toLog.Message}"); } finally { runner.KillAdbServer(); } return(Task.FromResult((int)ExitCodes.GENERAL_FAILURE)); }
protected override Task <int> InvokeInternal() { _log.LogDebug($"Android Test command called: App = {_arguments.AppPackagePath}{Environment.NewLine}Instrumentation Name = {_arguments.InstrumentationName}"); _log.LogDebug($"Output Directory:{_arguments.OutputDirectory}{Environment.NewLine}Working Directory = {_arguments.WorkingDirectory}{Environment.NewLine}Timeout = {_arguments.Timeout.TotalSeconds} seconds."); _log.LogDebug("Arguments to instrumentation:"); if (!File.Exists(_arguments.AppPackagePath)) { _log.LogCritical($"Couldn't find {_arguments.AppPackagePath}!"); return(Task.FromResult((int)ExitCodes.PACKAGE_NOT_FOUND)); } var runner = new AdbRunner(_log); string apkName = Path.GetFileNameWithoutExtension(_arguments.AppPackagePath); try { using (_log.BeginScope("Initialization and setup of APK on device")) { runner.KillAdbServer(); runner.StartAdbServer(); runner.ClearAdbLog(); _log.LogDebug($"Working with {runner.GetAdbVersion()}"); // If anything changed about the app, Install will fail; uninstall it first. // (we'll ignore if it's not present) runner.UninstallApk(apkName); if (runner.InstallApk(_arguments.AppPackagePath) != 0) { _log.LogCritical("Install failure: Test command cannot continue"); return(Task.FromResult((int)ExitCodes.PACKAGE_INSTALLATION_FAILURE)); } runner.KillApk(apkName); // App needs to be able to read and write the storage folders we use for IO runner.GrantPermissions(apkName, new string[] { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" }); } // No class name = default Instrumentation runner.RunApkInstrumentation(apkName, _arguments.InstrumentationName, _arguments.InstrumentationArguments); using (_log.BeginScope("Post-test copy and cleanup")) { var logs = runner.PullFiles("/sdcard/Documents/helix-results", _arguments.OutputDirectory); foreach (string log in logs) { _log.LogDebug($"Detected output file: {log}"); } runner.DumpAdbLog(Path.Combine(_arguments.OutputDirectory, "adb-logcat.log")); runner.UninstallApk(apkName); } return(Task.FromResult((int)ExitCodes.SUCCESS)); } catch (Exception toLog) { _log.LogCritical(toLog, $"Failure to run test package: {toLog.Message}"); } finally { runner.KillAdbServer(); } return(Task.FromResult((int)ExitCodes.GENERAL_FAILURE)); }
public ExitCode RunApkInstrumentation( string apkPackageName, string?instrumentationName, Dictionary <string, string> instrumentationArguments, string outputDirectory, string?deviceOutputFolder, TimeSpan timeout, int expectedExitCode) { int?instrumentationExitCode = null; // No class name = default Instrumentation ProcessExecutionResults result = _runner.RunApkInstrumentation(apkPackageName, instrumentationName, instrumentationArguments, timeout); bool processCrashed = false; bool failurePullingFiles = false; bool logCatSucceeded; using (_logger.BeginScope("Post-test copy and cleanup")) { if (result.ExitCode == (int)ExitCode.SUCCESS) { (instrumentationExitCode, processCrashed, failurePullingFiles) = ParseInstrumentationResult(apkPackageName, outputDirectory, result.StandardOutput); } // Optionally copy off an entire folder if (!string.IsNullOrEmpty(deviceOutputFolder)) { try { var logs = _runner.PullFiles(apkPackageName, deviceOutputFolder, outputDirectory); foreach (string log in logs) { _logger.LogDebug($"Found output file: {log}"); } } catch (Exception toLog) { _logger.LogError(toLog, "Hit error (typically permissions) trying to pull {filePathOnDevice}", deviceOutputFolder); failurePullingFiles = true; } } logCatSucceeded = _runner.TryDumpAdbLog(Path.Combine(outputDirectory, $"adb-logcat-{apkPackageName}-{(instrumentationName ?? "default")}.log")); if (processCrashed) { _runner.DumpBugReport(Path.Combine(outputDirectory, $"adb-bugreport-{apkPackageName}")); } } // In case emulator crashes halfway through, we can tell by failing to pull ADB logs from it if (!logCatSucceeded) { return(ExitCode.SIMULATOR_FAILURE); } if (result.ExitCode == (int)AdbExitCodes.INSTRUMENTATION_TIMEOUT) { return(ExitCode.TIMED_OUT); } if (processCrashed) { return(ExitCode.APP_CRASH); } if (failurePullingFiles) { _logger.LogError($"Received expected instrumentation exit code ({instrumentationExitCode}), " + "but we hit errors pulling files from the device (see log for details.)"); return(ExitCode.DEVICE_FILE_COPY_FAILURE); } if (!instrumentationExitCode.HasValue) { return(ExitCode.RETURN_CODE_NOT_SET); } if (instrumentationExitCode != expectedExitCode) { _logger.LogError($"Non-success instrumentation exit code: {instrumentationExitCode}, expected: {expectedExitCode}"); return(ExitCode.TESTS_FAILED); } return(ExitCode.SUCCESS); }