1 /*
2  * Copyright (C) 2017 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.wm;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
26 
27 import static org.junit.Assert.assertArrayEquals;
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertTrue;
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.eq;
33 
34 import android.content.Context;
35 import android.platform.test.annotations.Presubmit;
36 import android.testing.DexmakerShareClassLoaderRule;
37 import android.util.proto.ProtoOutputStream;
38 import android.view.Choreographer;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.internal.util.Preconditions;
43 
44 import org.junit.After;
45 import org.junit.Before;
46 import org.junit.Ignore;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.mockito.Mock;
50 import org.mockito.MockitoAnnotations;
51 
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.InputStream;
55 import java.io.PrintWriter;
56 import java.nio.charset.StandardCharsets;
57 
58 /**
59  * Test class for {@link WindowTracing}.
60  *
61  * Build/Install/Run:
62  *  atest WmTests:WindowTracingTest
63  */
64 @SmallTest
65 @Presubmit
66 public class WindowTracingTest {
67 
68     private static final byte[] MAGIC_HEADER = new byte[]{
69             0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
70     };
71 
72     @Rule
73     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
74             new DexmakerShareClassLoaderRule();
75 
76     @Mock
77     private WindowManagerService mWmMock;
78     @Mock
79     private Choreographer mChoreographer;
80     private WindowTracing mWindowTracing;
81     private File mFile;
82 
83     @Before
setUp()84     public void setUp() throws Exception {
85         MockitoAnnotations.initMocks(this);
86 
87         final Context testContext = getInstrumentation().getContext();
88         mFile = testContext.getFileStreamPath("tracing_test.dat");
89         mFile.delete();
90 
91         mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
92                 new WindowManagerGlobalLock(), 1024);
93     }
94 
95     @After
tearDown()96     public void tearDown() throws Exception {
97         mFile.delete();
98     }
99 
100     @Test
isEnabled_returnsFalseByDefault()101     public void isEnabled_returnsFalseByDefault() {
102         assertFalse(mWindowTracing.isEnabled());
103     }
104 
105     @Test
isEnabled_returnsTrueAfterStart()106     public void isEnabled_returnsTrueAfterStart() {
107         mWindowTracing.startTrace(mock(PrintWriter.class));
108         assertTrue(mWindowTracing.isEnabled());
109     }
110 
111     @Test
isEnabled_returnsFalseAfterStop()112     public void isEnabled_returnsFalseAfterStop() {
113         mWindowTracing.startTrace(mock(PrintWriter.class));
114         mWindowTracing.stopTrace(mock(PrintWriter.class));
115         assertFalse(mWindowTracing.isEnabled());
116     }
117 
118     @Test
trace_discared_whenNotTracing()119     public void trace_discared_whenNotTracing() {
120         mWindowTracing.logState("where");
121         verifyZeroInteractions(mWmMock);
122     }
123 
124     @Test
trace_dumpsWindowManagerState_whenTracing()125     public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
126         mWindowTracing.startTrace(mock(PrintWriter.class));
127         mWindowTracing.logState("where");
128         verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTraceLogLevel.TRIM));
129     }
130 
131     @Test
traceFile_startsWithMagicHeader()132     public void traceFile_startsWithMagicHeader() throws Exception {
133         mWindowTracing.startTrace(mock(PrintWriter.class));
134         mWindowTracing.stopTrace(mock(PrintWriter.class));
135 
136         assertTrue("Trace file should exist", mFile.exists());
137 
138         byte[] header = new byte[MAGIC_HEADER.length];
139         try (InputStream is = new FileInputStream(mFile)) {
140             assertEquals(MAGIC_HEADER.length, is.read(header));
141             assertArrayEquals(MAGIC_HEADER, header);
142         }
143     }
144 
145     @Ignore("Figure out why this test is crashing when setting up mWmMock.")
146     @Test
tracing_endsUpInFile()147     public void tracing_endsUpInFile() throws Exception {
148         mWindowTracing.startTrace(mock(PrintWriter.class));
149 
150         doAnswer(inv -> {
151             inv.<ProtoOutputStream>getArgument(0).write(
152                     WindowManagerTraceProto.WHERE, "TEST_WM_PROTO");
153             return null;
154         }).when(mWmMock).dumpDebugLocked(any(), any());
155         mWindowTracing.logState("TEST_WHERE");
156 
157         mWindowTracing.stopTrace(mock(PrintWriter.class));
158 
159         byte[] file = new byte[1000];
160         int fileLength;
161         try (InputStream is = new FileInputStream(mFile)) {
162             fileLength = is.read(file);
163             assertTrue(containsBytes(file, fileLength,
164                     "TEST_WHERE".getBytes(StandardCharsets.UTF_8)));
165             assertTrue(containsBytes(file, fileLength,
166                     "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8)));
167         }
168     }
169 
170     /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */
containsBytes(byte[] haystack, int haystackLength, byte[] needle)171     private static boolean containsBytes(byte[] haystack, int haystackLength, byte[] needle) {
172         Preconditions.checkArgument(haystackLength > 0);
173         Preconditions.checkArgument(needle.length > 0);
174 
175         outer: for (int i = 0; i <= haystackLength - needle.length; i++) {
176             for (int j = 0; j < needle.length; j++) {
177                 if (haystack[i + j] != needle[j]) {
178                     continue outer;
179                 }
180             }
181             return true;
182         }
183         return false;
184     }
185 
186     @Test
test_containsBytes()187     public void test_containsBytes() {
188         byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8);
189         assertTrue(containsBytes(haystack, haystack.length,
190                 "hello".getBytes(StandardCharsets.UTF_8)));
191         assertTrue(containsBytes(haystack, haystack.length,
192                 "world".getBytes(StandardCharsets.UTF_8)));
193         assertFalse(containsBytes(haystack, 6,
194                 "world".getBytes(StandardCharsets.UTF_8)));
195         assertFalse(containsBytes(haystack, haystack.length,
196                 "world_".getBytes(StandardCharsets.UTF_8)));
197         assertFalse(containsBytes(haystack, haystack.length,
198                 "absent".getBytes(StandardCharsets.UTF_8)));
199     }
200 }
201