blob: d95dec411ffc3762fe6d0dcaf917e61ef36ab8da [file] [log] [blame] [edit]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fixture.h"
#include <regex.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <fbl/algorithm.h>
#include <fbl/array.h>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <fbl/vector.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/zx/event.h>
#include <trace-reader/reader.h>
#include <trace-reader/reader_internal.h>
#include <trace/handler.h>
#include <unittest/unittest.h>
namespace {
class Fixture : private trace::TraceHandler {
public:
Fixture(trace_buffering_mode_t mode, size_t buffer_size)
: loop_(&kAsyncLoopConfigNoAttachToThread),
buffering_mode_(mode),
buffer_(new uint8_t[buffer_size], buffer_size) {
zx_status_t status = zx::event::create(0u, &trace_stopped_);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
~Fixture() {
StopTracing(false);
}
void StartTracing() {
if (trace_running_)
return;
trace_running_ = true;
loop_.StartThread("trace test");
// Asynchronously start the engine.
zx_status_t status = trace_start_engine(loop_.dispatcher(), this,
buffering_mode_,
buffer_.get(), buffer_.size());
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "status=%d", status);
}
void StopTracing(bool hard_shutdown) {
if (!trace_running_)
return;
// Asynchronously stop the engine.
// If we're performing a hard shutdown, skip this step and begin immediately
// tearing down the loop. The trace engine should stop itself.
if (!hard_shutdown) {
zx_status_t status = trace_stop_engine(ZX_OK);
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "status=%d", status);
status = trace_stopped_.wait_one(ZX_EVENT_SIGNALED,
zx::deadline_after(zx::msec(1000)), nullptr);
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "status=%d", status);
}
// Shut down the loop (implicily joins the thread we started earlier).
// When this completes we know the trace engine is really stopped.
loop_.Shutdown();
ZX_DEBUG_ASSERT(observed_stopped_callback_);
trace_running_ = false;
}
zx_status_t disposition() const {
return disposition_;
}
bool ReadRecords(fbl::Vector<trace::Record>* out_records,
fbl::Vector<fbl::String>* out_errors) {
trace::TraceReader reader(
[out_records](trace::Record record) {
out_records->push_back(fbl::move(record));
},
[out_errors](fbl::String error) {
out_errors->push_back(fbl::move(error));
});
trace::internal::TraceBufferReader buffer_reader(
[&reader](trace::Chunk chunk) {
if (!reader.ReadRecords(chunk)) {
// Nothing to do, error already recorded.
}
},
[out_errors](fbl::String error) {
out_errors->push_back(fbl::move(error));
});
return buffer_reader.ReadChunks(buffer_.get(), buffer_.size());
}
private:
bool IsCategoryEnabled(const char* category) override {
// All categories which begin with + are enabled.
return category[0] == '+';
}
void TraceStopped(async_dispatcher_t* dispatcher,
zx_status_t disposition,
size_t buffer_bytes_written) override {
ZX_DEBUG_ASSERT(!observed_stopped_callback_);
observed_stopped_callback_ = true;
ZX_DEBUG_ASSERT(dispatcher = loop_.dispatcher());
disposition_ = disposition;
buffer_bytes_written_ = buffer_bytes_written;
trace_stopped_.signal(0u, ZX_EVENT_SIGNALED);
// The normal provider support does "delete this" here.
// We don't need nor want it as we still have to verify the results.
}
async::Loop loop_;
trace_buffering_mode_t buffering_mode_;
fbl::Array<uint8_t> buffer_;
bool trace_running_ = false;
zx_status_t disposition_ = ZX_ERR_INTERNAL;
size_t buffer_bytes_written_ = 0u;
zx::event trace_stopped_;
bool observed_stopped_callback_ = false;
};
Fixture* g_fixture{nullptr};
} // namespace
struct FixtureSquelch {
// Records the compiled regex.
regex_t regex;
};
void fixture_set_up(trace_buffering_mode_t mode, size_t buffer_size) {
ZX_DEBUG_ASSERT(!g_fixture);
g_fixture = new Fixture(mode, buffer_size);
}
void fixture_tear_down(void) {
ZX_DEBUG_ASSERT(g_fixture);
delete g_fixture;
g_fixture = nullptr;
}
void fixture_start_tracing() {
ZX_DEBUG_ASSERT(g_fixture);
g_fixture->StartTracing();
}
void fixture_stop_tracing() {
ZX_DEBUG_ASSERT(g_fixture);
g_fixture->StopTracing(false);
}
void fixture_stop_tracing_hard() {
ZX_DEBUG_ASSERT(g_fixture);
g_fixture->StopTracing(true);
}
zx_status_t fixture_get_disposition(void) {
ZX_DEBUG_ASSERT(g_fixture);
return g_fixture->disposition();
}
bool fixture_create_squelch(const char* regex_str, FixtureSquelch** out_squelch) {
// We don't make any assumptions about the copyability of |regex_t|.
// Therefore we construct it in place.
auto squelch = new FixtureSquelch;
if (regcomp(&squelch->regex, regex_str, REG_EXTENDED | REG_NEWLINE) != 0) {
return false;
}
*out_squelch = squelch;
return true;
}
void fixture_destroy_squelch(FixtureSquelch* squelch) {
regfree(&squelch->regex);
delete squelch;
}
fbl::String fixture_squelch(FixtureSquelch* squelch, const char* str) {
fbl::StringBuffer<1024u> buf;
const char* cur = str;
const char* end = str + strlen(str);
while (*cur) {
// size must be 1 + number of parenthesized subexpressions
size_t match_count = squelch->regex.re_nsub + 1;
regmatch_t match[match_count];
if (regexec(&squelch->regex, cur, match_count, match, 0) != 0) {
buf.Append(cur, end - cur);
break;
}
size_t offset = 0u;
for (size_t i = 1; i < match_count; i++) {
if (match[i].rm_so == -1)
continue;
buf.Append(cur, match[i].rm_so - offset);
buf.Append("<>");
cur += match[i].rm_eo - offset;
offset = match[i].rm_eo;
}
}
return buf;
}
bool fixture_compare_raw_records(const fbl::Vector<trace::Record>& records,
size_t start_record, size_t max_num_records,
const char* expected) {
BEGIN_HELPER;
// Append |num_records| records to the buffer, replacing each match of a parenthesized
// subexpression of the regex with "<>". This is used to strip out timestamps
// and other varying data that is not controlled by these tests.
FixtureSquelch* squelch;
ASSERT_TRUE(fixture_create_squelch(
"([0-9]+/[0-9]+)"
"|koid\\(([0-9]+)\\)"
"|koid: ([0-9]+)"
"|ts: ([0-9]+)"
"|(0x[0-9a-f]+)",
&squelch), "error creating squelch");
fbl::StringBuffer<16384u> buf;
size_t num_recs = 0;
for (size_t i = start_record; i < records.size(); ++i) {
if (num_recs == max_num_records)
break;
const auto& record = records[i];
fbl::String str = record.ToString();
buf.Append(fixture_squelch(squelch, str.c_str()));
buf.Append('\n');
++num_recs;
}
EXPECT_STR_EQ(expected, buf.c_str(), "unequal cstr");
fixture_destroy_squelch(squelch);
END_HELPER;
}
bool fixture_compare_n_records(size_t max_num_records, const char* expected,
fbl::Vector<trace::Record>* out_records) {
ZX_DEBUG_ASSERT(g_fixture);
BEGIN_HELPER;
g_fixture->StopTracing(false);
fbl::Vector<trace::Record> records;
fbl::Vector<fbl::String> errors;
EXPECT_TRUE(g_fixture->ReadRecords(&records, &errors), "read error");
for (const auto& error : errors)
printf("error: %s\n", error.c_str());
ASSERT_EQ(0u, errors.size(), "errors encountered");
ASSERT_GE(records.size(), 1u, "expected an initialization record");
ASSERT_EQ(trace::RecordType::kInitialization, records[0].type(),
"expected initialization record");
EXPECT_EQ(zx_ticks_per_second(),
records[0].GetInitialization().ticks_per_second);
records.erase(0);
EXPECT_TRUE(fixture_compare_raw_records(records, 0, max_num_records, expected));
if (out_records) {
*out_records = fbl::move(records);
}
END_HELPER;
}
bool fixture_compare_records(const char* expected) {
return fixture_compare_n_records(SIZE_MAX, expected, nullptr);
}