-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement proper testing of the MAKEFLAGS parsing, and the token acquire/release logic in the jobserver class.
- Loading branch information
Showing
2 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
// Copyright 2024 Google Inc. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#include "jobserver.h" | ||
|
||
#ifdef _WIN32 | ||
#include <windows.h> | ||
#else | ||
#include <fcntl.h> | ||
#include <sys/stat.h> | ||
#include <sys/types.h> | ||
#include <unistd.h> | ||
#endif | ||
|
||
#include <cassert> | ||
#include <cstdlib> | ||
#include <vector> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
/// Wrapper class to provide access to protected members of the Jobserver class. | ||
struct JobserverTest : public Jobserver { | ||
/// Forwards calls to the proctected Jobserver::ParseJobserverAuth() method. | ||
bool ParseJobserverAuth(const char* type) { | ||
return Jobserver::ParseJobserverAuth(type); | ||
} | ||
|
||
/// Provides access to the proctected Jobserver::jobserver_name_ member. | ||
const char* JobserverName() const { | ||
return jobserver_name_.c_str(); | ||
} | ||
}; | ||
|
||
/// Jobserver state class that provides helpers to create, configure, and remove | ||
/// "external" jobserver pools. | ||
struct JobserverTestState : public testing::Test { | ||
/// Save the initial MAKEFLAGS environment variable value to allow restoring | ||
/// it upon destruction. | ||
JobserverTestState(); | ||
|
||
/// Restores the MAKEFLAGS environment variable value recorded upon | ||
/// construction. | ||
~JobserverTestState(); | ||
|
||
/// Configure the --jobserver-auth=<type>:<name> argument in the MAKEFLAGS | ||
/// environment value. | ||
void ServerConfigure(const char* name); | ||
|
||
/// Creates an external token pool with the given \a name and \a count number | ||
/// of tokens. Also configures the MAKEFLAGS environment variable with the | ||
/// correct --jobserver-auth argument to make the Jobserver class use the | ||
/// created external token pool. | ||
void ServerCreate(const char* name, size_t count); | ||
|
||
/// Return the number of tokens currently available in the external token | ||
/// pool. | ||
size_t ServerCount(); | ||
|
||
/// Remove/close the handle to external token pool. | ||
void ServerRemove(); | ||
|
||
/// Wrapped jobserver object to test on. | ||
JobserverTest jobserver_; | ||
|
||
/// Stored makeflags read before starting tests. | ||
const char* makeflags_ = nullptr; | ||
|
||
/// Name of created external jobserver token pool. | ||
const char* name_ = nullptr; | ||
|
||
#ifdef _WIN32 | ||
/// Implementation of posix setenv() for windows that forwards calls to | ||
/// _putenv(). | ||
int setenv(const char* name, const char* value, int _) { | ||
std::string envstring; | ||
|
||
// _putenv() requires a single <name>=<value> string as argument. | ||
envstring += name; | ||
envstring += '='; | ||
envstring += value; | ||
|
||
return _putenv(envstring.c_str()); | ||
}; | ||
|
||
/// Implementation of posix unsetenv() for windows that forwards calls to | ||
/// _putenv(). | ||
int unsetenv(const char *name) { | ||
/// Call _putenv() with <name>="" to unset the env variable. | ||
return setenv(name, "", 0); | ||
} | ||
|
||
/// Handle of the semaphore used as external token pool. | ||
HANDLE sem_ = INVALID_HANDLE_VALUE; | ||
#else | ||
/// File descriptor of the fifo used as external token pool. | ||
int fd_ = -1; | ||
#endif | ||
}; | ||
|
||
JobserverTestState::JobserverTestState() { | ||
makeflags_ = getenv("MAKEFLAGS"); | ||
unsetenv("MAKEFLAGS"); | ||
} | ||
|
||
JobserverTestState::~JobserverTestState() { | ||
if (name_ != nullptr) { | ||
ServerRemove(); | ||
} | ||
|
||
if (makeflags_ != nullptr) { | ||
setenv("MAKEFLAGS", makeflags_, 1); | ||
} else { | ||
unsetenv("MAKEFLAGS"); | ||
} | ||
} | ||
|
||
void JobserverTestState::ServerConfigure(const char* name) | ||
{ | ||
std::string makeflags("--jobserver-auth="); | ||
#ifdef _WIN32 | ||
makeflags += "sem:"; | ||
#else | ||
makeflags += "fifo:"; | ||
#endif | ||
makeflags += name; | ||
|
||
assert(setenv("MAKEFLAGS", makeflags.c_str(), 1) == 0); | ||
|
||
} | ||
|
||
#ifdef _WIN32 | ||
void JobserverTestState::ServerCreate(const char* name, size_t count) { | ||
assert(name_ == nullptr); | ||
ServerConfigure(name); | ||
name_ = name; | ||
|
||
// One cannot create a semaphore with a max value of 0 on windows | ||
sem_ = CreateSemaphoreA(nullptr, count, count ? : 1, name); | ||
assert(sem_ != NULL); | ||
} | ||
|
||
size_t JobserverTestState::ServerCount() { | ||
assert(name_ != nullptr); | ||
size_t count = 0; | ||
|
||
// First acquire all the available tokens to count them | ||
while (WaitForSingleObject(sem_, 0) == WAIT_OBJECT_0) { | ||
count++; | ||
} | ||
|
||
// Then return the acquired tokens to let the client continue | ||
ReleaseSemaphore(sem_, count, nullptr); | ||
|
||
return count; | ||
} | ||
|
||
void JobserverTestState::ServerRemove() { | ||
assert(name_ != nullptr); | ||
CloseHandle(sem_); | ||
name_ = nullptr; | ||
} | ||
|
||
#else // _WIN32 | ||
|
||
void JobserverTestState::ServerCreate(const char* name, size_t count) { | ||
assert(name_ == nullptr); | ||
ServerConfigure(name); | ||
name_ = name; | ||
|
||
// Create and open the fifo | ||
assert(mkfifo(name, S_IWUSR | S_IRUSR) == 0); | ||
fd_ = open(name, O_RDWR | O_NONBLOCK); | ||
assert(fd_ >= 0); | ||
|
||
// Fill the fifo the requested number of tokens | ||
std::vector<char> tokens(count, '+'); | ||
assert(write(fd_, tokens.data(), count) == count); | ||
} | ||
|
||
size_t JobserverTestState::ServerCount() { | ||
assert(name_ != nullptr); | ||
size_t count = 0; | ||
char token; | ||
|
||
// First acquire all the available tokens to count them | ||
while (read(fd_, &token, 1) == 1) { | ||
count++; | ||
} | ||
|
||
// Then return the acquired tokens to let the client continue | ||
std::vector<char> tokens(count, '+'); | ||
assert(write(fd_, tokens.data(), tokens.size()) == tokens.size()); | ||
|
||
return count; | ||
} | ||
|
||
void JobserverTestState::ServerRemove() { | ||
assert(name_ != nullptr); | ||
close(fd_); | ||
fd_ = -1; | ||
unlink(name_); | ||
name_ = nullptr; | ||
} | ||
|
||
#endif // _WIN32 | ||
|
||
TEST_F(JobserverTestState, MakeFlags) { | ||
// Test with no make flags configured | ||
ASSERT_FALSE(unsetenv("MAKEFLAGS")); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("sem")); | ||
|
||
// Test with no --jobserver-auth in make flags | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--other-arg=val", 0)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("sem")); | ||
|
||
// Test fifo type | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo:jobserver-1.fifo", 1)); | ||
ASSERT_TRUE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_STREQ(jobserver_.JobserverName(), "jobserver-1.fifo"); | ||
|
||
// Test sem type | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=sem:jobserver-2-sem", 1)); | ||
ASSERT_TRUE(jobserver_.ParseJobserverAuth("sem")); | ||
ASSERT_STREQ(jobserver_.JobserverName(), "jobserver-2-sem"); | ||
|
||
// Test preceeding arguments | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--other=val --jobserver-auth=fifo:jobserver-3.fifo", 1)); | ||
ASSERT_TRUE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_STREQ(jobserver_.JobserverName(), "jobserver-3.fifo"); | ||
|
||
// Test following arguments | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo:jobserver-4.fifo", 1)); | ||
ASSERT_TRUE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_STREQ(jobserver_.JobserverName(), "jobserver-4.fifo"); | ||
|
||
// Test surrounding arguments | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--preceeding-arg=val --jobserver-auth=fifo:jobserver-5.fifo --following-arg=val", 1)); | ||
ASSERT_TRUE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_STREQ(jobserver_.JobserverName(), "jobserver-5.fifo"); | ||
|
||
// Test invalid type | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=bad:jobserver-6", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("sem")); | ||
|
||
// Test missing type | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("sem")); | ||
|
||
// Test missing colon | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
|
||
// Test missing colon following by another argument | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo --other-arg=val", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
|
||
// Test missing colon following by another argument with a colon | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo --other-arg=val0:val1", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
|
||
// Test missing value | ||
ASSERT_FALSE(setenv("MAKEFLAGS", "--jobserver-auth=fifo:", 1)); | ||
ASSERT_FALSE(jobserver_.ParseJobserverAuth("fifo")); | ||
} | ||
|
||
TEST_F(JobserverTestState, InitNoServer) { | ||
// Verify that the jobserver isn't enabled when no configuration is given | ||
jobserver_.Init(); | ||
ASSERT_FALSE(jobserver_.Enabled()); | ||
} | ||
|
||
TEST_F(JobserverTestState, InitServer) { | ||
// Verify that the jobserver is enabled when a (valid) configuration is given | ||
ServerCreate("jobserver-init", 0); | ||
jobserver_.Init(); | ||
ASSERT_TRUE(jobserver_.Enabled()); | ||
} | ||
|
||
TEST_F(JobserverTestState, InitFail) { | ||
// Verify that the jobserver exits with an error if a non-existing jobserver | ||
// is configured | ||
ServerConfigure("jobserver-missing"); | ||
ASSERT_DEATH(jobserver_.Init(), "ninja: fatal: "); | ||
} | ||
|
||
TEST_F(JobserverTestState, NoTokens) { | ||
// Verify that an empty token pool does in fact provide a "default" token | ||
ServerCreate("jobserver-empty", 0); | ||
|
||
jobserver_.Init(); | ||
ASSERT_TRUE(jobserver_.Acquire()); | ||
ASSERT_FALSE(jobserver_.Acquire()); | ||
jobserver_.Release(); | ||
} | ||
|
||
TEST_F(JobserverTestState, OneToken) { | ||
// Verify that a token pool with exactly one token allows acquisition of one | ||
// "default" token and one "external" token | ||
ServerCreate("jobserver-one", 1); | ||
jobserver_.Init(); | ||
|
||
for (int i = 0; i < 2; i++) { | ||
ASSERT_TRUE(jobserver_.Acquire()); | ||
} | ||
|
||
ASSERT_FALSE(jobserver_.Acquire()); | ||
|
||
for (int i = 0; i < 2; i++) { | ||
jobserver_.Release(); | ||
} | ||
} | ||
|
||
TEST_F(JobserverTestState, AcquireRelease) { | ||
// Verify that Acquire() takes a token from the external pool, and that | ||
// Release() returns it again. | ||
ServerCreate("jobserver-acquire-release", 5); | ||
jobserver_.Init(); | ||
|
||
ASSERT_TRUE(jobserver_.Acquire()); | ||
ASSERT_EQ(ServerCount(), 5); | ||
|
||
ASSERT_TRUE(jobserver_.Acquire()); | ||
ASSERT_EQ(ServerCount(), 4); | ||
|
||
jobserver_.Release(); | ||
ASSERT_EQ(ServerCount(), 5); | ||
|
||
jobserver_.Release(); | ||
ASSERT_EQ(ServerCount(), 5); | ||
} |