1 /* 2 * Copyright (C) 2024 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 org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyBoolean; 21 import static org.mockito.ArgumentMatchers.anyInt; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.never; 24 25 import static java.io.File.createTempFile; 26 import static java.nio.file.Files.createTempDirectory; 27 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.platform.test.annotations.Presubmit; 31 import android.tools.ScenarioBuilder; 32 import android.tools.Tag; 33 import android.tools.io.ResultArtifactDescriptor; 34 import android.tools.io.TraceType; 35 import android.tools.traces.TraceConfig; 36 import android.tools.traces.TraceConfigs; 37 import android.tools.traces.io.ResultReader; 38 import android.tools.traces.io.ResultWriter; 39 import android.tools.traces.monitors.PerfettoTraceMonitor; 40 41 import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClientArgs; 42 43 import com.google.common.truth.Truth; 44 import com.google.protobuf.InvalidProtocolBufferException; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.mockito.ArgumentCaptor; 50 import org.mockito.Captor; 51 import org.mockito.Mock; 52 import org.mockito.Mockito; 53 import org.mockito.junit.MockitoJUnitRunner; 54 55 import perfetto.protos.Protolog.ProtoLogViewerConfig; 56 import perfetto.protos.ProtologCommon; 57 import perfetto.protos.TraceOuterClass.Trace; 58 import perfetto.protos.TracePacketOuterClass.TracePacket; 59 60 import java.io.BufferedOutputStream; 61 import java.io.File; 62 import java.io.FileNotFoundException; 63 import java.io.FileOutputStream; 64 import java.io.IOException; 65 import java.io.PrintWriter; 66 import java.util.List; 67 68 /** 69 * Test class for {@link ProtoLogImpl}. 70 */ 71 @Presubmit 72 @RunWith(MockitoJUnitRunner.class) 73 public class ProtoLogConfigurationServiceTest { 74 75 private static final String TEST_GROUP = "MY_TEST_GROUP"; 76 private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP"; 77 78 private static final ProtoLogViewerConfig VIEWER_CONFIG = 79 ProtoLogViewerConfig.newBuilder() 80 .addGroups( 81 ProtoLogViewerConfig.Group.newBuilder() 82 .setId(1) 83 .setName(TEST_GROUP) 84 .setTag(TEST_GROUP) 85 ).addMessages( 86 ProtoLogViewerConfig.MessageData.newBuilder() 87 .setMessageId(1) 88 .setMessage("My Test Debug Log Message %b") 89 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG) 90 .setGroupId(1) 91 ).addMessages( 92 ProtoLogViewerConfig.MessageData.newBuilder() 93 .setMessageId(2) 94 .setMessage("My Test Verbose Log Message %b") 95 .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE) 96 .setGroupId(1) 97 ).build(); 98 99 @Mock 100 IProtoLogClient mMockClient; 101 102 @Mock 103 IProtoLogClient mSecondMockClient; 104 105 @Mock 106 IBinder mMockClientBinder; 107 108 @Mock 109 IBinder mSecondMockClientBinder; 110 111 private final File mTracingDirectory = createTempDirectory("temp").toFile(); 112 113 private final ResultWriter mWriter = new ResultWriter() 114 .forScenario(new ScenarioBuilder() 115 .forClass(createTempFile("temp", "").getName()).build()) 116 .withOutputDir(mTracingDirectory) 117 .setRunComplete(); 118 119 private final TraceConfigs mTraceConfig = new TraceConfigs( 120 new TraceConfig(false, true, false), 121 new TraceConfig(false, true, false), 122 new TraceConfig(false, true, false), 123 new TraceConfig(false, true, false) 124 ); 125 126 @Captor 127 ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientArgumentCaptor; 128 129 @Captor 130 ArgumentCaptor<IBinder.DeathRecipient> mSecondDeathRecipientArgumentCaptor; 131 132 private File mViewerConfigFile; 133 ProtoLogConfigurationServiceTest()134 public ProtoLogConfigurationServiceTest() throws IOException { 135 } 136 137 @Before setUp()138 public void setUp() { 139 Mockito.when(mMockClient.asBinder()).thenReturn(mMockClientBinder); 140 Mockito.when(mSecondMockClient.asBinder()).thenReturn(mSecondMockClientBinder); 141 142 try { 143 mViewerConfigFile = File.createTempFile("viewer-config", ".pb"); 144 try (var fos = new FileOutputStream(mViewerConfigFile); 145 BufferedOutputStream bos = new BufferedOutputStream(fos)) { 146 147 bos.write(VIEWER_CONFIG.toByteArray()); 148 } 149 } catch (IOException e) { 150 throw new RuntimeException(e); 151 } 152 } 153 154 @Test canRegisterClientWithGroupsOnly()155 public void canRegisterClientWithGroupsOnly() throws RemoteException { 156 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 157 158 final RegisterClientArgs args = new RegisterClientArgs(); 159 args.groups = new String[] { TEST_GROUP }; 160 args.groupsDefaultLogcatStatus = new boolean[] { true }; 161 service.registerClient(mMockClient, args); 162 163 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); 164 Truth.assertThat(service.getGroups()).asList().containsExactly(TEST_GROUP); 165 } 166 167 @Test willDumpViewerConfigOnlyOnceOnTraceStop()168 public void willDumpViewerConfigOnlyOnceOnTraceStop() 169 throws RemoteException, InvalidProtocolBufferException { 170 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 171 172 final RegisterClientArgs args = new RegisterClientArgs(); 173 args.groups = new String[] { TEST_GROUP }; 174 args.groupsDefaultLogcatStatus = new boolean[] { true }; 175 args.viewerConfigFile = mViewerConfigFile.getAbsolutePath(); 176 177 service.registerClient(mMockClient, args); 178 service.registerClient(mSecondMockClient, args); 179 180 PerfettoTraceMonitor traceMonitor = 181 PerfettoTraceMonitor.newBuilder().enableProtoLog().build(); 182 183 traceMonitor.start(); 184 traceMonitor.stop(mWriter); 185 final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); 186 final byte[] traceData = reader.getArtifact() 187 .readBytes(new ResultArtifactDescriptor(TraceType.PERFETTO, Tag.ALL)); 188 189 final Trace trace = Trace.parseFrom(traceData); 190 191 final List<TracePacket> configPackets = trace.getPacketList().stream() 192 .filter(it -> it.hasProtologViewerConfig()) 193 // Exclude viewer configs from regular system tracing 194 .filter(it -> 195 it.getProtologViewerConfig().getGroups(0).getName().equals(TEST_GROUP)) 196 .toList(); 197 Truth.assertThat(configPackets).hasSize(1); 198 Truth.assertThat(configPackets.get(0).getProtologViewerConfig().toString()) 199 .isEqualTo(VIEWER_CONFIG.toString()); 200 } 201 202 @Test willDumpViewerConfigOnLastClientDisconnected()203 public void willDumpViewerConfigOnLastClientDisconnected() 204 throws RemoteException, FileNotFoundException { 205 final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer = 206 Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class); 207 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer); 208 209 final RegisterClientArgs args = new RegisterClientArgs(); 210 args.groups = new String[] { TEST_GROUP }; 211 args.groupsDefaultLogcatStatus = new boolean[] { true }; 212 args.viewerConfigFile = mViewerConfigFile.getAbsolutePath(); 213 214 service.registerClient(mMockClient, args); 215 service.registerClient(mSecondMockClient, args); 216 217 Mockito.verify(mMockClientBinder) 218 .linkToDeath(mDeathRecipientArgumentCaptor.capture(), anyInt()); 219 Mockito.verify(mSecondMockClientBinder) 220 .linkToDeath(mSecondDeathRecipientArgumentCaptor.capture(), anyInt()); 221 222 mDeathRecipientArgumentCaptor.getValue().binderDied(); 223 Mockito.verify(tracer, never()).trace(any(), any()); 224 mSecondDeathRecipientArgumentCaptor.getValue().binderDied(); 225 Mockito.verify(tracer).trace(any(), eq(mViewerConfigFile.getAbsolutePath())); 226 } 227 228 @Test sendEnableLoggingToLogcatToClient()229 public void sendEnableLoggingToLogcatToClient() throws RemoteException { 230 final var service = new ProtoLogConfigurationServiceImpl(); 231 232 final RegisterClientArgs args = new RegisterClientArgs(); 233 args.groups = new String[] { TEST_GROUP }; 234 args.groupsDefaultLogcatStatus = new boolean[] { false }; 235 service.registerClient(mMockClient, args); 236 237 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); 238 service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); 239 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); 240 241 Mockito.verify(mMockClient).toggleLogcat(eq(true), 242 Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP))); 243 } 244 245 @Test sendDisableLoggingToLogcatToClient()246 public void sendDisableLoggingToLogcatToClient() throws RemoteException { 247 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 248 249 final RegisterClientArgs args = new RegisterClientArgs(); 250 args.groups = new String[] { TEST_GROUP }; 251 args.groupsDefaultLogcatStatus = new boolean[] { true }; 252 service.registerClient(mMockClient, args); 253 254 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); 255 service.disableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); 256 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); 257 258 Mockito.verify(mMockClient).toggleLogcat(eq(false), 259 Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP))); 260 } 261 262 @Test doNotSendLoggingToLogcatToClientWithoutRegisteredGroup()263 public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException { 264 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 265 266 final RegisterClientArgs args = new RegisterClientArgs(); 267 args.groups = new String[] { TEST_GROUP }; 268 args.groupsDefaultLogcatStatus = new boolean[] { false }; 269 270 service.registerClient(mMockClient, args); 271 272 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); 273 service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), OTHER_TEST_GROUP); 274 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse(); 275 276 Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any()); 277 } 278 279 @Test handlesToggleToLogcatBeforeClientIsRegistered()280 public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException { 281 final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 282 283 Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP); 284 service.enableProtoLogToLogcat(Mockito.mock(PrintWriter.class), TEST_GROUP); 285 Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); 286 287 final RegisterClientArgs args = new RegisterClientArgs(); 288 args.groups = new String[] { TEST_GROUP }; 289 args.groupsDefaultLogcatStatus = new boolean[] { false }; 290 291 service.registerClient(mMockClient, args); 292 293 Mockito.verify(mMockClient).toggleLogcat(eq(true), 294 Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP))); 295 } 296 } 297