Skip to content

Commit

Permalink
Add tests for build module
Browse files Browse the repository at this point in the history
Add tests that verify the token functionality of the builder main loop.
We replace the default fake command runner with a special version where
the tests can control each call to AcquireToken(), CanRunMore() and
WaitForCommand().
  • Loading branch information
stefanb2 committed Feb 19, 2023
1 parent cf02adc commit f99d747
Showing 1 changed file with 364 additions and 0 deletions.
364 changes: 364 additions & 0 deletions src/build_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "build.h"

#include <assert.h>
#include <stdarg.h>

#include "build_log.h"
#include "deps_log.h"
Expand Down Expand Up @@ -4298,3 +4299,366 @@ TEST_F(BuildTest, ValidationWithCircularDependency) {
EXPECT_FALSE(builder_.AddTarget("out", &err));
EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err);
}

/// The token tests are concerned with the main loop functionality when
// the CommandRunner has an active TokenPool. It is therefore intentional
// that the plan doesn't complete and that builder_.Build() returns false!

/// Fake implementation of CommandRunner that simulates a TokenPool
struct FakeTokenCommandRunner : public CommandRunner {
explicit FakeTokenCommandRunner() {}

// CommandRunner impl
virtual bool CanRunMore() const;
virtual bool AcquireToken();
virtual bool StartCommand(Edge* edge);
virtual bool WaitForCommand(Result* result, bool more_ready);
virtual vector<Edge*> GetActiveEdges();
virtual void Abort();

vector<string> commands_ran_;
vector<Edge *> edges_;

vector<bool> acquire_token_;
vector<bool> can_run_more_;
vector<bool> wait_for_command_;
};

bool FakeTokenCommandRunner::CanRunMore() const {
if (can_run_more_.size() == 0) {
EXPECT_FALSE("unexpected call to CommandRunner::CanRunMore()");
return false;
}

bool result = can_run_more_[0];

// Unfortunately CanRunMore() isn't "const" for tests
const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.erase(
const_cast<FakeTokenCommandRunner*>(this)->can_run_more_.begin()
);

return result;
}

bool FakeTokenCommandRunner::AcquireToken() {
if (acquire_token_.size() == 0) {
EXPECT_FALSE("unexpected call to CommandRunner::AcquireToken()");
return false;
}

bool result = acquire_token_[0];
acquire_token_.erase(acquire_token_.begin());
return result;
}

bool FakeTokenCommandRunner::StartCommand(Edge* edge) {
commands_ran_.push_back(edge->EvaluateCommand());
edges_.push_back(edge);
return true;
}

bool FakeTokenCommandRunner::WaitForCommand(Result* result, bool more_ready) {
if (wait_for_command_.size() == 0) {
EXPECT_FALSE("unexpected call to CommandRunner::WaitForCommand()");
return false;
}

bool expected = wait_for_command_[0];
if (expected != more_ready) {
EXPECT_EQ(expected, more_ready);
return false;
}
wait_for_command_.erase(wait_for_command_.begin());

if (edges_.size() == 0)
return false;

Edge* edge = edges_[0];
result->edge = edge;

if (more_ready &&
(edge->rule().name() == "token-available")) {
result->status = ExitTokenAvailable;
} else {
edges_.erase(edges_.begin());
result->status = ExitSuccess;
}

return true;
}

vector<Edge*> FakeTokenCommandRunner::GetActiveEdges() {
return edges_;
}

void FakeTokenCommandRunner::Abort() {
edges_.clear();
}

struct BuildTokenTest : public BuildTest {
virtual void SetUp();
virtual void TearDown();

FakeTokenCommandRunner token_command_runner_;

void ExpectAcquireToken(int count, ...);
void ExpectCanRunMore(int count, ...);
void ExpectWaitForCommand(int count, ...);

private:
void EnqueueBooleans(vector<bool>& booleans, int count, va_list ao);
};

void BuildTokenTest::SetUp() {
BuildTest::SetUp();

// replace FakeCommandRunner with FakeTokenCommandRunner
builder_.command_runner_.release();
builder_.command_runner_.reset(&token_command_runner_);
}
void BuildTokenTest::TearDown() {
EXPECT_EQ(0u, token_command_runner_.acquire_token_.size());
EXPECT_EQ(0u, token_command_runner_.can_run_more_.size());
EXPECT_EQ(0u, token_command_runner_.wait_for_command_.size());

BuildTest::TearDown();
}

void BuildTokenTest::ExpectAcquireToken(int count, ...) {
va_list ap;
va_start(ap, count);
EnqueueBooleans(token_command_runner_.acquire_token_, count, ap);
va_end(ap);
}

void BuildTokenTest::ExpectCanRunMore(int count, ...) {
va_list ap;
va_start(ap, count);
EnqueueBooleans(token_command_runner_.can_run_more_, count, ap);
va_end(ap);
}

void BuildTokenTest::ExpectWaitForCommand(int count, ...) {
va_list ap;
va_start(ap, count);
EnqueueBooleans(token_command_runner_.wait_for_command_, count, ap);
va_end(ap);
}

void BuildTokenTest::EnqueueBooleans(vector<bool>& booleans, int count, va_list ap) {
while (count--) {
int value = va_arg(ap, int);
booleans.push_back(!!value); // force bool
}
}

TEST_F(BuildTokenTest, CompleteNoWork) {
// plan should not execute anything
string err;

EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);

EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
}

TEST_F(BuildTokenTest, DoNotAquireToken) {
// plan should execute one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat1", &err));
ASSERT_EQ("", err);

// pretend we can't run anything
ExpectCanRunMore(1, false);

EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("stuck [this is a bug]", err);

EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
}

TEST_F(BuildTokenTest, DoNotStartWithoutToken) {
// plan should execute one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat1", &err));
ASSERT_EQ("", err);

// we could run a command but do not have a token for it
ExpectCanRunMore(1, true);
ExpectAcquireToken(1, false);

EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("stuck [this is a bug]", err);

EXPECT_EQ(0u, token_command_runner_.commands_ran_.size());
}

TEST_F(BuildTokenTest, CompleteOneStep) {
// plan should execute one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat1", &err));
ASSERT_EQ("", err);

// allow running of one command
ExpectCanRunMore(1, true);
ExpectAcquireToken(1, true);
// block and wait for command to finalize
ExpectWaitForCommand(1, false);

EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);

EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1");
}

TEST_F(BuildTokenTest, AcquireOneToken) {
// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat12", &err));
ASSERT_EQ("", err);

// allow running of one command
ExpectCanRunMore(3, true, false, false);
ExpectAcquireToken(1, true);
// block and wait for command to finalize
ExpectWaitForCommand(1, false);

EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("stuck [this is a bug]", err);

EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
// any of the two dependencies could have been executed
EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1" ||
token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2");
}

TEST_F(BuildTokenTest, WantTwoTokens) {
// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat12", &err));
ASSERT_EQ("", err);

// allow running of one command
ExpectCanRunMore(3, true, true, false);
ExpectAcquireToken(2, true, false);
// wait for command to finalize or token to become available
ExpectWaitForCommand(1, true);

EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("stuck [this is a bug]", err);

EXPECT_EQ(1u, token_command_runner_.commands_ran_.size());
// any of the two dependencies could have been executed
EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > cat1" ||
token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2");
}

TEST_F(BuildTokenTest, CompleteTwoSteps) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build out1: cat in1\n"
"build out2: cat out1\n"));

// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("out2", &err));
ASSERT_EQ("", err);

// allow running of two commands
ExpectCanRunMore(2, true, true);
ExpectAcquireToken(2, true, true);
// wait for commands to finalize
ExpectWaitForCommand(2, false, false);

EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);

EXPECT_EQ(2u, token_command_runner_.commands_ran_.size());
EXPECT_TRUE(token_command_runner_.commands_ran_[0] == "cat in1 > out1");
EXPECT_TRUE(token_command_runner_.commands_ran_[1] == "cat out1 > out2");
}

TEST_F(BuildTokenTest, TwoCommandsInParallel) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule token-available\n"
" command = cat $in > $out\n"
"build out1: token-available in1\n"
"build out2: token-available in2\n"
"build out12: cat out1 out2\n"));

// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("out12", &err));
ASSERT_EQ("", err);

// 1st command: token available -> allow running
// 2nd command: no token available but becomes available later
ExpectCanRunMore(4, true, true, true, false);
ExpectAcquireToken(3, true, false, true);
// 1st call waits for command to finalize or token to become available
// 2nd call waits for command to finalize
// 3rd call waits for command to finalize
ExpectWaitForCommand(3, true, false, false);

EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("stuck [this is a bug]", err);

EXPECT_EQ(2u, token_command_runner_.commands_ran_.size());
EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > out1" &&
token_command_runner_.commands_ran_[1] == "cat in2 > out2") ||
(token_command_runner_.commands_ran_[0] == "cat in2 > out2" &&
token_command_runner_.commands_ran_[1] == "cat in1 > out1"));
}

TEST_F(BuildTokenTest, CompleteThreeStepsSerial) {
// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("cat12", &err));
ASSERT_EQ("", err);

// allow running of all commands
ExpectCanRunMore(4, true, true, true, true);
ExpectAcquireToken(4, true, false, true, true);
// wait for commands to finalize
ExpectWaitForCommand(3, true, false, false);

EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);

EXPECT_EQ(3u, token_command_runner_.commands_ran_.size());
EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > cat1" &&
token_command_runner_.commands_ran_[1] == "cat in1 in2 > cat2") ||
(token_command_runner_.commands_ran_[0] == "cat in1 in2 > cat2" &&
token_command_runner_.commands_ran_[1] == "cat in1 > cat1" ));
EXPECT_TRUE(token_command_runner_.commands_ran_[2] == "cat cat1 cat2 > cat12");
}

TEST_F(BuildTokenTest, CompleteThreeStepsParallel) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule token-available\n"
" command = cat $in > $out\n"
"build out1: token-available in1\n"
"build out2: token-available in2\n"
"build out12: cat out1 out2\n"));

// plan should execute more than one command
string err;
EXPECT_TRUE(builder_.AddTarget("out12", &err));
ASSERT_EQ("", err);

// allow running of all commands
ExpectCanRunMore(4, true, true, true, true);
ExpectAcquireToken(4, true, false, true, true);
// wait for commands to finalize
ExpectWaitForCommand(4, true, false, false, false);

EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);

EXPECT_EQ(3u, token_command_runner_.commands_ran_.size());
EXPECT_TRUE((token_command_runner_.commands_ran_[0] == "cat in1 > out1" &&
token_command_runner_.commands_ran_[1] == "cat in2 > out2") ||
(token_command_runner_.commands_ran_[0] == "cat in2 > out2" &&
token_command_runner_.commands_ran_[1] == "cat in1 > out1"));
EXPECT_TRUE(token_command_runner_.commands_ran_[2] == "cat out1 out2 > out12");
}

0 comments on commit f99d747

Please sign in to comment.