OSDN Git Service

Add system to avoid a crash stopping tests from running
authorJason Monk <jmonk@google.com>
Tue, 27 Jun 2017 17:13:49 +0000 (13:13 -0400)
committerJason Monk <jmonk@google.com>
Tue, 27 Jun 2017 18:48:45 +0000 (14:48 -0400)
Add a system such that when a crash happens on the main thread, it is
delayed until the tests complete, then thrown.

Also mark some old flaky tests as not flaky as they should be good
now.

Test: runtest systemui
Change-Id: Ic18a38daf4a93dab63549d834fa00f295644fbf1
Fixes: 629357206225190362529569

14 files changed:
packages/SystemUI/tests/AndroidManifest.xml
packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
services/tests/notification/AndroidManifest.xml
tests/testables/src/android/testing/TestableInstrumentation.java [new file with mode: 0644]
tests/testables/src/android/testing/TestableLooper.java

index b12fd1c..9bb2180 100644 (file)
@@ -48,7 +48,7 @@
             android:process=":killable" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="android.testing.TestableInstrumentation"
         android:targetPackage="com.android.systemui.tests"
         android:label="Tests for SystemUI">
     </instrumentation>
index 945af34..507515a 100644 (file)
@@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -30,7 +29,6 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Fragment;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
@@ -48,12 +46,10 @@ import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
-@FlakyTest
 @SmallTest
 public class RoundedCornersTest extends SysuiTestCase {
 
@@ -72,6 +68,7 @@ public class RoundedCornersTest extends SysuiTestCase {
         mWindowManager = mock(WindowManager.class);
         mView = spy(new StatusBarWindowView(mContext, null));
         when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
+        when(mStatusBar.getNavigationBarWindow()).thenReturn(mView);
         mContext.putComponent(StatusBar.class, mStatusBar);
 
         Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
@@ -94,6 +91,8 @@ public class RoundedCornersTest extends SysuiTestCase {
     @Test
     public void testNoRounding() {
         mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(dimen.rounded_corner_content_padding, 0);
 
         mRoundedCorners.start();
         // No views added.
@@ -107,6 +106,8 @@ public class RoundedCornersTest extends SysuiTestCase {
     @Test
     public void testRounding() {
         mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(dimen.rounded_corner_content_padding, 20);
 
         mRoundedCorners.start();
         // Add 2 windows for rounded corners (top and bottom).
index 361a20f..66d00dd 100644 (file)
@@ -31,6 +31,8 @@ import android.util.Log;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -56,10 +58,14 @@ public abstract class SysuiTestCase {
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
-        when(inst.getContext()).thenThrow(new RuntimeException(
-                "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
-        when(inst.getTargetContext()).thenThrow(new RuntimeException(
-                "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
+        when(inst.getContext()).thenAnswer(invocation -> {
+            throw new RuntimeException(
+                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+        });
+        when(inst.getTargetContext()).thenAnswer(invocation -> {
+            throw new RuntimeException(
+                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+        });
         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
     }
 
index 2363b2a..8641fac 100644 (file)
@@ -27,7 +27,6 @@ import android.app.Instrumentation;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
index 6a85511..ed47fbb 100644 (file)
@@ -23,10 +23,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.After;
-import org.junit.Ignore;
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -41,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
index f4fda06..d25bbe1 100644 (file)
@@ -21,7 +21,6 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
index 637b244..85cdfcc 100644 (file)
@@ -20,7 +20,6 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -31,7 +30,6 @@ import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.customize.QSCustomizer;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,7 +38,6 @@ import java.util.Collections;
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
-@FlakyTest
 public class QSPanelTest extends SysuiTestCase {
 
     private MetricsLogger mMetricsLogger;
index 664ea71..2f6511c 100644 (file)
@@ -21,27 +21,21 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
-import com.android.systemui.SysuiTestCase;
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mGroup;
index 4dce2f5..436849c 100644 (file)
 
 package com.android.systemui.statusbar;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
 import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
+import com.android.systemui.SysuiTestCase;
+
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import com.android.systemui.SysuiTestCase;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationContentViewTest extends SysuiTestCase {
 
     NotificationContentView mView;
index d18e63b..6e59d10 100644 (file)
 
 package com.android.systemui.statusbar;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
@@ -32,13 +29,11 @@ import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationCustomViewWrapperTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mRow;
index bc6833d..5ac965c 100644 (file)
 
 package com.android.systemui.statusbar.stack;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.NotificationHeaderView;
@@ -31,13 +27,11 @@ import com.android.systemui.statusbar.NotificationTestHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationChildrenContainerTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mGroup;
index 99d9c7b..c20020a 100644 (file)
@@ -31,7 +31,7 @@
     </application>
 
     <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:name="android.testing.TestableInstrumentation"
         android:targetPackage="com.android.frameworks.tests.notification"
         android:label="Notification Tests" />
 </manifest>
diff --git a/tests/testables/src/android/testing/TestableInstrumentation.java b/tests/testables/src/android/testing/TestableInstrumentation.java
new file mode 100644 (file)
index 0000000..93fed85
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 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.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.TestLooperManager;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper around instrumentation that spins up a TestLooperManager around
+ * the main looper whenever a test is not using it to attempt to stop crashes
+ * from stopping other tests from running.
+ */
+public class TestableInstrumentation extends AndroidJUnitRunner {
+
+    private static final String TAG = "TestableInstrumentation";
+
+    private static final int MAX_CRASHES = 5;
+    private static MainLooperManager sManager;
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        sManager = new MainLooperManager();
+        Log.setWtfHandler((tag, what, system) -> {
+            if (system) {
+                Log.e(TAG, "WTF!!", what);
+            } else {
+                // These normally kill the app, but we don't want that in a test, instead we want
+                // it to throw.
+                throw new RuntimeException(what);
+            }
+        });
+        super.onCreate(arguments);
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        sManager.destroy();
+        super.finish(resultCode, results);
+    }
+
+    public static void acquireMain() {
+        if (sManager != null) {
+            sManager.acquireMain();
+        }
+    }
+
+    public static void releaseMain() {
+        if (sManager != null) {
+            sManager.releaseMain();
+        }
+    }
+
+    public class MainLooperManager implements Runnable {
+
+        private final ArrayList<Throwable> mExceptions = new ArrayList<>();
+        private Message mStopMessage;
+        private final Handler mMainHandler;
+        private TestLooperManager mManager;
+
+        public MainLooperManager() {
+            mMainHandler = new Handler(Looper.getMainLooper());
+            startManaging();
+        }
+
+        @Override
+        public void run() {
+            try {
+                synchronized (this) {
+                    // Let the thing starting us know we are up and ready to run.
+                    notify();
+                }
+                while (true) {
+                    Message m = mManager.next();
+                    if (m == mStopMessage) {
+                        mManager.recycle(m);
+                        return;
+                    }
+                    try {
+                        mManager.execute(m);
+                    } catch (Throwable t) {
+                        if (!checkStack(t) || (mExceptions.size() == MAX_CRASHES)) {
+                            throw t;
+                        }
+                        mExceptions.add(t);
+                        Log.d(TAG, "Ignoring exception to run more tests", t);
+                    }
+                    mManager.recycle(m);
+                }
+            } finally {
+                mManager.release();
+                synchronized (this) {
+                    // Let the caller know we are done managing the main thread.
+                    notify();
+                }
+            }
+        }
+
+        private boolean checkStack(Throwable t) {
+            StackTraceElement topStack = t.getStackTrace()[0];
+            String className = topStack.getClassName();
+            if (className.equals(TestLooperManager.class.getName())) {
+                topStack = t.getCause().getStackTrace()[0];
+                className = topStack.getClassName();
+            }
+            // Only interested in blocking exceptions from the app itself, not from android
+            // framework.
+            return !className.startsWith("android.")
+                    && !className.startsWith("com.android.internal");
+        }
+
+        public void destroy() {
+            mStopMessage.sendToTarget();
+            if (mExceptions.size() != 0) {
+                throw new RuntimeException("Exception caught during tests", mExceptions.get(0));
+            }
+        }
+
+        public void acquireMain() {
+            synchronized (this) {
+                mStopMessage.sendToTarget();
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        public void releaseMain() {
+            startManaging();
+        }
+
+        private void startManaging() {
+            mStopMessage = mMainHandler.obtainMessage();
+            synchronized (this) {
+                mManager = acquireLooperManager(Looper.getMainLooper());
+                // This bit needs to happen on a background thread or it will hang if called
+                // from the same thread we are looking to block.
+                new Thread(() -> {
+                    // Post a message to the main handler that will manage executing all future
+                    // messages.
+                    mMainHandler.post(this);
+                    while (!mManager.hasMessages(mMainHandler, null, this));
+                    // Lastly run the message that executes this so it can manage the main thread.
+                    Message next = mManager.next();
+                    // Run through messages until we reach ours.
+                    while (next.getCallback() != this) {
+                        mManager.execute(next);
+                        mManager.recycle(next);
+                        next = mManager.next();
+                    }
+                    mManager.execute(next);
+                }).start();
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+}
index f6c3cb3..f1a7092 100644 (file)
@@ -29,7 +29,6 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.lang.reflect.Field;
 import java.util.Map;
 
 /**
@@ -49,7 +48,7 @@ public class TestableLooper {
     private TestLooperManager mQueueWrapper;
 
     public TestableLooper(Looper l) throws Exception {
-        this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
+        this(acquireLooperManager(l), l);
     }
 
     private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
@@ -78,6 +77,9 @@ public class TestableLooper {
      */
     public void destroy() throws NoSuchFieldException, IllegalAccessException {
         mQueueWrapper.release();
+        if (mLooper == Looper.getMainLooper()) {
+            TestableInstrumentation.releaseMain();
+        }
     }
 
     /**
@@ -196,6 +198,13 @@ public class TestableLooper {
         }
     }
 
+    private static TestLooperManager acquireLooperManager(Looper l) {
+        if (l == Looper.getMainLooper()) {
+            TestableInstrumentation.acquireMain();
+        }
+        return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
+    }
+
     private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
 
     /**
@@ -247,8 +256,7 @@ public class TestableLooper {
             }
             boolean set = mTestableLooper.mQueueWrapper == null;
             if (set) {
-                mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
-                        .acquireLooperManager(mLooper);
+                mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
             }
             try {
                 Object[] ret = new Object[1];
@@ -283,6 +291,9 @@ public class TestableLooper {
                 if (set) {
                     mTestableLooper.mQueueWrapper.release();
                     mTestableLooper.mQueueWrapper = null;
+                    if (mLooper == Looper.getMainLooper()) {
+                        TestableInstrumentation.releaseMain();
+                    }
                 }
             }
         }