blob: 7367c6e95424754d59d52b6e7b55ada64c5b4a5f [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* 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.android.tradefed.testtype.blueberry;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.testtype.IRemoteTest;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Run PTS-bot tests. PTS-bot is a complete automation of the Bluetooth Profile Tuning Suite, which
* is the testing tool provided by the Bluetooth standard to run Bluetooth Host certification tests
* (see
* https://www.bluetooth.com/develop-with-bluetooth/qualification-listing/qualification-test-tools/profile-tuning-suite/).
*/
public class PtsBotTest implements IRemoteTest {
private static final int BLUEBERRY_SERVER_PORT = 8999;
private static final int HCI_ROOTCANAL_PORT_CUTTLEFISH = 7300;
private static final int HCI_ROOTCANAL_PORT = 6211;
private static final int HCI_PROXY_PORT = 1234;
@Option(name = "mmi2grpc", description = "mmi2grpc python module path.")
private File mmi2grpc = null;
@Option(
name = "tests-config-file",
description = "Tests config file.",
importance = Importance.ALWAYS)
private File testsConfigFile = null;
@Option(name = "profile", description = "Profile to be tested.", importance = Importance.ALWAYS)
private Set<String> profiles = new HashSet<>();
@Option(
name = "physical",
description = "Run PTS-bot with a physical Bluetooth communication.",
importance = Importance.ALWAYS)
private boolean physical = false;
private int hciPort;
@Override
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
// If tests config file cannot be found using full path, search in
// dependencies.
if (!testsConfigFile.exists()) {
try {
testsConfigFile = testInfo.getDependencyFile(testsConfigFile.getName(), false);
} catch (FileNotFoundException e) {
throw new RuntimeException("Tests config file does not exist");
}
}
CLog.i("Tests config file: %s", testsConfigFile.getPath());
CLog.i("Profiles to be tested: %s", profiles);
ITestDevice testDevice = testInfo.getDevice();
// Forward Blueberry Server port.
adbForwardPort(testDevice, BLUEBERRY_SERVER_PORT);
if (!physical) {
// Check product type to determine Root Canal port.
hciPort = HCI_ROOTCANAL_PORT_CUTTLEFISH;
if (!testDevice.getProductType().equals("cutf")) {
hciPort = HCI_ROOTCANAL_PORT;
// Forward Root Canal port.
adbForwardPort(testDevice, hciPort);
}
} else {
hciPort = HCI_PROXY_PORT;
}
CLog.i("HCI port: %s", hciPort);
// Run tests.
for (String profile : profiles) {
runPtsBotTestsForProfile(profile, listener);
}
// Remove forwarded ports.
adbForwardRemovePort(testDevice, BLUEBERRY_SERVER_PORT);
if (!physical && !testDevice.getProductType().equals("cutf")) {
adbForwardRemovePort(testDevice, HCI_ROOTCANAL_PORT);
}
}
private String[] listPtsBotTestsForProfile(String profile) {
try {
ProcessBuilder processBuilder =
new ProcessBuilder(
"pts-bot", "-c", testsConfigFile.getPath(), "--list", profile);
CLog.i("Running command: %s", String.join(" ", processBuilder.command()));
Process process = processBuilder.start();
BufferedReader stdInput =
new BufferedReader(new InputStreamReader(process.getInputStream()));
String line =
stdInput.lines().filter(l -> l.startsWith("Tests:")).findFirst().orElse(null);
stdInput.close();
if (line != null) {
String[] tests =
line.substring(line.indexOf("[") + 1, line.indexOf("]"))
.replaceAll("\"", "")
.split(", ");
return tests;
}
} catch (IOException e) {
CLog.e(e);
CLog.e("Cannot run pts-bot, make sure it is properly installed");
}
CLog.e("No tests have been found in tests config file");
return null;
}
private void runPtsBotTestsForProfile(String profile, ITestInvocationListener listener) {
String[] tests = listPtsBotTestsForProfile(profile);
if (tests == null || tests.length == 0) {
CLog.e("Cannot run PTS-bot for %s, no tests found", profile);
return;
} else {
CLog.i("Available tests for %s: [%s]", profile, String.join(", ", tests));
}
Map<String, String> runMetrics = new HashMap<>();
listener.testRunStarted(profile, tests.length);
long startTimestamp = System.currentTimeMillis();
for (int i = 0; i < tests.length; i++) {
runPtsBotTest(profile, tests[i], listener);
}
long endTimestamp = System.currentTimeMillis();
listener.testRunEnded(endTimestamp - startTimestamp, runMetrics);
}
private boolean runPtsBotTest(
String profile, String testName, ITestInvocationListener listener) {
TestDescription testDescription = new TestDescription(profile, testName);
boolean success = false;
listener.testStarted(testDescription);
CLog.i(testName);
try {
ProcessBuilder processBuilder;
processBuilder =
new ProcessBuilder(
"pts-bot",
"-c",
testsConfigFile.getPath(),
"--hci",
String.valueOf(hciPort),
testName);
if (mmi2grpc.exists()) {
// Add mmi2grpc python module path to process builder environment.
Map<String, String> env = processBuilder.environment();
env.put("PYTHONPATH", mmi2grpc.getPath());
}
CLog.i("Running command: %s", String.join(" ", processBuilder.command()));
Process process = processBuilder.start();
// Note: there is no need to implement a timeout here since it is handled in pts-bot.
BufferedReader stdInput =
new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stdError =
new BufferedReader(new InputStreamReader(process.getErrorStream()));
Optional<String> lastLine =
stdInput.lines().peek(CLog::i).reduce((last, value) -> value);
// Last line is providing success information.
success =
lastLine.map(
(line) -> {
try {
return Integer.parseInt(
line.split(", ")[1].substring(0, 1))
== 1;
} catch (Exception e) {
CLog.e("Failed to parse success");
return false;
}
})
.orElse(false);
stdInput.close();
stdError.lines().forEach(CLog::e);
stdError.close();
} catch (Exception e) {
CLog.e(e);
CLog.e("Cannot run pts-bot, make sure it is properly installed");
}
if (!success) {
listener.testFailed(testDescription, "Unknown");
}
listener.testEnded(testDescription, Collections.emptyMap());
return success;
}
private void adbForwardPort(ITestDevice testDevice, int port)
throws DeviceNotAvailableException {
testDevice.executeAdbCommand(
1000L, "forward", String.format("tcp:%s", port), String.format("tcp:%s", port));
}
private void adbForwardRemovePort(ITestDevice testDevice, int port)
throws DeviceNotAvailableException {
testDevice.executeAdbCommand(1000L, "forward", "--remove", String.format("tcp:%s", port));
}
}