1 /* 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 17 package android.testing; 18 19 import android.util.Log; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 23 import libcore.util.SneakyThrow; 24 25 import org.junit.rules.TestRule; 26 import org.junit.runner.Description; 27 import org.junit.runners.model.Statement; 28 29 import 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 */ 39 public 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 apply(Statement base, Description description)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 */ runWithDexmakerShareClassLoader(Runnable r)59 public static void runWithDexmakerShareClassLoader(Runnable r) { 60 try { 61 apply(r::run).run(); 62 } catch (Throwable t) { 63 SneakyThrow.sneakyThrow(t); 64 } 65 } 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 */ apply(ThrowingRunnable<T> r)71 private static <T extends Throwable> ThrowingRunnable<T> apply(ThrowingRunnable<T> r) { 72 return wrapInMutex(wrapInSetAndClearProperty(r)); 73 } 74 wrapInSetAndClearProperty( ThrowingRunnable<T> r)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 */ wrapInMutex(ThrowingRunnable<T> r)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 } 136