Skip to content

Commit 64fc7bf

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Test storage API cleanup.
Make API changes as discussed in review: - Make PlatformTestStorage public - Make the default implementation FileTestStorage instead of a no-op - Modify FileTestStorage implementation to: - read input files from disk instead of assets - remove support for reading from absolute paths, as that contradicts the PlatformTestStorage spec - Add more documentation on usage across build environments - Make TestStorage a restricted-to-androidx.test usage API PiperOrigin-RevId: 627524599
1 parent 5acf36a commit 64fc7bf

File tree

11 files changed

+199
-179
lines changed

11 files changed

+199
-179
lines changed

runner/monitor/CHANGELOG.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212

1313
**API Changes**
1414
* Make DeviceController an public API from ExperimentalTestApi
15-
* Upstream TestStorage.isTestStoragePath to PlatformTestStorage
16-
* Upstream TestStorage.getInputFileUri and getOutputFileUri to PlatformTestStorage
17-
* Change PlatformTestStorage methods to throw FileNotFoundException instead of
18-
IOException
15+
* Move PlatformTestStorage to a public API
1916
* Add internal ControlledLooper#isDrawCallbacksSupported.
2017

2118
**Breaking API Changes**

runner/monitor/java/androidx/test/api/current_internal.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,29 @@ package androidx.test.platform.concurrent {
213213

214214
}
215215

216+
package androidx.test.platform.io {
217+
218+
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class FileTestStorage implements androidx.test.platform.io.PlatformTestStorage {
219+
ctor public FileTestStorage();
220+
method public void addOutputProperties(java.util.Map<java.lang.String!,java.io.Serializable!>!);
221+
method public String! getInputArg(String!);
222+
method public java.util.Map<java.lang.String!,java.lang.String!>! getInputArgs();
223+
method public android.net.Uri! getInputFileUri(String);
224+
method public android.net.Uri! getOutputFileUri(String);
225+
method public java.util.Map<java.lang.String!,java.io.Serializable!>! getOutputProperties();
226+
method public boolean isTestStorageFilePath(String);
227+
method public java.io.InputStream! openInputFile(String!) throws java.io.FileNotFoundException;
228+
method public java.io.OutputStream! openOutputFile(String!) throws java.io.FileNotFoundException;
229+
method public java.io.OutputStream! openOutputFile(String!, boolean) throws java.io.FileNotFoundException;
230+
}
231+
232+
public interface PlatformTestStorage {
233+
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public default java.io.InputStream! openInternalInputFile(String!) throws java.io.FileNotFoundException;
234+
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public default java.io.OutputStream! openInternalOutputFile(String!) throws java.io.FileNotFoundException;
235+
}
236+
237+
}
238+
216239
package androidx.test.platform.tracing {
217240

218241
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface Tracer {

runner/monitor/java/androidx/test/api/current_public.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ package androidx.test.platform.graphics {
6363

6464
}
6565

66+
package androidx.test.platform.io {
67+
68+
public interface PlatformTestStorage {
69+
method public void addOutputProperties(java.util.Map<java.lang.String!,java.io.Serializable!>!);
70+
method public String! getInputArg(String!);
71+
method public java.util.Map<java.lang.String!,java.lang.String!>! getInputArgs();
72+
method public android.net.Uri! getInputFileUri(String);
73+
method public android.net.Uri! getOutputFileUri(String);
74+
method public java.util.Map<java.lang.String!,java.io.Serializable!>! getOutputProperties();
75+
method public boolean isTestStorageFilePath(String);
76+
method public java.io.InputStream! openInputFile(String!) throws java.io.FileNotFoundException;
77+
method public java.io.OutputStream! openOutputFile(String!) throws java.io.FileNotFoundException;
78+
method public java.io.OutputStream! openOutputFile(String!, boolean) throws java.io.FileNotFoundException;
79+
}
80+
81+
public final class PlatformTestStorageRegistry {
82+
method public static androidx.test.platform.io.PlatformTestStorage! getInstance();
83+
method public static void registerInstance(androidx.test.platform.io.PlatformTestStorage!);
84+
}
85+
86+
}
87+
6688
package androidx.test.platform.ui {
6789

6890
public class InjectEventSecurityException extends java.lang.Exception implements androidx.test.platform.TestFrameworkException {

runner/monitor/java/androidx/test/platform/io/FileTestStorage.java

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
import android.os.Bundle;
2020
import android.util.Log;
2121
import androidx.annotation.NonNull;
22-
import androidx.test.annotation.ExperimentalTestApi;
22+
import androidx.annotation.RestrictTo;
23+
import androidx.annotation.RestrictTo.Scope;
2324
import androidx.test.platform.app.InstrumentationRegistry;
2425
import java.io.File;
2526
import java.io.FileInputStream;
2627
import java.io.FileNotFoundException;
2728
import java.io.FileOutputStream;
28-
import java.io.IOException;
2929
import java.io.InputStream;
3030
import java.io.OutputStream;
3131
import java.io.Serializable;
@@ -34,41 +34,35 @@
3434
import java.util.Map;
3535

3636
/**
37-
* A class that reads/writes the runner data using the raw file system.
37+
* An implementation of {@link PlatformTestStorage} that reads and writes test data to the test
38+
* process's local storage.
3839
*
39-
* <p>This API is experimental and is subject to change or removal in future releases.
40+
* <p>Typically the test runner infrastructure will configure where test data is stored and
41+
* retrieved from by passing 'additionalTestOutputDir' and 'testInputDir' instrumentation arguments.
42+
* If these arguments are not provided, the implementation will choose an appropriate location based
43+
* on android API level that minimizes the need for any extra permissions. See TestDirCalculator.
44+
*
45+
* @hide
4046
*/
41-
@ExperimentalTestApi
47+
@RestrictTo(Scope.LIBRARY_GROUP)
4248
public final class FileTestStorage implements PlatformTestStorage {
4349

4450
private static final String TAG = FileTestStorage.class.getSimpleName();
45-
private final OutputDirCalculator outputDirCalculator;
51+
private final TestDirCalculator testDirCalculator;
4652

4753
public FileTestStorage() {
48-
outputDirCalculator = new OutputDirCalculator();
54+
testDirCalculator = new TestDirCalculator();
4955
}
5056

5157
/**
5258
* Provides an InputStream to a test file dependency.
5359
*
54-
* @param pathname path to the test file dependency. Should not be null. Can be either a relative
55-
* or absolute path. If relative, the implementation will read the input file from the test
56-
* apk's asset directory
60+
* @param pathname relative path to the test file dependency. Should not be null.
5761
*/
5862
@Override
5963
public InputStream openInputFile(String pathname) throws FileNotFoundException {
60-
File inputFile = new File(pathname);
61-
if (inputFile.isAbsolute()) {
62-
return new FileInputStream(inputFile);
63-
}
64-
try {
65-
return InstrumentationRegistry.getInstrumentation().getContext().getAssets().open(pathname);
66-
} catch (IOException e) {
67-
FileNotFoundException fe =
68-
new FileNotFoundException(String.format("failed to open %s from apk assets", pathname));
69-
fe.initCause(e);
70-
throw fe;
71-
}
64+
File inputFile = new File(testDirCalculator.getInputDir(), pathname);
65+
return new FileInputStream(inputFile);
7266
}
7367

7468
/**
@@ -85,11 +79,14 @@ public OutputStream openOutputFile(String pathname) throws FileNotFoundException
8579

8680
@Override
8781
public OutputStream openOutputFile(String pathname, boolean append) throws FileNotFoundException {
88-
File outputFile = new File(pathname);
89-
if (!outputFile.isAbsolute()) {
90-
outputFile = new File(outputDirCalculator.getOutputDir(), pathname);
91-
}
82+
File outputFile = new File(testDirCalculator.getOutputDir(), pathname);
9283
Log.d("FileTestStorage", "openOutputFile from " + outputFile.getAbsolutePath());
84+
if (!outputFile.getParentFile().exists()) {
85+
if (!outputFile.getParentFile().mkdirs()) {
86+
throw new FileNotFoundException(
87+
"Failed to create output dir " + outputFile.getParentFile().getAbsolutePath());
88+
}
89+
}
9390
return new FileOutputStream(outputFile, append);
9491
}
9592

@@ -153,21 +150,19 @@ public OutputStream openInternalOutputFile(String pathname) throws FileNotFoundE
153150

154151
@Override
155152
public Uri getInputFileUri(@NonNull String pathname) {
156-
throw new UnsupportedOperationException();
153+
File inputFile = new File(testDirCalculator.getInputDir(), pathname);
154+
return Uri.fromFile(inputFile);
157155
}
158156

159157
@Override
160158
public Uri getOutputFileUri(@NonNull String pathname) {
161-
File outputFile = new File(pathname);
162-
if (!outputFile.isAbsolute()) {
163-
outputFile = new File(outputDirCalculator.getOutputDir(), pathname);
164-
}
159+
File outputFile = new File(testDirCalculator.getOutputDir(), pathname);
165160
return Uri.fromFile(outputFile);
166161
}
167162

168163
@Override
169164
public boolean isTestStorageFilePath(@NonNull String pathname) {
170-
String outputDir = outputDirCalculator.getOutputDir().getAbsolutePath();
165+
String outputDir = testDirCalculator.getOutputDir().getAbsolutePath();
171166
return pathname.startsWith(outputDir);
172167
}
173168
}

runner/monitor/java/androidx/test/platform/io/PlatformTestStorage.java

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,61 +17,89 @@
1717

1818
import android.net.Uri;
1919
import androidx.annotation.NonNull;
20-
import androidx.test.annotation.ExperimentalTestApi;
20+
import androidx.annotation.RestrictTo;
21+
import androidx.annotation.RestrictTo.Scope;
22+
import androidx.test.platform.app.InstrumentationRegistry;
2123
import java.io.FileNotFoundException;
2224
import java.io.InputStream;
2325
import java.io.OutputStream;
2426
import java.io.Serializable;
2527
import java.util.Map;
2628

2729
/**
28-
* An interface represents on-device I/O operations in an Android test.
30+
* An interface representing on-device I/O operations in an Android test.
2931
*
30-
* <p>This is a low level API, typically used by higher level test frameworks. It is generally not
31-
* recommended for direct use by most tests.
32+
* <p>This API allows users to retrieve test data specified in the build configuration, and write
33+
* output test data that can be automatically collected by the test runner infrastructure, if the
34+
* environment supports it.
3235
*
33-
* <p>Use a concrete implementation class of this interface if you need to read/write files in your
34-
* tests. For example, in an Android Instrumentation test, use {@code
35-
* androidx.test.services.storage.TestStorage} when the test services is installed on the device.
36+
* <p>Typically users will retrieve the appropriate implementation via {@link
37+
* PlatformTestStorageRegistry#getInstance()}.
3638
*
37-
* <p>This API is experimental and is subject to change or removal in future releases.
39+
* <p>Implementers would need to also implement the appropriate test runner support for pushing and
40+
* pulling the test data to and from the device from the build environment.
3841
*/
39-
@ExperimentalTestApi
4042
public interface PlatformTestStorage {
4143

4244
/**
4345
* Provides an InputStream to a test file dependency.
4446
*
45-
* @param pathname path to the test file dependency. Should not be null.
46-
* @return an InputStream to the given test file.
47+
* <p>In bazel/blaze environments, this corresponds to files passed in the 'data' attribute of the
48+
* android_instrumentation_test or android_local_test build rule.
49+
*
50+
* <p>This API is currently not supported in gradle environments.
51+
*
52+
* @param pathname the path to the test file dependency, relative to the root where the storage
53+
* implementation stores input files. Should not be null.
54+
* @return a potentially unbuffered InputStream to the given test file. Users will typically want
55+
* to buffer the input in memory when reading from this stream.
4756
* @throws FileNotFoundException if pathname does not exist
4857
*/
4958
InputStream openInputFile(String pathname) throws FileNotFoundException;
5059

5160
/**
5261
* Returns the value of a given argument name.
5362
*
63+
* <p>In bazel/blaze environments, this corresponds to flags passed in the 'args' attribute of the
64+
* android_instrumentation_test or android_local_test build rule.
65+
*
66+
* <p>This API is currently unsupported in gradle environments. It is recommended to use {@link
67+
* InstrumentationRegistry#getArguments()} as an alternative.
68+
*
5469
* @param argName the argument name. Should not be null.
5570
*/
5671
String getInputArg(String argName);
5772

5873
/**
5974
* Returns the name/value map of all test arguments or an empty map if no arguments are defined.
75+
*
76+
* @see {@link #getInputArg(String)}
6077
*/
6178
Map<String, String> getInputArgs();
6279

6380
/**
64-
* Provides an OutputStream to a test output file.
81+
* Provides an OutputStream to a test output file. Will overwrite any data written to the same
82+
* pathname in the same test run.
6583
*
66-
* @param pathname path to the test output file. Should not be null.
67-
* @return an OutputStream to the given output file.
84+
* <p>Supported test runners will pull the files from the device once the test completes.
85+
*
86+
* <p>In gradle android instrumentation test environments, the files will typically be stored in
87+
* path_to_your_project/module_name/build/outputs/managed_device_android_test_additional_output
88+
* <br>
89+
*
90+
* @param pathname relative path to the test output file. Should not be null.
91+
* @return a potentially unbuffered OutputStream to the given output file. Users will typically
92+
* want to buffer the output in memory when writing to this stream.
6893
* @throws FileNotFoundException if pathname does not exist
6994
*/
7095
OutputStream openOutputFile(String pathname) throws FileNotFoundException;
7196

7297
/**
7398
* Provides an OutputStream to a test output file.
7499
*
100+
* <p>This API is identical to {@link #openOutputFile(String)} with the additional feature of
101+
* allowing appending or overwriting test data.
102+
*
75103
* @param pathname path to the test output file. Should not be null.
76104
* @param append if true, then the lines will be added to the end of the file rather than
77105
* overwriting.
@@ -85,6 +113,8 @@ public interface PlatformTestStorage {
85113
*
86114
* <p>Adding a property with the same name would append new values and overwrite the old values if
87115
* keys already exist.
116+
*
117+
* <p>This API is unsupported in gradle environments. <br>
88118
*/
89119
void addOutputProperties(Map<String, Serializable> properties);
90120

@@ -100,7 +130,9 @@ public interface PlatformTestStorage {
100130
* @param pathname path to the internal file. Should not be null.
101131
* @return an InputStream to the given test file.
102132
* @throws FileNotFoundException if pathname does not exist
133+
* @hide
103134
*/
135+
@RestrictTo(Scope.LIBRARY_GROUP)
104136
default InputStream openInternalInputFile(String pathname) throws FileNotFoundException {
105137
return openInputFile(pathname);
106138
}
@@ -111,7 +143,9 @@ default InputStream openInternalInputFile(String pathname) throws FileNotFoundEx
111143
* @param pathname path to the internal file. Should not be null.
112144
* @return an OutputStream to the given output file.
113145
* @throws FileNotFoundException if pathname does not exist
146+
* @hide
114147
*/
148+
@RestrictTo(Scope.LIBRARY_GROUP)
115149
default OutputStream openInternalOutputFile(String pathname) throws FileNotFoundException {
116150
return openOutputFile(pathname);
117151
}
@@ -123,11 +157,9 @@ default OutputStream openInternalOutputFile(String pathname) throws FileNotFound
123157
* InputStream to the input file content immediately. Only use this method if you would like to
124158
* store the file Uri and use it for I/O operations later.
125159
*
126-
* <p><b>Note:</b> temporary API - will be renamed to getInpFileUri in future
127-
*
128160
* @param pathname path to the test file dependency. Should not be null. This is a relative path
129-
* to where the storage service stores the input files. For example, if the storage service
130-
* stores the input files under "/sdcard/test_input_files", with a pathname
161+
* to where the storage implementation stores the input files. For example, if the storage
162+
* service stores the input files under "/sdcard/test_input_files", with a pathname
131163
* "/path/to/my_input.txt", the file will end up at
132164
* "/sdcard/test_input_files/path/to/my_input.txt" on device.
133165
* @return a content Uri to the test file dependency.
@@ -142,19 +174,17 @@ default OutputStream openInternalOutputFile(String pathname) throws FileNotFound
142174
* OutputStream to the output file content immediately. Only use this method if you would like to
143175
* store the file Uri and use it for I/O operations later.
144176
*
145-
* <p><b>Note:</b> temporary API - will be renamed to getOutputFileUri in future
146-
*
147177
* @param pathname path to the test output file. Should not be null. This is a relative path to
148-
* where the storage service stores the output files. For example, if the storage service
149-
* stores the output files under "/sdcard/test_output_files", with a pathname
178+
* where the storage implementation stores the output files. For example, if the storage
179+
* service stores the output files under "/sdcard/test_output_files", with a pathname
150180
* "/path/to/my_output.txt", the file will end up at
151181
* "/sdcard/test_output_files/path/to/my_output.txt" on device.
152182
*/
153183
Uri getOutputFileUri(@NonNull String pathname);
154184

155185
/**
156186
* Returns true if {@code pathname} corresponds to a file or directory that is in a directory
157-
* where the storage stores files.
187+
* where the storage implementation stores files.
158188
*
159189
* @param pathname path to a file or directory. Should not be null. This is an absolute path to a
160190
* file that may be a part of the storage service.

0 commit comments

Comments
 (0)