Support exchanging files during invocation

Provide an official support outside BUildInfo to pass file
information between TF objects.
Use it for the adb preparer + test.

Test: unit tests
Bug: 146371544
Change-Id: I1f5d71617e6c31bf1a99f2c20f64e60942744be7
diff --git a/invocation_interfaces/com/android/tradefed/invoker/ExecutionFiles.java b/invocation_interfaces/com/android/tradefed/invoker/ExecutionFiles.java
new file mode 100644
index 0000000..391147b
--- /dev/null
+++ b/invocation_interfaces/com/android/tradefed/invoker/ExecutionFiles.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.google.common.collect.ImmutableMap;
+
+import java.io.File;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/** Files generated during the execution of a test or invocation that need to be carried */
+public class ExecutionFiles {
+
+    /** Enumeration of known standard key for the map. */
+    public static enum FilesKey {
+        ADB_BINARY
+    }
+
+    private final ConcurrentMap<String, File> mProperties = new ConcurrentHashMap<>();
+
+    // Package private to avoid new instantiation.
+    ExecutionFiles() {}
+
+    /**
+     * Associates the specified value with the specified key in this map.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param value value to be associated with the specified key
+     * @return the previous value associated with {@code key}, or {@code null} if there was no
+     *     mapping for {@code key}.
+     * @see ConcurrentMap
+     */
+    public File put(String key, File value) {
+        return mProperties.put(key, value);
+    }
+
+    /**
+     * Variation of {@link #put(FilesKey, File)} with a known key.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param value value to be associated with the specified key
+     * @return the previous value associated with {@code key}, or {@code null} if there was no
+     *     mapping for {@code key}.
+     */
+    public File put(FilesKey key, File value) {
+        return mProperties.put(key.toString(), value);
+    }
+
+    /**
+     * If the specified key is not already associated with a value, associates it with the given
+     * value.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param value value to be associated with the specified key
+     * @return the previous value associated with the specified key, or {@code null} if there was no
+     *     mapping for the key.
+     */
+    public File putIfAbsent(String key, File value) {
+        return mProperties.putIfAbsent(key, value);
+    }
+
+    /**
+     * Copies all of the mappings from the specified map to this map.
+     *
+     * @param properties mappings to be stored in this map
+     * @return The final mapping
+     */
+    public ExecutionFiles putAll(Map<String, File> properties) {
+        mProperties.putAll(properties);
+        return this;
+    }
+
+    /** Returns whether or not the map of properties is empty. */
+    public boolean isEmpty() {
+        return mProperties.isEmpty();
+    }
+
+    /**
+     * Returns the value to which the specified key is mapped, or {@code null} if this map contains
+     * no mapping for the key.
+     *
+     * @param key the key whose associated value is to be returned
+     * @return the value to which the specified key is mapped, or {@code null} if this map contains
+     *     no mapping for the key
+     */
+    public File get(String key) {
+        return mProperties.get(key);
+    }
+
+    /**
+     * Variation of {@link #get(String)} with a known key.
+     *
+     * @param key the key whose associated value is to be returned
+     * @return the value to which the specified key is mapped, or {@code null} if this map contains
+     *     no mapping for the key
+     */
+    public File get(FilesKey key) {
+        return mProperties.get(key.toString());
+    }
+
+    /**
+     * Returns {@code true} if this map contains a mapping for the specified key.
+     *
+     * @param key key whose presence in this map is to be tested
+     * @return {@code true} if this map contains a mapping for the specified key
+     */
+    public boolean containsKey(String key) {
+        return mProperties.containsKey(key);
+    }
+
+    /** Returns all the properties in a copy of the map */
+    public ImmutableMap<String, File> getAll() {
+        return ImmutableMap.copyOf(mProperties);
+    }
+
+    /**
+     * Removes the mapping for a key from this map if it is present (optional operation).
+     *
+     * @param key key whose mapping is to be removed from the map
+     * @return the previous value associated with {@code key}, or {@code null} if there was no
+     *     mapping for {@code key}.
+     */
+    public File remove(String key) {
+        return mProperties.remove(key);
+    }
+}
diff --git a/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java b/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
index 1e818b0..4880260 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
@@ -19,9 +19,7 @@
 import com.android.tradefed.device.ITestDevice;
 
 import java.io.File;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Holder object that contains all the information and dependencies a test runner or test might need
@@ -32,24 +30,27 @@
     private final IInvocationContext mContext;
     /** Properties generated during execution. */
     private final ExecutionProperties mProperties;
+    /**
+     * Files generated during execution that needs to be carried, they will be deleted at the end of
+     * the invocation.
+     */
+    private final ExecutionFiles mExecutionFiles;
 
     /** Main folder for all dependencies of tests */
     private final File mDependenciesFolder;
-    /** The complete dependencies resolved for the invocation, keyed by file name requested */
-    private final Map<String, File> mDependencies;
 
     private TestInformation(Builder builder) {
         mContext = builder.mContext;
         mProperties = builder.mProperties;
         mDependenciesFolder = builder.mDependenciesFolder;
-        mDependencies = builder.mDependencies;
+        mExecutionFiles = builder.mExecutionFiles;
     }
 
     private TestInformation(TestInformation invocationInfo, IInvocationContext moduleContext) {
         mContext = moduleContext;
         mProperties = invocationInfo.mProperties;
         mDependenciesFolder = invocationInfo.mDependenciesFolder;
-        mDependencies = invocationInfo.mDependencies;
+        mExecutionFiles = invocationInfo.mExecutionFiles;
     }
 
     /** Create a builder for creating {@link TestInformation} instances. */
@@ -92,6 +93,15 @@
         return mProperties;
     }
 
+    /**
+     * Returns the files generated during the invocation execution. Passing files through the {@link
+     * ExecutionFiles} is the recommended way to make a file available between target_preparers and
+     * tests.
+     */
+    public ExecutionFiles executionFiles() {
+        return mExecutionFiles;
+    }
+
     /** Returns the folder where all the dependencies are stored for an invocation. */
     public File dependenciesFolder() {
         return mDependenciesFolder;
@@ -102,11 +112,11 @@
         private IInvocationContext mContext;
         private ExecutionProperties mProperties;
         private File mDependenciesFolder;
-        private final Map<String, File> mDependencies;
+        private ExecutionFiles mExecutionFiles;
 
         private Builder() {
             mProperties = new ExecutionProperties();
-            mDependencies = new HashMap<>();
+            mExecutionFiles = new ExecutionFiles();
         }
 
         public TestInformation build() {
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 3e6a837..5dc294a 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -889,6 +889,10 @@
             config.cleanConfigurationData();
             // Delete the invocation work directory at the end
             FileUtil.recursiveDelete(info.dependenciesFolder());
+            // Delete all the execution files
+            for (File f : info.executionFiles().getAll().values()) {
+                FileUtil.recursiveDelete(f);
+            }
         }
     }
 
diff --git a/test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java b/test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
index 0424612..a73a671 100644
--- a/test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/adb/AdbStopServerPreparer.java
@@ -22,7 +22,8 @@
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.IDeviceManager;
-import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.targetprep.BaseTargetPreparer;
 import com.android.tradefed.targetprep.BuildError;
@@ -63,9 +64,9 @@
 
     /** {@inheritDoc} */
     @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo)
+    public void setUp(TestInformation testInfo)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
-
+        IBuildInfo buildInfo = testInfo.getBuildInfo();
         getDeviceManager().stopAdbBridge();
 
         // Kill the default adb server
@@ -93,7 +94,7 @@
             adb = buildInfo.getFile("adb");
             adb = renameAdbBinary(adb);
             // Track the updated adb file.
-            buildInfo.setFile(ADB_BINARY_KEY, adb, "adb");
+            testInfo.executionFiles().put(FilesKey.ADB_BINARY, adb);
         }
 
         if (adb != null) {
@@ -107,19 +108,19 @@
                                 "Failed to restart adb with the build info one. stdout: %s.\n"
                                         + "sterr: %s",
                                 result.getStdout(), result.getStderr()),
-                        device.getDeviceDescriptor());
+                        testInfo.getDevice().getDeviceDescriptor());
             }
         } else {
             getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "start-server");
             throw new TargetSetupError(
-                    "Could not find a new version of adb to tests.", device.getDeviceDescriptor());
+                    "Could not find a new version of adb to tests.",
+                    testInfo.getDevice().getDeviceDescriptor());
         }
     }
 
     /** {@inheritDoc} */
     @Override
-    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
-            throws DeviceNotAvailableException {
+    public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
         FileUtil.recursiveDelete(mTmpDir);
         // Kill the test adb server
         getRunUtil().runTimedCmd(CMD_TIMEOUT, "adb", "kill-server");
diff --git a/test_framework/com/android/tradefed/testtype/python/PythonBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
index 71e087f..83d6a75 100644
--- a/test_framework/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/python/PythonBinaryHostTest.java
@@ -22,6 +22,7 @@
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -29,7 +30,6 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.ResultForwarder;
-import com.android.tradefed.targetprep.adb.AdbStopServerPreparer;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.PythonUnitTestResultParser;
 import com.android.tradefed.util.CommandResult;
@@ -105,7 +105,7 @@
                 continue;
             }
             pyFile.setExecutable(true);
-            runSinglePythonFile(listener, pyFile);
+            runSinglePythonFile(listener, testInfo, pyFile);
         }
     }
 
@@ -133,7 +133,8 @@
         return files;
     }
 
-    private void runSinglePythonFile(ITestInvocationListener listener, File pyFile) {
+    private void runSinglePythonFile(
+            ITestInvocationListener listener, TestInformation testInfo, File pyFile) {
         List<String> commandLine = new ArrayList<>();
         commandLine.add(pyFile.getAbsolutePath());
         // If we have a physical device, pass it to the python test by serial
@@ -148,7 +149,7 @@
                     .setEnvVariable(ANDROID_SERIAL_VAR, mTestInfo.getDevice().getSerialNumber());
         }
 
-        File updatedAdb = mTestInfo.getBuildInfo().getFile(AdbStopServerPreparer.ADB_BINARY_KEY);
+        File updatedAdb = testInfo.executionFiles().get(FilesKey.ADB_BINARY);
         if (updatedAdb == null) {
             String adbPath = getAdbPath();
             // Don't check if it's the adb on the $PATH
diff --git a/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java b/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java
index f2cf74b..dfc6b95 100644
--- a/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/adb/AdbStopServerPreparerTest.java
@@ -22,6 +22,8 @@
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.IDeviceManager;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
@@ -45,6 +47,7 @@
     private IRunUtil mMockRunUtil;
     private IDeviceManager mMockManager;
 
+    private TestInformation mTestInfo;
     private ITestDevice mMockDevice;
     private IBuildInfo mMockBuild;
     private File mFakeAdbFile;
@@ -79,6 +82,10 @@
 
         mMockRunUtil.sleep(2000);
         EasyMock.expect(mMockDevice.getDeviceDescriptor()).andStubReturn(null);
+        InvocationContext context = new InvocationContext();
+        context.addAllocatedDevice("device", mMockDevice);
+        context.addDeviceBuildInfo("device", mMockBuild);
+        mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
     }
 
     @After
@@ -105,8 +112,8 @@
         // tear down
         mockTearDown();
         EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
-        mPreparer.setUp(mMockDevice, mMockBuild);
-        mPreparer.tearDown(mMockDevice, mMockBuild, null);
+        mPreparer.setUp(mTestInfo);
+        mPreparer.tearDown(mTestInfo, null);
         EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
     }
 
@@ -133,12 +140,12 @@
         mockTearDown();
         EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
         try {
-            mPreparer.setUp(mMockDevice, mMockBuild);
+            mPreparer.setUp(mTestInfo);
             fail("Should have thrown an exception.");
         } catch (TargetSetupError expected) {
             // Expected
         }
-        mPreparer.tearDown(mMockDevice, mMockBuild, null);
+        mPreparer.tearDown(mTestInfo, null);
         EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
     }
 
@@ -160,7 +167,11 @@
                 .andReturn(result);
         EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
         try {
-            mPreparer.setUp(mMockDevice, new BuildInfo());
+            InvocationContext context = new InvocationContext();
+            context.addAllocatedDevice("device", mMockDevice);
+            context.addDeviceBuildInfo("device", new BuildInfo());
+            mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
+            mPreparer.setUp(mTestInfo);
             fail("Should have thrown an exception.");
         } catch (TargetSetupError expected) {
             // Expected
@@ -198,8 +209,8 @@
             // tear down
             mockTearDown();
             EasyMock.replay(mMockRunUtil, mMockManager, mMockDevice);
-            mPreparer.setUp(mMockDevice, mMockBuild);
-            mPreparer.tearDown(mMockDevice, mMockBuild, null);
+            mPreparer.setUp(mTestInfo);
+            mPreparer.tearDown(mTestInfo, null);
             EasyMock.verify(mMockRunUtil, mMockManager, mMockDevice);
         } finally {
             FileUtil.recursiveDelete(tmpDir);
diff --git a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
index d590cdd..b285530 100644
--- a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
@@ -19,13 +19,13 @@
 import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
 import com.android.tradefed.invoker.IInvocationContext;
 import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.targetprep.adb.AdbStopServerPreparer;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
@@ -71,8 +71,6 @@
                         return mFakeAdb.getAbsolutePath();
                     }
                 };
-        EasyMock.expect(mMockBuildInfo.getFile(AdbStopServerPreparer.ADB_BINARY_KEY))
-                .andReturn(null);
         IInvocationContext context = new InvocationContext();
         context.addAllocatedDevice("device", mMockDevice);
         context.addDeviceBuildInfo("device", mMockBuildInfo);
@@ -128,11 +126,7 @@
      */
     @Test
     public void testRun_withAdbPath() throws Exception {
-        mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
-        EasyMock.expect(mMockBuildInfo.getFile(AdbStopServerPreparer.ADB_BINARY_KEY))
-                .andReturn(new File("/test/adb"));
-        mTestInfo.getContext().addDeviceBuildInfo("device", mMockBuildInfo);
-
+        mTestInfo.executionFiles().put(FilesKey.ADB_BINARY, new File("/test/adb"));
         File binary = FileUtil.createTempFile("python-dir", "");
         try {
             OptionSetter setter = new OptionSetter(mTest);