diff --git a/tools/device_broker/java/com/google/android/apps/common/testing/broker/AdbController.java b/tools/device_broker/java/com/google/android/apps/common/testing/broker/AdbController.java index 9fc0634eb..57d5f7ee8 100644 --- a/tools/device_broker/java/com/google/android/apps/common/testing/broker/AdbController.java +++ b/tools/device_broker/java/com/google/android/apps/common/testing/broker/AdbController.java @@ -1219,128 +1219,165 @@ private String getClassPathForTestServices() { } private List runTest( - Instrumentation instrumentation, - String testMethodTarget, - boolean collectCodeCoverage, - @Nullable String coverageDataPath, - boolean enableDebug, - HostTestSize size, - boolean dumpHProfData, - boolean withAnimation, - Map extraInstrumentationOptions) { + Instrumentation instrumentation, + String testMethodTarget, + boolean collectCodeCoverage, + @Nullable String coverageDataPath, + boolean enableDebug, + HostTestSize size, + boolean dumpHProfData, + boolean withAnimation, + Map extraInstrumentationOptions) { + + long testTimeout = size.getTestTimeout(TimeUnit.SECONDS); + if (testTimeoutOverride.isPresent()) { + testTimeout = testTimeoutOverride.get(); + } - List adbArgs = Lists.newArrayList(); + String shellCommand = + buildInstrumentationShellCommand( + instrumentation, + testMethodTarget, + collectCodeCoverage, + coverageDataPath, + enableDebug, + dumpHProfData, + withAnimation, + extraInstrumentationOptions, + testTimeout); - if (installBasicServices) { - // We exec the instrumentation through a wrapper launcher defined in basic_services.apk to - // allow test code to execute shell commands with root|shell user privileges. - adbArgs.add("CLASSPATH=" + getClassPathForTestServices()); - adbArgs.add("SM_EXIT=1"); - adbArgs.add("app_process / androidx.test.services.shellexecutor.ShellMain"); - } + InstrumentationTestRunnerProcessor stdoutProcessor = + new InstrumentationTestRunnerProcessor(new EventBus()); + SimpleLineListProcessor stderrProcessor = new SimpleLineListProcessor(); + SubprocessCommunicator.Builder builder = + communicatorBuilderProvider + .get() + .withStdoutProcessor(stdoutProcessor) + .withStderrProcessor(stderrProcessor); - adbArgs.add("am"); - adbArgs.add("instrument"); + List partialArgs = Lists.newArrayList("shell", shellCommand); - if (collectCodeCoverage) { - adbArgs.addAll(Lists.newArrayList("-e", "coverage", "true")); - adbArgs.addAll(Lists.newArrayList( - "-e", "coverageDataPath", coverageDataPath)); - } - if (enableDebug) { - adbArgs.addAll(Lists.newArrayList("-e", "debug", "true")); - } - - if (dumpHProfData) { - adbArgs.addAll(Lists.newArrayList("-e", "hprofDataFile", "hprof.dump")); + try { + makeCheckedCall( + builder, + prefixArgsWithDeviceSerial(partialArgs.toArray(new String[0])), + testTimeout); + } catch (IllegalStateException e) { + StringBuilder allLines = new StringBuilder(); + for (ExecutedTest executedTest : stdoutProcessor.getResult()) { + allLines.append(executedTest.getAllLines()); } + throw new RuntimeException( + String.format( + "Error when executing adb.\n STDOUT: %s\n STDERR: %s", + allLines, Joiner.on("\n").join(stderrProcessor.getResult())), + e); + } - for (String key : extraInstrumentationOptions.keySet()) { - adbArgs.add("-e"); - adbArgs.add(ShellUtils.shellEscape(key)); - adbArgs.add(ShellUtils.shellEscape(extraInstrumentationOptions.get(key))); - } + return stdoutProcessor.getResult(); +} - if (!withAnimation) { - if (device.getApiVersion() >= 10) { - adbArgs.add("--no_window_animation"); - } // not supported for less then gingerbread. - } - long testTimeout = size.getTestTimeout(TimeUnit.SECONDS); - if (testTimeoutOverride.isPresent()) { - testTimeout = testTimeoutOverride.get(); - } +/** + * Constrói a linha de comando a ser passada para {@code adb shell} contendo a invocação completa + * do {@code am instrument}. + */ +private String buildInstrumentationShellCommand( + Instrumentation instrumentation, + String testMethodTarget, + boolean collectCodeCoverage, + @Nullable String coverageDataPath, + boolean enableDebug, + boolean dumpHProfData, + boolean withAnimation, + Map extraInstrumentationOptions, + long testTimeout) { + + List tokens = Lists.newArrayList(); + + // Prefixo opcional para serviços básicos (ShellMain) + if (installBasicServices) { + tokens.add("CLASSPATH=" + getClassPathForTestServices()); + tokens.add("SM_EXIT=1"); + tokens.add("app_process / androidx.test.services.shellexecutor.ShellMain"); + } - adbArgs.addAll( - Lists.newArrayList( - "-e", "testTimeoutSeconds", String.valueOf(testTimeout))); - - boolean runTestsThroughOrchestrator = installBasicServices - && ORCHESTRATOR_ENABLED_RUNNERS.contains(instrumentation.getInstrumentationClass()); - - - if (runTestsThroughOrchestrator) { - adbArgs.addAll( - Lists.newArrayList( - "-r", - "-w", - "-e", - "class", - ShellUtils.shellEscape(testMethodTarget), - "-e", - "targetInstrumentation", - instrumentation.getFullName(), - ORCHESTRATOR_COMPONENT_NAME)); - } else { - adbArgs.addAll( - Lists.newArrayList( - "-r", - "-w", - "-e", - "class", - ShellUtils.shellEscape(testMethodTarget), - instrumentation.getFullName())); - } + tokens.add("am"); + tokens.add("instrument"); + + // Opções de cobertura + if (collectCodeCoverage) { + tokens.add("-e"); + tokens.add("coverage"); + tokens.add("true"); + tokens.add("-e"); + tokens.add("coverageDataPath"); + tokens.add(coverageDataPath); + } - if (installBasicServices) { - // adb shell commands _may_ return their exit code from the device - if they are run on the - // right system image and the host machine has the right version of adb installed - // otherwise they wont. SM_EXIT kills itself at the end, so we do not want that - // exit code bubbling up. - adbArgs.add("||"); - adbArgs.add("true"); - } + // Debug + if (enableDebug) { + tokens.add("-e"); + tokens.add("debug"); + tokens.add("true"); + } - InstrumentationTestRunnerProcessor stdoutProcessor = - new InstrumentationTestRunnerProcessor(new EventBus()); - SimpleLineListProcessor stderrProcessor = new SimpleLineListProcessor(); - SubprocessCommunicator.Builder builder = communicatorBuilderProvider.get(); - builder - .withStdoutProcessor(stdoutProcessor) - .withStderrProcessor(stderrProcessor); - String shellArgs = Joiner.on(" ").join(adbArgs); + // HPROF + if (dumpHProfData) { + tokens.add("-e"); + tokens.add("hprofDataFile"); + tokens.add("hprof.dump"); + } - List partialArgs = Lists.newArrayList("shell", shellArgs); + // Extras genéricos + for (Map.Entry entry : extraInstrumentationOptions.entrySet()) { + tokens.add("-e"); + tokens.add(ShellUtils.shellEscape(entry.getKey())); + tokens.add(ShellUtils.shellEscape(entry.getValue())); + } - try { - makeCheckedCall(builder, - prefixArgsWithDeviceSerial(partialArgs.toArray(new String[partialArgs.size()])), - testTimeout); - } catch (IllegalStateException e) { - StringBuilder allLines = new StringBuilder(); - for (ExecutedTest executedTest : stdoutProcessor.getResult()) { - allLines.append(executedTest.getAllLines()); - } + // Animação (apenas API ≥ 10) + if (!withAnimation && device.getApiVersion() >= 10) { + tokens.add("--no_window_animation"); + } - throw new RuntimeException(String.format( - "Error when executing adb.\n STDOUT: %s\n STDERR: %s", - allLines, - Joiner.on("\n").join(stderrProcessor.getResult())), e); - } + // Timeout + tokens.add("-e"); + tokens.add("testTimeoutSeconds"); + tokens.add(String.valueOf(testTimeout)); + + // Decisão sobre orquestrador + boolean useOrchestrator = + installBasicServices + && ORCHESTRATOR_ENABLED_RUNNERS.contains(instrumentation.getInstrumentationClass()); + + if (useOrchestrator) { + tokens.add("-r"); + tokens.add("-w"); + tokens.add("-e"); + tokens.add("class"); + tokens.add(ShellUtils.shellEscape(testMethodTarget)); + tokens.add("-e"); + tokens.add("targetInstrumentation"); + tokens.add(instrumentation.getFullName()); + tokens.add(ORCHESTRATOR_COMPONENT_NAME); + } else { + tokens.add("-r"); + tokens.add("-w"); + tokens.add("-e"); + tokens.add("class"); + tokens.add(ShellUtils.shellEscape(testMethodTarget)); + tokens.add(instrumentation.getFullName()); + } - return stdoutProcessor.getResult(); + // Se usamos serviços básicos, ocultamos o código de saída do ShellMain + if (installBasicServices) { + tokens.add("||"); + tokens.add("true"); } + return Joiner.on(" ").join(tokens); +} + private boolean isApkAlreadyInstalled(String apkPath, String appPackageName) throws IOException { checkNotNull(apkPath); checkNotNull(appPackageName);