blob: cd4f701adb57f20f54accfafbe80fddac82e12d2 [file] [log] [blame]
/*
* Copyright (C) 2017 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.compatibility.common.tradefed.targetprep;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.util.DynamicConfigFileReader;
import com.android.compatibility.common.util.BusinessLogic;
import com.android.compatibility.common.util.FeatureUtil;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.suite.TestSuiteInfo;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.net.HttpHelper;
import com.android.tradefed.util.net.IHttpHelper;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Pushes business Logic to the host and the test device, for use by test cases in the test suite.
*/
@OptionClass(alias="business-logic-preparer")
public class BusinessLogicPreparer implements ITargetCleaner {
/* Placeholder in the service URL for the suite to be configured */
private static final String SUITE_PLACEHOLDER = "{suite-name}";
/* String for creating files to store the business logic configuration on the host */
private static final String FILE_LOCATION = "business-logic";
/* Extension of business logic files */
private static final String FILE_EXT = ".bl";
/* Dynamic config constants */
private static final String DYNAMIC_CONFIG_FEATURES_KEY = "business_logic_device_features";
private static final String DYNAMIC_CONFIG_PROPERTIES_KEY = "business_logic_device_properties";
@Option(name = "business-logic-url", description = "The URL to use when accessing the " +
"business logic service, parameters not included", mandatory = true)
private String mUrl;
@Option(name = "business-logic-api-key", description = "The API key to use when accessing " +
"the business logic service.", mandatory = true)
private String mApiKey;
@Option(name = "cleanup", description = "Whether to remove config files from the test " +
"target after test completion.")
private boolean mCleanup = true;
@Option(name = "ignore-business-logic-failure", description = "Whether to proceed with the " +
"suite invocation if retrieval of business logic fails.")
private boolean mIgnoreFailure = false;
private String mDeviceFilePushed;
private String mHostFilePushed;
/**
* {@inheritDoc}
*/
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
DeviceNotAvailableException {
String requestString = buildRequestString(device, buildInfo);
// Retrieve business logic string from service
String businessLogicString = null;
try {
URL request = new URL(requestString);
businessLogicString = StreamUtil.getStringFromStream(request.openStream());
} catch (IOException e) {
if (mIgnoreFailure) {
CLog.e("Failed to connect to business logic service.\nProceeding with test"
+ "invocation, tests depending on the remote configuration will fail.\n");
return;
} else {
throw new TargetSetupError(String.format(
"Cannot connect to business logic service for suite %s.\nIf this problem "
+ "persists, re-invoking with option '--ignore-business-logic-failure' will "
+ "cause tests to execute anyways (though tests depending on the remote "
+ "configuration will fail).", TestSuiteInfo.getInstance().getName()), e,
device.getDeviceDescriptor());
}
}
// Push business logic string to host file
try {
File hostFile = FileUtil.createTempFile(FILE_LOCATION, FILE_EXT);
FileUtil.writeToFile(businessLogicString, hostFile);
mHostFilePushed = hostFile.getAbsolutePath();
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
buildHelper.setBusinessLogicHostFile(hostFile);
} catch (IOException e) {
throw new TargetSetupError(String.format(
"Retrieved business logic for suite %s could not be written to host",
TestSuiteInfo.getInstance().getName()), device.getDeviceDescriptor());
}
// Push business logic string to device file
removeDeviceFile(device); // remove any existing business logic file from device
if (device.pushString(businessLogicString, BusinessLogic.DEVICE_FILE)) {
mDeviceFilePushed = BusinessLogic.DEVICE_FILE;
} else {
throw new TargetSetupError(String.format(
"Retrieved business logic for suite %s could not be written to device %s",
TestSuiteInfo.getInstance().getName(), device.getSerialNumber()),
device.getDeviceDescriptor());
}
}
/** Helper to populate the business logic service request with info about the device. */
private String buildRequestString(ITestDevice device, IBuildInfo buildInfo)
throws DeviceNotAvailableException {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(buildInfo);
String baseUrl = mUrl.replace(SUITE_PLACEHOLDER, getSuiteName());
MultiMap<String, String> paramMap = new MultiMap<>();
paramMap.put("key", mApiKey);
paramMap.put("suite_version", buildHelper.getSuiteVersion());
paramMap.put("oem", PropertyUtil.getManufacturer(device));
for (String feature : getBusinessLogicFeatures(device, buildInfo)) {
paramMap.put("features", feature);
}
for (String property : getBusinessLogicProperties(device, buildInfo)) {
paramMap.put("properties", property);
}
IHttpHelper helper = new HttpHelper();
return helper.buildUrl(baseUrl, paramMap);
}
/* Get device properties list, with element format "<property_name>:<property_value>" */
private List<String> getBusinessLogicProperties(ITestDevice device, IBuildInfo buildInfo)
throws DeviceNotAvailableException {
List<String> properties = new ArrayList<>();
Map<String, String> clientIds = PropertyUtil.getClientIds(device);
for (Map.Entry<String, String> id : clientIds.entrySet()) {
// add client IDs to the list of properties
properties.add(String.format("%s:%s", id.getKey(), id.getValue()));
}
try {
List<String> propertyNames = DynamicConfigFileReader.getValuesFromConfig(buildInfo,
getSuiteName(), DYNAMIC_CONFIG_PROPERTIES_KEY);
for (String name : propertyNames) {
// Use String.valueOf in case property is undefined for the device ("null")
String value = String.valueOf(device.getProperty(name));
properties.add(String.format("%s:%s", name, value));
}
} catch (XmlPullParserException | IOException e) {
CLog.e("Failed to pull business logic properties from dynamic config");
}
return properties;
}
/* Get device features list */
private List<String> getBusinessLogicFeatures(ITestDevice device, IBuildInfo buildInfo)
throws DeviceNotAvailableException {
try {
List<String> dynamicConfigFeatures = DynamicConfigFileReader.getValuesFromConfig(
buildInfo, getSuiteName(), DYNAMIC_CONFIG_FEATURES_KEY);
Set<String> deviceFeatures = FeatureUtil.getAllFeatures(device);
dynamicConfigFeatures.retainAll(deviceFeatures);
return dynamicConfigFeatures;
} catch (XmlPullParserException | IOException e) {
CLog.e("Failed to pull business logic features from dynamic config");
return new ArrayList<>();
}
}
private String getSuiteName() {
return TestSuiteInfo.getInstance().getName().toLowerCase();
}
/**
* {@inheritDoc}
*/
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
// Clean up host file
if (mCleanup) {
if (mHostFilePushed != null) {
FileUtil.deleteFile(new File(mHostFilePushed));
}
if (mDeviceFilePushed != null && !(e instanceof DeviceNotAvailableException)) {
removeDeviceFile(device);
}
}
}
/** Remove business logic file from the device */
private static void removeDeviceFile(ITestDevice device) throws DeviceNotAvailableException {
device.executeShellCommand(String.format("rm -rf %s", BusinessLogic.DEVICE_FILE));
}
}