| //===- 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); |
| } |
| |
| } |