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