1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <map>
6#include <utility>
7#include <vector>
8
9#include "base/bind.h"
10#include "base/memory/linked_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "testing/gtest/include/gtest/gtest.h"
13#include "tools/gn/build_settings.h"
14#include "tools/gn/err.h"
15#include "tools/gn/loader.h"
16#include "tools/gn/parse_tree.h"
17#include "tools/gn/parser.h"
18#include "tools/gn/scheduler.h"
19#include "tools/gn/tokenizer.h"
20
21namespace {
22
23class MockInputFileManager {
24 public:
25  typedef base::Callback<void(const ParseNode*)> Callback;
26
27  MockInputFileManager() {
28  }
29
30  LoaderImpl::AsyncLoadFileCallback GetCallback();
31
32  // Sets a given response for a given source file.
33  void AddCannedResponse(const SourceFile& source_file,
34                         const std::string& source);
35
36  // Returns true if there is/are pending load(s) matching the given file(s).
37  bool HasOnePending(const SourceFile& f) const;
38  bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const;
39
40  void IssueAllPending();
41
42 private:
43  struct CannedResult {
44    scoped_ptr<InputFile> input_file;
45    std::vector<Token> tokens;
46    scoped_ptr<ParseNode> root;
47  };
48
49  bool AsyncLoadFile(const LocationRange& origin,
50                     const BuildSettings* build_settings,
51                     const SourceFile& file_name,
52                     const Callback& callback,
53                     Err* err) {
54    pending_.push_back(std::make_pair(file_name, callback));
55    return true;
56  }
57
58  // Owning pointers.
59  typedef std::map<SourceFile, linked_ptr<CannedResult> > CannedResponseMap;
60  CannedResponseMap canned_responses_;
61
62  std::vector< std::pair<SourceFile, Callback> > pending_;
63};
64
65LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetCallback() {
66  return base::Bind(&MockInputFileManager::AsyncLoadFile,
67                    base::Unretained(this));
68}
69
70// Sets a given response for a given source file.
71void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
72                                             const std::string& source) {
73  CannedResult* canned = new CannedResult;
74  canned->input_file.reset(new InputFile(source_file));
75  canned->input_file->SetContents(source);
76
77  // Tokenize.
78  Err err;
79  canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
80  EXPECT_FALSE(err.has_error());
81
82  // Parse.
83  canned->root = Parser::Parse(canned->tokens, &err).Pass();
84  EXPECT_FALSE(err.has_error());
85
86  canned_responses_[source_file] = linked_ptr<CannedResult>(canned);
87}
88
89bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
90  return pending_.size() == 1u && pending_[0].first == f;
91}
92
93bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
94                                         const SourceFile& f2) const {
95  if (pending_.size() != 2u)
96    return false;
97  return pending_[0].first == f1 && pending_[1].first == f2;
98}
99
100void MockInputFileManager::IssueAllPending() {
101  BlockNode block(false);  // Default response.
102
103  for (size_t i = 0; i < pending_.size(); i++) {
104    CannedResponseMap::const_iterator found =
105        canned_responses_.find(pending_[i].first);
106    if (found == canned_responses_.end())
107      pending_[i].second.Run(&block);
108    else
109      pending_[i].second.Run(found->second->root.get());
110  }
111  pending_.clear();
112}
113
114// LoaderTest ------------------------------------------------------------------
115
116class LoaderTest : public testing::Test {
117 public:
118  LoaderTest() {
119    build_settings_.SetBuildDir(SourceDir("//out/Debug/"));
120  }
121  virtual ~LoaderTest() {
122  }
123
124 protected:
125  Scheduler scheduler_;
126  BuildSettings build_settings_;
127  MockInputFileManager mock_ifm_;
128};
129
130}  // namespace
131
132// -----------------------------------------------------------------------------
133
134TEST_F(LoaderTest, Foo) {
135  SourceFile build_config("//build/config/BUILDCONFIG.gn");
136  build_settings_.set_build_config_file(build_config);
137
138  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
139
140  // The default toolchain needs to be set by the build config file.
141  mock_ifm_.AddCannedResponse(build_config,
142                              "set_default_toolchain(\"//tc:tc\")");
143
144  loader->set_async_load_file(mock_ifm_.GetCallback());
145
146  // Request the root build file be loaded. This should kick off the default
147  // build config loading.
148  SourceFile root_build("//BUILD.gn");
149  loader->Load(root_build, Label());
150  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
151
152  // Completing the build config load should kick off the root build file load.
153  mock_ifm_.IssueAllPending();
154  scheduler_.main_loop()->RunUntilIdle();
155  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
156
157  // Load the root build file.
158  mock_ifm_.IssueAllPending();
159  scheduler_.main_loop()->RunUntilIdle();
160
161  // Schedule some other file to load in another toolchain.
162  Label second_tc(SourceDir("//tc2/"), "tc2");
163  SourceFile second_file("//foo/BUILD.gn");
164  loader->Load(second_file, second_tc);
165  EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
166
167  // Running the toolchain file should schedule the build config file to load
168  // for that toolchain.
169  mock_ifm_.IssueAllPending();
170  scheduler_.main_loop()->RunUntilIdle();
171
172  // We have to tell it we have a toolchain definition now (normally the
173  // builder would do this).
174  const Settings* default_settings = loader->GetToolchainSettings(Label());
175  Toolchain second_tc_object(default_settings, second_tc);
176  loader->ToolchainLoaded(&second_tc_object);
177  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
178
179  // Scheduling a second file to load in that toolchain should not make it
180  // pending yet (it's waiting for the build config).
181  SourceFile third_file("//bar/BUILD.gn");
182  loader->Load(third_file, second_tc);
183  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
184
185  // Running the build config file should make our third file pending.
186  mock_ifm_.IssueAllPending();
187  scheduler_.main_loop()->RunUntilIdle();
188  EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
189
190  EXPECT_FALSE(scheduler_.is_failed());
191}
192