blob: e4edd31a1a55ae147443ecc69761d3c9840c3088 [file] [log] [blame]
// Copyright 2018 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 "analyzer/report_master/report_stream.h"
#include <memory>
#include <sstream>
#include <vector>
#include "config/config_text_parser.h"
#include "config/report_config.h"
#include "glog/logging.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
namespace cobalt {
namespace analyzer {
using config::ReportRegistry;
namespace {
const uint32_t kCustomerId = 1;
const uint32_t kProjectId = 1;
const uint32_t kReportConfigId = 1;
const uint32_t kSomeDayIndex = 123456;
const char* kReportConfigText = R"(
element {
customer_id: 1
project_id: 1
id: 1
metric_id: 1
variable {
metric_part: "Rating"
}
export_configs {
csv {}
}
}
)";
ReportMetadataLite BuildHistogramMetadata() {
ReportMetadataLite metadata;
metadata.set_report_type(ReportType::HISTOGRAM);
metadata.add_variable_indices(0);
metadata.set_first_day_index(kSomeDayIndex);
metadata.set_last_day_index(kSomeDayIndex);
return metadata;
}
ReportRow HistogramReportIntValueRow(int value) {
ReportRow report_row;
HistogramReportRow* row = report_row.mutable_histogram();
row->mutable_value()->set_int_value(value);
return report_row;
}
// A FakeReportRowIterator is a ReportRowIterator that will do the following:
// (1) Return OK |num_success_first| times with the returned row being
// |first_report_row|.
// (2) Return |middle_status| one time with the returned row being
// |first_report_row|.
// (1) Return OK |num_success_second| times with the returned row being
// |second_report_row|.
struct FakeReportRowIterator : public ReportRowIterator {
size_t num_success_first;
ReportRow first_report_row;
grpc::Status middle_status;
size_t num_success_second;
ReportRow second_report_row;
grpc::Status Reset() override {
index_ = 0;
return grpc::Status::OK;
}
grpc::Status NextRow(const ReportRow** row) override {
index_++;
if (index_ <= num_success_first) {
*row = &first_report_row;
return grpc::Status::OK;
} else if (index_ == num_success_first + 1) {
*row = &first_report_row;
return middle_status;
} else if (index_ <= num_success_first + num_success_second + 1) {
*row = &second_report_row;
return grpc::Status::OK;
} else {
return grpc::Status(grpc::NOT_FOUND, "EOF");
}
}
grpc::Status HasMoreRows(bool* b) override {
CHECK(b);
*b = (index_ < num_success_first + num_success_second + 1);
return grpc::Status::OK;
}
private:
size_t index_ = 0;
};
} // namespace
// Tests of ReportStream.
class ReportStreamTest : public ::testing::Test {
public:
void SetUp() {
// Parse the report config string
auto report_parse_result =
config::FromString<RegisteredReports>(kReportConfigText, nullptr);
EXPECT_EQ(config::kOK, report_parse_result.second);
report_registry_.reset((report_parse_result.first.release()));
}
// Performs the following test actions:
// - Constructs a ReportSerializer for our one static ReportConfig.
// -Constructs a ReportStream wrapping that ReportSerializer and the given
// |row_iterator|, using the given |buffer_size|.
// - Invokes Start() on the ReportStream.
// -- Checks that the status is |expected_start_status|.
// -- Checks that the mime type is |expected_mime_type|.
// - Reads the entire serialized report from the ReportStream into a string.
// -- Checks that the status is |expected_end_status|.
// -- Checks that the serialized report is |expected_serialization|.
void DoStreamTestWithBufferSize(ReportRowIterator* row_iterator,
const std::string expected_mime_type,
const std::string& expected_serialization,
grpc::StatusCode expected_start_status,
grpc::StatusCode expected_end_status,
size_t buffer_size) {
// Construct a ReportSerializer.
auto metadata = BuildHistogramMetadata();
auto report_config =
report_registry_->Get(kCustomerId, kProjectId, kReportConfigId);
ReportSerializer serializer(report_config, &metadata,
&(report_config->export_configs(0)));
// Construct a ReportStream wrapping |serializer| and |row_iterator| that
// uses the given |buffer_size|.
ReportStream report_stream(&serializer, row_iterator, buffer_size);
// Invoke Start() and check the MIME type and status.
auto status = report_stream.Start();
EXPECT_EQ(expected_start_status, status.error_code())
<< status.error_message() << " ";
EXPECT_EQ(expected_mime_type, report_stream.mime_type());
for (auto test_iteration = 0; test_iteration < 3; test_iteration++) {
if (status.ok()) {
// Test that before reading from the stream, tellg() returns zero.
EXPECT_EQ(0, report_stream.tellg())
<< "test_iteration=" << test_iteration
<< " tellg() = " << report_stream.tellg();
// This should be a no-op seekg.
report_stream.seekg(0);
// tellg() should again return 0
EXPECT_EQ(0, report_stream.tellg())
<< "test_iteration=" << test_iteration
<< " tellg() = " << report_stream.tellg();
}
// Read the entire serialized report from the ReportStream into a string.
std::string serialized_report(
std::istreambuf_iterator<char>(report_stream), {});
// Check the serialized report.
EXPECT_EQ(expected_serialization.size(), serialized_report.size())
<< "test_iteration=" << test_iteration;
EXPECT_EQ(expected_serialization, serialized_report)
<< "test_iteration=" << test_iteration;
// Check the status.
status = report_stream.status();
EXPECT_EQ(expected_end_status, status.error_code())
<< status.error_message() << " "
<< " test_iteration=" << test_iteration;
if (!report_stream.status().ok()) {
EXPECT_TRUE(report_stream.fail());
EXPECT_TRUE(report_stream.bad());
EXPECT_FALSE(report_stream.good());
} else {
EXPECT_FALSE(report_stream.fail());
EXPECT_FALSE(report_stream.bad());
EXPECT_TRUE(report_stream.good());
EXPECT_NE(0, report_stream.tellg());
}
report_stream.clear(0);
report_stream.seekg(0);
}
}
// Invokes DoStreamTestWithBufferSize() four times with buffer sizes
// 1, 10, 20 and 1MB. The smaller values of buffer_size will cause
// underflow() to be invoked whereas the larger values of buffer_size
// will cause the entire report to be read during Start().
void DoStreamTest(ReportRowIterator* row_iterator,
const std::string expected_mime_type,
const std::string& expected_serialization,
grpc::StatusCode expected_start_status,
grpc::StatusCode expected_end_status) {
DoStreamTestWithBufferSize(row_iterator, expected_mime_type,
expected_serialization, expected_start_status,
expected_end_status, 1);
row_iterator->Reset();
DoStreamTestWithBufferSize(row_iterator, expected_mime_type,
expected_serialization, expected_start_status,
expected_end_status, 10);
row_iterator->Reset();
DoStreamTestWithBufferSize(row_iterator, expected_mime_type,
expected_serialization, expected_start_status,
expected_end_status, 20);
row_iterator->Reset();
DoStreamTestWithBufferSize(row_iterator, expected_mime_type,
expected_serialization, expected_start_status,
expected_end_status, 1024 * 1024);
}
private:
std::shared_ptr<ReportRegistry> report_registry_;
};
// Tests a ReportStream when the RowIterator yields no rows.
TEST_F(ReportStreamTest, NoRows) {
std::vector<ReportRow> report_rows;
ReportRowVectorIterator row_iterator(&report_rows);
const char* kExpectedCSV = R"(date,Rating,count,err
)";
DoStreamTest(&row_iterator, "text/csv", kExpectedCSV, grpc::OK, grpc::OK);
}
// Tests a ReportStream when the RowIterator yields a small number of rows.
TEST_F(ReportStreamTest, SomeRows) {
std::vector<ReportRow> report_rows;
for (int i = 0; i < 20; i++) {
report_rows.push_back(HistogramReportIntValueRow(i));
}
ReportRowVectorIterator row_iterator(&report_rows);
const char* kExpectedCSV = R"(date,Rating,count,err
2035-10-22,0,0,0
2035-10-22,1,0,0
2035-10-22,2,0,0
2035-10-22,3,0,0
2035-10-22,4,0,0
2035-10-22,5,0,0
2035-10-22,6,0,0
2035-10-22,7,0,0
2035-10-22,8,0,0
2035-10-22,9,0,0
2035-10-22,10,0,0
2035-10-22,11,0,0
2035-10-22,12,0,0
2035-10-22,13,0,0
2035-10-22,14,0,0
2035-10-22,15,0,0
2035-10-22,16,0,0
2035-10-22,17,0,0
2035-10-22,18,0,0
2035-10-22,19,0,0
)";
DoStreamTest(&row_iterator, "text/csv", kExpectedCSV, grpc::OK, grpc::OK);
}
// Tests a ReportStream when the RowIterator yields a large number of rows.
// In particulr we make sure overflow() will be invoked several times.
TEST_F(ReportStreamTest, ManyRows) {
FakeReportRowIterator fake_iterator;
fake_iterator.num_success_first = 1000;
fake_iterator.first_report_row = HistogramReportIntValueRow(1);
fake_iterator.middle_status = grpc::Status::OK;
fake_iterator.num_success_second = 1000;
fake_iterator.second_report_row = HistogramReportIntValueRow(2);
std::ostringstream expected_stream;
expected_stream << "date,Rating,count,err\n";
for (int i = 0; i <= 1000; i++) {
expected_stream << "2035-10-22,1,0,0\n";
}
for (int i = 0; i < 1000; i++) {
expected_stream << "2035-10-22,2,0,0\n";
}
DoStreamTest(&fake_iterator, "text/csv", expected_stream.str(), grpc::OK,
grpc::OK);
}
// Tests a ReportStream when the RowIterator yields some rows, then returns
// an error, then is willing to yield more rows. We expect the second batch
// of rows won't be requested.
TEST_F(ReportStreamTest, MiddleError) {
FakeReportRowIterator fake_iterator;
fake_iterator.num_success_first = 1000;
fake_iterator.first_report_row = HistogramReportIntValueRow(1);
fake_iterator.middle_status =
grpc::Status(grpc::DEADLINE_EXCEEDED, "Timeout");
fake_iterator.num_success_second = 1000;
fake_iterator.second_report_row = HistogramReportIntValueRow(2);
// The expected stream should contain just the first batch of rows
// before the DEADLINE_EXCEEDED occurred. The latter batch of rows should
// never be requested.
std::ostringstream expected_stream;
expected_stream << "date,Rating,count,err\n";
for (int i = 0; i < 1000; i++) {
expected_stream << "2035-10-22,1,0,0\n";
}
std::string expected_serialization = expected_stream.str();
// Test once with a buffer size that will cause Start() to succeed and then
// the DEADLINE_EXCEEDED to occur during underflow().
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::OK, grpc::DEADLINE_EXCEEDED, 1024);
// Test once with a larger buffer size that will cause the entire report to
// be read during Start() and so Start() will see the DEADLINE_EXCEEDED.
fake_iterator.Reset();
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::DEADLINE_EXCEEDED, grpc::DEADLINE_EXCEEDED,
1024 * 1024);
}
// Tests a ReportSttream when the RowIterator yields some rows, then starts
// yielding bad rows that cannot be serialized.
TEST_F(ReportStreamTest, BadSecondRowType) {
FakeReportRowIterator fake_iterator;
fake_iterator.num_success_first = 1000;
fake_iterator.first_report_row = HistogramReportIntValueRow(1);
fake_iterator.middle_status = grpc::Status::OK;
fake_iterator.num_success_second = 1000;
fake_iterator.second_report_row = ReportRow(); // This is an invalid row.
// The expected stream should contain the first batch of rows and the
// middle row but not the second batch of rows.
std::ostringstream expected_stream;
expected_stream << "date,Rating,count,err\n";
for (int i = 0; i <= 1000; i++) {
expected_stream << "2035-10-22,1,0,0\n";
}
std::string expected_serialization = expected_stream.str();
// Test once with a buffer size that will cause Start() to succeed and then
// the bad rows to be seen during underflow().
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::OK, grpc::INTERNAL, 1024);
// Test once with a larger buffer size that will cause the entire report to
// be read during Start() and so Start() will see the bad rows.
fake_iterator.Reset();
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::INTERNAL, grpc::INTERNAL, 1024 * 1024);
}
// Tests a ReportSttream when the RowIterator yields bad rows that cannot be
// serialized right away.
TEST_F(ReportStreamTest, BadFirstRowType) {
FakeReportRowIterator fake_iterator;
fake_iterator.num_success_first = 1000;
fake_iterator.first_report_row = ReportRow(); // This is an invalid row.
fake_iterator.middle_status = grpc::Status::OK;
fake_iterator.num_success_second = 1000;
fake_iterator.second_report_row = HistogramReportIntValueRow(2);
// The expected stream should contain only the report header.
std::ostringstream expected_stream;
expected_stream << "date,Rating,count,err\n";
std::string expected_serialization = expected_stream.str();
// Test once with a small buffer size. The bad row is still encountered during
// Start().
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::INTERNAL, grpc::INTERNAL, 1024);
// Test once with a larger buffer size. Again the bad row is encountered
// during Start().
fake_iterator.Reset();
DoStreamTestWithBufferSize(&fake_iterator, "text/csv", expected_serialization,
grpc::INTERNAL, grpc::INTERNAL, 1024 * 1024);
}
} // namespace analyzer
} // namespace cobalt