text: // Copyright 2017 The Fuchsia Authors
  //
  // 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.
  
  #ifndef COBALT_ANALYZER_REPORT_MASTER_REPORT_MASTER_SERVICE_ABSTRACT_TEST_H_
  #define COBALT_ANALYZER_REPORT_MASTER_REPORT_MASTER_SERVICE_ABSTRACT_TEST_H_
  
  #include "analyzer/report_master/report_master_service.h"
  
  #include <algorithm>
  #include <map>
  #include <memory>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "./observation.pb.h"
  #include "analyzer/store/report_store_test_utils.h"
  #include "config/config_text_parser.h"
  #include "encoder/client_secret.h"
  #include "encoder/encoder.h"
  #include "encoder/project_context.h"
  #include "glog/logging.h"
  #include "third_party/googletest/googletest/include/gtest/gtest.h"
  #include "util/datetime_util.h"
  
  // This file contains type-parameterized tests of ReportMasterService.
  //
  // We use C++ templates along with the macros TYPED_TEST_CASE_P and
  // TYPED_TEST_P in order to define test templates that may be instantiated to
  // to produce concrete tests that use various implementations of Datastore.
  //
  // See report_master_service_test.cc and
  // report_master_service_emulator_test.cc for the concrete instantiations.
  //
  // NOTE: If you add a new test to this file you must add its name to the
  // invocation REGISTER_TYPED_TEST_CASE_P macro at the bottom of this file.
  
  namespace cobalt {
  namespace analyzer {
  
  using util::CalendarDate;
  
  static const uint32_t kCustomerId = 1;
  static const uint32_t kProjectId = 1;
  static const uint32_t kMetricId1 = 1;
  static const uint32_t kMetricId2 = 2;
  static const uint32_t kMetricId3 = 3;
  static const uint32_t kReportConfigId1 = 1;
  static const uint32_t kReportConfigId2 = 2;
  static const uint32_t kReportConfigId3 = 3;
  static const uint32_t kReportConfigId4 = 4;
  static const uint32_t kReportConfigId5 = 5;
  static const uint32_t kReportConfigId6 = 6;
  static const uint32_t kForculusEncodingConfigId = 1;
  static const uint32_t kBasicRapporStringEncodingConfigId = 2;
  static const uint32_t kBasicRapporIntEncodingConfigId = 3;
  static const uint32_t kBasicRapporIndexEncodingConfigId = 4;
  static const uint32_t kNoOpEncodingConfigId = 5;
  static const char kPartName1[] = "Part1";
  static const char kPartName2[] = "Part2";
  static const size_t kForculusThreshold = 20;
  
  // This unix timestamp corresponds to Friday Dec 2, 2016 in UTC
  static const time_t kSomeTimestamp = 1480647356;
  // This is the day index for Friday Dec 2, 2016
  static const uint32_t kDayIndex = 17137;
  // We will use a fake clock with the time fixed to this time in order
  // to test that time-related fields are set correctly by ReportMasterService.
  static const int64_t kFixedTimeSeconds = 1234567;
  
  static const char* kMetricConfigText = R"(
  # Metric 1 has one string part and one integer part.
  element {
    customer_id: 1
    project_id: 1
    id: 1
    time_zone_policy: UTC
    parts {
      key: "Part1"
      value {
      }
    }
    parts {
      key: "Part2"
      value {
        data_type: INT
      }
    }
  }
  
  # Metric 2 has one string part and one integer part.
  element {
    customer_id: 1
    project_id: 1
    id: 2
    time_zone_policy: UTC
    parts {
      key: "Part1"
      value {
      }
    }
    parts {
      key: "Part2"
      value {
        data_type: INT
      }
    }
    system_profile_field: [BOARD_NAME]
  }
  
  # Metric 3 has one INDEX part.
  element {
    customer_id: 1
    project_id: 1
    id: 3
    time_zone_policy: UTC
    parts {
      key: "Part1"
      value {
        data_type: INDEX
      }
    }
    system_profile_field: [BOARD_NAME]
  }
  
  )";
  
  static const char* kEncodingConfigText = R"(
  # EncodingConfig 1 is Forculus.
  element {
    customer_id: 1
    project_id: 1
    id: 1
    forculus {
      threshold: 20
    }
  }
  
  # EncodingConfig 2 is Basic RAPPOR with string candidates (non-stochastic)
  element {
    customer_id: 1
    project_id: 1
    id: 2
    basic_rappor {
      prob_0_becomes_1: 0.0
      prob_1_stays_1: 1.0
      string_categories: {
        category: "Apple"
        category: "Banana"
        category: "Cantaloupe"
      }
    }
  }
  
  # EncodingConfig 3 is Basic RAPPOR with integer candidates (non-stochastic).
  element {
    customer_id: 1
    project_id: 1
    id: 3
    basic_rappor {
      prob_0_becomes_1: 0.0
      prob_1_stays_1: 1.0
      int_range_categories: {
        first: 1
        last:  10
      }
    }
  }
  
  # EncodingConfig 4 is Basic RAPPOR with INDEX categories (non-stochastic).
  element {
    customer_id: 1
    project_id: 1
    id: 4
    basic_rappor {
      prob_0_becomes_1: 0.0
      prob_1_stays_1: 1.0
      indexed_categories: {
        num_categories: 100
      }
    }
  }
  
  # EncodingConfig 5 is NoOp.
  element {
    customer_id: 1
    project_id: 1
    id: 5
    no_op_encoding {
    }
  }
  
  )";
  
  static const char* kReportConfigText = R"(
  # ReportConfig 1 specifies a report with one variable: part 1 of Metric 1.
  element {
    customer_id: 1
    project_id: 1
    id: 1
    metric_id: 1
    variable {
      metric_part: "Part1"
    }
    scheduling {
      report_finalization_days: 3
      aggregation_epoch_type: DAY
    }
    export_configs {
      csv {}
      gcs {
        bucket: "bucket.name.1"
      }
    }
  }
  
  # ReportConfig 2 specifies a report with 2 variables: Both parts of Metric 2.
  element {
    customer_id: 1
    project_id: 1
    id: 2
    metric_id: 2
    report_type: JOINT
    variable {
      metric_part: "Part1"
    }
    variable {
      metric_part: "Part2"
    }
  }
  
  # ReportConfig 3 is for metric 3 and gives labels for encoding config 4.
  element {
    customer_id: 1
    project_id: 1
    id: 3
    metric_id: 3
    variable {
      metric_part: "Part1"
      index_labels {
        labels {
           key: 0
           value: "Event A"
        }
        labels {
           key: 1
           value: "Event B"
        }
        labels {
           key: 25
           value: "Event Z"
        }
      }
    }
    scheduling {
      # report_finalization_days will default to 0.
      # aggregation_epoch_type will default to DAY.
    }
    export_configs {
      csv {}
      gcs {
        bucket: "bucket.name.3"
      }
    }
  }
  
  # ReportConfig 4 specifies a RAW_DUMP report of both variables of Metric 2.
  element {
    customer_id: 1
    project_id: 1
    id: 4
    metric_id: 2
    variable {
      metric_part: "Part1"
    }
    variable {
      metric_part: "Part2"
    }
    report_type: RAW_DUMP
    scheduling {
      # report_finalization_days will default to 0.
      # aggregation_epoch_type will default to DAY.
    }
    export_configs {
      csv {}
      gcs {
        bucket: "bucket.name.4"
      }
    }
  }
  
  # ReportConfig 5 specifies a RAW_DUMP report of both variables of Metric 2,
  # grouped by BOARD_NAME.
  element {
    customer_id: 1
    project_id: 1
    id: 5
    metric_id: 2
    variable {
      metric_part: "Part1"
    }
    variable {
      metric_part: "Part2"
    }
    report_type: RAW_DUMP
    scheduling {
      # report_finalization_days will default to 0.
      # aggregation_epoch_type will default to DAY.
    }
    export_configs {
      csv {}
      gcs {
        bucket: "bucket.name.5"
      }
    }
    system_profile_field: [BOARD_NAME]
  }
  
  # ReportConfig 6 is for metric 3 and gives labels for encoding config 4, grouped
  # by BOARD_NAME.
  element {
    customer_id: 1
    project_id: 1
    id: 6
    metric_id: 3
    variable {
      metric_part: "Part1"
      index_labels {
        labels {
           key: 0
           value: "Event A"
        }
        labels {
           key: 1
           value: "Event B"
        }
        labels {
           key: 25
           value: "Event Z"
        }
      }
    }
    scheduling {
      # report_finalization_days will default to 0.
      # aggregation_epoch_type will default to DAY.
    }
    export_configs {
      csv {}
      gcs {
        bucket: "bucket.name.6"
      }
    }
  system_profile_field: [BOARD_NAME]
  }
  
  )";
  
  // An implementation of grpc::Writer that keeps a copy of each object
  // written for later checking.
  class TestingQueryReportsResponseWriter
      : public grpc::WriterInterface<QueryReportsResponse> {
   public:
    bool Write(const QueryReportsResponse& response,
               grpc::WriteOptions options) override {
      responses.emplace_back(response);
      return true;
    }
  
    std::vector<QueryReportsResponse> responses;
  };
  
  // An implementation of GcsUploadInterface that saves its parameters and
  // returns OK.
  struct FakeGcsUploader : public GcsUploadInterface {
    grpc::Status UploadToGCS(const std::string& bucket, const std::string& path,
                             const std::string& mime_type,
                             ReportStream* report_stream) override {
      buckets.push_back(bucket);
      paths.push_back(path);
      mime_types.push_back(mime_type);
      std::string serialized_report(
          std::istreambuf_iterator<char>(*report_stream), {});
      reports.push_back(serialized_report);
      return grpc::Status::OK;
    }
  
    std::vector<std::string> buckets;
    std::vector<std::string> paths;
    std::vector<std::string> mime_types;
    std::vector<std::string> reports;
  };
  
  // ReportMasterServiceAbstractTest is templatized on the parameter
  // |StoreFactoryClass| which must be the name of a class that contains the
  // following method: static DataStore* NewStore()
  // See MemoryStoreFactory in store/memory_store_test_helper.h and
  // BigtableStoreEmulatorFactory in store/bigtable_emulator_helper.h.
  template <class StoreFactoryClass>
  class ReportMasterServiceAbstractTest : public ::testing::Test {
   protected:
    ReportMasterServiceAbstractTest()
        : data_store_(StoreFactoryClass::NewStore()),
          observation_store_(new store::ObservationStore(data_store_)),
          report_store_(new store::ReportStore(data_store_)),
          clock_(new util::IncrementingClock()) {
      clock_->set_time(util::FromUnixSeconds(kFixedTimeSeconds));
      clock_->set_increment(std::chrono::seconds(0));
      report_store_->set_clock(clock_);
    }
  
    void SetUp() {
      // Clear the DataStore.
      EXPECT_EQ(store::kOK,
                data_store_->DeleteAllRows(store::DataStore::kObservations));
      EXPECT_EQ(store::kOK,
                data_store_->DeleteAllRows(store::DataStore::kReportMetadata));
      EXPECT_EQ(store::kOK,
                data_store_->DeleteAllRows(store::DataStore::kReportRows));
  
      // Parse the metric config string
      auto metric_parse_result =
          config::FromString<RegisteredMetrics>(kMetricConfigText, nullptr);
      EXPECT_EQ(config::kOK, metric_parse_result.second);
      std::shared_ptr<config::MetricRegistry> metric_registry(
          metric_parse_result.first.release());
  
      // Parse the encoding config string
      auto encoding_parse_result =
          config::FromString<RegisteredEncodings>(kEncodingConfigText, nullptr);
      EXPECT_EQ(config::kOK, encoding_parse_result.second);
      std::shared_ptr<config::EncodingRegistry> encoding_config_registry(
          encoding_parse_result.first.release());
  
      // Parse the report config string
      auto report_parse_result =
          config::FromString<RegisteredReports>(kReportConfigText, nullptr);
      EXPECT_EQ(config::kOK, report_parse_result.second);
      std::shared_ptr<config::ReportRegistry> report_config_registry(
          report_parse_result.first.release());
      report_config_registry_ = report_config_registry;
  
      // Make a ProjectContext
      project_.reset(new encoder::ProjectContext(
          kCustomerId, kProjectId, metric_registry, encoding_config_registry));
  
      // Make an AnalyzerConfig
      std::shared_ptr<config::AnalyzerConfig> analyzer_config(
          new config::AnalyzerConfig(encoding_config_registry, metric_registry,
                                     report_config_registry_));
  
      // Make an AnalyzerConfigManager
      std::shared_ptr<config::AnalyzerConfigManager> config_manager(
          new config::AnalyzerConfigManager(analyzer_config));
      config_manager_ = config_manager;
  
      std::shared_ptr<AuthEnforcer> auth_enforcer(new NullEnforcer());
  
      fake_uploader_.reset(new FakeGcsUploader());
      std::unique_ptr<ReportExporter> report_exporter(
          new ReportExporter(fake_uploader_));
  
      report_master_service_.reset(new ReportMasterService(
          0, observation_store_, report_store_, config_manager,
          grpc::InsecureServerCredentials(), auth_enforcer,
          std::move(report_exporter)));
  
      report_master_service_->StartWorkerThread();
    }
  
    void WaitUntilIdle() { report_master_service_->WaitUntilIdle(); }
  
    // Makes an Observation with one string part and one int part, using the
    // two given values and the two given encodings for the given metric.
    std::unique_ptr<Observation> MakeObservation(std::string part1_value,
                                                 int part2_value,
                                                 uint32_t metric_id,
                                                 uint32_t encoding_config_id1,
                                                 uint32_t encoding_config_id2) {
      // Construct a new Encoder with a new client secret.
      encoder::Encoder encoder(project_,
                               encoder::ClientSecret::GenerateNewSecret());
      // Set a static current time so we know we have a static day_index.
      encoder.set_current_time(kSomeTimestamp);
  
      // Construct the two-part value to add.
      encoder::Encoder::Value value;
      value.AddStringPart(encoding_config_id1, kPartName1, part1_value);
      value.AddIntPart(encoding_config_id2, kPartName2, part2_value);
  
      // Encode an observation.
      encoder::Encoder::Result result = encoder.Encode(metric_id, value);
      EXPECT_EQ(encoder::Encoder::kOK, result.status);
      EXPECT_TRUE(result.observation.get() != nullptr);
      EXPECT_EQ(2, result.observation->parts_size());
      return std::move(result.observation);
    }
  
    // Makes an Observation with one INDEX value for the given metric and
    // encoding.
    std::unique_ptr<Observation> MakeIndexObservation(
        uint32_t index, uint32_t metric_id, uint32_t encoding_config_id) {
      // Construct a new Encoder with a new client secret.
      encoder::Encoder encoder(project_,
                               encoder::ClientSecret::GenerateNewSecret());
      // Set a static current time so we know we have a static day_index.
      encoder.set_current_time(kSomeTimestamp);
  
      auto result = encoder.EncodeIndex(metric_id, encoding_config_id, index);
  
      EXPECT_EQ(encoder::Encoder::kOK, result.status);
      CHECK_EQ(encoder::Encoder::kOK, result.status);
      EXPECT_TRUE(result.observation.get() != nullptr);
      CHECK(result.observation.get() != nullptr);
      EXPECT_EQ(1, result.observation->parts_size());
      return std::move(result.observation);
    }
  
    // Adds to the ObservationStore |num_clients| two-part observations that each
    // encode the given two values using the given metric and the
    // given two encodings. Each Observation is generated as if from a different
    // client.
    void AddObservations(std::string part1_value, int part2_value,
                         uint32_t metric_id, uint32_t encoding_config_id1,
                         uint32_t encoding_config_id2, int num_clients,
                         uint32_t day_index) {
      std::vector<Observation> observations;
      for (int i = 0; i < num_clients; i++) {
        observations.emplace_back(*MakeObservation(part1_value, part2_value,
                                                   metric_id, encoding_config_id1,
                                                   encoding_config_id2));
      }
      ObservationMetadata metadata;
      metadata.set_customer_id(kCustomerId);
      metadata.set_project_id(kProjectId);
      metadata.set_metric_id(metric_id);
      metadata.set_day_index(day_index);
      metadata.mutable_system_profile()->set_board_name("DummyBoard");
      EXPECT_EQ(store::kOK,
                observation_store_->AddObservationBatch(metadata, observations));
    }
  
    // Adds to the ObservationStore |num_clients| Observations with one INDEX
    // value using the given metric and encoding. Each Observation is generated
    // as if from a different client.
    void AddIndexObservations(uint32_t index, uint32_t metric_id,
                              uint32_t encoding_config_id1, int num_clients,
                              uint32_t day_index) {
      std::vector<Observation> observations;
      for (int i = 0; i < num_clients; i++) {
        observations.emplace_back(
            *MakeIndexObservation(index, metric_id, encoding_config_id1));
      }
      ObservationMetadata metadata;
      metadata.set_customer_id(kCustomerId);
      metadata.set_project_id(kProjectId);
      metadata.set_metric_id(metric_id);
      metadata.set_day_index(day_index);
      metadata.mutable_system_profile()->set_board_name("DummyBoard");
      EXPECT_EQ(store::kOK,
                observation_store_->AddObservationBatch(metadata, observations));
    }
  
    // Invokes ReportMaster::GetReport() and checks the returned ReportMetadata.
    void GetReportAndCheck(const std::string& report_id,
                           uint32_t expected_report_config_id, bool expect_part1,
                           bool expect_part2, bool check_completed,
                           Report* report_out) {
      GetReportRequest get_request;
      get_request.set_report_id(report_id);
      auto status =
          report_master_service_->GetReport(nullptr, &get_request, report_out);
      EXPECT_TRUE(status.ok()) << "error_code=" << status.error_code()
                               << " error_message=" << status.error_message();
  
      // Check report metadata
      CheckMetadata(report_id, expected_report_config_id, expect_part1,
                    expect_part2, check_completed, kFixedTimeSeconds,
                    report_out->metadata());
    }
  
    // Checks a ReportMetadata returned from GetReport or QueryReports.
    void CheckMetadata(const std::string& report_id,
                       uint32_t expected_report_config_id, bool expect_part1,
                       bool expect_part2, bool check_completed,
                       int64_t expected_current_time_seconds,
                       const ReportMetadata& metadata) {
      EXPECT_EQ(report_id, metadata.report_id());
      EXPECT_EQ(kCustomerId, metadata.customer_id());
      EXPECT_EQ(kProjectId, metadata.project_id());
      EXPECT_EQ(expected_report_config_id, metadata.report_config_id());
      EXPECT_EQ(expected_current_time_seconds,
                metadata.creation_time().seconds());
  
      bool expect_joint_report = expect_part1 && expect_part2;
  
      if (check_completed) {
        // Currently JOINT reports are not implemented so we expect the report
        // to have failed.
        ReportState expected_completion_state =
            (expect_joint_report ? TERMINATED : COMPLETED_SUCCESSFULLY);
        EXPECT_EQ(expected_completion_state, metadata.state());
        EXPECT_TRUE(metadata.start_time().seconds() >=
                    metadata.creation_time().seconds());
        EXPECT_TRUE(metadata.finish_time().seconds() >=
                    metadata.start_time().seconds());
      }
  
      EXPECT_EQ(kDayIndex, metadata.first_day_index());
      EXPECT_EQ(kDayIndex, metadata.last_day_index());
  
      // Check the metric parts.
      int expected_num_parts = (expect_joint_report ? 2 : 1);
      ASSERT_EQ(expected_num_parts, metadata.metric_parts_size());
      if (expect_part1) {
        EXPECT_EQ("Part1", metadata.metric_parts(0));
      } else if (!expect_part1 && expect_part2) {
        EXPECT_EQ("Part2", metadata.metric_parts(0));
      }
      if (expect_joint_report) {
        EXPECT_EQ("Part2", metadata.metric_parts(1));
      }
  
      // Check the associated_report_ids.
      if (expect_joint_report) {
        EXPECT_EQ(2, metadata.associated_report_ids_size());
      } else {
        EXPECT_EQ(0, metadata.associated_report_ids_size());
      }
  
      EXPECT_TRUE(metadata.one_off());
  
      // Check info_messages.
      if (check_completed && expect_joint_report) {
        ASSERT_NE(0, metadata.info_messages_size());
        EXPECT_NE(std::string::npos,
                  metadata.info_messages(0).message().find(
                      "Report type JOINT is not yet implemented"));
        EXPECT_EQ(expected_current_time_seconds,
                  metadata.info_messages(0).timestamp().seconds());
      }
    }
  
    // Invokes GetReportAndCheck() on the given joint report. Then extracts the
    // IDs of the two marginal reports and invokes GetReportAndCheck() on those
    // also.
    void CheckJointReportAndTwoMarginals(std::string report_id_joint,
                                         uint32_t expected_report_config_id,
                                         bool check_completed,
                                         Report* first_marginal_report_out,
                                         Report* second_marginal_report_out) {
      // Get and check the metadata of the joint report.
      Report joint_report;
      bool expect_part1 = true;
      bool expect_part2 = true;
      this->GetReportAndCheck(report_id_joint, expected_report_config_id,
                              expect_part1, expect_part2, check_completed,
                              &joint_report);
      // Currently joint reports are not yet implemented so there should be
      // no report rows.
      EXPECT_FALSE(joint_report.has_rows());
  
      // Extract the IDs of the two marginal reports.
      std::string report_id_11 = joint_report.metadata().associated_report_ids(0);
      std::string report_id_12 = joint_report.metadata().associated_report_ids(1);
  
      // Get and check the metadata of the first marginal report.
      expect_part1 = true;
      expect_part2 = false;
      this->GetReportAndCheck(report_id_11, expected_report_config_id,
                              expect_part1, expect_part2, check_completed,
                              first_marginal_report_out);
      if (check_completed) {
        EXPECT_TRUE(first_marginal_report_out->has_rows());
      }
  
      // Get and check the metadata of the second marginal report.
      expect_part1 = false;
      expect_part2 = true;
      this->GetReportAndCheck(report_id_12, expected_report_config_id,
                              expect_part1, expect_part2, check_completed,
                              second_marginal_report_out);
      if (check_completed) {
        EXPECT_TRUE(second_marginal_report_out->has_rows());
      }
    }
  
    // Returns a version of the given |report| with the rows sorted.
    // |report| is expected to be a serialized report that consist of a series
    // of report rows separated by newlines. The first row of the report should be
    // the header row. This method will build a new serialized report that
    // has the same header row but with the remaining rows sorted.
    std::string WithSortedRows(std::string report) {
      std::istringstream istream(report);
      std::vector<std::string> lines;
      std::string line;
      while (std::getline(istream, line)) {
        lines.push_back(line);
      }
      auto begin = lines.begin();
      begin++;
      std::sort(begin, lines.end());
      std::ostringstream ostream;
      for (const auto& line : lines) {
        ostream << line;
      }
      return ostream.str();
    }
  
    void CheckRawDumpReport(const std::string& expected_report,
                            const std::string& report) {
      EXPECT_EQ(WithSortedRows(expected_report), WithSortedRows(report));
    }
  
    // Invokes ReportMaster::QueryReportsInternal() using our fixed customer and
    // project and the given report_config_id and time interval. The responses
    // will be written to the given |response_writer|. Returns true for success
    // or false for failure.
    bool QueryReports(uint32_t report_config_id, uint64_t first_time_seconds,
                      uint64_t limit_time_seconds,
                      TestingQueryReportsResponseWriter* response_writer) {
      QueryReportsRequest request;
      request.set_customer_id(kCustomerId);
      request.set_project_id(kProjectId);
      request.set_report_config_id(report_config_id);
      request.mutable_first_timestamp()->set_seconds(first_time_seconds);
      request.mutable_limit_timestamp()->set_seconds(limit_time_seconds);
      auto status = report_master_service_->QueryReportsInternal(
          nullptr, &request, response_writer);
      EXPECT_TRUE(status.ok()) << "error_code=" << status.error_code()
                               << " error_message=" << status.error_message();
      return status.ok();
    }
  
    void set_current_time_seconds(int64_t current_time_seconds) {
      clock_->set_time(util::FromUnixSeconds(current_time_seconds));
    }
  
    // Writes Metadata directly into the ReportStore simulating the case that
    // StarReport() was invoked many times to form |num_reports| different
    // instances of the report with the given |report_config_id|.
    //
    // The creation time and start time for report i will be
    // kFixedTimeSeconds + i.
    //
    // The implementation of this function breaks several layers of abstraction
    // and writes directly into the underlying ReporStore. This is a convenient
    // way to efficiently set up the ReportMetadata table in order test the
    // QueryReports function. If we were to use the gRPC API to accomplish this
    // it would require many RPC roundtrips which would take a long time.
    // There is no reason for the gRPC API to support an efficient implementation
    // of this function as it is not useful outside of a test.
    //
    // The vector of string report IDs from the gRPC API are returned so that
    // they may be used to query in the gRPC API.
    std::vector<std::string> WriteManyNewReports(uint64_t report_config_id,
                                                 size_t num_reports) {
      ReportId report_id;
      report_id.set_customer_id(kCustomerId);
      report_id.set_project_id(kProjectId);
      report_id.set_report_config_id(report_config_id);
      std::vector<ReportId> report_ids(num_reports, report_id);
  
      ReportMetadataLite metadata;
      metadata.set_state(IN_PROGRESS);
      metadata.set_first_day_index(kDayIndex);
      metadata.set_last_day_index(kDayIndex);
      metadata.set_one_off(true);
      metadata.add_variable_indices(0);
      std::vector<ReportMetadataLite> report_metadata(num_reports, metadata);
  
      std::vector<std::string> string_report_ids(num_reports);
      for (size_t i = 0; i < num_reports; i++) {
        report_ids[i].set_creation_time_seconds(kFixedTimeSeconds + i);
        report_ids[i].set_instance_id(i);
        report_metadata[i].set_start_time_seconds(kFixedTimeSeconds + i);
        string_report_ids[i] =
            report_master_service_->MakeStringReportId(report_ids[i]);
      }
      // We write all the reports with a single RPC.
      store::ReportStoreTestUtils test_utils(report_store_);
      EXPECT_EQ(store::kOK,
                test_utils.WriteBulkMetadata(report_ids, report_metadata));
      return string_report_ids;
    }
  
    // Given a file_path of the form
    //    "1_1_1/report_1_1_1_20090314.csv"
    // returns the day_index corresponding to the calendar date encoded by the
    // last 8 digits (20090314 in the example above), or returns 0 if |file_path|
    // does not have the expected form.
    uint32_t ExtractDayIndexFromPath(const std::string& file_path,
                                     uint32_t report_config_id) {
      std::ostringstream stream;
      stream << kCustomerId << "_" << kProjectId << "_" << report_config_id
             << "/report_" << kCustomerId << "_" << kProjectId << "_"
             << report_config_id << "_";
      std::string expected_prefix = stream.str();
      if (file_path.find(expected_prefix) != 0) {
        ADD_FAILURE() << "file_path=" << file_path
                      << " expected_prefix=" << expected_prefix;
        return 0;
      }
      size_t left_index = expected_prefix.size();
      size_t index_of_dot = left_index + 8;
      EXPECT_EQ(index_of_dot + 4, file_path.size()) << file_path;
      EXPECT_EQ(".csv", file_path.substr(index_of_dot));
      CalendarDate cd;
      cd.year = std::stoi(file_path.substr(left_index, 4));
      cd.month = std::stoi(file_path.substr(left_index + 4, 2));
      cd.day_of_month = std::stoi(file_path.substr(left_index + 6, 2));
      return CalendarDateToDayIndex(cd);
    }
  
    // Replaces all occurrences of |date_token| within |report| with the string
    // representation of the date given by |day_index|.
    std::string ReplaceDateTokens(const std::string& report,
                                  const std::string& date_token,
                                  uint32_t day_index) {
      util::CalendarDate cd = util::DayIndexToCalendarDate(day_index);
      std::ostringstream stream;
      stream << cd.year << "-" << cd.month << "-" << cd.day_of_month;
      std::string date_string = stream.str();
      std::string report_out = report;
      size_t index = report_out.find(date_token);
      while (index != std::string::npos) {
        report_out = report_out.replace(index, date_token.size(), date_string);
        index = report_out.find(date_token);
      }
      return report_out;
    }
  
    std::shared_ptr<encoder::ProjectContext> project_;
    std::shared_ptr<store::DataStore> data_store_;
    std::shared_ptr<store::ObservationStore> observation_store_;
    std::shared_ptr<store::ReportStore> report_store_;
    std::unique_ptr<ReportMasterService> report_master_service_;
    std::shared_ptr<util::IncrementingClock> clock_;
    std::shared_ptr<config::ReportRegistry> report_config_registry_;
    std::shared_ptr<config::AnalyzerConfigManager> config_manager_;
    std::shared_ptr<FakeGcsUploader> fake_uploader_;
  };
  
  TYPED_TEST_CASE_P(ReportMasterServiceAbstractTest);
  
  // Adds observations to the ObservationStore and then uses the ReportMaster
  // to run two reports for our two registered ReportConfigs. Checks the
  // results. From the ReportMaster API we test the methods StartReport and
  // GetReport.
  TYPED_TEST_P(ReportMasterServiceAbstractTest, StartAndGetReports) {
    // Add some observations for metric 1. We use Basic RAPPOR for
    // both parts. We add 20 observations of the pair ("Apple", 10).
    // Our report will only analyze part 1; part 2 will be ignored. We have
    // set the RAPPOR parameters p and q so there is no randomness. We therefore
    // will expect the report to produce the following results:
    // ("Apple", 20), ("Banana", 0), ("Cantaloupe", 0).
    this->AddObservations("Apple", 10, kMetricId1,
                          kBasicRapporStringEncodingConfigId,
                          kBasicRapporIntEncodingConfigId, 20, kDayIndex);
  
    // Add some observations for metric 2. We use Forculus for part 1
    // and BasicRappor for part 2. For the Forculus part there will be
    // 20 observations of "Apple", 19 observations of "Banana", and 21
    // observations of "Cantaloupe" so we expect to see "Apple" and "Cantaloupe"
    // in the report but not "Banana". For the Basic RAPPOR part there will
    // be 20 observations of |10|, 19 observations of |9|, and 21 observations
    // of |8|. Joint reports are not implemented yet so we will only be checking
    // the results of the two marginal reports.
    this->AddObservations("Apple", 10, kMetricId2, kForculusEncodingConfigId,
                          kBasicRapporIntEncodingConfigId, kForculusThreshold,
                          kDayIndex);
    this->AddObservations("Banana", 9, kMetricId2, kForculusEncodingConfigId,
                          kBasicRapporIntEncodingConfigId, kForculusThreshold - 1,
                          kDayIndex);
    this->AddObservations("Cantaloupe", 8, kMetricId2, kForculusEncodingConfigId,
                          kBasicRapporIntEncodingConfigId, kForculusThreshold + 1,
                          kDayIndex);
  
    // Start the first report. This is a one-variable report of
    // part 1 of metric 1.
    StartReportRequest start_request;
    start_request.set_customer_id(kCustomerId);
    start_request.set_project_id(kProjectId);
    start_request.set_report_config_id(kReportConfigId1);
    start_request.set_first_day_index(kDayIndex);
    start_request.set_last_day_index(kDayIndex);
    StartReportResponse start_response;
    auto status = this->report_master_service_->StartReport(
        nullptr, &start_request, &start_response);
    EXPECT_TRUE(status.ok()) << "error_code=" << status.error_code()
                             << " error_message=" << status.error_message();
    // Capture the ID for report 1.
    std::string report_id1 = start_response.report_id();
    EXPECT_FALSE(report_id1.empty());
  
    // Start the second report. This is a joint two-variable report of metric 2.
    // The two marginal reports will be automatically started also but the
    // returned report_id will be for the joint report. Since joint reports are
    // not implemented yet we will only be checking the results of the two
    // marginal reports but we will be checking the metadata of the joint
    // report too.
    start_request.set_report_config_id(kReportConfigId2);
    status = this->report_master_service_->StartReport(nullptr, &start_request,
                                                       &start_response);
    EXPECT_TRUE(status.ok()) << "error_code=" << status.error_code()
                             << " error_message=" << status.error_message();
    // Capture the ID for report 2. This is the ID of the joint report.
    std::string report_id2 = start_response.report_id();
    EXPECT_FALSE(report_id2.empty());
  
    // Check the meta-data of the first report. It should include part 1 and
    // not part 2.
    Report report1;
    bool expect_part1 = true;
    bool expect_part2 = false;
    // The report is generated asynchronously and we don't know that it is
    // done yet so don't check that it is completed.
    bool check_completed = false;
    {
      SCOPED_TRACE("");
      this->GetReportAndCheck(report_id1, kReportConfigId1, expect_part1,
                              expect_part2, check_completed, &report1);
    }
  
    // Check the meta-data of the second report. We should find a joint report
    // and two associated marginal reports and we check the metadata of all three.
    // The joint report will have meta-data only because joint reports are not
    // implemented yet. But the two marginals will be returned to us so we can
    // check them. (But not yet because we don't know that the report generation
    // is completed yet.)
    Report first_marginal_report;
    Report second_marginal_report;
    {
      SCOPED_TRACE("");
      this->CheckJointReportAndTwoMarginals(
          report_id2, kReportConfigId2, check_completed, &first_marginal_report,
          &second_marginal_report);
    }
  
    // Wait until the report generation for all reports completes.
    this->WaitUntilIdle();
  
    // Check the reports again but this time check that they are completed
    // and then check the actual contents of the report rows.
    check_completed = true;
    {
      SCOPED_TRACE("");
      this->GetReportAndCheck(report_id1, kReportConfigId1, expect_part1,
                              expect_part2, check_completed, &report1);
    }
  
    // Check the rows of report 1.
    // Recall that when adding observations to metric 1 above we used Basic
    // RAPPOR with no randomness so we expect to see the results
    // ("Apple", 20), ("Banana", 0), ("Cantaloupe", 0).
    ASSERT_EQ(3, report1.rows().rows_size());
    std::map<std::string, int> report1_results;
    for (int i = 0; i < 3; i++) {
      report1_results[report1.rows().rows(i).histogram().value().string_value()] =
          report1.rows().rows(i).histogram().count_estimate();
    }
    ASSERT_EQ(3u, report1_results.size());
    EXPECT_EQ(20, report1_results["Apple"]);
    EXPECT_EQ(0, report1_results["Banana"]);
    EXPECT_EQ(0, report1_results["Cantaloupe"]);
  
    // Check report 2 again including its associated marginal reports, this
    // time checking that they are complete.
    {
      SCOPED_TRACE("");
      this->CheckJointReportAndTwoMarginals(
          report_id2, kReportConfigId2, check_completed, &first_marginal_report,
          &second_marginal_report);
    }
  
    // Check the rows of the first marginal of report 2. Recall that when adding
    // rows to part 1 of metric 2 above we used Forculus and we expect to see the
    // results ("Apple", 20), ("Cantaloupe", 21) and not to see "Banana" because
    // it should not have been decrypted.
    ASSERT_EQ(2, first_marginal_report.rows().rows_size());
    std::map<std::string, int> first_marginal_results;
    for (int i = 0; i < 2; i++) {
      first_marginal_results[first_marginal_report.rows()
                                 .rows(i)
                                 .histogram()
                                 .value()
                                 .string_value()] =
          first_marginal_report.rows().rows(i).histogram().count_estimate();
    }
    ASSERT_EQ(2u, first_marginal_results.size());
    EXPECT_EQ(20, first_marginal_results["Apple"]);
    EXPECT_EQ(21, first_marginal_results["Cantaloupe"]);
  
    // Check the rows of the second marginal of report 2. Recall that when adding
    // rows to part 2 of metric 2 above we used Basic RAPPOR with no randomness
    // so we expect to see the following results:
    // (a) A count of 0 for the numbers 1, 2, 3, 4, 5, 6, 7
    // (b) (8, 21), (9, 19), (10, 20)
    ASSERT_EQ(10, second_marginal_report.rows().rows_size());
    std::map<int, int> second_marginal_results;
    for (int i = 0; i < 10; i++) {
      second_marginal_results[second_marginal_report.rows()
                                  .rows(i)
                                  .histogram()
                                  .value()
                                  .int_value()] =
          second_marginal_report.rows().rows(i).histogram().count_estimate();
    }
    ASSERT_EQ(10u, second_marginal_results.size());
    for (int i = 1; i <= 7; i++) {
      EXPECT_EQ(0, second_marginal_results[i]);
    }
    EXPECT_EQ(21, second_marginal_results[8]);
    EXPECT_EQ(19, second_marginal_results[9]);
    EXPECT_EQ(20, second_marginal_results[10]);
  
    // Expect that no exporting was perofrmed.
    EXPECT_TRUE(this->fake_uploader_->reports.empty());
  }
  
  // Tests Cobalt analyzer end-to-end using a (metric, encoding, report) trio
  // in which Observations use the INDEX data type and the report config
  // specifies human-readable labels for some of the indices.
  TYPED_TEST_P(ReportMasterServiceAbstractTest, E2EWithIndexLabels) {
    for (int index = 0; index < 50; index++) {
      // Add |index| + 1 observations of |index|.
      this->AddIndexObservations(index, kMetricId3,
                                 kBasicRapporIndexEncodingConfigId, index + 1,
                                 kDayIndex);
    }
  
    // Start the report.
    StartReportRequest start_request;
    start_request.set_customer_id(kCustomerId);
    start_request.set_project_id(kProjectId);
    start_request.set_report_config_id(kReportConfigId3);
    start_request.set_first_day_index(kDayIndex);
    start_request.set_last_day_index(kDayIndex);
    StartReportResponse start_response;
    auto status = this->report_master_service_->StartReport(
        nullptr, &start_request, &start_response);
    EXPECT_TRUE(status.ok()) << "error_code=" << status.error_code()
                             << " error_message=" << status.error_message();
    // Capture the report ID.
    std::string report_id = start_response.report_id();
    EXPECT_FALSE(report_id.empty());
  
    // Wait until the report generation completes.
    this->WaitUntilIdle();
  
    Report report;
    {
      // Fetch the report and check the metadata.
      SCOPED_TRACE("");
      bool check_completed = true;
      bool expect_part1 = true;
      bool expect_part2 = false;
      this->GetReportAndCheck(report_id, kReportConfigId3, expect_part1,
                              expect_part2, check_completed, &report);
    }
    // Check the rows of the report including the labels.
    ASSERT_EQ(100, report.rows().rows_size());
    for (size_t i = 0; i < 100; i++) {
      auto index = report.rows().rows(i).histogram().value().index_value();
      auto count = report.rows().rows(i).histogram().count_estimate();
      auto label = report.rows().rows(i).histogram().label();
      auto expected_count = (index < 50 ? index + 1 : 0);
      EXPECT_EQ(expected_count, count)
          << "i=" << i << ", index=" << index << ", count=" << count;
      switch (index) {
        case 0:
          EXPECT_EQ("Event A", label);
          break;
  
        case 1:
          EXPECT_EQ("Event B", label);
          break;
  
        case 25:
          EXPECT_EQ("Event Z", label);
          break;
  
        default:
          EXPECT_EQ("", label);
      }
    }
  
    // Expect that no exporting was perofrmed.
    EXPECT_TRUE(this->fake_uploader_->reports.empty());
  }
  
  // Tests the method ReportMaster::QueryReports. We write into the ReportStore
  // many instance of ReportConfig 1 and then invoke QueryReports() and check the
  // results.
  TYPED_TEST_P(ReportMasterServiceAbstractTest, QueryReportsTest) {
    // Write Metadata into the ReportStore for 210 reports associated with
    // ReporcConfig 1  with creation_times that start at kFixedTimeSeconds and
    // increment by 1 second for each report.
    std::vector<std::string> report_ids =
        this->WriteManyNewReports(kReportConfigId1, 210);
  
    // Now invoke QueryReports. We specify a time window that will omit the
    // first three and the last 3 reports. So there should be 204 reports
    // returned.
    TestingQueryReportsResponseWriter response_writer;
    ASSERT_TRUE(this->QueryReports(kReportConfigId1, kFixedTimeSeconds + 3,
                                   kFixedTimeSeconds + 207, &response_writer));
  
    // Since we know that reports are returned in batches of 100 we expect there
    // to be 3 batches: Two batches of size 100 and one batch of size 4.
    EXPECT_EQ(3u, response_writer.responses.size());
  
    // Check the first batch.
    bool expect_part1 = true;
    bool expect_part2 = false;
    // We don't know that the reports are completed yet.
    bool check_completed = false;
    EXPECT_EQ(100, response_writer.responses[0].reports_size());
    for (int i = 0; i < 100; i++) {
      this->CheckMetadata(report_ids[i + 3], kReportConfigId1, expect_part1,
                          expect_part2, check_completed,
                          kFixedTimeSeconds + 3 + i,
                          response_writer.responses[0].reports(i));
    }
  
    // Check the second batch.
    EXPECT_EQ(100, response_writer.responses[1].reports_size());
    for (int i = 0; i < 100; i++) {
      this->CheckMetadata(report_ids[i + 103], kReportConfigId1, expect_part1,
                          expect_part2, check_completed,
                          kFixedTimeSeconds + 103 + i,
                          response_writer.responses[1].reports(i));
    }
  
    // Check the third batch.
    EXPECT_EQ(4, response_writer.responses[2].reports_size());
    for (int i = 0; i < 4; i++) {
      this->CheckMetadata(report_ids[i + 203], kReportConfigId1, expect_part1,
                          expect_part2, check_completed,
                          kFixedTimeSeconds + 203 + i,
                          response_writer.responses[2].reports(i));
    }
  }
  
  // Tests the interaction of the ReportScheduler with the rest of the
  // ReportMaster pipeline, including report exporting. We simulate 10 days of
  // activity of the ReportScheduler and then we check the exported reports.
  TYPED_TEST_P(ReportMasterServiceAbstractTest, EnableReportScheduling) {
    // First we populate the Observation Store with Observations so that the
    // reports will have something to analyzer. This part of the simulation is
    // unrealistic because we are going to add Observations for all of the days
    // at the beginning of the test instead of allowing the Observations to arrive
    // interspersed with the report generation. We add observations for metrics 1,
    // 2 and 3 for each day in the interval [kDayIndex - 30, kDayIndex + 15].
    for (uint32_t day_index = kDayIndex - 30; day_index < kDayIndex + 15;
         day_index++) {
      this->AddObservations("Apple", 1, kMetricId1,
                            kBasicRapporStringEncodingConfigId,
                            kBasicRapporIntEncodingConfigId, 20, day_index);
      this->AddObservations("a", 1, kMetricId2, kNoOpEncodingConfigId,
                            kNoOpEncodingConfigId, 1, day_index);
      this->AddObservations("b", 2, kMetricId2, kNoOpEncodingConfigId,
                            kNoOpEncodingConfigId, 2, day_index);
      this->AddObservations("c", 3, kMetricId2, kNoOpEncodingConfigId,
                            kNoOpEncodingConfigId, 3, day_index);
      this->AddIndexObservations(0, kMetricId3, kBasicRapporIndexEncodingConfigId,
                                 5, day_index);
    }
  
    /// We construct a ReportScheduler that uses our ReportMasterService as
    // its ReportStarter.
    std::shared_ptr<ReportStarter> report_starter(
        new ReportStarter(this->report_master_service_.get()));
    std::unique_ptr<ReportScheduler> report_scheduler(
        new ReportScheduler(this->config_manager_, this->report_store_,
                            report_starter, std::chrono::milliseconds(1)));
  
    // We arrange that the ReportScheduler loops every 1 ms and that each ms
    // it simulates 4 hours of time passing.
    std::shared_ptr<util::IncrementingClock> clock(new util::IncrementingClock());
    std::chrono::system_clock::time_point start_time =
        util::FromUnixSeconds(kSomeTimestamp);
    clock->set_time(start_time);
    clock->set_increment(std::chrono::seconds(60 * 60 * 4));
    report_scheduler->SetClockForTesting(clock);
  
    // We arrange for the scheduler thread to notify this thread after 10
    // days of simulated time has occurred.
    std::mutex mu;
    std::condition_variable cv;
    bool done = false;
    std::chrono::system_clock::time_point stop_time =
        start_time + std::chrono::hours(24 * 10);
  
    clock->set_callback(
        [&cv, &mu, &done,
         stop_time](std::chrono::system_clock::time_point simulated_time) {
          if (simulated_time > stop_time) {
            std::lock_guard<std::mutex> lock(mu);
            done = true;
            cv.notify_all();
          }
        });
  
    // Start the scheduler thread.
    report_scheduler->Start();
  
    // We wait for the scheduler thread to notify this thread that 10 days of
    // simulated time has occurred.
    {
      std::unique_lock<std::mutex> lock(mu);
      cv.wait(lock, [&done] { return done; });
    }
  
    // We delete the ReportScheduler, which stops the scheduler thread.
    report_scheduler.reset();
  
    // Now we wait for the ReportExecutor's worker thread to finish generating
    // all of the reports.
    this->WaitUntilIdle();
  
    // Now we check the exported reports.
  
    static const char kDateToken[] = "<DATE>";
    static const char kExpectedReport1[] = R"(date,Part1,count,err
  <DATE>,"Apple",20.000,0
  <DATE>,"Banana",0,0
  <DATE>,"Cantaloupe",0,0
  )";
  
    static const char kExpectedReport3[] = R"(date,Part1,count,err
  <DATE>,"Event A",5.000,0
  <DATE>,"Event B",0,0
  <DATE>,"Event Z",0,0
  )";
  
    static const char kExpectedReport4[] = R"(date,Part1,Part2
  <DATE>,"a",1
  <DATE>,"b",2
  <DATE>,"b",2
  <DATE>,"c",3
  <DATE>,"c",3
  <DATE>,"c",3
  )";
  
    static const char kExpectedReport5[] = R"(date,Part1,Part2,Board_Name
  <DATE>,"a",1,"DummyBoard"
  <DATE>,"b",2,"DummyBoard"
  <DATE>,"b",2,"DummyBoard"
  <DATE>,"c",3,"DummyBoard"
  <DATE>,"c",3,"DummyBoard"
  <DATE>,"c",3,"DummyBoard"
  )";
  
    static const char kExpectedReport6[] = R"(date,Part1,Board_Name,count,err
  <DATE>,"Event A","DummyBoard",5.000,0
  <DATE>,"Event B","DummyBoard",0,0
  <DATE>,"Event Z","DummyBoard",0,0
  )";
  
    // The keys to these maps are day indices and the values are the number of
    // reports found for that day.
    std::map<uint32_t, size_t> day_counts_for_report_1;
    std::map<uint32_t, size_t> day_counts_for_report_3;
    std::map<uint32_t, size_t> day_counts_for_report_4;
    std::map<uint32_t, size_t> day_counts_for_report_5;
    std::map<uint32_t, size_t> day_counts_for_report_6;
  
    size_t num_reports = this->fake_uploader_->buckets.size();
    ASSERT_TRUE(num_reports >= 120) << num_reports;
    ASSERT_EQ(num_reports, this->fake_uploader_->paths.size());
    ASSERT_EQ(num_reports, this->fake_uploader_->reports.size());
    ASSERT_EQ(num_reports, this->fake_uploader_->mime_types.size());
  
    for (size_t i = 0; i < num_reports; i++) {
      uint32_t report_config_id;
      EXPECT_EQ("text/csv", this->fake_uploader_->mime_types[i]);
      std::string expected_report;
      std::map<uint32_t, size_t>* day_counts;
      if (this->fake_uploader_->buckets[i] == "bucket.name.1") {
        report_config_id = kReportConfigId1;
        day_counts = &day_counts_for_report_1;
        expected_report = std::string(kExpectedReport1);
      } else if (this->fake_uploader_->buckets[i] == "bucket.name.3") {
        report_config_id = kReportConfigId3;
        day_counts = &day_counts_for_report_3;
        expected_report = std::string(kExpectedReport3);
      } else if (this->fake_uploader_->buckets[i] == "bucket.name.4") {
        report_config_id = kReportConfigId4;
        day_counts = &day_counts_for_report_4;
        expected_report = std::string(kExpectedReport4);
      } else if (this->fake_uploader_->buckets[i] == "bucket.name.5") {
        report_config_id = kReportConfigId5;
        day_counts = &day_counts_for_report_5;
        expected_report = std::string(kExpectedReport5);
      } else if (this->fake_uploader_->buckets[i] == "bucket.name.6") {
        report_config_id = kReportConfigId6;
        day_counts = &day_counts_for_report_6;
        expected_report = std::string(kExpectedReport6);
      } else {
        FAIL() << this->fake_uploader_->buckets[i];
      }
      uint32_t day_index = this->ExtractDayIndexFromPath(
          this->fake_uploader_->paths[i], report_config_id);
      ASSERT_GE(day_index, kDayIndex - 30);
      ASSERT_LE(day_index, kDayIndex + 100);
      expected_report =
          this->ReplaceDateTokens(expected_report, kDateToken, day_index);
      if (report_config_id == kReportConfigId4 ||
          report_config_id == kReportConfigId5) {
        this->CheckRawDumpReport(expected_report,
                                 this->fake_uploader_->reports[i]);
      } else {
        EXPECT_EQ(expected_report, this->fake_uploader_->reports[i]);
      }
  
      (*day_counts)[day_index]++;
    }
  
    for (uint32_t day_index = kDayIndex - 30; day_index < kDayIndex - 3;
         day_index++) {
      EXPECT_EQ(1u, day_counts_for_report_1[day_index]) << day_index;
      EXPECT_EQ(1u, day_counts_for_report_3[day_index]) << day_index;
      EXPECT_EQ(1u, day_counts_for_report_4[day_index]) << day_index;
    }
    for (uint32_t day_index = kDayIndex - 2; day_index <= kDayIndex + 9;
         day_index++) {
      EXPECT_TRUE(day_counts_for_report_1[day_index] >= 1) << day_index;
      EXPECT_EQ(1u, day_counts_for_report_3[day_index]) << day_index;
      EXPECT_EQ(1u, day_counts_for_report_4[day_index]) << day_index;
    }
  }  // namespace analyzer
  
  REGISTER_TYPED_TEST_CASE_P(ReportMasterServiceAbstractTest, StartAndGetReports,
                             E2EWithIndexLabels, QueryReportsTest,
                             EnableReportScheduling);
  
  }  // namespace analyzer
  }  // namespace cobalt
  
  #endif  // COBALT_ANALYZER_REPORT_MASTER_REPORT_MASTER_SERVICE_ABSTRACT_TEST_H_
  