blob: 82e0438ed1cb438894e7bcb310610bf293b5a5ce [file] [log] [blame]
//===- unittests/Core/DepsBuildEngineTest.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/Basic/LLVM.h"
#include "llbuild/Core/BuildDB.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "gtest/gtest.h"
#include <unordered_map>
#include <vector>
#include <thread>
using namespace llbuild;
using namespace llbuild::core;
namespace {
class SimpleBuildEngineDelegate : public core::BuildEngineDelegate {
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 {
// We never expect a cycle.
fprintf(stderr, "error: cycle\n");
abort();
}
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<int(const std::vector<int>&)> ComputeFnType;
private:
std::vector<KeyType> inputs;
std::vector<int> inputValues;
ComputeFnType compute;
public:
SimpleTask(const std::vector<KeyType>& inputs, ComputeFnType compute)
: inputs(inputs), compute(compute)
{
inputValues.resize(inputs.size());
}
virtual void start(BuildEngine& engine) override {
// Request all of the inputs.
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, compute)); };
}
// Test for a tricky case involving concurrent dependency scanning.
//
// The problem that this test is checking for is that we don't immediately start
// running rules we have *scanned* as part of an input dependency, before they
// have actually been requested. For example, if a rule requests something like
// the contents of a directory, then requests something based on each file in
// the directory, we don't want to possibly run a rule for a file which was
// previously present (and thus recorded as an input dependency) but which is no
// longer present.
TEST(DepsBuildEngineTest, BogusConcurrentDepScan) {
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
core::BuildEngine engine(delegate);
// This models a rule which retrieves some dynamic content (like a directory
// list).
//
// We need to model a separate input to trigger the continued scanning
// behavior (so that when "output" is considering its "dir-list" input, it
// isn't immediately obvious it needs to run).
int dirListValue = 2 * 3;
engine.addRule({
"dir-list-input",
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("dir-list-input");
return dirListValue; }),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return false;
} });
engine.addRule({
"dir-list",
simpleAction({ "dir-list-input" }, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("dir-list");
assert(inputs.size() == 1);
return inputs[0]; }) });
// These are the rules for individual discovered "files".
engine.addRule({
"input-2",
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("input-2");
return 5; }),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return false;
} });
engine.addRule({
"input-3",
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back("input-3");
return 7; }),
[&](BuildEngine&, const Rule& rule, const ValueType& value) {
// Always rebuild
return false;
} });
// This models a rule which uses the dynamic content to drive some other
// action (compiling files).
class DynamicTask : public Task {
int result = 1;
public:
virtual void start(BuildEngine& engine) override {
// Request the known input.
engine.taskNeedsInput(this, "dir-list", 0);
}
virtual void provideValue(BuildEngine& engine, uintptr_t inputID,
const ValueType& value) override {
int N = intFromValue(value);
result *= N;
// If this is the initial input, use it to derive
// the subsequent inputs.
if (inputID == 0) {
if (N == 2) {
engine.taskNeedsInput(this, "input-2", 1);
} else if (N == 3) {
engine.taskNeedsInput(this, "input-3", 1);
} else {
assert(N == 2 * 3);
engine.taskNeedsInput(this, "input-2", 1);
engine.taskNeedsInput(this, "input-3", 1);
}
}
}
virtual void inputsAvailable(BuildEngine& engine) override {
engine.taskIsComplete(this, intToValue(result));
}
};
engine.addRule({
"output",
[&builtKeys] (BuildEngine& engine) {
builtKeys.push_back("output");
return engine.registerTask(new DynamicTask());
} });
// Build the first result.
EXPECT_EQ(2 * 3 * 5 * 7, intFromValue(engine.build("output")));
EXPECT_EQ(5U, builtKeys.size());
EXPECT_EQ("output", builtKeys[0]);
EXPECT_EQ("dir-list-input", builtKeys[1]);
EXPECT_EQ("dir-list", builtKeys[2]);
EXPECT_EQ("input-2", builtKeys[3]);
EXPECT_EQ("input-3", builtKeys[4]);
// Now change the dynamic contents and rebuild.
builtKeys.clear();
// Suppress a static analyzer false positive (rdar://problem/22165179).
#ifndef __clang_analyzer__
dirListValue = 3;
#endif
EXPECT_EQ(3 * 7, intFromValue(engine.build("output")));
EXPECT_EQ(4U, builtKeys.size());
EXPECT_EQ("dir-list-input", builtKeys[0]);
EXPECT_EQ("dir-list", builtKeys[1]);
EXPECT_EQ("output", builtKeys[2]);
EXPECT_EQ("input-3", builtKeys[3]);
}
TEST(DepsBuildEngineTest, KeysWithNull) {
// Check build engine support for keys with embedded null characters.
std::vector<std::string> builtKeys;
SimpleBuildEngineDelegate delegate;
// Create a temporary file.
llvm::SmallString<256> dbPath;
auto ec = llvm::sys::fs::createTemporaryFile("build", "db", dbPath);
EXPECT_EQ(bool(ec), false);
fprintf(stderr, "using db: %s\n", dbPath.c_str());
for (int iteration = 0; iteration < 2; ++iteration) {
fprintf(stderr, "iteration: %d\n", iteration);
core::BuildEngine engine(delegate);
// Attach the database.
{
std::string error;
auto db = createSQLiteBuildDB(dbPath, 1, &error);
EXPECT_EQ(bool(db), true);
if (!db) {
fprintf(stderr, "unable to open database: %s\n", error.c_str());
return;
}
engine.attachDB(std::move(db), &error);
}
std::string inputA{"i\0A", 3};
std::string inputB{"i\0B", 3};
engine.addRule({
inputA,
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back(inputA);
return 2; }) });
engine.addRule({
inputB,
simpleAction({}, [&] (const std::vector<int>& inputs) {
builtKeys.push_back(inputB);
return 3; }) });
engine.addRule({
"output",
simpleAction({ inputA, inputB }, [&] (const std::vector<int>& inputs) {
assert(inputs.size() == 2);
builtKeys.push_back("output");
return inputs[0] * inputs[1]; }) });
// Run the build.
builtKeys.clear();
EXPECT_EQ(2 * 3, intFromValue(engine.build("output")));
if (iteration == 0) {
EXPECT_EQ(3U, builtKeys.size());
EXPECT_EQ(inputA, builtKeys[0]);
EXPECT_EQ(inputB, builtKeys[1]);
EXPECT_EQ("output", builtKeys[2]);
} else {
EXPECT_EQ(0U, builtKeys.size());
}
}
ec = llvm::sys::fs::remove(dbPath.str());
EXPECT_EQ(bool(ec), false);
}
}