Move ProtoLog tests to seperate test directory dedicated to tracing tests
Bug: 364255103
Flag: TEST_ONLY
Test: atest TracingTests
Change-Id: I1f53100c03e71647746d2214f1bf23ec30ec3129
diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp
new file mode 100644
index 0000000..5a7f12f
--- /dev/null
+++ b/tests/Tracing/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_team: "trendy_team_windowing_tools",
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "TracingTests",
+ proto: {
+ type: "nano",
+ },
+ // Include some source files directly to be able to access package members
+ srcs: ["src/**/*.java"],
+ libs: ["android.test.runner"],
+ static_libs: [
+ "junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth",
+ "platform-test-annotations",
+ "flickerlib-parsers",
+ "perfetto_trace_java_protos",
+ "flickerlib-trace_processor_shell",
+ ],
+ java_resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/Tracing/AndroidManifest.xml b/tests/Tracing/AndroidManifest.xml
new file mode 100644
index 0000000..7254f81
--- /dev/null
+++ b/tests/Tracing/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tracing.tests">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.BIND_WALLPAPER"/>
+ <!-- Allow the test to connect to perfetto trace processor -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application
+ android:requestLegacyExternalStorage="true"
+ android:networkSecurityConfig="@xml/network_security_config">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.tracing.tests"
+ android:label="Tracing Tests"/>
+</manifest>
diff --git a/tests/Tracing/AndroidTest.xml b/tests/Tracing/AndroidTest.xml
new file mode 100644
index 0000000..9a40420
--- /dev/null
+++ b/tests/Tracing/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<configuration description="Runs tests for tracing classes/utilities.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TracingTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TracingTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.tracing.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.tracing.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
\ No newline at end of file
diff --git a/tests/Tracing/OWNERS b/tests/Tracing/OWNERS
new file mode 100644
index 0000000..4a50338
--- /dev/null
+++ b/tests/Tracing/OWNERS
@@ -0,0 +1,3 @@
+# Tracing owners
+# Bug component: 1157642
+include platform/development:/tools/winscope/OWNERS
diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING
new file mode 100644
index 0000000..7f58fce
--- /dev/null
+++ b/tests/Tracing/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "TracingTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/Tracing/res/xml/network_security_config.xml b/tests/Tracing/res/xml/network_security_config.xml
new file mode 100644
index 0000000..fdf1dbb
--- /dev/null
+++ b/tests/Tracing/res/xml/network_security_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
new file mode 100644
index 0000000..8913e8c
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.internal.protolog.LegacyProtoLogImpl.PROTOLOG_VERSION;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LegacyProtoLogImplTest {
+
+ private static final byte[] MAGIC_HEADER = new byte[]{
+ 0x9, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47
+ };
+
+ private LegacyProtoLogImpl mProtoLog;
+ private File mFile;
+
+ @Mock
+ private LegacyProtoLogViewerConfigReader mReader;
+
+ private final String mViewerConfigFilename = "unused/file/path";
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context testContext = getInstrumentation().getContext();
+ mFile = testContext.getFileStreamPath("tracing_test.dat");
+ //noinspection ResultOfMethodCallIgnored
+ mFile.delete();
+ mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
+ 1024 * 1024, mReader, 1024, () -> {});
+ }
+
+ @After
+ public void tearDown() {
+ if (mFile != null) {
+ //noinspection ResultOfMethodCallIgnored
+ mFile.delete();
+ }
+ ProtoLogImpl.setSingleInstance(null);
+ }
+
+ @Test
+ public void isEnabled_returnsFalseByDefault() {
+ assertFalse(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsTrueAfterStart() {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ assertTrue(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsFalseAfterStop() {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ assertFalse(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void logFile_startsWithMagicHeader() throws Exception {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+
+ assertTrue("Log file should exist", mFile.exists());
+
+ byte[] header = new byte[MAGIC_HEADER.length];
+ try (InputStream is = new FileInputStream(mFile)) {
+ assertEquals(MAGIC_HEADER.length, is.read(header));
+ assertArrayEquals(MAGIC_HEADER, header);
+ }
+ }
+
+ @Test
+ public void log_logcatEnabledExternalMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
+ LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{true, 10000, 30000, "test", 0.000003});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO),
+ eq("test true 10000 % 0x7530 test 3.0E-6"));
+ verify(mReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatEnabledInvalidMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
+ LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{true, 10000, 0.0001, 0.00002, "test"});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO),
+ eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
+ verify(mReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatEnabledInlineMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %d");
+ LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO), eq("test 5"));
+ }
+
+ @Test
+ public void log_logcatEnabledNoMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn(null);
+ LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
+ verify(mReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatDisabled() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %d");
+ LegacyProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5});
+
+ verify(implSpy, never()).passToLogcat(any(), any(), any());
+ verify(mReader, never()).getViewerString(anyLong());
+ }
+
+ @Test
+ public void loadViewerConfigOnLogcatGroupRegistration() {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP);
+ verify(mReader).loadViewerConfig(any(), any());
+ }
+
+ private static class ProtoLogData {
+ Long mMessageHash = null;
+ Long mElapsedTime = null;
+ LinkedList<String> mStrParams = new LinkedList<>();
+ LinkedList<Long> mSint64Params = new LinkedList<>();
+ LinkedList<Double> mDoubleParams = new LinkedList<>();
+ LinkedList<Boolean> mBooleanParams = new LinkedList<>();
+ }
+
+ private ProtoLogData readProtoLogSingle(ProtoInputStream ip) throws IOException {
+ while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (ip.getFieldNumber() == (int) ProtoLogFileProto.VERSION) {
+ assertEquals(PROTOLOG_VERSION, ip.readString(ProtoLogFileProto.VERSION));
+ continue;
+ }
+ if (ip.getFieldNumber() != (int) ProtoLogFileProto.LOG) {
+ continue;
+ }
+ long token = ip.start(ProtoLogFileProto.LOG);
+ ProtoLogData data = new ProtoLogData();
+ while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (ip.getFieldNumber()) {
+ case (int) ProtoLogMessage.MESSAGE_HASH: {
+ data.mMessageHash = ip.readLong(ProtoLogMessage.MESSAGE_HASH);
+ break;
+ }
+ case (int) ProtoLogMessage.ELAPSED_REALTIME_NANOS: {
+ data.mElapsedTime = ip.readLong(ProtoLogMessage.ELAPSED_REALTIME_NANOS);
+ break;
+ }
+ case (int) ProtoLogMessage.STR_PARAMS: {
+ data.mStrParams.add(ip.readString(ProtoLogMessage.STR_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.SINT64_PARAMS: {
+ data.mSint64Params.add(ip.readLong(ProtoLogMessage.SINT64_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.DOUBLE_PARAMS: {
+ data.mDoubleParams.add(ip.readDouble(ProtoLogMessage.DOUBLE_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.BOOLEAN_PARAMS: {
+ data.mBooleanParams.add(ip.readBoolean(ProtoLogMessage.BOOLEAN_PARAMS));
+ break;
+ }
+ }
+ }
+ ip.end(token);
+ return data;
+ }
+ return null;
+ }
+
+ @Test
+ public void log_protoEnabled() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ long before = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b1110101001010100,
+ new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+ long after = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNotNull(data);
+ assertEquals(1234, data.mMessageHash.longValue());
+ assertTrue(before <= data.mElapsedTime && data.mElapsedTime <= after);
+ assertArrayEquals(new String[]{"test"}, data.mStrParams.toArray());
+ assertArrayEquals(new Long[]{1L, 2L, 3L}, data.mSint64Params.toArray());
+ assertArrayEquals(new Double[]{0.4, 0.5, 0.6}, data.mDoubleParams.toArray());
+ assertArrayEquals(new Boolean[]{true}, data.mBooleanParams.toArray());
+ }
+ }
+
+ @Test
+ public void log_invalidParamsMask() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ long before = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b01100100,
+ new Object[]{"test", 1, 0.1, true});
+ long after = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNotNull(data);
+ assertEquals(1234, data.mMessageHash.longValue());
+ assertTrue(before <= data.mElapsedTime && data.mElapsedTime <= after);
+ assertArrayEquals(new String[]{"test", "(INVALID PARAMS_MASK) true"},
+ data.mStrParams.toArray());
+ assertArrayEquals(new Long[]{1L}, data.mSint64Params.toArray());
+ assertArrayEquals(new Double[]{0.1}, data.mDoubleParams.toArray());
+ assertArrayEquals(new Boolean[]{}, data.mBooleanParams.toArray());
+ }
+ }
+
+ @Test
+ public void log_protoDisabled() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b11, new Object[]{true});
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNull(data);
+ }
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..2539653
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LegacyProtoLogViewerConfigReaderTest {
+ private static final String TEST_VIEWER_CONFIG = "{\n"
+ + " \"version\": \"1.0.0\",\n"
+ + " \"messages\": {\n"
+ + " \"70933285\": {\n"
+ + " \"message\": \"Test completed successfully: %b\",\n"
+ + " \"level\": \"ERROR\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1792430067\": {\n"
+ + " \"message\": \"Attempted to add window to a display that does not exist: %d."
+ + " Aborting.\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1352021864\": {\n"
+ + " \"message\": \"Test 2\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"409412266\": {\n"
+ + " \"message\": \"Window %s is already added\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"groups\": {\n"
+ + " \"GENERIC_WM\": {\n"
+ + " \"tag\": \"WindowManager\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+
+ private LegacyProtoLogViewerConfigReader
+ mConfig = new LegacyProtoLogViewerConfigReader();
+ private File mTestViewerConfig;
+
+ @Before
+ public void setUp() throws IOException {
+ mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+ writer.write(TEST_VIEWER_CONFIG);
+ writer.close();
+ }
+
+ @After
+ public void tearDown() {
+ //noinspection ResultOfMethodCallIgnored
+ mTestViewerConfig.delete();
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
+ assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+ assertEquals("Test 2", mConfig.getViewerString(1352021864));
+ assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig_invalidFile() {
+ mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
+ // No exception is thrown.
+ assertNull(mConfig.getViewerString(1));
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
new file mode 100644
index 0000000..e841d9e
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.io.File.createTempFile;
+
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+import android.tools.traces.protolog.ProtoLogTrace;
+import android.tracing.perfetto.DataSource;
+import android.util.proto.ProtoInputStream;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogDataType;
+import com.android.internal.protolog.common.LogLevel;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class PerfettoProtoLogImplTest {
+ private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
+ private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb";
+ private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getFilesDir();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ private static ProtoLogConfigurationService sProtoLogConfigurationService;
+ private static PerfettoProtoLogImpl sProtoLog;
+ private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder;
+ private static Runnable sCacheUpdater;
+
+ private static ProtoLogViewerConfigReader sReader;
+
+ public PerfettoProtoLogImplTest() throws IOException {
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ sViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TestProtoLogGroup.TEST_GROUP.toString())
+ .setTag(TestProtoLogGroup.TEST_GROUP.getTag())
+ ).addMessages(
+ Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Debug Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:123")
+ ).addMessages(
+ Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Verbose Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:342")
+ ).addMessages(
+ Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Warn Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:563")
+ ).addMessages(
+ Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Error Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:156")
+ ).addMessages(
+ Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test WTF Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:192")
+ );
+
+ ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
+ ViewerConfigInputStreamProvider.class);
+ Mockito.when(viewerConfigInputStreamProvider.getInputStream())
+ .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()));
+
+ sCacheUpdater = () -> {};
+ sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+
+ final ProtoLogDataSourceBuilder dataSourceBuilder =
+ (onStart, onFlush, onStop) -> new ProtoLogDataSource(
+ onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME);
+ final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> {
+ Utils.dumpViewerConfig(dataSource, () -> {
+ if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) {
+ throw new RuntimeException(
+ "Unexpected viewer config file path provided");
+ }
+ return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+ });
+ };
+ sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer);
+
+ if (android.tracing.Flags.clientSideProtoLogging()) {
+ sProtoLog = new PerfettoProtoLogImpl(
+ MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(),
+ TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
+ } else {
+ sProtoLog = new PerfettoProtoLogImpl(
+ viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
+ TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
+ }
+ }
+
+ @Before
+ public void before() {
+ Mockito.reset(sReader);
+
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+ }
+
+ @After
+ public void tearDown() {
+ ProtoLogImpl.setSingleInstance(null);
+ }
+
+ @Test
+ public void isEnabled_returnsFalseByDefault() {
+ assertFalse(sProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsTrueAfterStart() {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ assertTrue(sProtoLog.isProtoEnabled());
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+ }
+
+ @Test
+ public void isEnabled_returnsFalseAfterStop() {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ assertTrue(sProtoLog.isProtoEnabled());
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ assertFalse(sProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void defaultMode() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(false, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ // Shouldn't be logging anything except WTF unless explicitly requested in the group
+ // override.
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.getFirst().getLevel()).isEqualTo(LogLevel.WTF);
+ }
+
+ @Test
+ public void respectsOverrideConfigs_defaultMode() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(
+ true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG, true)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(5);
+ Truth.assertThat(protolog.messages.get(0).getLevel()).isEqualTo(LogLevel.DEBUG);
+ Truth.assertThat(protolog.messages.get(1).getLevel()).isEqualTo(LogLevel.VERBOSE);
+ Truth.assertThat(protolog.messages.get(2).getLevel()).isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(3).getLevel()).isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(4).getLevel()).isEqualTo(LogLevel.WTF);
+ }
+
+ @Test
+ public void respectsOverrideConfigs_allEnabledMode() throws IOException {
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(
+ true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN, false)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(3);
+ Truth.assertThat(protolog.messages.get(0).getLevel()).isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(1).getLevel()).isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(2).getLevel()).isEqualTo(LogLevel.WTF);
+ }
+
+ @Test
+ public void respectsAllEnabledMode() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(5);
+ Truth.assertThat(protolog.messages.get(0).getLevel()).isEqualTo(LogLevel.DEBUG);
+ Truth.assertThat(protolog.messages.get(1).getLevel()).isEqualTo(LogLevel.VERBOSE);
+ Truth.assertThat(protolog.messages.get(2).getLevel()).isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(3).getLevel()).isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(4).getLevel()).isEqualTo(LogLevel.WTF);
+ }
+
+ @Test
+ public void log_logcatEnabled() {
+ when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{true, 10000, 30000, "test", 0.000003});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO),
+ eq("test true 10000 % 0x7530 test 3.0E-6"));
+ verify(sReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatEnabledInvalidMessage() {
+ when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{true, 10000, 0.0001, 0.00002, "test"});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO),
+ eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
+ + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
+ verify(sReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatEnabledNoMessage() {
+ when(sReader.getViewerString(anyLong())).thenReturn(null);
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
+ verify(sReader).getViewerString(eq(1234L));
+ }
+
+ @Test
+ public void log_logcatDisabled() {
+ when(sReader.getViewerString(anyLong())).thenReturn("test %d");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5});
+
+ verify(implSpy, never()).passToLogcat(any(), any(), any());
+ verify(sReader, never()).getViewerString(anyLong());
+ }
+
+ @Test
+ public void log_protoEnabled() throws Exception {
+ final long messageHash = addMessageToConfig(
+ ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_INFO,
+ "My test message :: %s, %d, %o, %x, %f, %e, %g, %b");
+
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ long before;
+ long after;
+ try {
+ assertFalse(sProtoLog.isProtoEnabled());
+ traceMonitor.start();
+ assertTrue(sProtoLog.isProtoEnabled());
+
+ before = SystemClock.elapsedRealtimeNanos();
+ sProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
+ 0b1110101001010100,
+ new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+ after = SystemClock.elapsedRealtimeNanos();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtLeast(before);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtMost(after);
+ Truth.assertThat(protolog.messages.getFirst().getMessage())
+ .isEqualTo(
+ "My test message :: test, 1, 2, 3, 0.400000, 5.000000e-01, 0.6, true");
+ }
+
+ @Test
+ public void log_noProcessing() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ long before;
+ long after;
+ try {
+ traceMonitor.start();
+ assertTrue(sProtoLog.isProtoEnabled());
+
+ before = SystemClock.elapsedRealtimeNanos();
+ sProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
+ "My test message :: %s, %d, %x, %f, %b",
+ "test", 1, 3, 0.4, true);
+ after = SystemClock.elapsedRealtimeNanos();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtLeast(before);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtMost(after);
+ Truth.assertThat(protolog.messages.getFirst().getMessage())
+ .isEqualTo("My test message :: test, 1, 3, 0.400000, true");
+ }
+
+ @Test
+ public void supportsLocationInformation() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getLocation())
+ .isEqualTo("com/test/MyTestClass.java:123");
+ }
+
+ private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
+ final long messageId = new Random().nextLong();
+ sViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(messageId)
+ .setMessage(message)
+ .setLevel(logLevel)
+ .setGroupId(1)
+ );
+
+ return messageId;
+ }
+
+ @Test
+ public void log_invalidParamsMask() {
+ final long messageHash = addMessageToConfig(
+ ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_INFO,
+ "My test message :: %s, %d, %f, %b");
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ long before;
+ long after;
+ try {
+ traceMonitor.start();
+ before = SystemClock.elapsedRealtimeNanos();
+ sProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
+ 0b01100100,
+ new Object[]{"test", 1, 0.1, true});
+ after = SystemClock.elapsedRealtimeNanos();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ assertThrows(IllegalStateException.class, reader::readProtoLogTrace);
+ }
+
+ @Test
+ public void log_protoDisabled() throws Exception {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(false, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ 0b11, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).isEmpty();
+ }
+
+ @Test
+ public void stackTraceTrimmed() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(
+ true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ true)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
+ try {
+ traceMonitor.start();
+
+ ProtoLogImpl.setSingleInstance(sProtoLog);
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
+ 0b11, true);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ String stacktrace = protolog.messages.getFirst().getStacktrace();
+ Truth.assertThat(stacktrace)
+ .doesNotContain(PerfettoProtoLogImpl.class.getSimpleName() + ".java");
+ Truth.assertThat(stacktrace).doesNotContain(DataSource.class.getSimpleName() + ".java");
+ Truth.assertThat(stacktrace)
+ .doesNotContain(ProtoLogImpl.class.getSimpleName() + ".java");
+ Truth.assertThat(stacktrace).contains(PerfettoProtoLogImplTest.class.getSimpleName());
+ Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
+ }
+
+ @Test
+ public void cacheIsUpdatedWhenTracesStartAndStop() {
+ final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0);
+ sCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+
+ PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)), TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(0);
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(1);
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(2);
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(3);
+
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(4);
+ }
+
+ @Test
+ public void isEnabledUpdatesBasedOnRunningTraces() {
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
+
+ PerfettoTraceMonitor traceMonitor1 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
+ LogLevel.VERBOSE)).isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isFalse();
+ }
+
+ @Test
+ public void supportsNullString() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My test null string: %s", (Object) null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My test null string: null");
+ }
+
+ @Test
+ public void supportNullParams() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My null args: %d, %f, %b", null, null, null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My null args: 0, 0.000000, false");
+ }
+
+ @Test
+ public void handlesConcurrentTracingSessions() throws IOException {
+ PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ final ResultWriter writer2 = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ try {
+ traceMonitor1.start();
+ traceMonitor2.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor1.stop(mWriter);
+ traceMonitor2.stop(writer2);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protologFromMonitor1 = reader.readProtoLogTrace();
+
+ final ResultReader reader2 = new ResultReader(writer2.write(), mTraceConfig);
+ final ProtoLogTrace protologFromMonitor2 = reader2.readProtoLogTrace();
+
+ Truth.assertThat(protologFromMonitor1.messages).hasSize(1);
+ Truth.assertThat(protologFromMonitor1.messages.get(0).getMessage())
+ .isEqualTo("My Test Debug Log Message true");
+
+ Truth.assertThat(protologFromMonitor2.messages).hasSize(1);
+ Truth.assertThat(protologFromMonitor2.messages.get(0).getMessage())
+ .isEqualTo("My Test Debug Log Message true");
+ }
+
+ @Test
+ public void usesDefaultLogFromLevel() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(LogLevel.WARN, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "This message should not be logged");
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+ "This message should be logged %d", 123);
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+ "This message should also be logged %d", 567);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(2);
+
+ Truth.assertThat(protolog.messages.get(0).getLevel())
+ .isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("This message should be logged 123");
+
+ Truth.assertThat(protolog.messages.get(1).getLevel())
+ .isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(1).getMessage())
+ .isEqualTo("This message should also be logged 567");
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "TEST_TAG");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
new file mode 100644
index 0000000..be0c7da
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogCommandHandlerTest {
+
+ @Mock
+ ProtoLogConfigurationService mProtoLogConfigurationService;
+ @Mock
+ PrintWriter mPrintWriter;
+ @Mock
+ Binder mMockBinder;
+
+ @Test
+ public void printsHelpForAllAvailableCommands() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.onHelp();
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void printsHelpIfCommandIsNull() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.onCommand(null);
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void handlesGroupListCommand() {
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "list" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_TEST_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_OTHER_GROUP"));
+ }
+
+ @Test
+ public void handlesIncompleteGroupsCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommand() {
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_GROUP"});
+ Mockito.when(mProtoLogConfigurationService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("LOG_TO_LOGCAT = true"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandOfUnregisteredGroups() {
+ Mockito.when(mProtoLogConfigurationService.getGroups()).thenReturn(new String[] {});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("UNREGISTERED"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesIncompleteLogcatCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatEnableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
+ new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService)
+ .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatDisableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
+ new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService)
+ .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatEnableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatDisableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ private void validateOnHelpPrinted() {
+ Mockito.verify(mPrintWriter, times(1)).println(endsWith("help"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("groups (list | status)"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("logcat (enable | disable) <group>"));
+ Mockito.verify(mPrintWriter, atLeast(0)).println(anyString());
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
new file mode 100644
index 0000000..e1bdd77
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.Tag;
+import android.tools.io.ResultArtifactDescriptor;
+import android.tools.io.TraceType;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import perfetto.protos.Protolog.ProtoLogViewerConfig;
+import perfetto.protos.ProtologCommon;
+import perfetto.protos.TraceOuterClass.Trace;
+import perfetto.protos.TracePacketOuterClass.TracePacket;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogConfigurationServiceTest {
+
+ private static final String TEST_GROUP = "MY_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
+
+ private static final ProtoLogViewerConfig VIEWER_CONFIG =
+ ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP)
+ .setTag(TEST_GROUP)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Debug Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Verbose Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).build();
+
+ @Mock
+ IProtoLogClient mMockClient;
+
+ @Mock
+ IProtoLogClient mSecondMockClient;
+
+ @Mock
+ IBinder mMockClientBinder;
+
+ @Mock
+ IBinder mSecondMockClientBinder;
+
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientArgumentCaptor;
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mSecondDeathRecipientArgumentCaptor;
+
+ private File mViewerConfigFile;
+
+ public ProtoLogConfigurationServiceTest() throws IOException {
+ }
+
+ @Before
+ public void setUp() {
+ Mockito.when(mMockClient.asBinder()).thenReturn(mMockClientBinder);
+ Mockito.when(mSecondMockClient.asBinder()).thenReturn(mSecondMockClientBinder);
+
+ try {
+ mViewerConfigFile = File.createTempFile("viewer-config", ".pb");
+ try (var fos = new FileOutputStream(mViewerConfigFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+
+ bos.write(VIEWER_CONFIG.toByteArray());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void canRegisterClientWithGroupsOnly() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ Truth.assertThat(service.getGroups()).asList().containsExactly(TEST_GROUP);
+ }
+
+ @Test
+ public void willDumpViewerConfigOnlyOnceOnTraceStop()
+ throws RemoteException, InvalidProtocolBufferException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+
+ traceMonitor.start();
+ traceMonitor.stop(mWriter);
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] traceData = reader.getArtifact()
+ .readBytes(new ResultArtifactDescriptor(TraceType.PERFETTO, Tag.ALL));
+
+ final Trace trace = Trace.parseFrom(traceData);
+
+ final List<TracePacket> configPackets = trace.getPacketList().stream()
+ .filter(it -> it.hasProtologViewerConfig())
+ // Exclude viewer configs from regular system tracing
+ .filter(it ->
+ it.getProtologViewerConfig().getGroups(0).getName().equals(TEST_GROUP))
+ .toList();
+ Truth.assertThat(configPackets).hasSize(1);
+ Truth.assertThat(configPackets.get(0).getProtologViewerConfig().toString())
+ .isEqualTo(VIEWER_CONFIG.toString());
+ }
+
+ @Test
+ public void willDumpViewerConfigOnLastClientDisconnected()
+ throws RemoteException, FileNotFoundException {
+ final ProtoLogConfigurationService.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class);
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer);
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ Mockito.verify(mMockClientBinder)
+ .linkToDeath(mDeathRecipientArgumentCaptor.capture(), anyInt());
+ Mockito.verify(mSecondMockClientBinder)
+ .linkToDeath(mSecondDeathRecipientArgumentCaptor.capture(), anyInt());
+
+ mDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer, never()).trace(any(), any());
+ mSecondDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer).trace(any(), eq(mViewerConfigFile.getAbsolutePath()));
+ }
+
+ @Test
+ public void sendEnableLoggingToLogcatToClient() throws RemoteException {
+ final var service = new ProtoLogConfigurationService();
+
+ final var args = new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void sendDisableLoggingToLogcatToClient() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ service.disableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(false),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(OTHER_TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any());
+ }
+
+ @Test
+ public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationService();
+
+ Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ final ProtoLogConfigurationService.RegisterClientArgs args =
+ new ProtoLogConfigurationService.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationService.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
new file mode 100644
index 0000000..0496240
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.LogLevel;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogImplTest {
+ @After
+ public void tearDown() {
+ ProtoLogImpl.setSingleInstance(null);
+ }
+
+ @Test
+ public void getSingleInstance() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ assertSame(mockedProtoLog, ProtoLogImpl.getSingleInstance());
+ }
+
+ @Test
+ public void d_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.DEBUG), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ @Test
+ public void v_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.VERBOSE), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ @Test
+ public void i_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.INFO), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ @Test
+ public void w_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.WARN), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ @Test
+ public void e_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ @Test
+ public void wtf_logCalled() {
+ IProtoLog mockedProtoLog = mock(IProtoLog.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
+ 1234, 4321);
+ verify(mockedProtoLog).log(eq(LogLevel.WTF), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234L), eq(4321), eq(new Object[]{}));
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return ordinal();
+ }
+
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 0000000..9d56a92
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+ @Test
+ public void canRunProtoLogInitMultipleTimes() {
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_2);
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+ }
+
+ private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+ private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+
+ private static class ProtoLogGroup implements IProtoLogGroup {
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+ private final int mId;
+
+ ProtoLogGroup(String tag, int id) {
+ this(true, true, false, tag, id);
+ }
+
+ ProtoLogGroup(
+ boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ this.mId = id;
+ }
+
+ @Override
+ public String name() {
+ return mTag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..28d7b42
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import perfetto.protos.ProtologCommon;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogViewerConfigReaderTest {
+ private static final String TEST_GROUP_NAME = "MY_TEST_GROUP";
+ private static final String TEST_GROUP_TAG = "TEST";
+
+ private static final String OTHER_TEST_GROUP_NAME = "MY_OTHER_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP_TAG = "OTHER_TEST";
+
+ private static final byte[] TEST_VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP_NAME)
+ .setTag(TEST_GROUP_TAG)
+ ).addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(2)
+ .setName(OTHER_TEST_GROUP_NAME)
+ .setTag(OTHER_TEST_GROUP_TAG)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Log Message 1 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Log Message 2 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Log Message 3 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Log Message 4 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(2)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test Log Message 5 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(2)
+ ).build().toByteArray();
+
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
+ () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+
+ private ProtoLogViewerConfigReader mConfig;
+
+ @Before
+ public void before() {
+ mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider);
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertEquals("My Test Log Message 1 %b", mConfig.getViewerString(1));
+ assertEquals("My Test Log Message 2 %b", mConfig.getViewerString(2));
+ assertEquals("My Test Log Message 3 %b", mConfig.getViewerString(3));
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
+ }
+
+ @Test
+ public void unloadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME, OTHER_TEST_GROUP_NAME });
+ mConfig.unloadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(1));
+ assertNull(mConfig.getViewerString(2));
+ assertNull(mConfig.getViewerString(3));
+ assertEquals("My Test Log Message 4 %b", mConfig.getViewerString(4));
+ assertEquals("My Test Log Message 5 %b", mConfig.getViewerString(5));
+
+ mConfig.unloadViewerConfig(new String[] { OTHER_TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
new file mode 100644
index 0000000..ce519b7a
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.tracing.perfetto.CreateTlsStateArgs;
+import android.util.proto.ProtoInputStream;
+
+import com.android.internal.protolog.common.LogLevel;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import perfetto.protos.DataSourceConfigOuterClass;
+import perfetto.protos.ProtologCommon;
+import perfetto.protos.ProtologConfig;
+
+public class ProtologDataSourceTest {
+ @Before
+ public void before() {
+ assumeTrue(android.tracing.Flags.perfettoProtologTracing());
+ }
+
+ @Test
+ public void noConfig() {
+ final ProtoLogDataSource.TlsState tlsState = createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig.newBuilder().build());
+
+ Truth.assertThat(tlsState.getLogFromLevel("SOME_TAG")).isEqualTo(LogLevel.WTF);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_TAG")).isFalse();
+ }
+
+ @Test
+ public void defaultTraceMode() {
+ final ProtoLogDataSource.TlsState tlsState = createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig.newBuilder()
+ .setProtologConfig(
+ ProtologConfig.ProtoLogConfig.newBuilder()
+ .setTracingMode(
+ ProtologConfig.ProtoLogConfig.TracingMode
+ .ENABLE_ALL)
+ .build()
+ ).build());
+
+ Truth.assertThat(tlsState.getLogFromLevel("SOME_TAG")).isEqualTo(LogLevel.DEBUG);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_TAG")).isFalse();
+ }
+
+ @Test
+ public void allEnabledTraceMode() {
+ final ProtoLogDataSource ds =
+ new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
+
+ final ProtoLogDataSource.TlsState tlsState = createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
+ ProtologConfig.ProtoLogConfig.newBuilder()
+ .setTracingMode(
+ ProtologConfig.ProtoLogConfig.TracingMode.ENABLE_ALL)
+ .build()
+ ).build()
+ );
+
+ Truth.assertThat(tlsState.getLogFromLevel("SOME_TAG")).isEqualTo(LogLevel.DEBUG);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_TAG")).isFalse();
+ }
+
+ @Test
+ public void requireGroupTagInOverrides() {
+ Exception exception = assertThrows(RuntimeException.class, () -> {
+ createTlsState(DataSourceConfigOuterClass.DataSourceConfig.newBuilder()
+ .setProtologConfig(
+ ProtologConfig.ProtoLogConfig.newBuilder()
+ .addGroupOverrides(
+ ProtologConfig.ProtoLogGroup.newBuilder()
+ .setLogFrom(
+ ProtologCommon.ProtoLogLevel
+ .PROTOLOG_LEVEL_WARN)
+ .setCollectStacktrace(true)
+ )
+ .build()
+ ).build());
+ });
+
+ Truth.assertThat(exception).hasMessageThat().contains("group override without a group tag");
+ }
+
+ @Test
+ public void stackTraceCollection() {
+ final ProtoLogDataSource.TlsState tlsState = createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
+ ProtologConfig.ProtoLogConfig.newBuilder()
+ .addGroupOverrides(
+ ProtologConfig.ProtoLogGroup.newBuilder()
+ .setGroupName("SOME_TAG")
+ .setCollectStacktrace(true)
+ )
+ .build()
+ ).build());
+
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_TAG")).isTrue();
+ }
+
+ @Test
+ public void groupLogFromOverrides() {
+ final ProtoLogDataSource.TlsState tlsState = createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
+ ProtologConfig.ProtoLogConfig.newBuilder()
+ .addGroupOverrides(
+ ProtologConfig.ProtoLogGroup.newBuilder()
+ .setGroupName("SOME_TAG")
+ .setLogFrom(
+ ProtologCommon.ProtoLogLevel
+ .PROTOLOG_LEVEL_DEBUG)
+ .setCollectStacktrace(true)
+ )
+ .addGroupOverrides(
+ ProtologConfig.ProtoLogGroup.newBuilder()
+ .setGroupName("SOME_OTHER_TAG")
+ .setLogFrom(
+ ProtologCommon.ProtoLogLevel
+ .PROTOLOG_LEVEL_WARN)
+ )
+ .build()
+ ).build());
+
+ Truth.assertThat(tlsState.getLogFromLevel("SOME_TAG")).isEqualTo(LogLevel.DEBUG);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_TAG")).isTrue();
+
+ Truth.assertThat(tlsState.getLogFromLevel("SOME_OTHER_TAG")).isEqualTo(LogLevel.WARN);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("SOME_OTHER_TAG")).isFalse();
+
+ Truth.assertThat(tlsState.getLogFromLevel("UNKNOWN_TAG")).isEqualTo(LogLevel.WTF);
+ Truth.assertThat(tlsState.getShouldCollectStacktrace("UNKNOWN_TAG")).isFalse();
+ }
+
+ private ProtoLogDataSource.TlsState createTlsState(
+ DataSourceConfigOuterClass.DataSourceConfig config) {
+ final ProtoLogDataSource ds =
+ Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}));
+
+ ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
+ final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
+ ds.createInstance(configStream, 8));
+ Mockito.doNothing().when(dsInstance).release();
+ final CreateTlsStateArgs mockCreateTlsStateArgs = Mockito.mock(CreateTlsStateArgs.class);
+ Mockito.when(mockCreateTlsStateArgs.getDataSourceInstanceLocked()).thenReturn(dsInstance);
+ return ds.createTlsState(mockCreateTlsStateArgs);
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java
new file mode 100644
index 0000000..9c2f74ea
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog.common;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LogDataTypeTest {
+ @Test
+ public void parseFormatString() {
+ String str = "%b %d %x %f %s %%";
+ List<Integer> out = LogDataType.parseFormatString(str);
+ assertEquals(Arrays.asList(
+ LogDataType.BOOLEAN,
+ LogDataType.LONG,
+ LogDataType.LONG,
+ LogDataType.DOUBLE,
+ LogDataType.STRING
+ ), out);
+ }
+
+ @Test(expected = InvalidFormatStringException.class)
+ public void parseFormatString_invalid() {
+ String str = "%q";
+ LogDataType.parseFormatString(str);
+ }
+
+ @Test
+ public void logDataTypesToBitMask() {
+ List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+ LogDataType.LONG, LogDataType.BOOLEAN);
+ int mask = LogDataType.logDataTypesToBitMask(types);
+ assertEquals(0b11011000, mask);
+ }
+
+ @Test(expected = BitmaskConversionException.class)
+ public void logDataTypesToBitMask_toManyParams() {
+ ArrayList<Integer> types = new ArrayList<>();
+ for (int i = 0; i <= 16; i++) {
+ types.add(LogDataType.STRING);
+ }
+ LogDataType.logDataTypesToBitMask(types);
+ }
+
+ @Test
+ public void bitmaskToLogDataTypes() {
+ int bitmask = 0b11011000;
+ List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+ LogDataType.LONG, LogDataType.BOOLEAN);
+ for (int i = 0; i < types.size(); i++) {
+ assertEquals(types.get(i).intValue(), LogDataType.bitmaskToLogDataType(bitmask, i));
+ }
+ }
+}