// Copyright 2019 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 "src/media/audio/audio_core/threading_model.h"

#include <lib/async/cpp/task.h>

#include <gtest/gtest.h>

namespace media::audio {
namespace {

void ValidateThreadingModel(ThreadingModel* threading_model) {
  // Sanity test we can run task on all loops.
  bool fidl_task_run = false;
  async::PostTask(threading_model->FidlDomain().dispatcher(),
                  [&fidl_task_run] { fidl_task_run = true; });
  bool io_task_run = false;
  async::PostTask(threading_model->IoDomain().dispatcher(), [&io_task_run] { io_task_run = true; });

  // For threading models that use dynamically allocated loops, we submit a task to one loop we
  // immediately release and another to a loop we retain to validate both modes of operation work.
  bool mix1_task_run = false;
  auto mix_domain1 = threading_model->AcquireMixDomain("");
  async::PostTask(mix_domain1->dispatcher(), [&mix1_task_run] { mix1_task_run = true; });

  bool mix2_task_run = false;
  auto mix_domain2 = threading_model->AcquireMixDomain("");
  async::PostTask(mix_domain2->dispatcher(), [&mix2_task_run] { mix2_task_run = true; });

  // We quit first here to cause |RunAndJoinAllThreads| to exit after all currently queued tasks
  // have executed.
  threading_model->Quit();
  threading_model->RunAndJoinAllThreads();

  EXPECT_TRUE(fidl_task_run);
  EXPECT_TRUE(io_task_run);
  EXPECT_TRUE(mix1_task_run);
  EXPECT_TRUE(mix2_task_run);
}

TEST(ThreadingModelTest, MixOnFidlThreadModel) {
  auto threading_model = ThreadingModel::CreateWithMixStrategy(MixStrategy::kMixOnFidlThread);

  // Expect |AcquireMixDomain| to be the same dispatcher as |FidlDomain().dispatcher|.
  {
    auto mix_domain = threading_model->AcquireMixDomain("");
    EXPECT_EQ(threading_model->FidlDomain().dispatcher(), mix_domain->dispatcher());
  }
  // Expect |AcquireMixDomain| to return the same dispatcher across multiple calls.
  {
    auto mix_domain1 = threading_model->AcquireMixDomain("");
    auto mix_domain2 = threading_model->AcquireMixDomain("");
    EXPECT_EQ(mix_domain1->dispatcher(), mix_domain2->dispatcher());
  }

  // |IoDomain().dispatcher| should be different from |FidlDomain().dispatcher|.
  EXPECT_NE(threading_model->FidlDomain().dispatcher(), threading_model->IoDomain().dispatcher());

  ValidateThreadingModel(threading_model.get());
}

TEST(ThreadingModelTest, MixOnSingleThreadModel) {
  auto threading_model = ThreadingModel::CreateWithMixStrategy(MixStrategy::kMixOnSingleThread);

  // Expect all dispatchers to be unique.
  {
    auto mix_domain = threading_model->AcquireMixDomain("");
    EXPECT_NE(threading_model->FidlDomain().dispatcher(), mix_domain->dispatcher());
  }
  {
    auto mix_domain = threading_model->AcquireMixDomain("");
    EXPECT_NE(threading_model->IoDomain().dispatcher(), mix_domain->dispatcher());
  }
  EXPECT_NE(threading_model->FidlDomain().dispatcher(), threading_model->IoDomain().dispatcher());

  // But |AcquireMixDomain| always returns the same dispatcher.
  {
    auto mix_domain1 = threading_model->AcquireMixDomain("");
    auto mix_domain2 = threading_model->AcquireMixDomain("");
    EXPECT_EQ(mix_domain1->dispatcher(), mix_domain2->dispatcher());
  }

  ValidateThreadingModel(threading_model.get());
}

TEST(ThreadingModelTest, ThreadPerMixModel) {
  auto threading_model = ThreadingModel::CreateWithMixStrategy(MixStrategy::kThreadPerMix);

  // Expect all dispatchers to be unique.
  {
    auto mix_domain = threading_model->AcquireMixDomain("");
    EXPECT_NE(threading_model->FidlDomain().dispatcher(), mix_domain->dispatcher());
  }
  {
    auto mix_domain = threading_model->AcquireMixDomain("");
    EXPECT_NE(threading_model->IoDomain().dispatcher(), mix_domain->dispatcher());
  }
  EXPECT_NE(threading_model->FidlDomain().dispatcher(), threading_model->IoDomain().dispatcher());

  // And |AcquireMixDomain| returns different instances
  {
    auto mix_domain1 = threading_model->AcquireMixDomain("");
    auto mix_domain2 = threading_model->AcquireMixDomain("");
    EXPECT_NE(mix_domain1->dispatcher(), mix_domain2->dispatcher());
  }

  ValidateThreadingModel(threading_model.get());
}

}  // namespace
}  // namespace media::audio
