blob: 74968eb89e001dbeec1609fe13f934a01962ab39 [file] [log] [blame]
//===- unittests/Core/BuildEngineTest.cpp ---------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "llbuild/Core/BuildEngine.h"
#include "llbuild/Core/BuildDB.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/ErrorHandling.h"
#include "gtest/gtest.h"
#include <unordered_map>
#include <vector>
using namespace llbuild;
using namespace llbuild::core;
namespace {
class SimpleBuildEngineDelegate : public core::BuildEngineDelegate {
/// The cycle, if one was detected during building.
public:
std::vector<std::string> cycle;
private:
virtual core::Rule lookupRule(const core::KeyType& key) override {
// We never expect dynamic rule lookup.
fprintf(stderr, "error: unexpected rule lookup for \"%s\"\n",
key.c_str());
abort();
return core::Rule();
}
virtual void cycleDetected(const std::vector<core::Rule*>& items) override {
cycle.clear();
std::transform(items.begin(), items.end(), std::back_inserter(cycle),
[](auto rule) { return std::string(rule->key); });
}
virtual void error(const Twine& message) override {
fprintf(stderr, "error: %s\n", message.str().c_str());
abort();
}
};
static int32_t intFromValue(const core::ValueType& value) {
assert(value.size() == 4);
return ((value[0] << 0) |
(value[1] << 8) |
(value[2] << 16) |
(value[3] << 24));
}
static core::ValueType intToValue(int32_t value) {
std::vector<uint8_t> result(4);
result[0] = (value >> 0) & 0xFF;
result[1] = (value >> 8) & 0xFF;
result[2] = (value >> 16) & 0xFF;
result[3] = (value >> 24) & 0xFF;
return result;
}
// Simple task implementation which takes a fixed set of dependencies, evaluates
// them all, and then provides the output.
class SimpleTask : public Task {
public:
typedef std::function<std::vector<KeyType>()> InputListingFnType;
typedef std::function<int(const std::vector<int>&)> ComputeFnType;
private:
InputListingFnType listInputs;
std::vector<int> inputValues;
ComputeFnType compute;
public:
SimpleTask(InputListingFnType listInputs, ComputeFnType compute)
: listInputs(listInputs), compute(compute)
{
}
virtual void start(BuildEngine& engine) override {
// Compute the list of inputs.
auto inputs = listInputs();
// Request all of the inputs.
inputValues.resize(inputs.size());
for (int i = 0, e = inputs.size(); i != e; ++i) {
engine.taskNeedsInput(this, inputs[i], i);
}
}
virtual void provideValue(BuildEngine&, uintptr_t inputID,
const ValueType& value) override {
// Update the input values.
assert(inputID < inputValues.size());
inputValues[inputID] = intFromValue(value);
}
virtual void inputsAvailable(core::BuildEngine& engine) override {
engine.taskIsComplete(this, intToValue(compute(inputValues)));
}
};
// Helper function for creating a simple action.
typedef std::function<Task*(BuildEngine&)> ActionFn;
static ActionFn simpleAction(const std::vector<KeyType>& inputs,
SimpleTask::ComputeFnType compute) {
return [=] (BuildEngine& engine) {
return engine.registerTask(new SimpleTask([inputs]{ return inputs; },
compute)); };
}
TEST(BuildEngineTest, basic) {
// Check a trivial build graph.
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
engine.addRule({
"value-A", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-A");
return 2; }) });
engine.addRule({
"value-B", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-B");
return 3; }) });
engine.addRule({
"result",
simpleAction({"value-A", "value-B"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(2, inputs[0]);
EXPECT_EQ(3, inputs[1]);
builtKeys.push_back("result");
return inputs[0] * inputs[1] * 5;
}) });
// Build the result.
EXPECT_EQ(2 * 3 * 5, intFromValue(engine.build("result")));
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-B", builtKeys[1]);
EXPECT_EQ("result", builtKeys[2]);
// Check that we can get results for already built nodes, without building
// anything.
builtKeys.clear();
EXPECT_EQ(2, intFromValue(engine.build("value-A")));
EXPECT_TRUE(builtKeys.empty());
builtKeys.clear();
EXPECT_EQ(3, intFromValue(engine.build("value-B")));
EXPECT_TRUE(builtKeys.empty());
}
TEST(BuildEngineTest, basicWithSharedInput) {
// Check a build graph with a input key shared by multiple rules.
//
// Dependencies:
// value-C: (value-A, value-B)
// value-R: (value-A, value-C)
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
engine.addRule({
"value-A", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-A");
return 2; }) });
engine.addRule({
"value-B", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-B");
return 3; }) });
engine.addRule({
"value-C",
simpleAction({"value-A", "value-B"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(2, inputs[0]);
EXPECT_EQ(3, inputs[1]);
builtKeys.push_back("value-C");
return inputs[0] * inputs[1] * 5;
}) });
engine.addRule({
"value-R",
simpleAction({"value-A", "value-C"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(2, inputs[0]);
EXPECT_EQ(2 * 3 * 5, inputs[1]);
builtKeys.push_back("value-R");
return inputs[0] * inputs[1] * 7;
}) });
// Build the result.
EXPECT_EQ(2 * 2 * 3 * 5 * 7, intFromValue(engine.build("value-R")));
EXPECT_EQ(4U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-B", builtKeys[1]);
EXPECT_EQ("value-C", builtKeys[2]);
EXPECT_EQ("value-R", builtKeys[3]);
}
TEST(BuildEngineTest, veryBasicIncremental) {
// Check a trivial build graph responds to incremental changes appropriately.
//
// Dependencies:
// value-R: (value-A, value-B)
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
int valueA = 2;
int valueB = 3;
engine.addRule({
"value-A", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-A");
return valueA; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType value) {
return valueA == intFromValue(value);
} });
engine.addRule({
"value-B", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-B");
return valueB; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType& value) {
return valueB == intFromValue(value);
} });
engine.addRule({
"value-R",
simpleAction({"value-A", "value-B"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(valueA, inputs[0]);
EXPECT_EQ(valueB, inputs[1]);
builtKeys.push_back("value-R");
return inputs[0] * inputs[1] * 5;
}) });
// Build the first result.
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("value-R")));
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-B", builtKeys[1]);
EXPECT_EQ("value-R", builtKeys[2]);
// Mark value-A as having changed, then rebuild and sanity check.
valueA = 7;
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("value-R")));
EXPECT_EQ(2U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-R", builtKeys[1]);
// Check that a subsequent build is null.
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("value-R")));
EXPECT_EQ(0U, builtKeys.size());
}
TEST(BuildEngineTest, basicIncremental) {
// Check a build graph responds to incremental changes appropriately.
//
// Dependencies:
// value-C: (value-A, value-B)
// value-R: (value-A, value-C)
// value-D: (value-R)
// value-R2: (value-D)
//
// If value-A or value-B change, then value-C and value-R are recomputed when
// value-R is built, but value-R2 is not recomputed.
//
// If value-R2 is built, then value-B changes and value-R is built, then when
// value-R2 is built again only value-D and value-R2 are rebuilt.
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
int valueA = 2;
int valueB = 3;
engine.addRule({
"value-A", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-A");
return valueA; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType& value) {
return valueA == intFromValue(value);
} });
engine.addRule({
"value-B", simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-B");
return valueB; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType& value) {
return valueB == intFromValue(value);
} });
engine.addRule({
"value-C",
simpleAction({"value-A", "value-B"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(valueA, inputs[0]);
EXPECT_EQ(valueB, inputs[1]);
builtKeys.push_back("value-C");
return inputs[0] * inputs[1] * 5;
}) });
engine.addRule({
"value-R",
simpleAction({"value-A", "value-C"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(2U, inputs.size());
EXPECT_EQ(valueA, inputs[0]);
EXPECT_EQ(valueA * valueB * 5, inputs[1]);
builtKeys.push_back("value-R");
return inputs[0] * inputs[1] * 7;
}) });
engine.addRule({
"value-D",
simpleAction({"value-R"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(1U, inputs.size());
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
inputs[0]);
builtKeys.push_back("value-D");
return inputs[0] * 11;
}) });
engine.addRule({
"value-R2",
simpleAction({"value-D"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(1U, inputs.size());
EXPECT_EQ(valueA * valueA * valueB * 5 * 7 * 11,
inputs[0]);
builtKeys.push_back("value-R2");
return inputs[0] * 13;
}) });
// Build the first result.
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
intFromValue(engine.build("value-R")));
EXPECT_EQ(4U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-B", builtKeys[1]);
EXPECT_EQ("value-C", builtKeys[2]);
EXPECT_EQ("value-R", builtKeys[3]);
// Mark value-A as having changed, then rebuild and sanity check.
valueA = 17;
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
intFromValue(engine.build("value-R")));
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ("value-A", builtKeys[0]);
EXPECT_EQ("value-C", builtKeys[1]);
EXPECT_EQ("value-R", builtKeys[2]);
// Mark value-B as having changed, then rebuild and sanity check.
valueB = 19;
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
intFromValue(engine.build("value-R")));
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ("value-B", builtKeys[0]);
EXPECT_EQ("value-C", builtKeys[1]);
EXPECT_EQ("value-R", builtKeys[2]);
// Build value-R2 for the first time.
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7 * 11 * 13,
intFromValue(engine.build("value-R2")));
EXPECT_EQ(2U, builtKeys.size());
EXPECT_EQ("value-D", builtKeys[0]);
EXPECT_EQ("value-R2", builtKeys[1]);
// Now mark value-B as having changed, then rebuild value-R, then build
// value-R2 and sanity check.
valueB = 23;
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
intFromValue(engine.build("value-R")));
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ("value-B", builtKeys[0]);
EXPECT_EQ("value-C", builtKeys[1]);
EXPECT_EQ("value-R", builtKeys[2]);
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7 * 11 * 13,
intFromValue(engine.build("value-R2")));
EXPECT_EQ(2U, builtKeys.size());
EXPECT_EQ("value-D", builtKeys[0]);
EXPECT_EQ("value-R2", builtKeys[1]);
// Final sanity check.
builtKeys.clear();
EXPECT_EQ(valueA * valueA * valueB * 5 * 7,
intFromValue(engine.build("value-R")));
EXPECT_EQ(valueA * valueA * valueB * 5 * 7 * 11 * 13,
intFromValue(engine.build("value-R2")));
EXPECT_EQ(0U, builtKeys.size());
}
TEST(BuildEngineTest, incrementalDependency) {
// Check that the engine properly clears the individual result dependencies
// when a rule is rerun.
//
// Dependencies:
// value-R: (value-A)
//
// FIXME: This test is rather cumbersome for all it is trying to check, maybe
// we need a logging BuildDB implementation or something that would be easier
// to test against (or just update the trace to track it).
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
// Attach a custom database, used to get the results.
class CustomDB : public BuildDB {
public:
llvm::StringMap<bool> keyTable;
std::unordered_map<KeyType, Result> ruleResults;
virtual KeyID getKeyID(const KeyType& key) override {
auto it = keyTable.insert(std::make_pair(key, false)).first;
return (KeyID)(uintptr_t)it->getKey().data();
}
virtual KeyType getKeyForID(KeyID keyID) override {
return llvm::StringMapEntry<bool>::GetStringMapEntryFromKeyData(
(const char*)(uintptr_t)keyID).getKey();
}
virtual uint64_t getCurrentIteration(bool* success_out, std::string* error_out) override {
return 0;
}
virtual bool setCurrentIteration(uint64_t value, std::string* error_out) override { return true; }
virtual bool lookupRuleResult(KeyID keyID,
const Rule& rule,
Result* result_out,
std::string* error_out) override {
return false;
}
virtual bool setRuleResult(KeyID key,
const Rule& rule,
const Result& result,
std::string* error_out) override {
ruleResults[rule.key] = result;
return true;
}
virtual bool buildStarted(std::string* error_out) override { return true; }
virtual void buildComplete() override {}
};
CustomDB *db = new CustomDB();
std::string error;
engine.attachDB(std::unique_ptr<CustomDB>(db), &error);
int valueA = 2;
engine.addRule({
"value-A", simpleAction({}, [&] (const std::vector<int>& inputs) {
return valueA; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType& value) {
return valueA == intFromValue(value);
} });
engine.addRule({
"value-R",
simpleAction({"value-A"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(1U, inputs.size());
EXPECT_EQ(valueA, inputs[0]);
return inputs[0] * 3;
}) });
// Build the first result.
EXPECT_EQ(valueA * 3, intFromValue(engine.build("value-R")));
// Mark value-A as having changed, then rebuild.
valueA = 5;
EXPECT_EQ(valueA * 3, intFromValue(engine.build("value-R")));
// Check the rule results.
const Result& valueRResult = db->ruleResults["value-R"];
EXPECT_EQ(valueA * 3, intFromValue(valueRResult.value));
EXPECT_EQ(1U, valueRResult.dependencies.size());
}
TEST(BuildEngineTest, deepDependencyScanningStack) {
// Check that the engine can handle dependency scanning of a very deep stack,
// which would probably crash blowing the stack if the engine used naive
// recursion.
//
// FIXME: It would be nice to run this on a thread with a small stack to
// guarantee this, and to be able to make the depth small so the test runs
// faster.
int depth = 10000;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
int lastInputValue = 0;
for (int i = 0; i != depth; ++i) {
char name[32];
sprintf(name, "input-%d", i);
if (i != depth-1) {
char inputName[32];
sprintf(inputName, "input-%d", i+1);
engine.addRule({
name, simpleAction({ inputName },
[] (const std::vector<int>& inputs) {
return inputs[0]; }) });
} else {
engine.addRule({
name,
simpleAction({},
[&] (const std::vector<int>& inputs) {
return lastInputValue; }),
[&](core::BuildEngine&, const Rule& rule, const ValueType& value) {
// FIXME: Once we have custom ValueType objects, we would like to
// have timestamps on the value and just compare to a timestamp
// (similar to what we would do for a file).
return lastInputValue == intFromValue(value);
} });
}
}
// Build the first result.
lastInputValue = 42;
EXPECT_EQ(lastInputValue, intFromValue(engine.build("input-0")));
// Perform a null build on the result.
EXPECT_EQ(lastInputValue, intFromValue(engine.build("input-0")));
// Perform a full rebuild on the result.
lastInputValue = 52;
EXPECT_EQ(lastInputValue, intFromValue(engine.build("input-0")));
}
TEST(BuildEngineTest, discoveredDependencies) {
// Check basic support for tasks to report discovered dependencies.
// This models a task which has some out-of-band way to read the input.
class TaskWithDiscoveredDependency : public Task {
int& valueB;
int computedInputValue = -1;
public:
TaskWithDiscoveredDependency(int& valueB) : valueB(valueB) { }
virtual void start(BuildEngine& engine) override {
// Request the known input.
engine.taskNeedsInput(this, "value-A", 0);
}
virtual void provideValue(BuildEngine&, uintptr_t inputID,
const ValueType& value) override {
assert(inputID == 0);
computedInputValue = intFromValue(value);
}
virtual void inputsAvailable(core::BuildEngine& engine) override {
// Report the discovered dependency.
engine.taskDiscoveredDependency(this, "value-B");
engine.taskIsComplete(this, intToValue(computedInputValue * valueB * 5));
}
};
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
int valueA = 2;
int valueB = 3;
engine.addRule({
"value-A",
simpleAction({ },
[&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-A");
return valueA;
}),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
return valueA == intFromValue(value);
} });
engine.addRule({
"value-B",
simpleAction({ },
[&] (const std::vector<int>& inputs) {
builtKeys.push_back("value-B");
return valueB;
}),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
return valueB == intFromValue(value);
} });
engine.addRule({
"output",
[&valueB, &builtKeys] (BuildEngine& engine) {
builtKeys.push_back("output");
return engine.registerTask(new TaskWithDiscoveredDependency(valueB));
} });
// Build the first result.
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("output")));
EXPECT_EQ(std::vector<std::string>({ "output", "value-A", "value-B" }),
builtKeys);
// Verify that the next build is a null build.
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("output")));
EXPECT_EQ(std::vector<std::string>(), builtKeys);
// Verify that the build depends on valueB.
valueB = 7;
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("output")));
EXPECT_EQ(std::vector<std::string>({ "value-B", "output" }),
builtKeys);
// Verify again that the next build is a null build.
builtKeys.clear();
EXPECT_EQ(valueA * valueB * 5, intFromValue(engine.build("output")));
EXPECT_EQ(std::vector<std::string>(), builtKeys);
}
TEST(BuildEngineTest, unchangedOutputs) {
// Check building with unchanged outputs.
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
engine.addRule({
"value",
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("value");
return 2; }),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return false;
} });
engine.addRule({
"result",
simpleAction({"value"},
[&] (const std::vector<int>& inputs) {
EXPECT_EQ(1U, inputs.size());
EXPECT_EQ(2, inputs[0]);
builtKeys.push_back("result");
return inputs[0] * 3;
}) });
// Build the result.
EXPECT_EQ(2 * 3, intFromValue(engine.build("result")));
EXPECT_EQ(2U, builtKeys.size());
EXPECT_EQ("value", builtKeys[0]);
EXPECT_EQ("result", builtKeys[1]);
// Rebuild the result.
//
// Only "value" should rebuild, as it explicitly declares itself invalid each
// time, but "result" should not need to rerun.
builtKeys.clear();
EXPECT_EQ(2 * 3, intFromValue(engine.build("result")));
EXPECT_EQ(1U, builtKeys.size());
EXPECT_EQ("value", builtKeys[0]);
}
TEST(BuildEngineTest, StatusCallbacks) {
unsigned numScanned = 0;
unsigned numComplete = 0;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
engine.addRule({
"input",
simpleAction({}, [&] (const std::vector<int>& inputs) {
return 2; }),
nullptr,
[&] (BuildEngine&, core::Rule::StatusKind status) {
if (status == core::Rule::StatusKind::IsScanning) {
++numScanned;
} else {
assert(status == core::Rule::StatusKind::IsComplete);
++numComplete;
}
} });
engine.addRule({
"output",
simpleAction({"input"},
[&] (const std::vector<int>& inputs) {
return inputs[0] * 3;
}),
nullptr,
[&] (BuildEngine&, core::Rule::StatusKind status) {
if (status == core::Rule::StatusKind::IsScanning) {
++numScanned;
} else {
assert(status == core::Rule::StatusKind::IsComplete);
++numComplete;
}
} });
// Build the result.
EXPECT_EQ(2 * 3, intFromValue(engine.build("output")));
EXPECT_EQ(2U, numScanned);
EXPECT_EQ(2U, numComplete);
}
/// Check basic cycle detection.
TEST(BuildEngineTest, SimpleCycle) {
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
engine.addRule({
"A",
simpleAction({"B"}, [&](const std::vector<int>& inputs) {
return 2; }) });
engine.addRule({
"B",
simpleAction({"A"}, [&](const std::vector<int>& inputs) {
return 2; }) });
// Build the result.
auto result = engine.build("A");
EXPECT_EQ(ValueType{}, result);
EXPECT_EQ(std::vector<std::string>({ "A", "B", "A" }), delegate.cycle);
}
/// Check detection of a cycle discovered during scanning from input.
TEST(BuildEngineTest, CycleDuringScanningFromTop) {
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
unsigned iteration = 0;
engine.addRule({
"A",
[&](BuildEngine& engine) {
return engine.registerTask(
new SimpleTask(
[&]() -> std::vector<std::string> {
switch (iteration) {
case 0:
return { "B", "C" };
case 1:
return { "B" };
default:
llvm::report_fatal_error("unexpected iterator");
}
},
[&](const std::vector<int>& inputs) {
return 2; }));
},
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return true;
}
});
engine.addRule({
"B",
[&](BuildEngine& engine) {
return engine.registerTask(
new SimpleTask(
[&]() -> std::vector<std::string> {
switch (iteration) {
case 0:
return { "C" };
case 1:
return { "C" };
default:
llvm::report_fatal_error("unexpected iterator");
}
},
[&](const std::vector<int>& inputs) {
return 2; }));
},
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return true;
}
});
engine.addRule({
"C",
[&](BuildEngine& engine) {
return engine.registerTask(
new SimpleTask(
[&]() -> std::vector<std::string> {
switch (iteration) {
case 0:
return { };
case 1:
return { "B" };
default:
llvm::report_fatal_error("unexpected iterator");
}
},
[&](const std::vector<int>& inputs) {
return 2; }));
},
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return false;
}
});
// Build the result.
{
EXPECT_EQ(2, intFromValue(engine.build("A")));
EXPECT_EQ(std::vector<std::string>({}), delegate.cycle);
}
// Introduce a cycle, and rebuild.
{
iteration = 1;
auto result = engine.build("A");
EXPECT_EQ(ValueType{}, result);
EXPECT_EQ(std::vector<std::string>({ "A", "C", "B", "C" }), delegate.cycle);
}
}
}