blob: 7057a90c7672d0ed10148b18cf5894c9f7aecf6f [file] [log] [blame]
Adrian Roos3150dbf2018-03-28 18:06:52 +02001/*
2 * Copyright (C) 2018 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
17package android.testing;
18
19import android.util.Log;
20
21import com.android.internal.annotations.VisibleForTesting;
22
Sunny Goyal87fccf02019-08-13 17:39:10 -070023import libcore.util.SneakyThrow;
24
Adrian Roos3150dbf2018-03-28 18:06:52 +020025import org.junit.rules.TestRule;
26import org.junit.runner.Description;
27import org.junit.runners.model.Statement;
28
29import java.util.ConcurrentModificationException;
30
31
32/**
33 * Runs the test such that mocks created in it don't use a dedicated classloader.
34 *
35 * This allows mocking package-private methods.
36 *
37 * WARNING: This is absolutely incompatible with running tests in parallel!
38 */
39public class DexmakerShareClassLoaderRule implements TestRule {
40
41 private static final String TAG = "ShareClassloaderRule";
42 @VisibleForTesting
43 static final String DEXMAKER_SHARE_CLASSLOADER_PROPERTY = "dexmaker.share_classloader";
44
45 private static Thread sOwningThread = null;
46
47 @Override
48 public Statement apply(Statement base, Description description) {
49 return apply(base::evaluate).toStatement();
50 }
51
52 /**
53 * Runs the runnable such that mocks created in it don't use a dedicated classloader.
54 *
55 * This allows mocking package-private methods.
56 *
57 * WARNING: This is absolutely incompatible with running tests in parallel!
58 */
59 public static void runWithDexmakerShareClassLoader(Runnable r) {
Sunny Goyal87fccf02019-08-13 17:39:10 -070060 try {
61 apply(r::run).run();
62 } catch (Throwable t) {
63 SneakyThrow.sneakyThrow(t);
64 }
Adrian Roos3150dbf2018-03-28 18:06:52 +020065 }
66
67 /**
68 * Returns a statement that first makes sure that only one thread at the time is modifying
69 * the property. Then actually sets the property, and runs the statement.
70 */
71 private static <T extends Throwable> ThrowingRunnable<T> apply(ThrowingRunnable<T> r) {
72 return wrapInMutex(wrapInSetAndClearProperty(r));
73 }
74
75 private static <T extends Throwable> ThrowingRunnable<T> wrapInSetAndClearProperty(
76 ThrowingRunnable<T> r) {
77 return () -> {
78 final String previousValue = System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
79 try {
80 System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, "true");
81 r.run();
82 } finally {
83 if (previousValue != null) {
84 System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, previousValue);
85 } else {
86 System.clearProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
87 }
88 }
89 };
90 }
91
92 /**
93 * Runs the given statement, and while doing so prevents other threads from running statements.
94 */
95 private static <T extends Throwable> ThrowingRunnable<T> wrapInMutex(ThrowingRunnable<T> r) {
96 return () -> {
97 final boolean isOwner;
98 synchronized (DexmakerShareClassLoaderRule.class) {
99 isOwner = (sOwningThread == null);
100 if (isOwner) {
101 sOwningThread = Thread.currentThread();
102 } else if (sOwningThread != Thread.currentThread()) {
103 final RuntimeException e = new ConcurrentModificationException(
104 "Tried to set dexmaker.share_classloader from " + Thread.currentThread()
105 + ", but was already set from " + sOwningThread);
106 // Also log in case exception gets swallowed.
107 Log.e(TAG, e.getMessage(), e);
108 throw e;
109 }
110 }
111 try {
112 r.run();
113 } finally {
114 synchronized (DexmakerShareClassLoaderRule.class) {
115 if (isOwner) {
116 sOwningThread = null;
117 }
118 }
119 }
120 };
121 }
122
123 private interface ThrowingRunnable<T extends Throwable> {
124 void run() throws T;
125
126 default Statement toStatement() {
127 return new Statement() {
128 @Override
129 public void evaluate() throws Throwable {
130 ThrowingRunnable.this.run();
131 }
132 };
133 }
134 }
135}