• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.internal.protolog;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static com.android.internal.protolog.LegacyProtoLogImpl.PROTOLOG_VERSION;
22 
23 import static org.junit.Assert.assertArrayEquals;
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.anyLong;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.never;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.content.Context;
38 import android.os.SystemClock;
39 import android.platform.test.annotations.Presubmit;
40 import android.util.proto.ProtoInputStream;
41 
42 import androidx.test.filters.SmallTest;
43 
44 import com.android.internal.protolog.common.IProtoLogGroup;
45 import com.android.internal.protolog.common.LogLevel;
46 
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.junit.runners.JUnit4;
52 import org.mockito.Mock;
53 import org.mockito.Mockito;
54 import org.mockito.MockitoAnnotations;
55 
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.PrintWriter;
61 import java.util.LinkedList;
62 
63 /**
64  * Test class for {@link ProtoLogImpl}.
65  */
66 @SuppressWarnings("ConstantConditions")
67 @SmallTest
68 @Presubmit
69 @RunWith(JUnit4.class)
70 public class LegacyProtoLogImplTest {
71 
72     private static final byte[] MAGIC_HEADER = new byte[]{
73             0x9, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47
74     };
75 
76     private LegacyProtoLogImpl mProtoLog;
77     private File mFile;
78 
79     @Mock
80     private LegacyProtoLogViewerConfigReader mReader;
81 
82     private final String mViewerConfigFilename = "unused/file/path";
83 
84     @Before
setUp()85     public void setUp() throws Exception {
86         MockitoAnnotations.initMocks(this);
87         final Context testContext = getInstrumentation().getContext();
88         mFile = testContext.getFileStreamPath("tracing_test.dat");
89         //noinspection ResultOfMethodCallIgnored
90         mFile.delete();
91         mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
92                 1024 * 1024, mReader, 1024, (instance) -> {});
93     }
94 
95     @After
tearDown()96     public void tearDown() {
97         if (mFile != null) {
98             //noinspection ResultOfMethodCallIgnored
99             mFile.delete();
100         }
101         ProtoLogImpl.setSingleInstance(null);
102     }
103 
104     @Test
isEnabled_returnsFalseByDefault()105     public void isEnabled_returnsFalseByDefault() {
106         assertFalse(mProtoLog.isProtoEnabled());
107     }
108 
109     @Test
isEnabled_returnsTrueAfterStart()110     public void isEnabled_returnsTrueAfterStart() {
111         mProtoLog.startProtoLog(mock(PrintWriter.class));
112         assertTrue(mProtoLog.isProtoEnabled());
113     }
114 
115     @Test
isEnabled_returnsFalseAfterStop()116     public void isEnabled_returnsFalseAfterStop() {
117         mProtoLog.startProtoLog(mock(PrintWriter.class));
118         mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
119         assertFalse(mProtoLog.isProtoEnabled());
120     }
121 
122     @Test
logFile_startsWithMagicHeader()123     public void logFile_startsWithMagicHeader() throws Exception {
124         mProtoLog.startProtoLog(mock(PrintWriter.class));
125         mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
126 
127         assertTrue("Log file should exist", mFile.exists());
128 
129         byte[] header = new byte[MAGIC_HEADER.length];
130         try (InputStream is = new FileInputStream(mFile)) {
131             assertEquals(MAGIC_HEADER.length, is.read(header));
132             assertArrayEquals(MAGIC_HEADER, header);
133         }
134     }
135 
136     @Test
log_logcatEnabledExternalMessage()137     public void log_logcatEnabledExternalMessage() {
138         when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
139         LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
140         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
141         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
142 
143         implSpy.log(
144                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
145                 new Object[]{true, 10000, 30000, "test", 0.000003});
146 
147         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
148                 LogLevel.INFO),
149                 eq("test true 10000 % 0x7530 test 3.0E-6"));
150         verify(mReader).getViewerString(eq(1234L));
151     }
152 
153     @Test
log_logcatEnabledInvalidMessage()154     public void log_logcatEnabledInvalidMessage() {
155         when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
156         LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
157         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
158         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
159 
160         implSpy.log(
161                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
162                 new Object[]{true, 10000, 0.0001, 0.00002, "test"});
163 
164         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
165                 LogLevel.INFO),
166                 eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
167         verify(mReader).getViewerString(eq(1234L));
168     }
169 
170     @Test
log_logcatEnabledInlineMessage()171     public void log_logcatEnabledInlineMessage() {
172         when(mReader.getViewerString(anyLong())).thenReturn("test %d");
173         LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
174         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
175         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
176 
177         implSpy.log(
178                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
179                 new Object[]{5});
180 
181         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
182                 LogLevel.INFO), eq("test 5"));
183     }
184 
185     @Test
log_logcatEnabledNoMessage()186     public void log_logcatEnabledNoMessage() {
187         when(mReader.getViewerString(anyLong())).thenReturn(null);
188         LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
189         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
190         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
191 
192         implSpy.log(
193                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
194                 new Object[]{5});
195 
196         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
197                 LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
198         verify(mReader).getViewerString(eq(1234L));
199     }
200 
201     @Test
log_logcatDisabled()202     public void log_logcatDisabled() {
203         when(mReader.getViewerString(anyLong())).thenReturn("test %d");
204         LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
205         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
206         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
207 
208         implSpy.log(
209                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
210                 new Object[]{5});
211 
212         verify(implSpy, never()).passToLogcat(any(), any(), any());
213         verify(mReader, never()).getViewerString(anyLong());
214     }
215 
216     @Test
loadViewerConfigOnLogcatGroupRegistration()217     public void loadViewerConfigOnLogcatGroupRegistration() {
218         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
219         mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP);
220         verify(mReader).loadViewerConfig(any(), any());
221     }
222 
223     private static class ProtoLogData {
224         Long mMessageHash = null;
225         Long mElapsedTime = null;
226         LinkedList<String> mStrParams = new LinkedList<>();
227         LinkedList<Long> mSint64Params = new LinkedList<>();
228         LinkedList<Double> mDoubleParams = new LinkedList<>();
229         LinkedList<Boolean> mBooleanParams = new LinkedList<>();
230     }
231 
readProtoLogSingle(ProtoInputStream ip)232     private ProtoLogData readProtoLogSingle(ProtoInputStream ip) throws IOException {
233         while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
234             if (ip.getFieldNumber() == (int) ProtoLogFileProto.VERSION) {
235                 assertEquals(PROTOLOG_VERSION, ip.readString(ProtoLogFileProto.VERSION));
236                 continue;
237             }
238             if (ip.getFieldNumber() != (int) ProtoLogFileProto.LOG) {
239                 continue;
240             }
241             long token = ip.start(ProtoLogFileProto.LOG);
242             ProtoLogData data = new ProtoLogData();
243             while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
244                 switch (ip.getFieldNumber()) {
245                     case (int) ProtoLogMessage.MESSAGE_HASH: {
246                         data.mMessageHash = ip.readLong(ProtoLogMessage.MESSAGE_HASH);
247                         break;
248                     }
249                     case (int) ProtoLogMessage.ELAPSED_REALTIME_NANOS: {
250                         data.mElapsedTime = ip.readLong(ProtoLogMessage.ELAPSED_REALTIME_NANOS);
251                         break;
252                     }
253                     case (int) ProtoLogMessage.STR_PARAMS: {
254                         data.mStrParams.add(ip.readString(ProtoLogMessage.STR_PARAMS));
255                         break;
256                     }
257                     case (int) ProtoLogMessage.SINT64_PARAMS: {
258                         data.mSint64Params.add(ip.readLong(ProtoLogMessage.SINT64_PARAMS));
259                         break;
260                     }
261                     case (int) ProtoLogMessage.DOUBLE_PARAMS: {
262                         data.mDoubleParams.add(ip.readDouble(ProtoLogMessage.DOUBLE_PARAMS));
263                         break;
264                     }
265                     case (int) ProtoLogMessage.BOOLEAN_PARAMS: {
266                         data.mBooleanParams.add(ip.readBoolean(ProtoLogMessage.BOOLEAN_PARAMS));
267                         break;
268                     }
269                 }
270             }
271             ip.end(token);
272             return data;
273         }
274         return null;
275     }
276 
277     @Test
log_protoEnabled()278     public void log_protoEnabled() throws Exception {
279         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
280         TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
281         mProtoLog.startProtoLog(mock(PrintWriter.class));
282         long before = SystemClock.elapsedRealtimeNanos();
283         mProtoLog.log(
284                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
285                 0b1110101001010100,
286                 new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
287         long after = SystemClock.elapsedRealtimeNanos();
288         mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
289         try (InputStream is = new FileInputStream(mFile)) {
290             ProtoInputStream ip = new ProtoInputStream(is);
291             ProtoLogData data = readProtoLogSingle(ip);
292             assertNotNull(data);
293             assertEquals(1234, data.mMessageHash.longValue());
294             assertTrue(before <= data.mElapsedTime && data.mElapsedTime <= after);
295             assertArrayEquals(new String[]{"test"}, data.mStrParams.toArray());
296             assertArrayEquals(new Long[]{1L, 2L, 3L}, data.mSint64Params.toArray());
297             assertArrayEquals(new Double[]{0.4, 0.5, 0.6}, data.mDoubleParams.toArray());
298             assertArrayEquals(new Boolean[]{true}, data.mBooleanParams.toArray());
299         }
300     }
301 
302     @Test
log_invalidParamsMask()303     public void log_invalidParamsMask() throws Exception {
304         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
305         TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
306         mProtoLog.startProtoLog(mock(PrintWriter.class));
307         long before = SystemClock.elapsedRealtimeNanos();
308         mProtoLog.log(
309                 LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
310                 0b01100100,
311                 new Object[]{"test", 1, 0.1, true});
312         long after = SystemClock.elapsedRealtimeNanos();
313         mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
314         try (InputStream is = new FileInputStream(mFile)) {
315             ProtoInputStream ip = new ProtoInputStream(is);
316             ProtoLogData data = readProtoLogSingle(ip);
317             assertNotNull(data);
318             assertEquals(1234, data.mMessageHash.longValue());
319             assertTrue(before <= data.mElapsedTime && data.mElapsedTime <= after);
320             assertArrayEquals(new String[]{"test", "(INVALID PARAMS_MASK) true"},
321                     data.mStrParams.toArray());
322             assertArrayEquals(new Long[]{1L}, data.mSint64Params.toArray());
323             assertArrayEquals(new Double[]{0.1}, data.mDoubleParams.toArray());
324             assertArrayEquals(new Boolean[]{}, data.mBooleanParams.toArray());
325         }
326     }
327 
328     @Test
log_protoDisabled()329     public void log_protoDisabled() throws Exception {
330         TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
331         TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
332         mProtoLog.startProtoLog(mock(PrintWriter.class));
333         mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
334                 0b11, new Object[]{true});
335         mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
336         try (InputStream is = new FileInputStream(mFile)) {
337             ProtoInputStream ip = new ProtoInputStream(is);
338             ProtoLogData data = readProtoLogSingle(ip);
339             assertNull(data);
340         }
341     }
342 
343     private enum TestProtoLogGroup implements IProtoLogGroup {
344         TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
345 
346         private final boolean mEnabled;
347         private volatile boolean mLogToProto;
348         private volatile boolean mLogToLogcat;
349         private final String mTag;
350 
351         /**
352          * @param enabled     set to false to exclude all log statements for this group from
353          *                    compilation,
354          *                    they will not be available in runtime.
355          * @param logToProto  enable binary logging for the group
356          * @param logToLogcat enable text logging for the group
357          * @param tag         name of the source of the logged message
358          */
TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag)359         TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
360             this.mEnabled = enabled;
361             this.mLogToProto = logToProto;
362             this.mLogToLogcat = logToLogcat;
363             this.mTag = tag;
364         }
365 
366         @Override
isEnabled()367         public boolean isEnabled() {
368             return mEnabled;
369         }
370 
371         @Override
isLogToProto()372         public boolean isLogToProto() {
373             return mLogToProto;
374         }
375 
376         @Override
isLogToLogcat()377         public boolean isLogToLogcat() {
378             return mLogToLogcat;
379         }
380 
381         @Override
isLogToAny()382         public boolean isLogToAny() {
383             return mLogToLogcat || mLogToProto;
384         }
385 
386         @Override
getTag()387         public String getTag() {
388             return mTag;
389         }
390 
391         @Override
setLogToProto(boolean logToProto)392         public void setLogToProto(boolean logToProto) {
393             this.mLogToProto = logToProto;
394         }
395 
396         @Override
setLogToLogcat(boolean logToLogcat)397         public void setLogToLogcat(boolean logToLogcat) {
398             this.mLogToLogcat = logToLogcat;
399         }
400 
401         @Override
getId()402         public int getId() {
403             return ordinal();
404         }
405 
406     }
407 }
408