blob: a9fdec59dc085453f66b2bfa8672eec5607e5ec6 [file] [log] [blame]
/*
* Copyright (C) 2016 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.invoker;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.BuildSerializedVersion;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.proto.BuildInformation;
import com.android.tradefed.config.ConfigurationDescriptor;
import com.android.tradefed.config.proto.ConfigurationDescription.Metadata;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.suite.ITestSuite;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.UniqueMultiMap;
import com.google.common.base.Joiner;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Generic implementation of a {@link IInvocationContext}.
*/
public class InvocationContext implements IInvocationContext {
private static final long serialVersionUID = BuildSerializedVersion.VERSION;
// Transient field are not serialized
private transient Map<ITestDevice, IBuildInfo> mAllocatedDeviceAndBuildMap;
/** Map of the configuration device name and the actual {@link ITestDevice} * */
private transient Map<String, ITestDevice> mNameAndDeviceMap;
private Map<String, IBuildInfo> mNameAndBuildinfoMap;
private final UniqueMultiMap<String, String> mInvocationAttributes =
new UniqueMultiMap<String, String>();
/** Invocation test-tag **/
private String mTestTag;
/** configuration descriptor */
private ConfigurationDescriptor mConfigurationDescriptor;
/** module invocation context (when running as part of a {@link ITestSuite} */
private IInvocationContext mModuleContext;
/**
* List of map the device serials involved in the sharded invocation, empty if not a sharded
* invocation.
*/
private Map<Integer, List<String>> mShardSerials;
private boolean mLocked;
private boolean mReleasedEarly = false;
/**
* Creates a {@link BuildInfo} using default attribute values.
*/
public InvocationContext() {
mAllocatedDeviceAndBuildMap = new LinkedHashMap<ITestDevice, IBuildInfo>();
// Use LinkedHashMap to ensure key ordering by insertion order
mNameAndDeviceMap = new LinkedHashMap<String, ITestDevice>();
mNameAndBuildinfoMap = new LinkedHashMap<String, IBuildInfo>();
mShardSerials = new LinkedHashMap<Integer, List<String>>();
}
@Override
public String getInvocationId() {
List<String> values = mInvocationAttributes.get(INVOCATION_ID);
return values == null || values.isEmpty() ? null : values.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public int getNumDevicesAllocated() {
return mAllocatedDeviceAndBuildMap.size();
}
/**
* {@inheritDoc}
*/
@Override
public void addAllocatedDevice(String devicename, ITestDevice testDevice) {
mNameAndDeviceMap.put(devicename, testDevice);
// back fill the information if possible
if (mNameAndBuildinfoMap.get(devicename) != null) {
mAllocatedDeviceAndBuildMap.put(testDevice, mNameAndBuildinfoMap.get(devicename));
}
}
/**
* {@inheritDoc}
*/
@Override
public void addAllocatedDevice(Map<String, ITestDevice> deviceWithName) {
mNameAndDeviceMap.putAll(deviceWithName);
// back fill the information if possible
for (Entry<String, ITestDevice> entry : deviceWithName.entrySet()) {
if (mNameAndBuildinfoMap.get(entry.getKey()) != null) {
mAllocatedDeviceAndBuildMap.put(
entry.getValue(), mNameAndBuildinfoMap.get(entry.getKey()));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Map<ITestDevice, IBuildInfo> getDeviceBuildMap() {
return mAllocatedDeviceAndBuildMap;
}
/**
* {@inheritDoc}
*/
@Override
public List<ITestDevice> getDevices() {
return new ArrayList<ITestDevice>(mNameAndDeviceMap.values());
}
/**
* {@inheritDoc}
*/
@Override
public List<IBuildInfo> getBuildInfos() {
return new ArrayList<IBuildInfo>(mNameAndBuildinfoMap.values());
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getSerials() {
List<String> listSerials = new ArrayList<String>();
for (ITestDevice testDevice : mNameAndDeviceMap.values()) {
listSerials.add(testDevice.getSerialNumber());
}
return listSerials;
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getDeviceConfigNames() {
List<String> listNames = new ArrayList<String>();
listNames.addAll(mNameAndDeviceMap.keySet());
return listNames;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDevice(String deviceName) {
return mNameAndDeviceMap.get(deviceName);
}
/**
* {@inheritDoc}
*/
@Override
public IBuildInfo getBuildInfo(String deviceName) {
return mNameAndBuildinfoMap.get(deviceName);
}
/**
* {@inheritDoc}
*/
@Override
public IBuildInfo getBuildInfo(ITestDevice testDevice) {
return mAllocatedDeviceAndBuildMap.get(testDevice);
}
/**
* {@inheritDoc}
*/
@Override
public void addDeviceBuildInfo(String deviceName, IBuildInfo buildinfo) {
mNameAndBuildinfoMap.put(deviceName, buildinfo);
mAllocatedDeviceAndBuildMap.put(getDevice(deviceName), buildinfo);
}
/**
* {@inheritDoc}
*/
@Override
public void addInvocationAttribute(String attributeName, String attributeValue) {
if (mLocked) {
throw new IllegalStateException(
"Attempting to add invocation attribute during a test.");
}
mInvocationAttributes.put(attributeName, attributeValue);
}
/** {@inheritDoc} */
@Override
public void addInvocationAttributes(MultiMap<String, String> attributesMap) {
if (mLocked) {
throw new IllegalStateException(
"Attempting to add invocation attribute during a test.");
}
mInvocationAttributes.putAll(attributesMap);
}
/** {@inheritDoc} */
@Override
public MultiMap<String, String> getAttributes() {
// Return a copy of the map to avoid unwanted modifications.
UniqueMultiMap<String, String> copy = new UniqueMultiMap<>();
copy.putAll(mInvocationAttributes);
return copy;
}
/**
* {@inheritDoc}
*/
@Override
public ITestDevice getDeviceBySerial(String serial) {
for (ITestDevice testDevice : mNameAndDeviceMap.values()) {
if (testDevice.getSerialNumber().equals(serial)) {
return testDevice;
}
}
CLog.d("Device with serial '%s', not found in the metadata", serial);
return null;
}
/** {@inheritDoc} */
@Override
public String getDeviceName(ITestDevice device) {
for (String name : mNameAndDeviceMap.keySet()) {
if (device.equals(getDevice(name))) {
return name;
}
}
CLog.d(
"Device with serial '%s' doesn't match a name in the metadata",
device.getSerialNumber());
return null;
}
/** {@inheritDoc} */
@Override
public String getBuildInfoName(IBuildInfo info) {
for (String name : mNameAndBuildinfoMap.keySet()) {
if (info.equals(getBuildInfo(name))) {
return name;
}
}
CLog.d("Build info doesn't match a name in the metadata");
return null;
}
/** {@inheritDoc} */
@Override
public String getTestTag() {
return mTestTag;
}
/**
* {@inheritDoc}
*/
@Override
public void setTestTag(String testTag) {
mTestTag = testTag;
}
@Override
public boolean wasReleasedEarly() {
return mReleasedEarly;
}
@Override
public void markReleasedEarly() {
mReleasedEarly = true;
}
/**
* {@inheritDoc}
*/
@Override
public void setRecoveryModeForAllDevices(RecoveryMode mode) {
for (ITestDevice device : getDevices()) {
device.setRecoveryMode(mode);
}
}
/** {@inheritDoc} */
@Override
public void setConfigurationDescriptor(ConfigurationDescriptor configurationDescriptor) {
mConfigurationDescriptor = configurationDescriptor;
}
/** {@inheritDoc} */
@Override
public ConfigurationDescriptor getConfigurationDescriptor() {
return mConfigurationDescriptor;
}
/** {@inheritDoc} */
@Override
public void setModuleInvocationContext(IInvocationContext invocationContext) {
mModuleContext = invocationContext;
}
/** {@inheritDoc} */
@Override
public IInvocationContext getModuleInvocationContext() {
return mModuleContext;
}
/** Lock the context to prevent more invocation attributes to be added. */
public void lockAttributes() {
mLocked = true;
}
/** Private method to unlock the attributes. Used for sandbox test mode only. */
@SuppressWarnings("unused")
private void unlock() {
mLocked = false;
}
/** Log the {@link InvocationMetricLogger} attributes to the invocation. */
public void logInvocationMetrics() {
Map<String, String> metrics = InvocationMetricLogger.getInvocationMetrics();
if (!metrics.isEmpty()) {
mInvocationAttributes.putAll(new MultiMap<>(metrics));
}
Map<String, Long> usage = TfObjectTracker.getUsage();
if (!usage.isEmpty()) {
mInvocationAttributes.put(
TfObjectTracker.TF_OBJECTS_TRACKING_KEY, Joiner.on(",").join(usage.entrySet()));
}
}
/** {@inheritDoc} */
@Override
public void addSerialsFromShard(Integer index, List<String> serials) {
if (mLocked) {
throw new IllegalStateException(
"Attempting to add serial from shard attribute during a test.");
}
mShardSerials.put(index, serials);
}
/** {@inheritDoc} */
@Override
public Map<Integer, List<String>> getShardsSerials() {
return new LinkedHashMap<>(mShardSerials);
}
/** Special java method that allows for custom deserialization. */
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// our "pseudo-constructor"
in.defaultReadObject();
// now we are a "live" object again, so let's init the transient field
mAllocatedDeviceAndBuildMap = new LinkedHashMap<ITestDevice, IBuildInfo>();
mNameAndDeviceMap = new LinkedHashMap<String, ITestDevice>();
}
/** {@inheritDoc} */
@Override
public Context toProto() {
Context.Builder contextBuilder = Context.newBuilder();
// The invocation test tag.
if (mTestTag != null) {
contextBuilder.setTestTag(mTestTag);
}
// Map name to build info
Map<String, BuildInformation.BuildInfo> mapBuild = new LinkedHashMap<>();
for (String name : mNameAndBuildinfoMap.keySet()) {
mapBuild.put(name, mNameAndBuildinfoMap.get(name).toProto());
}
contextBuilder.putAllNameBuildInfo(mapBuild);
// Metadata
List<Metadata> metadatas = new ArrayList<>();
for (String key : mInvocationAttributes.keySet()) {
if (mInvocationAttributes.get(key) != null) {
try {
Metadata value =
Metadata.newBuilder()
.setKey(key)
.addAllValue(mInvocationAttributes.get(key))
.build();
metadatas.add(value);
} catch (RuntimeException e) {
CLog.e(
"Invocation attribute key '%s' raised exception. values: %s",
key, mInvocationAttributes.get(key));
CLog.e(e);
}
} else {
CLog.e("Invocation attribute Key '%s' has null value.", key);
}
}
contextBuilder.addAllMetadata(metadatas);
// Configuration Description
if (mConfigurationDescriptor != null) {
contextBuilder.setConfigurationDescription(mConfigurationDescriptor.toProto());
}
// Module Context if it exists
if (mModuleContext != null) {
contextBuilder.setModuleContext(mModuleContext.toProto());
}
return contextBuilder.build();
}
/** Inverse operation to {@link InvocationContext#toProto()} to get the instance back. */
public static InvocationContext fromProto(Context protoContext) {
InvocationContext context = new InvocationContext();
// Test Tag.
context.mTestTag = protoContext.getTestTag();
// Map Build Info
for (String key : protoContext.getNameBuildInfoMap().keySet()) {
context.mNameAndBuildinfoMap.put(
key, BuildInfo.fromProto(protoContext.getNameBuildInfoMap().get(key)));
}
// Metadata
for (Metadata meta : protoContext.getMetadataList()) {
for (String value : meta.getValueList()) {
context.mInvocationAttributes.put(meta.getKey(), value);
}
}
// Configuration Description
context.mConfigurationDescriptor =
ConfigurationDescriptor.fromProto(protoContext.getConfigurationDescription());
// Module Context - context module will have some property set: module-id at the minimum
if (protoContext.hasModuleContext()) {
// TODO: Check explicitly for module-id
context.mModuleContext = InvocationContext.fromProto(protoContext.getModuleContext());
}
return context;
}
}