Skip to content

Commit

Permalink
Internal change
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 725956223
  • Loading branch information
DeviceInfra authored and copybara-github committed Feb 12, 2025
1 parent f53ed91 commit 8c6b336
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public static AndroidInstrumentationSetting create(
boolean showRawResults,
boolean prefixAndroidTest,
boolean noIsolatedStorage,
boolean useTestStorageService) {
boolean useTestStorageService,
boolean enableCoverage) {
return new AutoValue_AndroidInstrumentationSetting(
checkNotNull(packageName),
checkNotNull(runnerName),
Expand All @@ -46,7 +47,8 @@ public static AndroidInstrumentationSetting create(
showRawResults,
prefixAndroidTest,
noIsolatedStorage,
useTestStorageService);
useTestStorageService,
enableCoverage);
}

/** The Android package name of the test package. */
Expand Down Expand Up @@ -78,6 +80,9 @@ public static AndroidInstrumentationSetting create(
/** Whether to whether to disable isolated-storage for instrumentation command. */
public abstract boolean noIsolatedStorage();

/** Whether to set useTesstStorageService in instrumentation command */
/** Whether to set useTestStorageService in instrumentation command */
public abstract boolean useTestStorageService();

/** Whether to enable coverage for the instrumentation command. */
public abstract boolean enableCoverage();
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ public interface AppInstallFinishHandler {
static final String ADB_SHELL_NOHUP_END = "\"";

/** Internal use device path for AndroidX test library. */
private static final String ANDROID_TEST_DEVICE_PATH_INTERNAL_USE =
@VisibleForTesting
static final String ANDROID_TEST_DEVICE_PATH_INTERNAL_USE =
TestStorageConstants.ON_DEVICE_PATH_INTERNAL_USE;

/** Test running files device path for AndroidX test library. */
Expand Down Expand Up @@ -286,6 +287,7 @@ public String instrument(
boolean prefixAndroidTest = instrumentationSetting.prefixAndroidTest();
boolean noIsolatedStorage = instrumentationSetting.noIsolatedStorage();
boolean useTestStorageService = instrumentationSetting.useTestStorageService();
boolean enableCoverage = instrumentationSetting.enableCoverage();

if (async) {
command.append(ADB_SHELL_NOHUP_START);
Expand Down Expand Up @@ -336,6 +338,13 @@ public String instrument(
command.append(" -e useTestStorageService true");
}

if (enableCoverage) {
command.append(" -e coverage true");
command
.append(" -e coverageFile ")
.append(StrUtil.convertClassNameToCoverageFileName(className));
}

command.append(' ');
command.append(packageName);
command.append('/');
Expand Down Expand Up @@ -879,6 +888,92 @@ public void processInstrumentOutputFiles(
}
}

/**
* Processes the test instrument coverage files generated when coverage is enabled. Pulls them out
* from device and will be sent to Sponge at the end of the whole processing workflow.
*
* @param testInfo The target {@code testInfo} of this test
* @param deviceId The related Android device
* @param externalStoragePath External storage path of the generated files
* @throws MobileHarnessException if fails to execute the commands or timeout
* @throws InterruptedException InterruptedException when adb pull gets interrupted
*/
public void processInstrumentCoverageFiles(
TestInfo testInfo, String deviceId, String externalStoragePath)
throws InterruptedException, MobileHarnessException {
if (isNullExternalStoragePath(testInfo, externalStoragePath)) {
return;
}

int deviceSdkVersion = getDeviceSdkVersion(testInfo.warnings(), deviceId);
if (deviceSdkVersion == 0) {
return;
}

String tmpHostDir = PathUtil.join(testInfo.getTmpFileDir(), "pull_" + UUID.randomUUID());
boolean hasInternalUseFile;
try {
if (deviceSdkVersion > AndroidVersion.PI.getEndSdkVersion()) {
// Use content read for Android Q and above.
hasInternalUseFile =
contentReadFolder(
deviceId,
String.format("content://%s", TestStorageConstants.INTERNAL_USE_PROVIDER_AUTHORITY),
tmpHostDir,
/* subPath= */ null,
getCurrentUser(deviceId, deviceSdkVersion).orElse(null));
} else {
// Use adb pull for Android Q-.
// For Android P-, there is no isolated-storage feature, so no need to check sandbox folder.
String devicePath =
PathUtil.join(externalStoragePath, ANDROID_TEST_DEVICE_PATH_INTERNAL_USE);
// Do not create the host folder before pull files.
// Pulls the device output folder /sdcard/googletest/test_outputfiles/, to new TMP_FILE host
// folder which doesn't exist, to make sure it doesn't create the sub-directory
// test_outputfiles under it. See b/32065697.
hasInternalUseFile =
pullInstrumentFiles(
testInfo, deviceId, devicePath, tmpHostDir, /* shouldCreateFolder= */ false);
}

if (!hasInternalUseFile) {
testInfo
.log()
.atInfo()
.alsoTo(logger)
.log(
"Skip pulling instrumentation internal use files from device %s as directory"
+ " doesn't exist",
deviceId);
return;
}

String desHostDir = testInfo.getGenFileDir();
// Moves only the coverage file from tmp dir to test GEN_FILE dir.
for (File subFileOrDir : localFileUtil.listFilesOrDirs(tmpHostDir)) {
String subFileOrDirPath = subFileOrDir.getPath();
if (subFileOrDirPath.endsWith(".ec")) {
localFileUtil.moveFileOrDir(subFileOrDirPath, desHostDir);
testInfo
.log()
.atInfo()
.alsoTo(logger)
.log("Moving test coverage file %s to GEN_FILE dir %s", subFileOrDirPath, desHostDir);
}
}
// Leaves the tmp dir alone, MH framework will clean it up after the test.
} catch (MobileHarnessException e) {
testInfo
.warnings()
.addAndLog(
new MobileHarnessException(
AndroidErrorId.ANDROID_INSTRUMENTATION_PROCESS_TEST_OUTPUT_ERROR,
"Failed to pull test output file from device: " + e.getMessage(),
e),
logger);
}
}

/**
* Processes the test instrument property files generated by {@code
* AndroidTestUtil.addStatsToSponge()}. This will be pulled out from the device and then parsed to
Expand Down Expand Up @@ -983,6 +1078,15 @@ public void pullInstrumentationFilesFromDevice(
if (isDeviceOnline) {
processInstrumentOutputFiles(testInfo, deviceId, externalStoragePath);
processInstrumentPropertyFiles(testInfo, deviceId, externalStoragePath);

// Only process coverage files when the enable_coverage is true.
if (testInfo
.jobInfo()
.params()
.getBool(AndroidInstrumentationDriverSpec.PARAM_ENABLE_COVERAGE, false)) {
testInfo.log().atInfo().alsoTo(logger).log("[Debug] Process coverage files: true");
processInstrumentCoverageFiles(testInfo, deviceId, externalStoragePath);
}
} else {
testInfo
.log()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,17 @@ public static String truncateAtMaxLength(String source, int maxLength, boolean a
return unicodePreservingSubstring(source, 0, maxLength);
}

/**
* Converts a test class name to a valid coverage file name.
*
* <p>The dots and hashes are replaced with underscores. The extension is set to ".ec". For
* example, "com.google.codelab.mobileharness.android.hellomobileharness#testMethod" will be
* converted to "com_google_codelab_mobileharness_android_hellomobileharness_testMethod.ec".
*/
public static String convertClassNameToCoverageFileName(String str) {
return str.replace('.', '_').replace('#', '_') + ".ec";
}

/**
* Returns a substring of {@code str} that respects Unicode character boundaries.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ private void switchLocale(
/* showRawResults= */ false,
/* prefixAndroidTest= */ false,
/* noIsolatedStorage= */ false,
/* useTestStorageService= */ false),
/* useTestStorageService= */ false,
/* enableCoverage= */ false),
/* timeout= */ Duration.ofMinutes(1));
try {
adbUtil.waitForSignalInLog(deviceId, LOG_SIGNAL, timeout);
Expand Down Expand Up @@ -313,6 +314,7 @@ private void switchLocale(
testInfo.log().atInfo().alsoTo(logger).log("%s", e.getMessage());
}
}
@SuppressWarnings("UnsafeLocaleUsage")
Locale locale =
country == null
? Locale.forLanguageTag(language.replace('_', '-'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ private void asyncRun(
job.params()
.getBool(AndroidInstrumentationDriverSpec.PARAM_PREFIX_ANDROID_TEST, false),
noIsolatedStorage,
/* useTestStorageService= */ true),
/* useTestStorageService= */ true,
job.params().getBool(AndroidInstrumentationDriverSpec.PARAM_ENABLE_COVERAGE, false)),
Duration.ofMillis(testTimeoutMs));
} catch (MobileHarnessException e) {
if (e.getErrorId() == AndroidErrorId.ANDROID_INSTRUMENTATION_COMMAND_EXEC_TIMEOUT) {
Expand Down Expand Up @@ -473,7 +474,9 @@ private void syncRun(
job.params()
.getBool(AndroidInstrumentationDriverSpec.PARAM_PREFIX_ANDROID_TEST, false),
noIsolatedStorage,
/* useTestStorageService= */ true),
/* useTestStorageService= */ true,
job.params()
.getBool(AndroidInstrumentationDriverSpec.PARAM_ENABLE_COVERAGE, false)),
Duration.ofMillis(testTimeoutMs));
} catch (MobileHarnessException e) {
testInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ public interface AndroidInstrumentationDriverSpec {
+ " repeated tests. By default, this is false.")
String PARAM_FILTER_INSTRUMENTATION_REPEATED_TESTS = "filter_instrumentation_repeated_tests";

@ParamAnnotation(
required = false,
help =
"Whether to enable coverage generation for the instrumentation command. By default, this"
+ " is false.")
String PARAM_ENABLE_COVERAGE = "enable_coverage";

/** Resource path of the Android basic_services.apk which is needed for reading test_args. */
String BASIC_SERVICE_APK_PATH =
"/com/google/android/apps/common/testing/services/basic_services.apk";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,26 @@ private AndroidInstrumentationSetting mockInstrumentSetting(
boolean prefixAndroidTest,
boolean noIsolatedStorage,
boolean useTestStorageService) {
return mockInstrumentSetting(
className,
otherOptions,
async,
showRawResults,
prefixAndroidTest,
noIsolatedStorage,
useTestStorageService,
/* enableCoverage= */ false);
}

private AndroidInstrumentationSetting mockInstrumentSetting(
@Nullable String className,
@Nullable Map<String, String> otherOptions,
boolean async,
boolean showRawResults,
boolean prefixAndroidTest,
boolean noIsolatedStorage,
boolean useTestStorageService,
boolean enableCoverage) {
return AndroidInstrumentationSetting.create(
TEST_PACKAGE,
RUNNER,
Expand All @@ -665,6 +685,7 @@ private AndroidInstrumentationSetting mockInstrumentSetting(
showRawResults,
prefixAndroidTest,
noIsolatedStorage,
useTestStorageService);
useTestStorageService,
enableCoverage);
}
}

0 comments on commit 8c6b336

Please sign in to comment.