text: // Copyright (c) 2013 The Chromium 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 <map>
  #include <utility>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/run_loop.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "tools/gn/build_settings.h"
  #include "tools/gn/err.h"
  #include "tools/gn/loader.h"
  #include "tools/gn/parse_tree.h"
  #include "tools/gn/parser.h"
  #include "tools/gn/scheduler.h"
  #include "tools/gn/tokenizer.h"
  
  namespace {
  
  class MockInputFileManager {
   public:
    typedef base::Callback<void(const ParseNode*)> Callback;
  
    MockInputFileManager() {
    }
  
    LoaderImpl::AsyncLoadFileCallback GetCallback();
  
    // Sets a given response for a given source file.
    void AddCannedResponse(const SourceFile& source_file,
                           const std::string& source);
  
    // Returns true if there is/are pending load(s) matching the given file(s).
    bool HasOnePending(const SourceFile& f) const;
    bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const;
  
    void IssueAllPending();
  
   private:
    struct CannedResult {
      std::unique_ptr<InputFile> input_file;
      std::vector<Token> tokens;
      std::unique_ptr<ParseNode> root;
    };
  
    bool AsyncLoadFile(const LocationRange& origin,
                       const BuildSettings* build_settings,
                       const SourceFile& file_name,
                       const Callback& callback,
                       Err* err) {
      pending_.push_back(std::make_pair(file_name, callback));
      return true;
    }
  
    typedef std::map<SourceFile, std::unique_ptr<CannedResult>> CannedResponseMap;
    CannedResponseMap canned_responses_;
  
    std::vector< std::pair<SourceFile, Callback> > pending_;
  };
  
  LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetCallback() {
    return base::Bind(&MockInputFileManager::AsyncLoadFile,
                      base::Unretained(this));
  }
  
  // Sets a given response for a given source file.
  void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
                                               const std::string& source) {
    std::unique_ptr<CannedResult> canned(new CannedResult);
    canned->input_file.reset(new InputFile(source_file));
    canned->input_file->SetContents(source);
  
    // Tokenize.
    Err err;
    canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
    EXPECT_FALSE(err.has_error());
  
    // Parse.
    canned->root = Parser::Parse(canned->tokens, &err);
    EXPECT_FALSE(err.has_error());
  
    canned_responses_[source_file] = std::move(canned);
  }
  
  bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
    return pending_.size() == 1u && pending_[0].first == f;
  }
  
  bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
                                           const SourceFile& f2) const {
    if (pending_.size() != 2u)
      return false;
    return pending_[0].first == f1 && pending_[1].first == f2;
  }
  
  void MockInputFileManager::IssueAllPending() {
    BlockNode block(BlockNode::DISCARDS_RESULT);  // Default response.
  
    for (const auto& cur : pending_) {
      CannedResponseMap::const_iterator found = canned_responses_.find(cur.first);
      if (found == canned_responses_.end())
        cur.second.Run(&block);
      else
        cur.second.Run(found->second->root.get());
    }
    pending_.clear();
  }
  
  class MockBuilder {
   public:
    void OnItemDefined(std::unique_ptr<Item> item);
  
    bool HasOneDefined(std::set<std::string> source_files) const;
    void Cleanup();
  
   private:
    std::vector<std::unique_ptr<Item>> items_;
  };
  
  void MockBuilder::OnItemDefined(std::unique_ptr<Item> item) {
    items_.push_back(std::move(item));
  }
  
  bool MockBuilder::HasOneDefined(std::set<std::string> source_files) const {
    if (items_.size() != 1u)
      return false;
  
    const auto& input_files = items_[0]->input_files();
    for (const InputFile* input_file : input_files) {
      auto found_source_file = source_files.find(input_file->name().value());
      if (found_source_file == source_files.end())
        return false;
  
      source_files.erase(found_source_file);
    }
  
    return source_files.empty();
  };
  
  void MockBuilder::Cleanup() {
    items_.clear();
  }
  
  // LoaderTest ------------------------------------------------------------------
  
  class LoaderTest : public testing::Test {
   public:
    LoaderTest() {
      build_settings_.SetBuildDir(SourceDir("//out/Debug/"));
    }
  
   protected:
    Scheduler scheduler_;
    BuildSettings build_settings_;
    MockBuilder mock_builder_;
    MockInputFileManager mock_ifm_;
    std::vector<std::unique_ptr<Item>> items_;
  };
  
  }  // namespace
  
  // -----------------------------------------------------------------------------
  
  TEST_F(LoaderTest, Foo) {
    SourceFile build_config("//build/config/BUILDCONFIG.gn");
    build_settings_.set_build_config_file(build_config);
  
    scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
  
    // The default toolchain needs to be set by the build config file.
    mock_ifm_.AddCannedResponse(build_config,
                                "set_default_toolchain(\"//tc:tc\")");
  
    loader->set_async_load_file(mock_ifm_.GetCallback());
  
    // Request the root build file be loaded. This should kick off the default
    // build config loading.
    SourceFile root_build("//BUILD.gn");
    loader->Load(root_build, LocationRange(), Label());
    EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
  
    // Completing the build config load should kick off the root build file load.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
  
    // Load the root build file.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
  
    // Schedule some other file to load in another toolchain.
    Label second_tc(SourceDir("//tc2/"), "tc2");
    SourceFile second_file("//foo/BUILD.gn");
    loader->Load(second_file, LocationRange(), second_tc);
    EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
  
    // Running the toolchain file should schedule the build config file to load
    // for that toolchain.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
  
    // We have to tell it we have a toolchain definition now (normally the
    // builder would do this).
    const Settings* default_settings = loader->GetToolchainSettings(Label());
    Toolchain second_tc_object(default_settings, second_tc, {});
    loader->ToolchainLoaded(&second_tc_object);
    EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
  
    // Scheduling a second file to load in that toolchain should not make it
    // pending yet (it's waiting for the build config).
    SourceFile third_file("//bar/BUILD.gn");
    loader->Load(third_file, LocationRange(), second_tc);
    EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
  
    // Running the build config file should make our third file pending.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
  
    EXPECT_FALSE(scheduler_.is_failed());
  }
  
  TEST_F(LoaderTest, SourceFilesAreCollected) {
    SourceFile build_config("//build/config/BUILDCONFIG.gn");
    SourceFile root_build("//BUILD.gn");
    build_settings_.set_build_config_file(build_config);
  
    build_settings_.set_item_defined_callback(base::Bind(
        &MockBuilder::OnItemDefined, base::Unretained(&mock_builder_)));
  
    scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
  
    mock_ifm_.AddCannedResponse(build_config,
                                "set_default_toolchain(\"//tc:tc\")");
    mock_ifm_.AddCannedResponse(root_build,
                                "executable(\"a\") { sources = [ \"a.cc\" ] }");
  
    loader->set_async_load_file(mock_ifm_.GetCallback());
  
    // Request the root build file be loaded. This should kick off the default
    // build config loading.
    loader->Load(root_build, LocationRange(), Label());
    EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
  
    // Completing the build config load should kick off the root build file load.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
  
    // Completing the root build file should define a target which must have
    // set of source files hashes.
    mock_ifm_.IssueAllPending();
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(mock_builder_.HasOneDefined({"//BUILD.gn"}));
  
    EXPECT_FALSE(scheduler_.is_failed());
  }
  