Skip to content

Commit eb7cae1

Browse files
authored
Add StrictModeRule for use by SDKs. (#2023)
* Add StrictModeRule for use by SDKs. * Add copyright. * Add comment on why gc needs to run.
1 parent 26f1b13 commit eb7cae1

File tree

9 files changed

+268
-22
lines changed

9 files changed

+268
-22
lines changed

firebase-common/firebase-common.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dependencies {
8484

8585
annotationProcessor 'com.google.auto.value:auto-value:1.6.5'
8686

87+
androidTestImplementation project(':integ-testing')
8788
androidTestImplementation 'junit:junit:4.13'
8889
androidTestImplementation 'androidx.test:runner:1.3.0'
8990
androidTestImplementation 'androidx.test.ext:junit:1.1.2'

firebase-common/src/androidTest/java/com/google/firebase/StrictModeTest.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,21 @@
1414

1515
package com.google.firebase;
1616

17-
import android.os.StrictMode;
18-
import android.os.StrictMode.ThreadPolicy;
19-
import android.os.StrictMode.VmPolicy;
2017
import androidx.test.core.app.ApplicationProvider;
2118
import com.google.firebase.FirebaseOptions.Builder;
19+
import com.google.firebase.testing.integ.StrictModeRule;
20+
import org.junit.Rule;
2221
import org.junit.Test;
2322
import org.junit.runner.RunWith;
2423

2524
@RunWith(androidx.test.ext.junit.runners.AndroidJUnit4.class)
2625
public class StrictModeTest {
2726

28-
interface Fn<E extends Throwable> {
29-
void call() throws E;
30-
}
31-
32-
static <E extends Throwable> void withStrictMode(Fn<E> fn) throws E {
33-
ThreadPolicy threadPolicy = StrictMode.getThreadPolicy();
34-
VmPolicy vmPolicy = StrictMode.getVmPolicy();
35-
36-
StrictMode.setThreadPolicy(new ThreadPolicy.Builder().detectAll().penaltyDeath().build());
37-
StrictMode.setVmPolicy(new VmPolicy.Builder().detectAll().penaltyDeath().build());
38-
try {
39-
fn.call();
40-
} finally {
41-
StrictMode.setThreadPolicy(threadPolicy);
42-
StrictMode.setVmPolicy(vmPolicy);
43-
}
44-
}
27+
@Rule public StrictModeRule strictMode = new StrictModeRule();
4528

4629
@Test
4730
public void initializingFirebaseApp_shouldNotViolateStrictMode() {
48-
withStrictMode(
31+
strictMode.runOnMainThread(
4932
() -> {
5033
FirebaseApp app =
5134
FirebaseApp.initializeApp(
@@ -56,7 +39,6 @@ public void initializingFirebaseApp_shouldNotViolateStrictMode() {
5639
.setApplicationId("appId")
5740
.build(),
5841
"hello");
59-
6042
app.initializeAllComponents();
6143
});
6244
}

integ-testing/gradle.properties

Whitespace-only changes.

integ-testing/integ-testing.gradle

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
plugins {
16+
id 'com.android.library'
17+
}
18+
19+
android {
20+
compileSdkVersion project.targetSdkVersion
21+
defaultConfig {
22+
minSdkVersion project.minSdkVersion
23+
targetSdkVersion project.targetSdkVersion
24+
}
25+
26+
compileOptions {
27+
sourceCompatibility JavaVersion.VERSION_1_8
28+
targetCompatibility JavaVersion.VERSION_1_8
29+
}
30+
}
31+
32+
dependencies {
33+
implementation 'junit:junit:4.13'
34+
implementation 'androidx.test:runner:1.3.0'
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Copyright 2020 Google LLC -->
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+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
17+
xmlns:tools="http://schemas.android.com/tools"
18+
package="com.google.firebase.testing.integ">
19+
<!--Although the *SdkVersion is captured in gradle build files, this is required for non gradle builds-->
20+
<!--<uses-sdk android:minSdkVersion="14"/>-->
21+
</manifest>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.testing.integ;
16+
17+
public interface MaybeThrowingCallable<T, E extends Throwable> {
18+
T call() throws E;
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.testing.integ;
16+
17+
public interface MaybeThrowingRunnable<E extends Throwable> {
18+
void run() throws E;
19+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.testing.integ;
16+
17+
import android.os.Build;
18+
import android.os.StrictMode;
19+
import android.os.StrictMode.ThreadPolicy;
20+
import android.os.StrictMode.VmPolicy;
21+
import androidx.test.internal.runner.junit4.statement.UiThreadStatement;
22+
import androidx.test.platform.app.InstrumentationRegistry;
23+
import java.util.ArrayList;
24+
import java.util.Collection;
25+
import java.util.concurrent.ConcurrentLinkedQueue;
26+
import java.util.concurrent.Executor;
27+
import java.util.concurrent.atomic.AtomicReference;
28+
import org.junit.rules.TestRule;
29+
import org.junit.runner.Description;
30+
import org.junit.runners.model.MultipleFailureException;
31+
import org.junit.runners.model.Statement;
32+
33+
/**
34+
* This rule enables {@link StrictMode} on the <a
35+
* href="https://developer.android.com/guide/components/processes-and-threads#Threads">Main
36+
* thread</a>.
37+
*
38+
* <p>Just adding it as a {@link @Rule} to your test is enough to enable it.
39+
*
40+
* <p>Note however that the tests don't run on the Main thread by default, so if you expect the code
41+
* under test to run in the Main thread in production, please use the provided {@link
42+
* #runOnMainThread(MaybeThrowingRunnable)} to execute it on the Main thread.
43+
*
44+
* <p>Example use:
45+
*
46+
* <pre>{@code
47+
* @Test
48+
* public class MyTests {
49+
* @Rule public StrictModeRule strictMode = new StrictModeRule();
50+
*
51+
* @Test public void myTest() {
52+
* // runs on the instrumentation thread
53+
* runMyCode();
54+
*
55+
* // runs on Main thread.
56+
* strictMode.runOnMainThread(() -> {
57+
* runCodeOnMainThread();
58+
* });
59+
* }
60+
* }
61+
* }</pre>
62+
*/
63+
public class StrictModeRule implements TestRule {
64+
65+
private static final Executor penaltyListenerExecutor = Runnable::run;
66+
67+
/** Runs {@code runnable} on Main thread. */
68+
public <E extends Throwable> void runOnMainThread(MaybeThrowingRunnable<E> runnable) throws E {
69+
try {
70+
new UiThreadStatement(
71+
new Statement() {
72+
@Override
73+
public void evaluate() throws E {
74+
runnable.run();
75+
}
76+
},
77+
true)
78+
.evaluate();
79+
} catch (Throwable throwable) {
80+
@SuppressWarnings("unchecked")
81+
E e = (E) throwable;
82+
throw e;
83+
}
84+
}
85+
86+
/** Runs {@code callable} on Main thread and returns it result. */
87+
public <T, E extends Throwable> T runOnMainThread(MaybeThrowingCallable<T, E> callable) throws E {
88+
try {
89+
AtomicReference<T> result = new AtomicReference<>();
90+
new UiThreadStatement(
91+
new Statement() {
92+
@Override
93+
public void evaluate() throws E {
94+
result.set(callable.call());
95+
}
96+
},
97+
true)
98+
.evaluate();
99+
return result.get();
100+
} catch (Throwable throwable) {
101+
@SuppressWarnings("unchecked")
102+
E e = (E) throwable;
103+
throw e;
104+
}
105+
}
106+
107+
@Override
108+
public Statement apply(Statement base, Description description) {
109+
return new Statement() {
110+
@Override
111+
public void evaluate() throws Throwable {
112+
AtomicReference<ThreadPolicy> originalThreadPolicy = new AtomicReference<>();
113+
VmPolicy originalVmPolicy = StrictMode.getVmPolicy();
114+
115+
ConcurrentLinkedQueue<Throwable> violations = new ConcurrentLinkedQueue<>();
116+
117+
InstrumentationRegistry.getInstrumentation()
118+
.runOnMainSync(
119+
() -> {
120+
originalThreadPolicy.set(StrictMode.getThreadPolicy());
121+
122+
StrictMode.setThreadPolicy(createThreadPolicy(violations));
123+
StrictMode.setVmPolicy(createVmPolicy(violations));
124+
});
125+
try {
126+
base.evaluate();
127+
} catch (Throwable e) {
128+
violations.add(e);
129+
} finally {
130+
InstrumentationRegistry.getInstrumentation()
131+
.runOnMainSync(() -> StrictMode.setThreadPolicy(originalThreadPolicy.get()));
132+
// Make sure GC happens, so that the VM policy can detect unclosed resources.
133+
runGc();
134+
StrictMode.setVmPolicy(originalVmPolicy);
135+
}
136+
MultipleFailureException.assertEmpty(new ArrayList<>(violations));
137+
}
138+
};
139+
}
140+
141+
private static ThreadPolicy createThreadPolicy(Collection<Throwable> violations) {
142+
ThreadPolicy.Builder builder = new ThreadPolicy.Builder().detectAll();
143+
if (Build.VERSION.SDK_INT >= 28) {
144+
builder.penaltyListener(penaltyListenerExecutor, violations::add);
145+
} else {
146+
builder.penaltyDeath();
147+
}
148+
return builder.build();
149+
}
150+
151+
private static VmPolicy createVmPolicy(Collection<Throwable> violations) {
152+
VmPolicy.Builder builder = new VmPolicy.Builder().detectAll();
153+
if (Build.VERSION.SDK_INT >= 28) {
154+
builder.penaltyListener(penaltyListenerExecutor, violations::add);
155+
} else {
156+
builder.penaltyDeath();
157+
}
158+
return builder.build();
159+
}
160+
161+
private static void runGc() {
162+
Runtime.getRuntime().gc();
163+
Runtime.getRuntime().runFinalization();
164+
Runtime.getRuntime().gc();
165+
Runtime.getRuntime().runFinalization();
166+
}
167+
}

subprojects.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ encoders:firebase-encoders-processor
4343
encoders:firebase-encoders-reflective
4444
encoders:firebase-decoders-json
4545

46+
integ-testing
47+
4648
tools:errorprone
4749
tools:lint
4850

0 commit comments

Comments
 (0)