• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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