blob: b6202733437ab8a1585ca18c2620843bfafc1f0c [file]
/*
* Copyright (C) 2026 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.
*/
// Integration test for the Java DataSource JNI path.
//
// This test exercises the exact same C ABI call sequence that
// perfetto_datasource_jni.cc uses:
// 1. PerfettoDsImplCreate + register callbacks + PerfettoDsImplRegister
// 2. PerfettoDsImplTraceIterateBegin/Next (instance iteration)
// 3. PerfettoDsTracerImplPacketBegin + PerfettoStreamWriterAppendBytes +
// PerfettoDsTracerImplPacketEnd (packet writing)
// 4. PerfettoDsImplGetIncrementalState (incremental state check)
//
// The packet bytes are pre-encoded in the test (mimicking ProtoWriter output)
// and written to the stream writer via AppendBytes, exactly as the JNI does.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include "perfetto/public/abi/atomic.h"
#include "perfetto/public/abi/data_source_abi.h"
#include "perfetto/public/abi/stream_writer_abi.h"
#include "perfetto/public/data_source.h"
#include "perfetto/public/pb_utils.h"
#include "perfetto/public/producer.h"
#include "perfetto/public/protos/trace/test_event.pzc.h"
#include "perfetto/public/protos/trace/trace.pzc.h"
#include "perfetto/public/protos/trace/trace_packet.pzc.h"
#include "perfetto/public/stream_writer.h"
#include "src/shared_lib/reset_for_testing.h"
#include "src/shared_lib/test/utils.h"
#include "test/gtest_and_gmock.h"
using ::perfetto::shlib::test_utils::FieldView;
using ::perfetto::shlib::test_utils::IdFieldView;
using ::perfetto::shlib::test_utils::MsgField;
using ::perfetto::shlib::test_utils::PbField;
using ::perfetto::shlib::test_utils::StringField;
using ::perfetto::shlib::test_utils::TracingSession;
using ::perfetto::shlib::test_utils::VarIntField;
using ::testing::_;
using ::testing::ElementsAre;
namespace {
constexpr char kDataSourceName[] = "dev.perfetto.java_datasource_test";
// Per-instance incremental state, same as JNI IncrState.
struct IncrState {
bool was_cleared;
};
void* OnCreateIncr(struct PerfettoDsImpl*,
PerfettoDsInstanceIndex,
struct PerfettoDsTracerImpl*,
void*) {
auto* incr = new IncrState();
incr->was_cleared = true;
return incr;
}
void OnDeleteIncr(void* obj) {
delete static_cast<IncrState*>(obj);
}
bool OnClearIncr(void* obj, void*) {
auto* incr = static_cast<IncrState*>(obj);
incr->was_cleared = true;
return true;
}
// Helper: encode a varint into buf. Returns number of bytes written.
size_t EncodeVarInt(uint64_t value, uint8_t* buf) {
size_t n = 0;
while (value >= 0x80) {
buf[n++] = static_cast<uint8_t>((value & 0x7F) | 0x80);
value >>= 7;
}
buf[n++] = static_cast<uint8_t>(value);
return n;
}
// Helper: encode a 4-byte redundant varint (same as ProtoWriter.endNested).
void EncodeRedundantVarInt(uint32_t value, uint8_t* buf) {
buf[0] = static_cast<uint8_t>((value & 0x7F) | 0x80);
buf[1] = static_cast<uint8_t>(((value >> 7) & 0x7F) | 0x80);
buf[2] = static_cast<uint8_t>(((value >> 14) & 0x7F) | 0x80);
buf[3] = static_cast<uint8_t>((value >> 21) & 0x7F);
}
// Helper: build a TracePacket with a for_testing.payload.str field,
// using the same encoding ProtoWriter would produce.
// Returns the encoded bytes.
std::vector<uint8_t> BuildTestPacket(const std::string& test_string) {
std::vector<uint8_t> buf;
buf.reserve(256);
// TracePacket.for_testing (field 900, wire type 2)
// for_testing is a nested message
uint8_t tmp[16];
size_t n;
// Tag for field 900, wire type 2 (length-delimited)
uint32_t tag = (900 << 3) | 2;
n = EncodeVarInt(tag, tmp);
buf.insert(buf.end(), tmp, tmp + n);
// Reserve 4 bytes for outer nested length (redundant varint)
size_t outer_len_pos = buf.size();
buf.resize(buf.size() + 4);
size_t outer_data_start = buf.size();
// TestEvent.payload (field 5, wire type 2)
tag = (5 << 3) | 2;
n = EncodeVarInt(tag, tmp);
buf.insert(buf.end(), tmp, tmp + n);
// Reserve 4 bytes for inner nested length
size_t inner_len_pos = buf.size();
buf.resize(buf.size() + 4);
size_t inner_data_start = buf.size();
// TestEvent.TestPayload.str (field 1, wire type 2)
tag = (1 << 3) | 2;
n = EncodeVarInt(tag, tmp);
buf.insert(buf.end(), tmp, tmp + n);
// String length
n = EncodeVarInt(test_string.size(), tmp);
buf.insert(buf.end(), tmp, tmp + n);
// String data
buf.insert(buf.end(), test_string.begin(), test_string.end());
// Backfill inner nested length
uint32_t inner_size = static_cast<uint32_t>(buf.size() - inner_data_start);
EncodeRedundantVarInt(inner_size, buf.data() + inner_len_pos);
// Backfill outer nested length
uint32_t outer_size = static_cast<uint32_t>(buf.size() - outer_data_start);
EncodeRedundantVarInt(outer_size, buf.data() + outer_len_pos);
return buf;
}
// Helper: write pre-encoded packet bytes to all active instances.
// This is the EXACT same logic as nativeWritePacketToAllInstances in
// perfetto_datasource_jni.cc.
void WritePacketToAllInstances(struct PerfettoDsImpl* ds_impl,
const uint8_t* data,
size_t len) {
struct PerfettoDsImplTracerIterator it =
PerfettoDsImplTraceIterateBegin(ds_impl);
while (it.tracer) {
struct PerfettoStreamWriter writer =
PerfettoDsTracerImplPacketBegin(it.tracer);
PerfettoStreamWriterAppendBytes(&writer, data, len);
PerfettoDsTracerImplPacketEnd(it.tracer, &writer);
PerfettoDsImplTraceIterateNext(ds_impl, &it);
}
}
// Helper: check if any instance had incremental state cleared.
// Same logic as nativeCheckAnyIncrementalStateCleared.
bool CheckAnyIncrementalStateCleared(struct PerfettoDsImpl* ds_impl) {
bool any_cleared = false;
struct PerfettoDsImplTracerIterator it =
PerfettoDsImplTraceIterateBegin(ds_impl);
while (it.tracer) {
auto* incr = static_cast<IncrState*>(
PerfettoDsImplGetIncrementalState(ds_impl, it.tracer, it.inst_id));
if (incr && incr->was_cleared) {
any_cleared = true;
incr->was_cleared = false;
}
PerfettoDsImplTraceIterateNext(ds_impl, &it);
}
return any_cleared;
}
class JavaDataSourceTest : public testing::Test {
protected:
void SetUp() override {
struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
args.backends = PERFETTO_BACKEND_IN_PROCESS;
PerfettoProducerInit(args);
ds_impl_ = PerfettoDsImplCreate();
PerfettoDsSetOnCreateIncr(ds_impl_, OnCreateIncr);
PerfettoDsSetOnDeleteIncr(ds_impl_, OnDeleteIncr);
PerfettoDsSetOnClearIncr(ds_impl_, OnClearIncr);
// Build DataSourceDescriptor proto: field 1 = name string
uint8_t desc[256];
uint8_t* p = desc;
*p++ = (1 << 3) | 2; // field 1, wire type 2
size_t name_len = strlen(kDataSourceName);
*p++ = static_cast<uint8_t>(name_len);
memcpy(p, kDataSourceName, name_len);
p += name_len;
size_t desc_size = static_cast<size_t>(p - desc);
bool ok = PerfettoDsImplRegister(ds_impl_, &enabled_ptr_, desc, desc_size);
ASSERT_TRUE(ok);
}
void TearDown() override {
perfetto::shlib::ResetForTesting();
if (ds_impl_) {
perfetto::shlib::DsImplDestroy(ds_impl_);
ds_impl_ = nullptr;
}
}
struct PerfettoDsImpl* ds_impl_ = nullptr;
PERFETTO_ATOMIC(bool) * enabled_ptr_ = nullptr;
};
// Test: disabled data source doesn't execute
TEST_F(JavaDataSourceTest, DisabledNotExecuted) {
struct PerfettoDsImplTracerIterator it =
PerfettoDsImplTraceIterateBegin(ds_impl_);
EXPECT_EQ(it.tracer, nullptr);
}
// Test: basic packet write through the JNI path
TEST_F(JavaDataSourceTest, WritePacketViaAppendBytes) {
TracingSession tracing_session =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
// Build a packet with for_testing.payload.str = "HELLO_FROM_JAVA"
std::vector<uint8_t> packet = BuildTestPacket("HELLO_FROM_JAVA");
// Write it using the same path as the JNI
WritePacketToAllInstances(ds_impl_, packet.data(), packet.size());
tracing_session.StopBlocking();
std::vector<uint8_t> data = tracing_session.ReadBlocking();
// Verify: find the for_testing field with our string
bool found = false;
for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
MsgField(_)));
IdFieldView for_testing(
trace_field, perfetto_protos_TracePacket_for_testing_field_number);
ASSERT_TRUE(for_testing.ok());
if (for_testing.size() == 0) {
continue;
}
found = true;
ASSERT_EQ(for_testing.size(), 1u);
// Check payload.str
EXPECT_THAT(FieldView(for_testing.front()),
ElementsAre(PbField(
perfetto_protos_TestEvent_payload_field_number,
MsgField(ElementsAre(PbField(
perfetto_protos_TestEvent_TestPayload_str_field_number,
StringField("HELLO_FROM_JAVA")))))));
}
EXPECT_TRUE(found);
}
// Test: multiple packets
TEST_F(JavaDataSourceTest, WriteMultiplePackets) {
TracingSession tracing_session =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
std::vector<uint8_t> pkt1 = BuildTestPacket("PACKET_ONE");
std::vector<uint8_t> pkt2 = BuildTestPacket("PACKET_TWO");
std::vector<uint8_t> pkt3 = BuildTestPacket("PACKET_THREE");
WritePacketToAllInstances(ds_impl_, pkt1.data(), pkt1.size());
WritePacketToAllInstances(ds_impl_, pkt2.data(), pkt2.size());
WritePacketToAllInstances(ds_impl_, pkt3.data(), pkt3.size());
tracing_session.StopBlocking();
std::vector<uint8_t> data = tracing_session.ReadBlocking();
int found_count = 0;
for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
MsgField(_)));
IdFieldView for_testing(
trace_field, perfetto_protos_TracePacket_for_testing_field_number);
ASSERT_TRUE(for_testing.ok());
if (for_testing.size() > 0) {
found_count++;
}
}
EXPECT_EQ(found_count, 3);
}
// Test: incremental state tracking (used for interning)
TEST_F(JavaDataSourceTest, IncrementalStateCleared) {
TracingSession tracing_session =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
// First check: state should be cleared (new state)
bool cleared = CheckAnyIncrementalStateCleared(ds_impl_);
EXPECT_TRUE(cleared);
// Second check: state should NOT be cleared (we consumed the flag)
cleared = CheckAnyIncrementalStateCleared(ds_impl_);
EXPECT_FALSE(cleared);
// Write a packet to ensure everything works
std::vector<uint8_t> pkt = BuildTestPacket("AFTER_INCR_CHECK");
WritePacketToAllInstances(ds_impl_, pkt.data(), pkt.size());
tracing_session.StopBlocking();
std::vector<uint8_t> data = tracing_session.ReadBlocking();
bool found = false;
for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
MsgField(_)));
IdFieldView for_testing(
trace_field, perfetto_protos_TracePacket_for_testing_field_number);
ASSERT_TRUE(for_testing.ok());
if (for_testing.size() > 0) {
found = true;
}
}
EXPECT_TRUE(found);
}
// Test: two concurrent tracing sessions (multi-instance)
TEST_F(JavaDataSourceTest, MultiInstance) {
TracingSession session1 =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
TracingSession session2 =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
// Count how many instances are active
int instance_count = 0;
struct PerfettoDsImplTracerIterator it =
PerfettoDsImplTraceIterateBegin(ds_impl_);
while (it.tracer) {
instance_count++;
PerfettoDsImplTraceIterateNext(ds_impl_, &it);
}
EXPECT_EQ(instance_count, 2);
// Write a packet -- should go to both sessions
std::vector<uint8_t> pkt = BuildTestPacket("MULTI_INSTANCE");
WritePacketToAllInstances(ds_impl_, pkt.data(), pkt.size());
session1.StopBlocking();
session2.StopBlocking();
// Both sessions should have the packet
auto data1 = session1.ReadBlocking();
auto data2 = session2.ReadBlocking();
auto count_test_packets = [](const std::vector<uint8_t>& data) -> int {
int count = 0;
for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
IdFieldView for_testing(
trace_field, perfetto_protos_TracePacket_for_testing_field_number);
if (for_testing.ok() && for_testing.size() > 0) {
count++;
}
}
return count;
};
EXPECT_EQ(count_test_packets(data1), 1);
EXPECT_EQ(count_test_packets(data2), 1);
}
// Test: large packet that spans multiple chunks
TEST_F(JavaDataSourceTest, LargePacket) {
TracingSession tracing_session =
TracingSession::Builder().set_data_source_name(kDataSourceName).Build();
// Build a large test string (8KB -- bigger than a typical chunk)
std::string large_string(8192, 'X');
std::vector<uint8_t> pkt = BuildTestPacket(large_string);
WritePacketToAllInstances(ds_impl_, pkt.data(), pkt.size());
tracing_session.StopBlocking();
std::vector<uint8_t> data = tracing_session.ReadBlocking();
bool found = false;
for (struct PerfettoPbDecoderField trace_field : FieldView(data)) {
ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number,
MsgField(_)));
IdFieldView for_testing(
trace_field, perfetto_protos_TracePacket_for_testing_field_number);
ASSERT_TRUE(for_testing.ok());
if (for_testing.size() > 0) {
found = true;
// Verify the payload string matches
EXPECT_THAT(
FieldView(for_testing.front()),
ElementsAre(PbField(
perfetto_protos_TestEvent_payload_field_number,
MsgField(ElementsAre(PbField(
perfetto_protos_TestEvent_TestPayload_str_field_number,
StringField(large_string)))))));
}
}
EXPECT_TRUE(found);
}
} // namespace