text: // 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 <fuchsia/cobalt/llcpp/fidl.h>
  #include <lib/async-loop/cpp/loop.h>
  #include <lib/async-loop/default.h>
  #include <lib/async-loop/loop.h>
  #include <lib/async/dispatcher.h>
  #include <lib/fidl-async/cpp/bind.h>
  #include <lib/fit/function.h>
  #include <lib/zx/channel.h>
  #include <lib/zx/time.h>
  #include <limits.h>
  #include <zircon/assert.h>
  
  #include <cstdint>
  #include <memory>
  #include <string_view>
  #include <thread>
  #include <type_traits>
  #include <utility>
  #include <vector>
  
  #include <cobalt-client/cpp/collector_internal.h>
  #include <cobalt-client/cpp/in_memory_logger.h>
  #include <cobalt-client/cpp/metric_options.h>
  #include <cobalt-client/cpp/types_internal.h>
  #include <fbl/auto_lock.h>
  #include <fbl/mutex.h>
  #include <zxtest/zxtest.h>
  
  namespace cobalt_client {
  namespace internal {
  namespace {
  
  using Status = fuchsia_cobalt::wire::Status;
  using EventData = fuchsia_cobalt::wire::EventPayload;
  
  // Fake Implementation for fuchsia::cobalt::LoggerFactory.
  class FakeLoggerFactoryService : public fidl::WireServer<fuchsia_cobalt::LoggerFactory> {
   public:
    void CreateLoggerFromProjectId(CreateLoggerFromProjectIdRequestView request,
                                   CreateLoggerFromProjectIdCompleter::Sync& completer) final {
      completer.Reply(create_logger_handler_(request->project_id, std::move(request->logger)));
    }
  
    void CreateLoggerSimpleFromProjectId(
        CreateLoggerSimpleFromProjectIdRequestView request,
        CreateLoggerSimpleFromProjectIdCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void CreateLoggerFromProjectSpec(CreateLoggerFromProjectSpecRequestView request,
                                     CreateLoggerFromProjectSpecCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void set_create_logger_handler(
        fit::function<Status(uint32_t, ::fidl::ServerEnd<fuchsia_cobalt::Logger>)> handler) {
      create_logger_handler_ = std::move(handler);
    }
  
   private:
    fit::function<Status(uint32_t, ::fidl::ServerEnd<fuchsia_cobalt::Logger>)> create_logger_handler_;
  };
  
  // Fake Implementation for fuchsia::cobalt::Logger.
  class FakeLoggerService : public fidl::WireServer<fuchsia_cobalt::Logger> {
   public:
    void LogEvent(LogEventRequestView request, LogEventCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogEventCount(LogEventCountRequestView request,
                       LogEventCountCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogElapsedTime(LogElapsedTimeRequestView request,
                        LogElapsedTimeCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogFrameRate(LogFrameRateRequestView request, LogFrameRateCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogMemoryUsage(LogMemoryUsageRequestView request,
                        LogMemoryUsageCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void StartTimer(StartTimerRequestView request, StartTimerCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void EndTimer(EndTimerRequestView request, EndTimerCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogIntHistogram(LogIntHistogramRequestView request,
                         LogIntHistogramCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogCustomEvent(LogCustomEventRequestView request,
                        LogCustomEventCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void LogCobaltEvent(LogCobaltEventRequestView request,
                        LogCobaltEventCompleter::Sync& completer) final {
      // Use MetricOptions as a key.
      MetricOptions info;
      auto& event = request->event;
      info.metric_dimensions = static_cast<uint32_t>(event.event_codes.count());
      if (event_code_count_tracker_ != nullptr) {
        *event_code_count_tracker_ = info.metric_dimensions;
      }
      info.metric_id = event.metric_id;
      info.component = event.component.data();
      for (uint64_t i = 0; i < MetricOptions::kMaxEventCodes; ++i) {
        if (i < info.metric_dimensions) {
          info.event_codes[i] = event.event_codes[i];
        } else {
          info.event_codes[i] = 0;
        }
      }
  
      switch (event.payload.which()) {
        case EventData::Tag::kIntHistogram:
          storage_.Log(info, event.payload.int_histogram().data(),
                       event.payload.int_histogram().count());
          break;
        case EventData::Tag::kEventCount:
          storage_.Log(info, event.payload.event_count().count);
          break;
        default:
          ZX_ASSERT_MSG(false, "Not Supported.");
          break;
      }
      completer.Reply(log_return_status_);
    }
  
    void LogCobaltEvents(LogCobaltEventsRequestView request,
                         LogCobaltEventsCompleter::Sync& completer) final {
      ZX_PANIC("Not Implemented.");
    }
  
    void set_log_return_status(Status status) { log_return_status_ = status; }
  
    void set_log_event_code_count_tracker(uint32_t* event_count) {
      event_code_count_tracker_ = event_count;
    }
  
    // Returns the |InMemoryLogger| used for backing the storage of this |cobalt.Logger|.
    const InMemoryLogger& storage() const { return storage_; }
  
   private:
    Status log_return_status_ = Status::kOk;
    uint32_t* event_code_count_tracker_ = nullptr;
    InMemoryLogger storage_;
  };
  
  // Struct for argument validation.
  struct CreateLoggerValidationArgs {
    void Check() const {
      EXPECT_TRUE(is_id_ok);
      EXPECT_TRUE(is_channel_ok);
    }
  
    uint32_t project_id;
  
    // Return status for the fidl call.
    Status return_status = Status::kOk;
  
    // Used for validating the args and validation on the main thread.
    fbl::Mutex result_lock_;
    bool is_id_ok = false;
    bool is_channel_ok = false;
  };
  
  void BindLoggerFactoryService(FakeLoggerFactoryService* bindee,
                                fidl::ServerEnd<fuchsia_cobalt::LoggerFactory> channel,
                                async_dispatcher_t* dispatcher) {
    fidl::BindSingleInFlightOnly(dispatcher, std::move(channel), bindee);
  }
  
  void BindLoggerToLoggerFactoryService(FakeLoggerFactoryService* binder, FakeLoggerService* bindee,
                                        CreateLoggerValidationArgs* checker,
                                        async_dispatcher_t* dispatcher) {
    binder->set_create_logger_handler(
        [bindee, checker, dispatcher](uint32_t project_id,
                                      ::fidl::ServerEnd<fuchsia_cobalt::Logger> channel) {
          fbl::AutoLock lock(&checker->result_lock_);
          checker->is_id_ok = (checker->project_id == project_id);
          checker->is_channel_ok = channel.is_valid();
          fidl::BindSingleInFlightOnly(dispatcher, std::move(channel), bindee);
  
          return checker->return_status;
        });
  }
  
  constexpr uint32_t kProjectId = 1234;
  
  class LoggerServiceFixture : public zxtest::Test {
   public:
    void SetUp() final {
      // Initialize the service loop.
      service_loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
  
      checker_.project_id = kProjectId;
      checker_.return_status = Status::kOk;
  
      BindLoggerToLoggerFactoryService(&logger_factory_impl_, &logger_impl_, &checker_,
                                       service_loop_->dispatcher());
    }
  
    std::unique_ptr<CobaltLogger> MakeLogger() { return std::make_unique<CobaltLogger>(Options()); }
  
    CobaltOptions Options() {
      CobaltOptions options;
      options.project_id = kProjectId;
      options.service_connect = [this](const char* path, zx::channel service_channel) {
        BindLoggerFactoryService(
            &logger_factory_impl_,
            fidl::ServerEnd<fuchsia_cobalt::LoggerFactory>(std::move(service_channel)),
            service_loop_->dispatcher());
        return ZX_OK;
      };
      return options;
    }
  
    void StartServiceLoop() {
      ASSERT_NOT_NULL(service_loop_);
      ASSERT_TRUE(service_loop_->GetState() == ASYNC_LOOP_RUNNABLE);
      service_loop_->StartThread("LoggerServiceThread");
    }
  
    void StopServiceLoop() {
      service_loop_->Quit();
      service_loop_->JoinThreads();
      service_loop_->ResetQuit();
    }
  
    void TearDown() final { StopServiceLoop(); }
  
    const InMemoryLogger& GetStorage() const { return logger_impl_.storage(); }
  
    async::Loop* GetLoop() { return service_loop_.get(); }
  
    void SetLoggerLogReturnStatus(Status status) { logger_impl_.set_log_return_status(status); }
    void SetLoggerLogEventCountTracker(uint32_t* event_code_count_tracker) {
      logger_impl_.set_log_event_code_count_tracker(event_code_count_tracker);
    }
  
   protected:
    CreateLoggerValidationArgs checker_;
  
   private:
    std::unique_ptr<async::Loop> service_loop_ = nullptr;
  
    FakeLoggerFactoryService logger_factory_impl_;
    FakeLoggerService logger_impl_;
  };
  
  using CobaltLoggerTest = LoggerServiceFixture;
  
  constexpr uint64_t kBucketCount = 10;
  
  constexpr int64_t kCounter = 1;
  
  TEST_F(CobaltLoggerTest, LogHistogramReturnsTrueWhenServiceReturnsOk) {
    auto logger = MakeLogger();
    std::vector<HistogramBucket> buckets;
  
    MetricOptions info;
    uint32_t event_code_count_tracker = 0;
  
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 0, 0};
    info.metric_dimensions = 3;
    SetLoggerLogEventCountTracker(&event_code_count_tracker);
  
    for (uint32_t i = 0; i < kBucketCount; ++i) {
      buckets.push_back({.index = i, .count = 2 * i});
    }
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_TRUE(logger->Log(info, buckets.data(), buckets.size()));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    auto itr = GetStorage().histograms().find(info);
    ASSERT_NE(GetStorage().histograms().end(), itr);
    ASSERT_EQ(itr->second.size(), kBucketCount);
  
    EXPECT_EQ(info.metric_dimensions, event_code_count_tracker);
  
    for (uint32_t i = 0; i < itr->second.size(); ++i) {
      EXPECT_EQ(buckets[i].count, (itr->second).at(i));
    }
  }
  
  TEST_F(CobaltLoggerTest, LogHistogramReturnsFalseWhenFactoryServiceReturnsError) {
    auto logger = MakeLogger();
    std::vector<HistogramBucket> buckets;
  
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    checker_.return_status = Status::kInternalError;
  
    for (uint32_t i = 0; i < kBucketCount; ++i) {
      buckets.push_back({.index = i, .count = 2 * i});
    }
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_FALSE(logger->Log(info, buckets.data(), buckets.size()));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    EXPECT_TRUE(GetStorage().histograms().empty());
    EXPECT_TRUE(GetStorage().counters().empty());
  }
  
  TEST_F(CobaltLoggerTest, LogHistogramReturnsFalseWhenLoggerServiceReturnsError) {
    auto logger = MakeLogger();
    std::vector<HistogramBucket> buckets;
  
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    SetLoggerLogReturnStatus(Status::kInternalError);
  
    for (uint32_t i = 0; i < kBucketCount; ++i) {
      buckets.push_back({.index = i, .count = 2 * i});
    }
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_FALSE(logger->Log(info, buckets.data(), buckets.size()));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
  }
  
  TEST_F(CobaltLoggerTest, LogHistogramWaitsUntilServiceBecomesAvailable) {
    auto logger = MakeLogger();
    std::vector<HistogramBucket> buckets;
    std::atomic<bool> log_result(false);
  
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    for (uint32_t i = 0; i < kBucketCount; ++i) {
      buckets.push_back({.index = i, .count = 2 * i});
    }
  
    std::thread blocks_until_starts(
        [info, &log_result, &buckets](internal::Logger* logger) {
          log_result.store(logger->Log(info, buckets.data(), buckets.size()));
        },
        logger.get());
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    // This should wait until Log finishes.
    blocks_until_starts.join();
  
    ASSERT_TRUE(log_result);
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    auto itr = GetStorage().histograms().find(info);
    ASSERT_NE(GetStorage().histograms().end(), itr);
    ASSERT_EQ(itr->second.size(), kBucketCount);
  
    for (uint32_t i = 0; i < itr->second.size(); ++i) {
      EXPECT_EQ(buckets[i].count, (itr->second).at(i));
    }
  }
  
  TEST_F(CobaltLoggerTest, LogCounterReturnsTrueWhenServiceReturnsOk) {
    auto logger = MakeLogger();
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_TRUE(logger->Log(info, kCounter));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    auto itr = GetStorage().counters().find(info);
    ASSERT_NE(GetStorage().counters().end(), itr);
  
    EXPECT_EQ(itr->second, kCounter);
  }
  
  TEST_F(CobaltLoggerTest, LogCounterReturnsFalseWhenFactoryServiceReturnsError) {
    auto logger = MakeLogger();
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
    checker_.return_status = Status::kInternalError;
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_FALSE(logger->Log(info, kCounter));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    EXPECT_TRUE(GetStorage().histograms().empty());
    EXPECT_TRUE(GetStorage().counters().empty());
  }
  
  TEST_F(CobaltLoggerTest, LogCounterReturnsFalseWhenLoggerServiceReturnsError) {
    auto logger = MakeLogger();
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    SetLoggerLogReturnStatus(Status::kInternalError);
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    ASSERT_FALSE(logger->Log(info, kCounter));
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
  }
  
  TEST_F(CobaltLoggerTest, LogCounterWaitsUntilServiceBecomesAvailable) {
    auto logger = MakeLogger();
    std::atomic<bool> log_result(false);
    MetricOptions info;
    info.metric_id = 1;
    info.component = "SomeComponent";
    info.event_codes = {1, 2, 3, 4, 5};
    info.metric_dimensions = MetricOptions::kMaxEventCodes;
  
    std::thread blocks_until_starts(
        [info, &log_result](internal::Logger* logger) {
          log_result.store(logger->Log(info, kCounter));
        },
        logger.get());
  
    ASSERT_NO_FAILURES(StartServiceLoop(), "Failed to initialize the service async dispatchers.");
  
    // This should wait until Log finishes.
    blocks_until_starts.join();
  
    ASSERT_TRUE(log_result.load());
    ASSERT_NO_FATAL_FAILURES(checker_.Check());
    auto itr = GetStorage().counters().find(info);
    ASSERT_NE(GetStorage().counters().end(), itr);
  
    EXPECT_EQ(itr->second, kCounter);
  }
  
  }  // namespace
  }  // namespace internal
  }  // namespace cobalt_client
  