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 com.android.server; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.support.test.runner.AndroidJUnit4; 22 23 import java.io.PrintWriter; 24 import java.io.StringWriter; 25 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 29 /** 30 * Unit tests for {@link WatchdogDiagnostics} 31 */ 32 @RunWith(AndroidJUnit4.class) 33 public class WatchdogDiagnosticsTest { 34 35 private static class TestThread1 extends Thread { 36 Object lock1; 37 Object lock2; 38 volatile boolean inB = false; 39 TestThread1(Object lock1, Object lock2)40 public TestThread1(Object lock1, Object lock2) { 41 super("TestThread1"); 42 this.lock1 = lock1; 43 this.lock2 = lock2; 44 } 45 46 @Override run()47 public void run() { 48 a(); 49 } 50 a()51 private void a() { 52 synchronized(lock1) { 53 b(); 54 } 55 } 56 b()57 private void b() { 58 inB = true; 59 synchronized(lock2) { 60 // Nothing. 61 } 62 } 63 } 64 65 private static class TestThread2 extends Thread { 66 Object lock1; 67 Object lock2; 68 volatile boolean inY = false; 69 TestThread2(Object lock1, Object lock2)70 public TestThread2(Object lock1, Object lock2) { 71 super("TestThread2"); 72 this.lock1 = lock1; 73 this.lock2 = lock2; 74 } 75 76 @Override run()77 public void run() { 78 x(); 79 } 80 x()81 private void x() { 82 synchronized(lock1) { 83 y(); 84 } 85 } 86 y()87 private void y() { 88 synchronized(lock2) { 89 inY = true; 90 try { 91 lock2.wait(); 92 } catch (Exception exc) { 93 throw new RuntimeException(exc); 94 } 95 } 96 } 97 } 98 99 @Test printAnnotatedStack()100 public void printAnnotatedStack() throws Exception { 101 // Preparation. 102 103 Object heldLock1 = new Object(); 104 Object heldLock2 = 0; 105 Object waitLock = "123"; 106 107 TestThread1 thread1 = new TestThread1(heldLock1, heldLock2); 108 TestThread2 thread2 = new TestThread2(heldLock2, waitLock); 109 110 // Start the second thread, ensure it grabs heldLock2. 111 thread2.start(); 112 while(!thread2.inY) { 113 Thread.yield(); 114 } 115 116 // Start the first thread, ensure it made progress. 117 thread1.start(); 118 while(!thread1.inB) { 119 Thread.yield(); 120 } 121 122 // Now wait till both are no longer in runnable state. 123 while (thread1.getState() == Thread.State.RUNNABLE) { 124 Thread.yield(); 125 } 126 while (thread2.getState() == Thread.State.RUNNABLE) { 127 Thread.yield(); 128 } 129 130 // Now do the test. 131 StringWriter stringBuffer = new StringWriter(); 132 PrintWriter print = new PrintWriter(stringBuffer, true); 133 134 { 135 WatchdogDiagnostics.printAnnotatedStack(thread1, print); 136 137 String output = stringBuffer.toString(); 138 String expected = 139 "TestThread1 annotated stack trace:\n" + 140 " at com.android.server.WatchdogDiagnosticsTest$TestThread1.b(" + 141 "WatchdogDiagnosticsTest.java:59)\n" + 142 " - waiting to lock <HASH> (a java.lang.Integer)\n" + 143 " at com.android.server.WatchdogDiagnosticsTest$TestThread1.a(" + 144 "WatchdogDiagnosticsTest.java:53)\n" + 145 " - locked <HASH> (a java.lang.Object)\n" + 146 " at com.android.server.WatchdogDiagnosticsTest$TestThread1.run(" + 147 "WatchdogDiagnosticsTest.java:48)\n"; 148 assertEquals(expected, filterHashes(output)); 149 } 150 151 stringBuffer.getBuffer().setLength(0); 152 153 { 154 WatchdogDiagnostics.printAnnotatedStack(thread2, print); 155 156 String output = stringBuffer.toString(); 157 String expected = 158 "TestThread2 annotated stack trace:\n" + 159 " at java.lang.Object.wait(Native Method)\n" + 160 " at com.android.server.WatchdogDiagnosticsTest$TestThread2.y(" + 161 "WatchdogDiagnosticsTest.java:91)\n" + 162 " - locked <HASH> (a java.lang.String)\n" + 163 " at com.android.server.WatchdogDiagnosticsTest$TestThread2.x(" + 164 "WatchdogDiagnosticsTest.java:83)\n" + 165 " - locked <HASH> (a java.lang.Integer)\n" + 166 " at com.android.server.WatchdogDiagnosticsTest$TestThread2.run(" + 167 "WatchdogDiagnosticsTest.java:78)\n"; 168 assertEquals(expected, filterHashes(output)); 169 } 170 171 // Let the threads finish. 172 synchronized (waitLock) { 173 waitLock.notifyAll(); 174 } 175 176 thread1.join(); 177 thread2.join(); 178 } 179 180 /** 181 * A filter function that removes hash codes (which will change between tests and cannot be 182 * controlled.) 183 * <p> 184 * Note: leaves "<HASH>" to indicate that something was replaced. 185 */ filterHashes(String t)186 private static String filterHashes(String t) { 187 return t.replaceAll("<0x[0-9a-f]{8}>", "<HASH>"); 188 } 189 } 190