blob: 36dacf51026e789dca57dd0058db2a3f0301a85c [file] [log] [blame]
/*
* 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.
*/
#include <chrono>
#include <condition_variable>
#include <functional>
#include <list>
#include <mutex>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "perfetto/trace/test_event.pbzero.h"
#include "perfetto/trace/trace.pb.h"
#include "perfetto/trace/trace_packet.pbzero.h"
#include "perfetto/tracing.h"
// Deliberately not pulling any non-public perfetto header to spot accidental
// header public -> non-public dependency while building this file.
// TODO(primiano): move these generated classes to /public/.
#include "perfetto/tracing/core/data_source_descriptor.h"
#include "perfetto/tracing/core/trace_config.h"
namespace {
using ::testing::_;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Property;
using ::testing::StrEq;
// ------------------------------
// Declarations of helper classes
// ------------------------------
static constexpr auto kWaitEventTimeout = std::chrono::seconds(5);
struct WaitableTestEvent {
std::mutex mutex_;
std::condition_variable cv_;
bool notified_ = false;
void Wait() {
std::unique_lock<std::mutex> lock(mutex_);
if (!cv_.wait_for(lock, kWaitEventTimeout, [this] { return notified_; })) {
fprintf(stderr, "Timed out while waiting for event\n");
abort();
}
}
void Notify() {
std::unique_lock<std::mutex> lock(mutex_);
notified_ = true;
cv_.notify_one();
}
};
class MockDataSource;
// We can't easily use gmock here because instances of data sources are lazily
// created by the service and are not owned by the test fixture.
struct TestDataSourceHandle {
WaitableTestEvent on_create;
WaitableTestEvent on_setup;
WaitableTestEvent on_start;
WaitableTestEvent on_stop;
MockDataSource* instance;
perfetto::DataSourceConfig config;
};
class MockDataSource : public perfetto::DataSource<MockDataSource> {
public:
void OnSetup(const SetupArgs&) override;
void OnStart(const StartArgs&) override;
void OnStop(const StopArgs&) override;
TestDataSourceHandle* handle_ = nullptr;
};
// A convenience wrapper around TracingSession that allows to do block on
//
struct TestTracingSessionHandle {
perfetto::TracingSession* get() { return session.get(); }
std::unique_ptr<perfetto::TracingSession> session;
WaitableTestEvent on_stop;
};
// -------------------------
// Declaration of test class
// -------------------------
class PerfettoApiTest : public ::testing::Test {
public:
static PerfettoApiTest* instance;
void SetUp() override {
instance = this;
perfetto::TracingInitArgs args;
args.backends = perfetto::kInProcessBackend;
perfetto::Tracing::Initialize(args);
}
void TearDown() override { instance = nullptr; }
template <typename DataSourceType>
TestDataSourceHandle* RegisterDataSource(std::string name) {
EXPECT_EQ(data_sources_.count(name), 0);
TestDataSourceHandle* handle = &data_sources_[name];
perfetto::DataSourceDescriptor dsd;
dsd.set_name(name);
DataSourceType::Register(dsd);
return handle;
}
TestTracingSessionHandle* NewTrace(const perfetto::TraceConfig& cfg) {
sessions_.emplace_back();
TestTracingSessionHandle* handle = &sessions_.back();
handle->session =
perfetto::Tracing::NewTrace(perfetto::BackendType::kInProcessBackend);
handle->session->SetOnStopCallback([handle] { handle->on_stop.Notify(); });
handle->session->Setup(cfg);
return handle;
}
std::map<std::string, TestDataSourceHandle> data_sources_;
std::list<TestTracingSessionHandle> sessions_; // Needs stable pointers.
};
// ---------------------------------------------
// Definitions for non-inlineable helper methods
// ---------------------------------------------
PerfettoApiTest* PerfettoApiTest::instance;
void MockDataSource::OnSetup(const SetupArgs& args) {
EXPECT_EQ(handle_, nullptr);
auto it = PerfettoApiTest::instance->data_sources_.find(args.config->name());
// We should not see an OnSetup for a data source that we didn't register
// before via PerfettoApiTest::RegisterDataSource().
EXPECT_NE(it, PerfettoApiTest::instance->data_sources_.end());
handle_ = &it->second;
handle_->config = *args.config; // Deliberate copy.
handle_->on_setup.Notify();
}
void MockDataSource::OnStart(const StartArgs&) {
EXPECT_NE(handle_, nullptr);
handle_->on_start.Notify();
}
void MockDataSource::OnStop(const StopArgs&) {
EXPECT_NE(handle_, nullptr);
handle_->on_stop.Notify();
}
// -------------
// Test fixtures
// -------------
TEST_F(PerfettoApiTest, OneDataSourceOneEvent) {
auto* data_source = RegisterDataSource<MockDataSource>("my_data_source");
// Setup the trace config.
perfetto::TraceConfig cfg;
cfg.set_duration_ms(500);
cfg.add_buffers()->set_size_kb(1024);
auto* ds_cfg = cfg.add_data_sources()->mutable_config();
ds_cfg->set_name("my_data_source");
ds_cfg->set_legacy_config("test config");
// Create a new trace session.
auto* tracing_session = NewTrace(cfg);
MockDataSource::Trace([](MockDataSource::TraceContext) {
FAIL() << "Should not be called because the trace was not started";
});
tracing_session->get()->Start();
data_source->on_setup.Wait();
EXPECT_EQ(data_source->config.legacy_config(), "test config");
data_source->on_start.Wait();
// Emit one trace event.
std::atomic<int> trace_lambda_calls{0};
MockDataSource::Trace(
[&trace_lambda_calls](MockDataSource::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(42);
packet->set_for_testing()->set_str("event 1");
trace_lambda_calls++;
packet->Finalize();
// The SMB scraping logic will skip the last packet because it cannot
// guarantee it's finalized. Create an empty packet so we get the
// previous one and this empty one is ignored.
packet = ctx.NewTracePacket();
});
data_source->on_stop.Wait();
tracing_session->on_stop.Wait();
EXPECT_EQ(trace_lambda_calls, 1);
MockDataSource::Trace([](MockDataSource::TraceContext) {
FAIL() << "Should not be called because the trace is now stopped";
});
std::vector<char> raw_trace = tracing_session->get()->ReadTraceBlocking();
ASSERT_GE(raw_trace.size(), 0u);
perfetto::protos::Trace trace;
ASSERT_TRUE(trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())));
bool test_packet_found = false;
for (const auto& packet : trace.packet()) {
if (!packet.has_for_testing())
continue;
EXPECT_FALSE(test_packet_found);
EXPECT_EQ(packet.timestamp(), 42U);
EXPECT_EQ(packet.for_testing().str(), "event 1");
test_packet_found = true;
}
EXPECT_TRUE(test_packet_found);
}
} // namespace
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MockDataSource);