WM Tests: Use a separate WindowManager instance per test

Fixes a bunch of flakes, where the WindowManagerService instance
was reused between tests, which caused delayed callbacks from a
previous test affecting state of a future test.

Also introduces a DexmakerShareClassLoaderRule to manage
the 'dexmaker.share_classloader' property instead of sprinkling
error prone System.setProperty() invocations all over the tests.

Change-Id: Ic9445d1b2cef594e79365c425632aabced6343a9
Fixes: 76111404
Fixes: 75991352
Fixes: 75991878
Fixes: 75992153
Test: atest services/tests/servicestests DexmakerShareClassLoaderRuleTest packages/SystemUI/tests packages/SystemUI/shared/tests
This commit is contained in:
Adrian Roos
2018-03-28 18:06:52 +02:00
parent 52c15f1699
commit 3150dbf7a5
14 changed files with 524 additions and 126 deletions

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.testing;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.ConcurrentModificationException;
/**
* Runs the test such that mocks created in it don't use a dedicated classloader.
*
* This allows mocking package-private methods.
*
* WARNING: This is absolutely incompatible with running tests in parallel!
*/
public class DexmakerShareClassLoaderRule implements TestRule {
private static final String TAG = "ShareClassloaderRule";
@VisibleForTesting
static final String DEXMAKER_SHARE_CLASSLOADER_PROPERTY = "dexmaker.share_classloader";
private static Thread sOwningThread = null;
@Override
public Statement apply(Statement base, Description description) {
return apply(base::evaluate).toStatement();
}
/**
* Runs the runnable such that mocks created in it don't use a dedicated classloader.
*
* This allows mocking package-private methods.
*
* WARNING: This is absolutely incompatible with running tests in parallel!
*/
public static void runWithDexmakerShareClassLoader(Runnable r) {
apply(r::run).run();
}
/**
* Returns a statement that first makes sure that only one thread at the time is modifying
* the property. Then actually sets the property, and runs the statement.
*/
private static <T extends Throwable> ThrowingRunnable<T> apply(ThrowingRunnable<T> r) {
return wrapInMutex(wrapInSetAndClearProperty(r));
}
private static <T extends Throwable> ThrowingRunnable<T> wrapInSetAndClearProperty(
ThrowingRunnable<T> r) {
return () -> {
final String previousValue = System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
try {
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, "true");
r.run();
} finally {
if (previousValue != null) {
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, previousValue);
} else {
System.clearProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
}
}
};
}
/**
* Runs the given statement, and while doing so prevents other threads from running statements.
*/
private static <T extends Throwable> ThrowingRunnable<T> wrapInMutex(ThrowingRunnable<T> r) {
return () -> {
final boolean isOwner;
synchronized (DexmakerShareClassLoaderRule.class) {
isOwner = (sOwningThread == null);
if (isOwner) {
sOwningThread = Thread.currentThread();
} else if (sOwningThread != Thread.currentThread()) {
final RuntimeException e = new ConcurrentModificationException(
"Tried to set dexmaker.share_classloader from " + Thread.currentThread()
+ ", but was already set from " + sOwningThread);
// Also log in case exception gets swallowed.
Log.e(TAG, e.getMessage(), e);
throw e;
}
}
try {
r.run();
} finally {
synchronized (DexmakerShareClassLoaderRule.class) {
if (isOwner) {
sOwningThread = null;
}
}
}
};
}
private interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
default Statement toStatement() {
return new Statement() {
@Override
public void evaluate() throws Throwable {
ThrowingRunnable.this.run();
}
};
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.testing;
import static android.testing.DexmakerShareClassLoaderRule.DEXMAKER_SHARE_CLASSLOADER_PROPERTY;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import java.util.ConcurrentModificationException;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DexmakerShareClassLoaderRuleTest {
// Intentionally not @Rule, so we have more control.
private final DexmakerShareClassLoaderRule mRule = new DexmakerShareClassLoaderRule();
@Before
public void setUp() throws Exception {
System.clearProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
}
@Test
public void rule_setsProperty() throws Throwable {
mRule.apply(ASSERT_STATEMENT, null).evaluate();
}
@Test
public void rule_resetsProperty() throws Throwable {
mRule.apply(ASSERT_STATEMENT, null).evaluate();
assertThat(readProperty(), is(false));
}
@Test
public void rule_resetsProperty_toExactValue() throws Throwable {
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, "asdf");
mRule.apply(ASSERT_STATEMENT, null).evaluate();
assertThat(System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY), is("asdf"));
}
@Test
public void rule_preventsOtherThreadFromInterfering() throws Throwable {
mRule.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
assertThat(readProperty(), is(true));
final Throwable[] thrown = new Throwable[1];
final Thread t = new Thread(() -> {
try {
new DexmakerShareClassLoaderRule().apply(ASSERT_STATEMENT, null).evaluate();
fail("Expected a ConcurrentModificationException");
} catch (ConcurrentModificationException e) {
// Success.
} catch (Throwable tr) {
thrown[0] = tr;
}
});
t.start();
t.join();
if (thrown[0] != null) {
throw thrown[0];
}
}
}, null).evaluate();
assertThat(readProperty(), is(false));
}
@Test
public void rule_isReentrant() throws Throwable {
mRule.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
assertThat(readProperty(), is(true));
new DexmakerShareClassLoaderRule().apply(ASSERT_STATEMENT, null).evaluate();
assertThat(readProperty(), is(true));
}
}, null).evaluate();
assertThat(readProperty(), is(false));
}
private static boolean readProperty() {
return Boolean.parseBoolean(System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY));
}
private static final Statement ASSERT_STATEMENT = new Statement() {
@Override
public void evaluate() throws Throwable {
assertThat(readProperty(), is(true));
}
};
}