/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.android;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.InstallException;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.BuckEventBusFactory;
import com.facebook.buck.step.AdbOptions;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.TargetDeviceOptions;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.util.Console;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
import org.kohsuke.args4j.CmdLineException;
public class AdbHelperTest {
private AdbHelper basicAdbHelper;
@Before
public void setUp() throws CmdLineException {
basicAdbHelper = createAdbHelper(new AdbOptions(), new TargetDeviceOptions());
}
private TestDevice createRealDevice(String serial, IDevice.DeviceState state) {
TestDevice device = TestDevice.createRealDevice(serial);
device.setState(state);
return device;
}
private TestDevice createEmulator(String serial, IDevice.DeviceState state) {
TestDevice device = TestDevice.createEmulator(serial);
device.setState(state);
return device;
}
private TestDevice createDeviceForShellCommandTest(final String output) {
return new TestDevice() {
@Override
public void executeShellCommand(
String cmd, IShellOutputReceiver receiver, long timeout, TimeUnit timeoutUnit) {
byte[] outputBytes = output.getBytes(StandardCharsets.UTF_8);
receiver.addOutput(outputBytes, 0, outputBytes.length);
receiver.flush();
}
};
}
private AdbHelper createAdbHelper(AdbOptions adbOptions, TargetDeviceOptions targetDeviceOptions)
throws CmdLineException {
return createAdbHelper(TestExecutionContext.newInstance(), adbOptions, targetDeviceOptions);
}
private AdbHelper createAdbHelper(
ExecutionContext executionContext,
AdbOptions adbOptions,
TargetDeviceOptions targetDeviceOptions)
throws CmdLineException {
Console console = new TestConsole();
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
return new AdbHelper(
adbOptions, targetDeviceOptions, executionContext, console, eventBus, true) {
@Override
protected boolean isDeviceTempWritable(IDevice device, String name) {
return true;
}
};
}
/** Verify that null is returned when no devices are present. */
@Test
public void testDeviceFilterNoDevices() throws CmdLineException {
IDevice[] devices = new IDevice[] {};
assertNull(basicAdbHelper.filterDevices(devices));
}
/** Verify that non-online devices will not appear in result list. */
@Test
public void testDeviceFilterOnlineOnly() throws CmdLineException {
IDevice[] devices =
new IDevice[] {
createEmulator("1", IDevice.DeviceState.OFFLINE),
createEmulator("2", IDevice.DeviceState.BOOTLOADER),
createEmulator("3", IDevice.DeviceState.RECOVERY),
createRealDevice("4", IDevice.DeviceState.OFFLINE),
createRealDevice("5", IDevice.DeviceState.BOOTLOADER),
createRealDevice("6", IDevice.DeviceState.RECOVERY),
};
assertNull(basicAdbHelper.filterDevices(devices));
}
@Test
public void testEmulatorAddsGenymotionDevices() throws Throwable {
AdbHelper adbHelper =
createAdbHelper(new AdbOptions(), new TargetDeviceOptions(true, false, Optional.empty()));
IDevice[] devices =
new IDevice[] {
TestDevice.createRealDevice("foobarblahblah"),
TestDevice.createRealDevice("192.168.57.101:5555")
};
List<IDevice> filtered = adbHelper.filterDevices(devices);
assertNotNull(filtered);
assertEquals(1, filtered.size());
assertEquals("192.168.57.101:5555", filtered.get(0).getSerialNumber());
}
@Test
public void testGenymotionIsntARealDevice() throws Throwable {
AdbHelper adbHelper =
createAdbHelper(new AdbOptions(), new TargetDeviceOptions(false, true, Optional.empty()));
IDevice[] devices =
new IDevice[] {
TestDevice.createRealDevice("foobar"), TestDevice.createRealDevice("192.168.57.101:5555")
};
List<IDevice> filtered = adbHelper.filterDevices(devices);
assertNotNull(filtered);
assertEquals(1, filtered.size());
assertEquals("foobar", filtered.get(0).getSerialNumber());
}
/**
* Verify that multi-install is not enabled and multiple devices pass the filter null is returned.
* Also verify that if multiple devices are passing the filter and multi-install mode is enabled
* they all appear in resulting list.
*/
@Test
public void testDeviceFilterMultipleDevices() throws CmdLineException {
IDevice[] devices =
new IDevice[] {
createEmulator("1", IDevice.DeviceState.ONLINE),
createEmulator("2", IDevice.DeviceState.ONLINE),
createRealDevice("4", IDevice.DeviceState.ONLINE),
createRealDevice("5", IDevice.DeviceState.ONLINE)
};
List<IDevice> filteredDevicesNoMultiInstall = basicAdbHelper.filterDevices(devices);
assertNotNull(filteredDevicesNoMultiInstall);
assertEquals(devices.length, filteredDevicesNoMultiInstall.size());
AdbHelper myAdbHelper = createAdbHelper(new AdbOptions(0, true), new TargetDeviceOptions());
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(devices.length, filteredDevices.size());
}
/** Verify that when emulator-only mode is enabled only emulators appear in result. */
@Test
public void testDeviceFilterEmulator() throws CmdLineException {
AdbHelper myAdbHelper =
createAdbHelper(new AdbOptions(), new TargetDeviceOptions(true, false, Optional.empty()));
IDevice[] devices =
new IDevice[] {
createEmulator("1", IDevice.DeviceState.ONLINE),
createRealDevice("2", IDevice.DeviceState.ONLINE),
};
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(devices[0], filteredDevices.get(0));
}
/** Verify that when real-device-only mode is enabled only real devices appear in result. */
@Test
public void testDeviceFilterRealDevices() throws CmdLineException {
AdbHelper myAdbHelper =
createAdbHelper(new AdbOptions(), new TargetDeviceOptions(false, true, Optional.empty()));
IDevice[] devices =
new IDevice[] {
createRealDevice("1", IDevice.DeviceState.ONLINE),
createEmulator("2", IDevice.DeviceState.ONLINE)
};
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(devices[0], filteredDevices.get(0));
}
/** Verify that filtering by serial number works. */
@Test
public void testDeviceFilterBySerial() throws CmdLineException {
IDevice[] devices =
new IDevice[] {
createRealDevice("1", IDevice.DeviceState.ONLINE),
createEmulator("2", IDevice.DeviceState.ONLINE),
createRealDevice("3", IDevice.DeviceState.ONLINE),
createEmulator("4", IDevice.DeviceState.ONLINE)
};
for (int i = 0; i < devices.length; i++) {
AdbHelper myAdbHelper =
createAdbHelper(
new AdbOptions(),
new TargetDeviceOptions(false, false, Optional.of(devices[i].getSerialNumber())));
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(devices[i], filteredDevices.get(0));
}
}
/** Verify that filtering by environment variable works. */
@Test
public void whenSerialNumberSetInEnvironmentThenCorrectDeviceFound() throws CmdLineException {
IDevice[] devices =
new IDevice[] {
createRealDevice("1", IDevice.DeviceState.ONLINE),
createEmulator("2", IDevice.DeviceState.ONLINE),
createRealDevice("3", IDevice.DeviceState.ONLINE),
createEmulator("4", IDevice.DeviceState.ONLINE)
};
for (int i = 0; i < devices.length; i++) {
AdbHelper myAdbHelper =
createAdbHelper(
TestExecutionContext.newBuilder()
.setEnvironment(
ImmutableMap.of(AdbHelper.SERIAL_NUMBER_ENV, devices[i].getSerialNumber()))
.build(),
new AdbOptions(),
new TargetDeviceOptions());
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(devices[i], filteredDevices.get(0));
}
}
/** Verify that if no devices match filters null is returned. */
@Test
public void testDeviceFilterNoMatchingDevices() throws CmdLineException {
IDevice[] devices =
new IDevice[] {
createRealDevice("1", IDevice.DeviceState.ONLINE),
createEmulator("2", IDevice.DeviceState.ONLINE),
createRealDevice("3", IDevice.DeviceState.ONLINE),
createEmulator("4", IDevice.DeviceState.ONLINE)
};
AdbHelper myAdbHelper =
createAdbHelper(
new AdbOptions(), new TargetDeviceOptions(false, false, Optional.of("invalid-serial")));
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNull(filteredDevices);
}
/** Verify that different combinations of arguments work correctly. */
@Test
public void testDeviceFilterCombos() throws CmdLineException {
TestDevice realDevice1 = createRealDevice("1", IDevice.DeviceState.ONLINE);
TestDevice realDevice2 = createRealDevice("2", IDevice.DeviceState.ONLINE);
TestDevice emulator1 = createEmulator("3", IDevice.DeviceState.ONLINE);
TestDevice emulator2 = createEmulator("4", IDevice.DeviceState.ONLINE);
IDevice[] devices = new IDevice[] {realDevice1, emulator1, realDevice2, emulator2};
AdbHelper myAdbHelper;
// Filter by serial in "real device" mode with serial number for real device.
myAdbHelper =
createAdbHelper(
new AdbOptions(),
new TargetDeviceOptions(false, true, Optional.of(realDevice1.getSerialNumber())));
List<IDevice> filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(realDevice1, filteredDevices.get(0));
// Filter by serial in "real device" mode with serial number for emulator.
myAdbHelper =
createAdbHelper(
new AdbOptions(),
new TargetDeviceOptions(false, true, Optional.of(emulator1.getSerialNumber())));
filteredDevices = myAdbHelper.filterDevices(devices);
assertNull(filteredDevices);
// Filter by serial in "emulator" mode with serial number for real device.
myAdbHelper =
createAdbHelper(
new AdbOptions(),
new TargetDeviceOptions(true, false, Optional.of(realDevice1.getSerialNumber())));
filteredDevices = myAdbHelper.filterDevices(devices);
assertNull(filteredDevices);
// Filter by serial in "real device" mode with serial number for emulator.
myAdbHelper =
createAdbHelper(
new AdbOptions(),
new TargetDeviceOptions(true, false, Optional.of(emulator1.getSerialNumber())));
filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(1, filteredDevices.size());
assertSame(emulator1, filteredDevices.get(0));
// Filter in both "real device" mode and "emulator mode".
myAdbHelper =
createAdbHelper(
new AdbOptions(0, true), new TargetDeviceOptions(true, true, Optional.empty()));
filteredDevices = myAdbHelper.filterDevices(devices);
assertNotNull(filteredDevices);
assertEquals(devices.length, filteredDevices.size());
for (IDevice device : devices) {
assertTrue(filteredDevices.contains(device));
}
}
/** Verify that successful installation on device results in true. */
@Test
public void testSuccessfulDeviceInstall() {
File apk = new File("/some/file.apk");
final AtomicReference<String> apkPath = new AtomicReference<>();
TestDevice device =
new TestDevice() {
@Override
public void installPackage(String s, boolean b, String... strings)
throws InstallException {
apkPath.set(s);
}
};
device.setSerialNumber("serial#1");
device.setName("testDevice");
assertTrue(basicAdbHelper.installApkOnDevice(device, apk, false, false));
assertEquals(apk.getAbsolutePath(), apkPath.get());
}
@Test
public void testQuietDeviceInstall() throws InterruptedException {
final File apk = new File("/some/file.apk");
final AtomicReference<String> apkPath = new AtomicReference<>();
TestDevice device =
new TestDevice() {
@Override
public void installPackage(String s, boolean b, String... strings)
throws InstallException {
apkPath.set(s);
}
};
device.setSerialNumber("serial#1");
device.setName("testDevice");
final List<IDevice> deviceList = Lists.newArrayList((IDevice) device);
TestConsole console = new TestConsole();
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
AdbHelper adbHelper =
new AdbHelper(
new AdbOptions(),
new TargetDeviceOptions(),
TestExecutionContext.newInstance(),
console,
eventBus,
true) {
@Override
protected boolean isDeviceTempWritable(IDevice device, String name) {
return true;
}
@Override
public List<IDevice> getDevices(boolean quiet) {
return deviceList;
}
};
boolean success =
adbHelper.adbCall(
new AdbHelper.AdbCallable() {
@Override
public boolean call(IDevice device) throws Exception {
return basicAdbHelper.installApkOnDevice(device, apk, false, true);
}
@Override
public String toString() {
return "install apk";
}
},
true);
assertTrue(success);
assertEquals(apk.getAbsolutePath(), apkPath.get());
assertEquals("", console.getTextWrittenToStdOut());
assertEquals("", console.getTextWrittenToStdErr());
}
@Test
public void testNonQuietShowsOutput() throws InterruptedException {
final File apk = new File("/some/file.apk");
final AtomicReference<String> apkPath = new AtomicReference<>();
TestDevice device =
new TestDevice() {
@Override
public void installPackage(String s, boolean b, String... strings)
throws InstallException {
apkPath.set(s);
}
};
device.setSerialNumber("serial#1");
device.setName("testDevice");
final List<IDevice> deviceList = Lists.newArrayList((IDevice) device);
TestConsole console = new TestConsole();
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
AdbHelper adbHelper =
new AdbHelper(
new AdbOptions(),
new TargetDeviceOptions(),
TestExecutionContext.newInstance(),
console,
eventBus,
true) {
@Override
protected boolean isDeviceTempWritable(IDevice device, String name) {
return true;
}
@Override
public List<IDevice> getDevices(boolean quiet) {
return deviceList;
}
};
boolean success =
adbHelper.adbCall(
new AdbHelper.AdbCallable() {
@Override
public boolean call(IDevice device) throws Exception {
return basicAdbHelper.installApkOnDevice(device, apk, false, false);
}
@Override
public String toString() {
return "install apk";
}
},
false);
assertTrue(success);
assertEquals(apk.getAbsolutePath(), apkPath.get());
assertEquals("", console.getTextWrittenToStdOut());
assertEquals("Successfully ran install apk on 1 device(s)\n", console.getTextWrittenToStdErr());
}
/** Also make sure we're not erroneously parsing "Exception" and "Error". */
@Test
public void testDeviceStartActivitySuccess() {
TestDevice device =
createDeviceForShellCommandTest(
"Starting: Intent { cmp=com.example.ExceptionErrorActivity }\r\n");
assertNull(basicAdbHelper.deviceStartActivity(device, "com.foo/.Activity", false));
}
@Test
public void testDeviceStartActivityAmDoesntExist() {
TestDevice device = createDeviceForShellCommandTest("sh: am: not found\r\n");
assertNotNull(basicAdbHelper.deviceStartActivity(device, "com.foo/.Activity", false));
}
@Test
public void testDeviceStartActivityActivityDoesntExist() {
String errorLine = "Error: Activity class {com.foo/.Activiqy} does not exist.\r\n";
TestDevice device =
createDeviceForShellCommandTest(
"Starting: Intent { cmp=com.foo/.Activiqy }\r\n" + "Error type 3\r\n" + errorLine);
assertEquals(
errorLine.trim(),
basicAdbHelper.deviceStartActivity(device, "com.foo/.Activiy", false).trim());
}
@Test
public void testDeviceStartActivityException() {
String errorLine =
"java.lang.SecurityException: Permission Denial: "
+ "starting Intent { flg=0x10000000 cmp=com.foo/.Activity } from null "
+ "(pid=27581, uid=2000) not exported from uid 10002\r\n";
TestDevice device =
createDeviceForShellCommandTest(
"Starting: Intent { cmp=com.foo/.Activity }\r\n"
+ errorLine
+ " at android.os.Parcel.readException(Parcel.java:1425)\r\n"
+ " at android.os.Parcel.readException(Parcel.java:1379)\r\n"
+
// (...)
" at dalvik.system.NativeStart.main(Native Method)\r\n");
assertEquals(
errorLine.trim(),
basicAdbHelper.deviceStartActivity(device, "com.foo/.Activity", false).trim());
}
/** Verify that if failure reason is returned, installation is marked as failed. */
@Test
public void testFailedDeviceInstallWithReason() {
File apk = new File("/some/file.apk");
TestDevice device =
new TestDevice() {
@Override
public void installPackage(String s, boolean b, String... strings)
throws InstallException {
throw new InstallException("[SOME REASON]");
}
};
device.setSerialNumber("serial#1");
device.setName("testDevice");
assertFalse(basicAdbHelper.installApkOnDevice(device, apk, false, false));
}
/** Verify that if exception is thrown during installation, installation is marked as failed. */
@Test
public void testFailedDeviceInstallWithException() {
File apk = new File("/some/file.apk");
TestDevice device =
new TestDevice() {
@Override
public void installPackage(String s, boolean b, String... strings)
throws InstallException {
throw new InstallException("Failed to install on test device.", null);
}
};
device.setSerialNumber("serial#1");
device.setName("testDevice");
assertFalse(basicAdbHelper.installApkOnDevice(device, apk, false, false));
}
@Test
public void testDeviceStartActivityWaitForDebugger() throws Exception {
final AtomicReference<String> runDeviceCommand = new AtomicReference<>();
TestDevice device =
new TestDevice() {
@Override
public void executeShellCommand(
String command,
IShellOutputReceiver receiver,
long maxTimeToOutputResponse,
TimeUnit maxTimeUnits) {
runDeviceCommand.set(command);
}
};
assertNull(basicAdbHelper.deviceStartActivity(device, "com.foo/.Activity", true));
assertTrue(runDeviceCommand.get().contains(" -D"));
}
@Test
public void testDeviceStartActivityDoNotWaitForDebugger() throws Exception {
final AtomicReference<String> runDeviceCommand = new AtomicReference<>();
TestDevice device =
new TestDevice() {
@Override
public void executeShellCommand(
String command,
IShellOutputReceiver receiver,
long maxTimeToOutputResponse,
TimeUnit maxTimeUnits) {
runDeviceCommand.set(command);
}
};
assertNull(basicAdbHelper.deviceStartActivity(device, "com.foo/.Activity", false));
assertFalse(runDeviceCommand.get().contains(" -D"));
}
}