Skip to content

Commit 22c865c

Browse files
Introduced the platform test storage API and plugged it into Espresso.
PiperOrigin-RevId: 374800866
1 parent 6097db4 commit 22c865c

File tree

9 files changed

+291
-3
lines changed

9 files changed

+291
-3
lines changed

espresso/core/java/androidx/test/espresso/BaseLayerComponent.java

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import androidx.test.espresso.base.MainThread;
2323
import androidx.test.espresso.base.UiControllerModule;
2424
import androidx.test.internal.platform.os.ControlledLooper;
25+
import androidx.test.platform.io.PlatformTestStorage;
2526
import dagger.Component;
2627
import java.util.concurrent.Executor;
2728
import javax.inject.Singleton;
@@ -46,4 +47,6 @@ public interface BaseLayerComponent {
4647
Executor mainThreadExecutor();
4748

4849
ControlledLooper controlledLooper();
50+
51+
PlatformTestStorage testStorage();
4952
}

espresso/core/java/androidx/test/espresso/GraphHolder.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020

21+
import android.util.Log;
2122
import androidx.test.espresso.base.ActiveRootLister;
22-
import androidx.test.internal.platform.util.TestOutputEmitter;
2323
import androidx.test.internal.runner.tracker.UsageTrackerRegistry;
2424
import androidx.test.internal.runner.tracker.UsageTrackerRegistry.AxtVersions;
25+
import androidx.test.platform.io.PlatformTestStorage;
2526
import java.io.Serializable;
2627
import java.util.HashMap;
2728
import java.util.Map;
2829
import java.util.concurrent.atomic.AtomicReference;
2930

3031
/** Holds Espresso's object graph. */
3132
public final class GraphHolder {
33+
private static final String TAG = GraphHolder.class.getSimpleName();
3234

3335
private static final AtomicReference<GraphHolder> instance =
3436
new AtomicReference<GraphHolder>(null);
@@ -48,7 +50,7 @@ static BaseLayerComponent baseLayer() {
4850
// Also adds the usage data as test output properties. By default it's no-op.
4951
Map<String, Serializable> usageProperties = new HashMap<>();
5052
usageProperties.put("Espresso", AxtVersions.ESPRESSO_VERSION);
51-
TestOutputEmitter.addOutputProperties(usageProperties);
53+
addUsageToOutputProperties(usageProperties, instanceRef.component.testStorage());
5254
return instanceRef.component;
5355
} else {
5456
return instance.get().component;
@@ -57,4 +59,20 @@ static BaseLayerComponent baseLayer() {
5759
return instanceRef.component;
5860
}
5961
}
62+
63+
private static void addUsageToOutputProperties(
64+
Map<String, Serializable> usageProperties, PlatformTestStorage testStorage) {
65+
try {
66+
testStorage.addOutputProperties(usageProperties);
67+
} catch (Exception e) {
68+
// The properties.dat file can be created only once on an automotive emulator with API 30,
69+
// which causes the `addOutputProperties` call to fail when running multiple test cases. Catch
70+
// the exception and log until the issue is fixed in the emulator.
71+
Log.d(
72+
TAG,
73+
"Failed to add the output properties. This could happen when running on an"
74+
+ " automotive emulator with API 30. Ignore for now.",
75+
e);
76+
}
77+
}
6078
}

espresso/core/java/androidx/test/espresso/base/BaseLayerModule.java

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import androidx.test.internal.platform.ServiceLoaderWrapper;
2828
import androidx.test.internal.platform.os.ControlledLooper;
2929
import androidx.test.platform.app.InstrumentationRegistry;
30+
import androidx.test.platform.io.PlatformTestStorage;
31+
import androidx.test.platform.io.PlatformTestStorageRegistry;
3032
import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
3133
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
3234
import com.google.common.base.Optional;
@@ -198,4 +200,10 @@ public ControlledLooper provideControlledLooper() {
198200
return ServiceLoaderWrapper.loadSingleService(
199201
ControlledLooper.class, () -> ControlledLooper.NO_OP_CONTROLLED_LOOPER);
200202
}
203+
204+
@Provides
205+
@Singleton
206+
PlatformTestStorage provideTestStorage() {
207+
return PlatformTestStorageRegistry.getInstance();
208+
}
201209
}

runner/android_junit_runner/java/androidx/test/runner/AndroidJUnitRunner.java

+12
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@
4848
import androidx.test.internal.runner.tracker.AnalyticsBasedUsageTracker;
4949
import androidx.test.internal.runner.tracker.UsageTrackerRegistry.AxtVersions;
5050
import androidx.test.orchestrator.callback.OrchestratorV1Connection;
51+
import androidx.test.platform.io.PlatformTestStorageRegistry;
5152
import androidx.test.runner.lifecycle.ApplicationLifecycleCallback;
5253
import androidx.test.runner.lifecycle.ApplicationLifecycleMonitorRegistry;
5354
import androidx.test.runner.screenshot.ScreenCaptureProcessor;
5455
import androidx.test.runner.screenshot.Screenshot;
56+
import androidx.test.services.storage.TestStorage;
5557
import java.util.HashSet;
5658
import java.util.ServiceLoader;
5759
import java.util.concurrent.TimeUnit;
@@ -433,9 +435,11 @@ public void onStart() {
433435
return;
434436
}
435437

438+
// TODO(b/187349527): Clean up the runner I/O.
436439
if (runnerArgs.useTestStorageService) {
437440
runnerIO = new RunnerTestStorageIO();
438441
}
442+
registerTestStorage(runnerArgs);
439443

440444
Bundle results = new Bundle();
441445
try {
@@ -625,4 +629,12 @@ private void registerUserTracker() {
625629
TestRequestBuilder createTestRequestBuilder(Instrumentation instr, Bundle arguments) {
626630
return new TestRequestBuilder(instr, arguments);
627631
}
632+
633+
private void registerTestStorage(RunnerArgs runnerArgs) {
634+
// TODO: Uses the app folder for test storage otherwise. By default, the no_op one will be used.
635+
if (runnerArgs.useTestStorageService) {
636+
Log.d(LOG_TAG, "Use the test storage service for managing file I/O.");
637+
PlatformTestStorageRegistry.registerInstance(new TestStorage());
638+
}
639+
}
628640
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (C) 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.test.platform.io;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.io.OutputStream;
21+
import java.io.Serializable;
22+
import java.util.Map;
23+
24+
/**
25+
* An interface represents on-device I/O operations in an Android test.
26+
*
27+
* <p>This is a low level API, typically used by higher level test frameworks. It is generally not
28+
* recommended for direct use by most tests.
29+
*
30+
* <p>Use a concrete implementation class of this interface if you need to read/write files in your
31+
* tests. For example, in an Android Instrumentation test, use {@code
32+
* androidx.test.services.storage.TestStorage} when the test services is installed on the device.
33+
*/
34+
public interface PlatformTestStorage {
35+
/**
36+
* Provides an InputStream to a test file dependency.
37+
*
38+
* @param pathname path to the test file dependency. Should not be null. This is a relative path
39+
* to where the storage service stores the input files. For example, if the storage service
40+
* stores the input files under "/sdcard/test_input_files", with a pathname
41+
* "/path/to/my_input.txt", the file will end up at
42+
* "/sdcard/test_input_files/path/to/my_input.txt" on device.
43+
* @return an InputStream to the given test file.
44+
*/
45+
InputStream openInputFile(String pathname) throws IOException;
46+
47+
/**
48+
* Returns the value of a given argument name.
49+
*
50+
* <p>There should be one and only one argument defined with the given argument name. Otherwise,
51+
* it will throw a TestStorageException if zero or more than one arguments are found.
52+
*
53+
* <p>We suggest using some naming convention when defining the argument name to avoid possible
54+
* conflict, e.g. defining "namespaces" for your arguments which helps clarify how the argument is
55+
* used and also its scope. For example, for arguments used for authentication purposes, you could
56+
* name the account email argument as something like "google_account.email" and its password as
57+
* "google_account.password".
58+
*
59+
* @param argName the argument name. Should not be null.
60+
*/
61+
String getInputArg(String argName);
62+
63+
/**
64+
* Returns the name/value map of all test arguments or an empty map if no arguments are defined.
65+
*/
66+
Map<String, String> getInputArgs();
67+
68+
/**
69+
* Provides an OutputStream to a test output file.
70+
*
71+
* @param pathname path to the test output file. Should not be null. This is a relative path to
72+
* where the storage service stores the output files. For example, if the storage service
73+
* stores the output files under "/sdcard/test_output_files", with a pathname
74+
* "/path/to/my_output.txt", the file will end up at
75+
* "/sdcard/test_output_files/path/to/my_output.txt" on device.
76+
* @return an OutputStream to the given output file.
77+
*/
78+
OutputStream openOutputFile(String pathname) throws IOException;
79+
80+
/**
81+
* Adds the given properties.
82+
*
83+
* <p>Adding a property with the same name would append new values and overwrite the old values if
84+
* keys already exist.
85+
*/
86+
void addOutputProperties(Map<String, Serializable> properties);
87+
88+
/**
89+
* Returns a map of all the output test properties. If no properties exist, an empty map will be
90+
* returned.
91+
*/
92+
Map<String, Serializable> getOutputProperties();
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (C) 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.test.platform.io;
17+
18+
import static androidx.test.internal.util.Checks.checkNotNull;
19+
20+
import androidx.test.internal.platform.ServiceLoaderWrapper;
21+
import java.io.InputStream;
22+
import java.io.OutputStream;
23+
import java.io.Serializable;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
27+
/**
28+
* An exposed registry instance that holds a reference to an {@code PlatformTestStorage} instance.
29+
*
30+
* <p>{@code PlatformTestStorage} and {@code PlatformTestStorageRegistry} are low level APIs,
31+
* typically used by higher level test frameworks. It is generally not recommended for direct use by
32+
* most tests.
33+
*/
34+
public final class PlatformTestStorageRegistry {
35+
private static PlatformTestStorage testStorageInstance;
36+
37+
static {
38+
// By default, uses the instance loaded by the service loader if available; otherwise, uses a
39+
// default no_op implementation.
40+
testStorageInstance =
41+
ServiceLoaderWrapper.loadSingleService(
42+
PlatformTestStorage.class, NoOpPlatformTestStorage::new);
43+
}
44+
45+
private PlatformTestStorageRegistry() {}
46+
47+
/**
48+
* Registers a new {@code PlatformTestStorage} instance. This will override any previously set
49+
* instance.
50+
*
51+
* @param instance the instance to be registered. Cannot be null.
52+
*/
53+
public static synchronized void registerInstance(PlatformTestStorage instance) {
54+
testStorageInstance = checkNotNull(instance);
55+
}
56+
57+
/**
58+
* Returns the registered {@code PlatformTestStorage} instance.
59+
*
60+
* <p>This method returns the instance last registered by the {@link
61+
* #registerInstance(PlatformTestStorage)} method, or the default instance if none is ever
62+
* registered.
63+
*/
64+
public static synchronized PlatformTestStorage getInstance() {
65+
return testStorageInstance;
66+
}
67+
68+
/** A test storage that does nothing. All the I/O operations in this class are ignored. */
69+
static class NoOpPlatformTestStorage implements PlatformTestStorage {
70+
71+
@Override
72+
public InputStream openInputFile(String pathname) {
73+
return new NullInputStream();
74+
}
75+
76+
@Override
77+
public String getInputArg(String argName) {
78+
return null;
79+
}
80+
81+
@Override
82+
public Map<String, String> getInputArgs() {
83+
return new HashMap<>();
84+
}
85+
86+
@Override
87+
public OutputStream openOutputFile(String pathname) {
88+
return new NullOutputStream();
89+
}
90+
91+
@Override
92+
public void addOutputProperties(Map<String, Serializable> properties) {}
93+
94+
@Override
95+
public Map<String, Serializable> getOutputProperties() {
96+
return new HashMap<>();
97+
}
98+
99+
static class NullInputStream extends InputStream {
100+
@Override
101+
public int read() {
102+
return 0;
103+
}
104+
}
105+
106+
static class NullOutputStream extends OutputStream {
107+
@Override
108+
public void write(int b) {}
109+
}
110+
}
111+
}

runner/monitor/javatests/androidx/test/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ android_library_instrumentation_tests(
4747
"//runner/monitor",
4848
"//runner/monitor/javatests/androidx/test/internal/platform:fixtures",
4949
"//runner/rules",
50+
"//services/storage",
5051
"@maven//:com_google_truth_truth",
5152
"@maven//:com_linkedin_dexmaker_dexmaker",
5253
"@maven//:com_linkedin_dexmaker_dexmaker_mockito",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (C) 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.test.platform.io;
17+
18+
import static com.google.common.truth.Truth.assertThat;
19+
20+
import androidx.test.ext.junit.runners.AndroidJUnit4;
21+
import androidx.test.services.storage.TestStorage;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
25+
/** Unit test cases for {@link PlatformTestStorage}. */
26+
@RunWith(AndroidJUnit4.class)
27+
public class PlatformTestStorageRegistryTest {
28+
29+
@Test
30+
public void registerInstance() {
31+
TestStorage testStorage = new TestStorage();
32+
PlatformTestStorageRegistry.registerInstance(testStorage);
33+
assertThat(PlatformTestStorageRegistry.getInstance()).isSameInstanceAs(testStorage);
34+
}
35+
}

0 commit comments

Comments
 (0)