// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.android; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertNotNull; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link AndroidDevice}. */ @RunWith(JUnit4.class) public class AndroidDeviceTest extends BuildViewTestCase { private static final String SYSTEM_IMAGE_LABEL = "//sdk/system_images:emulator_images_android_21_x86"; private static final String SYSTEM_IMAGE_DIRECTORY = "sdk/system_images/android_21/x86/"; private static final ImmutableList<String> SYSTEM_IMAGE_FILES = ImmutableList.of( SYSTEM_IMAGE_DIRECTORY + "kernel-qemu", SYSTEM_IMAGE_DIRECTORY + "ramdisk.img", SYSTEM_IMAGE_DIRECTORY + "system.img.tar.gz", SYSTEM_IMAGE_DIRECTORY + "userdata.img.tar.gz"); private static final String SOURCE_PROPERTIES = SYSTEM_IMAGE_DIRECTORY + "source.properties"; private static final String REQUIRES_KVM = "requires-kvm"; @Before public void setup() throws IOException { scratch.file( "sdk/system_images/BUILD", "filegroup(", " name = 'emulator_images_android_21_x86',", " srcs = [", " 'android_21/x86/kernel-qemu',", " 'android_21/x86/ramdisk.img',", " 'android_21/x86/source.properties',", " 'android_21/x86/system.img.tar.gz',", " 'android_21/x86/userdata.img.tar.gz'", " ],", ")"); } private FilesToRunProvider getToolDependency(String label) throws Exception { return getHostConfiguredTarget(ruleClassProvider.getToolsRepository() + label) .getProvider(FilesToRunProvider.class); } private String getToolDependencyExecPathString(String label) throws Exception { return getToolDependency(label).getExecutable().getExecPathString(); } private String getToolDependencyRunfilesPathString(String label) throws Exception { return getToolDependency(label).getExecutable().getRunfilesPathString(); } @Test public void testWellFormedDevice() throws Exception { ConfiguredTarget target = scratchConfiguredTarget( "tools/android/emulated_device", "nexus_6", "android_device(", " name = 'nexus_6', ", " ram = 2048, ", " horizontal_resolution = 720, ", " vertical_resolution = 1280, ", " cache = 32, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 280, ", " vm_heap = 256", ")"); Set<String> outputBasenames = new HashSet<>(); for (Artifact outArtifact : getFilesToBuild(target)) { outputBasenames.add(outArtifact.getPath().getBaseName()); } assertWithMessage("Not generating expected outputs.").that(outputBasenames) .containsExactly("nexus_6", "userdata_images.dat", "emulator-meta-data.pb"); Runfiles runfiles = getDefaultRunfiles(target); assertThat(ActionsTestUtil.execPaths(runfiles.getUnconditionalArtifacts())).containsAllOf( getToolDependencyExecPathString("//tools/android/emulator:support_file1"), getToolDependencyExecPathString("//tools/android/emulator:support_file2")); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), "nexus_6_images/userdata_images.dat"); String systemImageString = Joiner.on(" ").join(SYSTEM_IMAGE_FILES); Iterable<String> biosFilesExecPathStrings = Iterables.transform( getToolDependency("//tools/android/emulator:emulator_x86_bios").getFilesToRun(), new Function<Artifact, String>() { @Override public String apply(Artifact artifact) { return artifact.getExecPathString(); } }); assertWithMessage("Invalid boot commandline.") .that(action.getArguments()) .containsExactly( getToolDependencyExecPathString("//tools/android/emulator:unified_launcher"), "--action=boot", "--density=280", "--memory=2048", "--skin=720x1280", "--cache=32", "--vm_size=256", "--system_images=" + systemImageString, "--bios_files=" + Joiner.on(",").join(biosFilesExecPathStrings), "--source_properties_file=" + SOURCE_PROPERTIES, "--generate_output_dir=" + targetConfig.getBinFragment() + "/tools/android/emulated_device/nexus_6_images", "--adb_static=" + getToolDependencyExecPathString("//tools/android:adb_static"), "--emulator_x86=" + getToolDependencyExecPathString("//tools/android/emulator:emulator_x86"), "--emulator_arm=" + getToolDependencyExecPathString("//tools/android/emulator:emulator_arm"), "--adb=" + getToolDependencyExecPathString("//tools/android:adb"), "--mksdcard=" + getToolDependencyExecPathString("//tools/android/emulator:mksd"), "--empty_snapshot_fs=" + getToolDependencyExecPathString("//tools/android/emulator:empty_snapshot_fs"), "--flag_configured_android_tools", "--nocopy_system_images", "--single_image_file", "--android_sdk_path=" + getToolDependencyExecPathString("//tools/android/emulator:sdk_path"), "--platform_apks="); assertThat(action.getExecutionInfo()).doesNotContainKey(REQUIRES_KVM); assertThat(ActionsTestUtil.execPaths(action.getInputs())) .containsAllOf( getToolDependencyExecPathString("//tools/android/emulator:support_file1"), getToolDependencyExecPathString("//tools/android/emulator:support_file2")); assertNotNull(target.get(ExecutionInfoProvider.SKYLARK_CONSTRUCTOR.getKey())); ExecutionInfoProvider executionInfoProvider = (ExecutionInfoProvider) target.get(ExecutionInfoProvider.SKYLARK_CONSTRUCTOR.getKey()); assertThat(executionInfoProvider.getExecutionInfo()).doesNotContainKey(REQUIRES_KVM); TemplateExpansionAction stubAction = (TemplateExpansionAction) getGeneratingAction( getExecutable(target)); String stubContents = stubAction.getFileContents(); assertThat(stubContents) .contains( String.format( "unified_launcher=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:unified_launcher"))); assertThat(stubContents) .contains( String.format( "adb=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android:adb"))); assertThat(stubContents) .contains( String.format( "adb_static=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android:adb_static"))); assertThat(stubContents) .contains( String.format( "emulator_arm=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:emulator_arm"))); assertThat(stubContents) .contains( String.format( "emulator_x86=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:emulator_x86"))); assertThat(stubContents).contains("source_properties_file=\"${WORKSPACE_DIR}/" + SOURCE_PROPERTIES + "\""); assertThat(stubContents).contains("emulator_system_images=\"" + systemImageString + "\""); } @Test public void testWellFormedDevice_withKvm() throws Exception { ConfiguredTarget target = scratchConfiguredTarget( "tools/android/emulated_device", "nexus_6", "android_device(", " name = 'nexus_6', ", " ram = 2048, ", " horizontal_resolution = 720, ", " vertical_resolution = 1280, ", " cache = 32, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 280, ", " vm_heap = 256,", " tags = ['requires-kvm']", ")"); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), "nexus_6_images/userdata_images.dat"); assertThat(action.getExecutionInfo()) .containsEntry(REQUIRES_KVM, ""); assertNotNull(target.get(ExecutionInfoProvider.SKYLARK_CONSTRUCTOR.getKey())); assertThat( ((ExecutionInfoProvider) target.get(ExecutionInfoProvider.SKYLARK_CONSTRUCTOR.getKey())) .getExecutionInfo()) .containsKey(REQUIRES_KVM); } @Test public void testWellFormedDevice_defaultPropertiesPresent() throws Exception { String dummyPropPackage = "tools/android/emulated_device/data"; String dummyPropFile = "default.properties"; String dummyPropLabel = String.format("//%s:%s", dummyPropPackage, dummyPropFile); scratch.file(String.format("%s/%s", dummyPropPackage, dummyPropFile), "ro.build.id=HiThere"); scratch.file(String.format("%s/BUILD", dummyPropPackage), "exports_files(['default.properties'])"); ConfiguredTarget target = scratchConfiguredTarget( "tools/android/emulated_device", "nexus_6", "android_device(", " name = 'nexus_6', ", " ram = 2048, ", " horizontal_resolution = 720, ", " vertical_resolution = 1280, ", " cache = 32, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 280, ", " vm_heap = 256,", " default_properties = '" + dummyPropLabel + "'", ")"); Set<String> outputBasenames = new HashSet<>(); for (Artifact outArtifact : getFilesToBuild(target)) { outputBasenames.add(outArtifact.getPath().getBaseName()); } assertWithMessage("Not generating expected outputs.").that(outputBasenames) .containsExactly("nexus_6", "userdata_images.dat", "emulator-meta-data.pb"); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), "nexus_6_images/userdata_images.dat"); String systemImageString = Joiner.on(" ").join(SYSTEM_IMAGE_FILES); Iterable<String> biosFilesExecPathStrings = Iterables.transform( getToolDependency("//tools/android/emulator:emulator_x86_bios").getFilesToRun(), new Function<Artifact, String>() { @Override public String apply(Artifact artifact) { return artifact.getExecPathString(); } }); assertWithMessage("Invalid boot commandline.") .that(action.getArguments()) .containsExactly( getToolDependencyExecPathString("//tools/android/emulator:unified_launcher"), "--action=boot", "--density=280", "--memory=2048", "--skin=720x1280", "--cache=32", "--vm_size=256", "--system_images=" + systemImageString, "--bios_files=" + Joiner.on(",").join(biosFilesExecPathStrings), "--source_properties_file=" + SOURCE_PROPERTIES, "--generate_output_dir=" + targetConfig.getBinFragment() + "/tools/android/emulated_device/nexus_6_images", "--adb_static=" + getToolDependencyExecPathString("//tools/android:adb_static"), "--emulator_x86=" + getToolDependencyExecPathString("//tools/android/emulator:emulator_x86"), "--emulator_arm=" + getToolDependencyExecPathString("//tools/android/emulator:emulator_arm"), "--adb=" + getToolDependencyExecPathString("//tools/android:adb"), "--mksdcard=" + getToolDependencyExecPathString("//tools/android/emulator:mksd"), "--empty_snapshot_fs=" + getToolDependencyExecPathString("//tools/android/emulator:empty_snapshot_fs"), "--flag_configured_android_tools", "--nocopy_system_images", "--single_image_file", "--android_sdk_path=" + getToolDependencyExecPathString("//tools/android/emulator:sdk_path"), "--platform_apks=", "--default_properties_file=" + String.format("%s/%s", dummyPropPackage, dummyPropFile)); TemplateExpansionAction stubAction = (TemplateExpansionAction) getGeneratingAction( getExecutable(target)); String stubContents = stubAction.getFileContents(); assertThat(stubContents) .contains( String.format( "unified_launcher=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:unified_launcher"))); assertThat(stubContents) .contains( String.format( "adb=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android:adb"))); assertThat(stubContents) .contains( String.format( "adb_static=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android:adb_static"))); assertThat(stubContents) .contains( String.format( "emulator_arm=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:emulator_arm"))); assertThat(stubContents) .contains( String.format( "emulator_x86=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:emulator_x86"))); assertThat(stubContents).contains( "source_properties_file=\"${WORKSPACE_DIR}/" + SOURCE_PROPERTIES + "\""); assertThat(stubContents).contains("emulator_system_images=\"" + systemImageString + "\""); assertThat(stubContents) .contains( String.format( "android_sdk_path=\"${WORKSPACE_DIR}/%s\"", getToolDependencyRunfilesPathString("//tools/android/emulator:sdk_path"))); assertThat(target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getArtifacts()) .contains(getToolDependency("//tools/android/emulator:unified_launcher").getExecutable()); } @Test public void testPlatformApksFlag_multipleApks() throws Exception { String dummyPlatformApkPackage = "tools/android/emulated_device/data"; List<String> dummyPlatformApkFiles = Lists.newArrayList( "dummy1.apk", "dummy2.apk", "dummy3.apk", "dummy4.apk"); List<String> platformApkFullPaths = Lists.newArrayList(); List<String> dummyPlatformApkLabels = Lists.newArrayList(); for (String dummyPlatformApkFile : dummyPlatformApkFiles) { String platformApkFullPath = String.format("%s/%s", dummyPlatformApkPackage, dummyPlatformApkFile); platformApkFullPaths.add(platformApkFullPath); String dummyPlatformApkLabel = String.format("'//%s:%s'", dummyPlatformApkPackage, dummyPlatformApkFile); dummyPlatformApkLabels.add(dummyPlatformApkLabel); scratch.file(String.format("%s/%s", dummyPlatformApkPackage, dummyPlatformApkFile), dummyPlatformApkFile); } scratch.file(String.format("%s/BUILD", dummyPlatformApkPackage), "exports_files(['dummy1.apk', 'dummy2.apk', 'dummy3.apk', 'dummy4.apk'])"); ConfiguredTarget target = scratchConfiguredTarget( "tools/android/emulated_device", "nexus_6", "android_device(", " name = 'nexus_6', ", " ram = 2048, ", " horizontal_resolution = 720, ", " vertical_resolution = 1280, ", " cache = 32, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 280, ", " vm_heap = 256,", " platform_apks = [" + Joiner.on(", ").join(dummyPlatformApkLabels) + "]", ")"); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), "nexus_6_images/userdata_images.dat"); assertWithMessage("Missing platform_apks flag").that(action.getArguments()) .contains("--platform_apks=" + Joiner.on(",").join(platformApkFullPaths)); } @Test public void testPlatformApksFlag() throws Exception { String dummyPlatformApkPackage = "tools/android/emulated_device/data"; String dummyPlatformApkFile = "dummy.apk"; String dummyPlatformApkLabel = String.format("//%s:%s", dummyPlatformApkPackage, dummyPlatformApkFile); scratch.file(String.format("%s/%s", dummyPlatformApkPackage, dummyPlatformApkFile), "dummyApk"); scratch.file(String.format("%s/BUILD", dummyPlatformApkPackage), "exports_files(['dummy.apk'])"); ConfiguredTarget target = scratchConfiguredTarget( "tools/android/emulated_device", "nexus_6", "android_device(", " name = 'nexus_6', ", " ram = 2048, ", " horizontal_resolution = 720, ", " vertical_resolution = 1280, ", " cache = 32, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 280, ", " vm_heap = 256,", " platform_apks = ['" + dummyPlatformApkLabel + "']", ")"); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), "nexus_6_images/userdata_images.dat"); assertWithMessage("Missing platform_apks flag").that(action.getArguments()) .contains("--platform_apks=" + String.format("%s/%s", dummyPlatformApkPackage, dummyPlatformApkFile)); } @Test public void testBadAttributes() throws Exception { checkError("bad/ram", "bad_ram", "ram must be", "android_device(name = 'bad_ram', ", " ram = -1, ", " vm_heap = 24, ", " cache = 123, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 456, ", " horizontal_resolution = 640, ", " vertical_resolution = 800) "); checkError("bad/vm", "bad_vm", "heap must be", "android_device(name = 'bad_vm', ", " ram = 512, ", " vm_heap = -24, ", " cache = 123, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 456, ", " horizontal_resolution = 640, ", " vertical_resolution = 800) "); checkError("bad/cache", "bad_cache", "cache must be", "android_device(name = 'bad_cache', ", " ram = 512, ", " vm_heap = 24, ", " cache = -123, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = 456, ", " horizontal_resolution = 640, ", " vertical_resolution = 800) "); checkError("bad/density", "bad_density", "density must be", "android_device(name = 'bad_density', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = -456, ", " horizontal_resolution = 640, ", " vertical_resolution = 800) "); checkError("bad/horizontal", "bad_horizontal", "horizontal must be", "android_device(name = 'bad_horizontal', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " screen_density = -456, ", " horizontal_resolution = 100, ", " vertical_resolution = 800) "); checkError("bad/vertical", "bad_vertical", "vertical must be", "android_device(name = 'bad_vertical', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " screen_density = -456, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " horizontal_resolution = 640, ", " vertical_resolution = 100) "); checkError("bad/bogus_default_prop", "bogus_default_prop", "no such package", "android_device(name = 'bogus_default_prop', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " screen_density = 311, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " default_properties = '//something/somewhere',", " horizontal_resolution = 640, ", " vertical_resolution = 100) "); checkError("bad/multi_default_prop", "multi_default_prop", "expected a single artifact", "android_device(name = 'multi_default_prop', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " screen_density = 311, ", " system_image = '" + SYSTEM_IMAGE_LABEL + "',", " default_properties = '" + SYSTEM_IMAGE_LABEL + "',", " horizontal_resolution = 640, ", " vertical_resolution = 100) "); checkError("bad/filegroup", "bad_filegroup", "No source.properties", "filegroup(name = 'empty',", " srcs = [])", "android_device(name = 'bad_filegroup', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " screen_density = -456, ", " system_image = ':empty',", " horizontal_resolution = 640, ", " vertical_resolution = 100) "); checkError("bad/filegroup_2", "bad_filegroup2", "Multiple source.properties", "filegroup(name = 'empty',", " srcs = ['source.properties', 'foo/source.properties'])", "android_device(name = 'bad_filegroup2', ", " ram = 512, ", " vm_heap = 24, ", " cache = 23, ", " screen_density = -456, ", " system_image = ':empty',", " horizontal_resolution = 640, ", " vertical_resolution = 100) "); } }