diff options
Diffstat (limited to 'tests')
5 files changed, 922 insertions, 34 deletions
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 2a82d5f9bd7c..351ec4635977 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -212,9 +212,10 @@ class InputManagerServiceTests { verify(native).setMotionClassifierEnabled(anyBoolean()) verify(native).setMaximumObscuringOpacityForTouch(anyFloat()) verify(native).setStylusPointerIconEnabled(anyBoolean()) - // Called twice at boot, since there are individual callbacks to update the - // key repeat timeout and the key repeat delay. - verify(native, times(2)).setKeyRepeatConfiguration(anyInt(), anyInt()) + // Called thrice at boot, since there are individual callbacks to update the + // key repeat timeout, the key repeat delay and whether key repeat enabled. + verify(native, times(3)).setKeyRepeatConfiguration(anyInt(), anyInt(), + anyBoolean()) } @Test diff --git a/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java b/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java new file mode 100644 index 000000000000..bbeb18dfbecd --- /dev/null +++ b/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java @@ -0,0 +1,709 @@ +/* + * Copyright (C) 2023 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 android.tracing.perfetto; + +import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.PAYLOAD; +import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.TestPayload.SINGLE_INT; +import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.FOR_TESTING; + +import static java.io.File.createTempFile; +import static java.nio.file.Files.createTempDirectory; + +import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig; +import android.internal.perfetto.protos.TestConfigOuterClass.TestConfig; +import android.tools.ScenarioBuilder; +import android.tools.Tag; +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 android.tools.traces.monitors.TraceMonitor; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.truth.Truth; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import perfetto.protos.PerfettoConfig; +import perfetto.protos.TracePacketOuterClass; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +public class DataSourceTest { + 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) + ); + + private static TestDataSource sTestDataSource; + + private static TestDataSource.DataSourceInstanceProvider sInstanceProvider; + private static TestDataSource.TlsStateProvider sTlsStateProvider; + private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider; + + public DataSourceTest() throws IOException {} + + @BeforeClass + public static void beforeAll() { + Producer.init(InitArguments.DEFAULTS); + setupProviders(); + sTestDataSource = new TestDataSource( + (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream), + args -> sTlsStateProvider.provide(args), + args -> sIncrementalStateProvider.provide(args)); + sTestDataSource.register(DataSourceParams.DEFAULTS); + } + + private static void setupProviders() { + sInstanceProvider = (ds, idx, configStream) -> + new TestDataSource.TestDataSourceInstance(ds, idx); + sTlsStateProvider = args -> new TestDataSource.TestTlsState(); + sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState(); + } + + @Before + public void setup() { + setupProviders(); + } + + @Test + public void canTraceData() throws InvalidProtocolBufferException { + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, 10); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + @Test + public void canUseTlsStateForCustomState() { + final int expectedStateTestValue = 10; + final AtomicInteger actualStateTestValue = new AtomicInteger(); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = expectedStateTestValue; + }); + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + actualStateTestValue.set(state.testStateValue); + }); + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue); + } + + @Test + public void eachInstanceHasOwnTlsState() { + final int[] expectedStateTestValues = new int[] { 1, 2 }; + final int[] actualStateTestValues = new int[] { 0, 0 }; + + final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor1.start(); + try { + traceMonitor2.start(); + + AtomicInteger index = new AtomicInteger(0); + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = expectedStateTestValues[index.getAndIncrement()]; + }); + + index.set(0); + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + actualStateTestValues[index.getAndIncrement()] = state.testStateValue; + }); + } finally { + traceMonitor1.stop(mWriter); + } + } finally { + traceMonitor2.stop(mWriter); + } + + Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]); + Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]); + } + + @Test + public void eachThreadHasOwnTlsState() throws InterruptedException { + final int thread1ExpectedStateValue = 1; + final int thread2ExpectedStateValue = 2; + + final AtomicInteger thread1ActualStateValue = new AtomicInteger(); + final AtomicInteger thread2ActualStateValue = new AtomicInteger(); + + final CountDownLatch setUpLatch = new CountDownLatch(2); + final CountDownLatch setStateLatch = new CountDownLatch(2); + final CountDownLatch setOutStateLatch = new CountDownLatch(2); + + final RunnableCreator createTask = (stateValue, stateOut) -> () -> { + Producer.init(InitArguments.DEFAULTS); + + setUpLatch.countDown(); + + try { + setUpLatch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = stateValue; + setStateLatch.countDown(); + }); + + try { + setStateLatch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + sTestDataSource.trace((ctx) -> { + stateOut.set(ctx.getCustomTlsState().testStateValue); + setOutStateLatch.countDown(); + }); + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + new Thread( + createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start(); + new Thread( + createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start(); + + setOutStateLatch.await(3, TimeUnit.SECONDS); + + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue); + Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue); + } + + @Test + public void incrementalStateIsReset() throws InterruptedException { + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()) + .setIncrementalTimeout(10) + .build(); + + final AtomicInteger testStateValue = new AtomicInteger(); + try { + traceMonitor.start(); + + sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1); + + // Timeout to make sure the incremental state is cleared. + Thread.sleep(1000); + + sTestDataSource.trace(ctx -> + testStateValue.set(ctx.getIncrementalState().testStateValue)); + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(testStateValue.get()).isNotEqualTo(1); + } + + @Test + public void getInstanceConfigOnCreateInstance() throws IOException { + final int expectedDummyIntValue = 10; + AtomicReference<ProtoInputStream> configStream = new AtomicReference<>(); + sInstanceProvider = (ds, idx, config) -> { + configStream.set(config); + return new TestDataSource.TestDataSourceInstance(ds, idx); + }; + + final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name) + .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields( + PerfettoConfig.TestConfig.DummyFields.newBuilder() + .setFieldInt32(expectedDummyIntValue) + .build()) + .build()) + .build()) + .build(); + + try { + monitor.start(); + } finally { + monitor.stop(mWriter); + } + + int configDummyIntValue = 0; + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) DataSourceConfig.FOR_TESTING) { + final long forTestingToken = configStream.get() + .start(DataSourceConfig.FOR_TESTING); + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) TestConfig.DUMMY_FIELDS) { + final long dummyFieldsToken = configStream.get() + .start(TestConfig.DUMMY_FIELDS); + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) TestConfig.DummyFields.FIELD_INT32) { + int val = configStream.get().readInt( + TestConfig.DummyFields.FIELD_INT32); + if (val != 0) { + configDummyIntValue = val; + break; + } + } + } + configStream.get().end(dummyFieldsToken); + break; + } + } + configStream.get().end(forTestingToken); + break; + } + } + + Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue); + } + + @Test + public void multipleTraceInstances() throws IOException, InterruptedException { + final int instanceCount = 3; + + final List<TraceMonitor> monitors = new ArrayList<>(); + final List<ResultWriter> writers = new ArrayList<>(); + + for (int i = 0; i < instanceCount; i++) { + final ResultWriter writer = new ResultWriter() + .forScenario(new ScenarioBuilder() + .forClass(createTempFile("temp", "").getName()).build()) + .withOutputDir(mTracingDirectory) + .setRunComplete(); + writers.add(writer); + } + + // Start at 1 because 0 is considered null value so payload will be ignored in that case + TestDataSource.TestTlsState.lastIndex = 1; + + final AtomicInteger traceCallCount = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(instanceCount); + + try { + // Start instances + for (int i = 0; i < instanceCount; i++) { + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + monitors.add(traceMonitor); + traceMonitor.start(); + } + + // Trace the stateIndex of the tracing instance. + sTestDataSource.trace(ctx -> { + final int testIntValue = ctx.getCustomTlsState().stateIndex; + traceCallCount.incrementAndGet(); + + final ProtoOutputStream os = ctx.newTracePacket(); + long forTestingToken = os.start(FOR_TESTING); + long payloadToken = os.start(PAYLOAD); + os.write(SINGLE_INT, testIntValue); + os.end(payloadToken); + os.end(forTestingToken); + + latch.countDown(); + }); + } finally { + // Stop instances + for (int i = 0; i < instanceCount; i++) { + final TraceMonitor monitor = monitors.get(i); + final ResultWriter writer = writers.get(i); + monitor.stop(writer); + } + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount); + + for (int i = 0; i < instanceCount; i++) { + final int expectedTracedValue = i + 1; + final ResultWriter writer = writers.get(i); + final ResultReader reader = new ResultReader(writer.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = + perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + Truth.assertWithMessage("One packet has for testing data") + .that(tracePackets).hasSize(1); + + final List<TracePacketOuterClass.TracePacket> matchingPackets = + tracePackets.stream() + .filter(it -> it.getForTesting().getPayload() + .getSingleInt() == expectedTracedValue).toList(); + Truth.assertWithMessage( + "One packet has testing data with a payload with the expected value") + .that(matchingPackets).hasSize(1); + } + } + + @Test + public void onStartCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + }, + (args) -> {}, + (args) -> {} + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + Truth.assertThat(callbackCalled.get()).isFalse(); + try { + traceMonitor.start(); + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } finally { + traceMonitor.stop(mWriter); + } + } + + @Test + public void onFlushCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> + new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> {}, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + }, + (args) -> {} + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + Truth.assertThat(callbackCalled.get()).isFalse(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, 10); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } + + @Test + public void onStopCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> + new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> {}, + (args) -> {}, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + } + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + Truth.assertThat(callbackCalled.get()).isFalse(); + } finally { + traceMonitor.stop(mWriter); + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } + + @Test + public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException { + final Object testObject = new Object(); + + sInstanceProvider = (ds, idx, configStream) -> { + final TestDataSource.TestDataSourceInstance dsInstance = + new TestDataSource.TestDataSourceInstance(ds, idx); + dsInstance.testObject = testObject; + return dsInstance; + }; + + sTlsStateProvider = args -> { + final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState(); + + try (TestDataSource.TestDataSourceInstance dataSourceInstance = + args.getDataSourceInstanceLocked()) { + if (dataSourceInstance != null) { + tlsState.testStateValue = dataSourceInstance.testObject.hashCode(); + } + } + + return tlsState; + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() + == testObject.hashCode()).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + @Test + public void canUseDataSourceInstanceToCreateIncrementalState() + throws InvalidProtocolBufferException { + final Object testObject = new Object(); + + sInstanceProvider = (ds, idx, configStream) -> { + final TestDataSource.TestDataSourceInstance dsInstance = + new TestDataSource.TestDataSourceInstance(ds, idx); + dsInstance.testObject = testObject; + return dsInstance; + }; + + sIncrementalStateProvider = args -> { + final TestDataSource.TestIncrementalState incrementalState = + new TestDataSource.TestIncrementalState(); + + try (TestDataSource.TestDataSourceInstance dataSourceInstance = + args.getDataSourceInstanceLocked()) { + if (dataSourceInstance != null) { + incrementalState.testStateValue = dataSourceInstance.testObject.hashCode(); + } + } + + return incrementalState; + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() + == testObject.hashCode()).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + @Test + public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException { + final int singleIntValue = 101; + sInstanceProvider = (ds, idx, config) -> + new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> {}, + (args) -> sTestDataSource.trace(ctx -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, singleIntValue); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }), + (args) -> {} + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() + == singleIntValue).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + interface RunnableCreator { + Runnable create(int state, AtomicInteger stateOut); + } +} diff --git a/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java b/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java new file mode 100644 index 000000000000..d78f78b1cb0e --- /dev/null +++ b/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2023 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 android.tracing.perfetto; + +import android.util.proto.ProtoInputStream; + +import java.util.UUID; +import java.util.function.Consumer; + +public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance, + TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> { + private final DataSourceInstanceProvider mDataSourceInstanceProvider; + private final TlsStateProvider mTlsStateProvider; + private final IncrementalStateProvider mIncrementalStateProvider; + + interface DataSourceInstanceProvider { + TestDataSourceInstance provide( + TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream); + } + + interface TlsStateProvider { + TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args); + } + + interface IncrementalStateProvider { + TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args); + } + + public TestDataSource() { + this((ds, idx, config) -> new TestDataSourceInstance(ds, idx), + args -> new TestTlsState(), args -> new TestIncrementalState()); + } + + public TestDataSource( + DataSourceInstanceProvider dataSourceInstanceProvider, + TlsStateProvider tlsStateProvider, + IncrementalStateProvider incrementalStateProvider + ) { + super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString()); + this.mDataSourceInstanceProvider = dataSourceInstanceProvider; + this.mTlsStateProvider = tlsStateProvider; + this.mIncrementalStateProvider = incrementalStateProvider; + } + + @Override + public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { + return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream); + } + + @Override + public TestTlsState createTlsState(CreateTlsStateArgs args) { + return mTlsStateProvider.provide(args); + } + + @Override + public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) { + return mIncrementalStateProvider.provide(args); + } + + public static class TestTlsState { + public int testStateValue; + public int stateIndex = lastIndex++; + + public static int lastIndex = 0; + } + + public static class TestIncrementalState { + public int testStateValue; + } + + public static class TestDataSourceInstance extends DataSourceInstance { + public Object testObject; + Consumer<StartCallbackArguments> mStartCallback; + Consumer<FlushCallbackArguments> mFlushCallback; + Consumer<StopCallbackArguments> mStopCallback; + + public TestDataSourceInstance(DataSource dataSource, int instanceIndex) { + this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {}); + } + + public TestDataSourceInstance( + DataSource dataSource, + int instanceIndex, + Consumer<StartCallbackArguments> startCallback, + Consumer<FlushCallbackArguments> flushCallback, + Consumer<StopCallbackArguments> stopCallback) { + super(dataSource, instanceIndex); + this.mStartCallback = startCallback; + this.mFlushCallback = flushCallback; + this.mStopCallback = stopCallback; + } + + @Override + public void onStart(StartCallbackArguments args) { + this.mStartCallback.accept(args); + } + + @Override + public void onFlush(FlushCallbackArguments args) { + this.mFlushCallback.accept(args); + } + + @Override + public void onStop(StopCallbackArguments args) { + this.mStopCallback.accept(args); + } + } +} diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index e841d9ea0880..cfb2645dee0d 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import static android.tools.traces.Utils.executeShellCommand; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -42,12 +44,13 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.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 com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -57,12 +60,14 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -166,7 +171,8 @@ public class PerfettoProtoLogImplTest { return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); }); }; - sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + sProtoLogConfigurationService = + new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer); if (android.tracing.Flags.clientSideProtoLogging()) { sProtoLog = new PerfettoProtoLogImpl( @@ -177,6 +183,8 @@ public class PerfettoProtoLogImplTest { viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); } + + waitDataSourceIsAvailable(); } @Before @@ -862,6 +870,54 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + private static void waitDataSourceIsAvailable() { + final int timeoutMs = 10000; + final int busyWaitIntervalMs = 100; + + int elapsedMs = 0; + + while (!isDataSourceAvailable()) { + SystemClock.sleep(busyWaitIntervalMs); + elapsedMs += busyWaitIntervalMs; + if (elapsedMs >= timeoutMs) { + throw new RuntimeException("Data source didn't become available." + + " Waited for: " + timeoutMs + " ms"); + } + } + } + + private static boolean isDataSourceAvailable() { + byte[] proto = executeShellCommand("perfetto --query-raw"); + + try { + TracingServiceState state = TracingServiceState.parseFrom(proto); + + Optional<Integer> producerId = Optional.empty(); + + for (TracingServiceState.Producer producer : state.getProducersList()) { + if (producer.getPid() == android.os.Process.myPid()) { + producerId = Optional.of(producer.getId()); + break; + } + } + + if (producerId.isEmpty()) { + return false; + } + + for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { + if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME) + && ds.getProducerId() == producerId.get()) { + return true; + } + } + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + return false; + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java index e1bdd777dc5f..a3d03a8278ed 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java @@ -150,11 +150,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void canRegisterClientWithGroupsOnly() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -165,11 +165,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnlyOnceOnTraceStop() throws RemoteException, InvalidProtocolBufferException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -200,13 +200,13 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnLastClientDisconnected() throws RemoteException, FileNotFoundException { - final ProtoLogConfigurationService.ViewerConfigFileTracer tracer = - Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class); - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer); + final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer = + Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -225,10 +225,10 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendEnableLoggingToLogcatToClient() throws RemoteException { - final var service = new ProtoLogConfigurationService(); + final var service = new ProtoLogConfigurationServiceImpl(); - final var args = new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -242,11 +242,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendDisableLoggingToLogcatToClient() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -260,11 +260,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -277,15 +277,15 @@ public class ProtoLogConfigurationServiceTest { @Test public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); 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 + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); |