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