| // 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 |