blob: 80a685d410691e93b0c601518afefda804b101be [file] [log] [blame]
// Copyright 2023 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 "persistent_mode.h"
#include <inttypes.h>
#include <stdlib.h>
#include "async_loop.h"
#include "persistent_mode_test_lib.cc"
#include "process_utils.h"
#include "stdio_redirection.h"
#include "test.h"
#include "util.h"
TEST(PersistentMode, GetCurrentProcessStatus) {
static const char kStatusVarName[] = "NINJA_PERSISTENT_MODE";
{
static const char* const kValues[] = { nullptr, "off", "0" };
for (const char* value : kValues) {
ScopedEnvironmentVariable env(kStatusVarName, value);
EXPECT_EQ(PersistentMode::Disabled,
PersistentMode::GetCurrentProcessStatus());
}
}
{
static const char* const kValues[] = { "on", "1", "client" };
for (const char* value : kValues) {
ScopedEnvironmentVariable env(kStatusVarName, value);
ASSERT_TRUE(getenv(kStatusVarName));
EXPECT_EQ(std::string(value), getenv(kStatusVarName));
EXPECT_EQ(PersistentMode::IsClient,
PersistentMode::GetCurrentProcessStatus());
}
}
{
static const char* const kValues[] = { "server" };
for (const char* value : kValues) {
ScopedEnvironmentVariable env(kStatusVarName, value);
EXPECT_EQ(PersistentMode::IsServer,
PersistentMode::GetCurrentProcessStatus());
}
}
}
TEST(PersistentMode, Compatibility) {
// Convenience helper to return a value with default values
// appropriate for this test.
auto new_compat = []() -> PersistentMode::Compatibility {
return PersistentMode::Compatibility()
.SetNinjaVersionForTest("this_ninja")
.SetInputFile("input_file")
.SetBuildDir("build_dir");
};
PersistentMode::Compatibility compat = new_compat();
std::string reason;
ASSERT_TRUE(compat.IsCompatibleWith(compat, &reason));
std::string encoded = compat.ToEncodedString();
std::string error;
auto compat2 =
PersistentMode::Compatibility::FromEncodedString(encoded, &error);
ASSERT_TRUE(error.empty());
auto to_string = [](const std::vector<std::string>& args) -> std::string {
std::string result;
for (const auto& arg : args) {
if (!result.empty())
result += " ";
result += arg;
}
return result;
};
ASSERT_TRUE(compat.IsCompatibleWith(compat2, &reason));
ASSERT_TRUE(compat2.IsCompatibleWith(compat, &reason));
compat2 = new_compat().SetBuildDir("another_build_dir");
ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
EXPECT_EQ(
reason,
"Working dir mismatch, expected [build_dir] vs [another_build_dir]");
EXPECT_EQ(
"ninja -C another_build_dir -f input_file -wdupbuild=err "
"-wphonycycle=warn",
to_string(compat2.ToServerArgs("ninja")));
compat2 = new_compat().SetInputFile("another_input_file");
ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
EXPECT_EQ(
reason,
"Input file mismatch, expected [input_file] vs [another_input_file]");
EXPECT_EQ(
"ninja -C build_dir -f another_input_file -wdupbuild=err "
"-wphonycycle=warn",
to_string(compat2.ToServerArgs("ninja")));
compat2 = new_compat().SetFlagDupeEdgesShouldErr(false);
ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
EXPECT_EQ(reason,
"Flag dupe_edges_should_err mismatch, expected true vs false");
EXPECT_EQ(
"ninja -C build_dir -f input_file -wdupbuild=warn -wphonycycle=warn",
to_string(compat2.ToServerArgs("ninja")));
compat2 = new_compat().SetFlagPhonyCycleShouldErr(true);
ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
EXPECT_EQ(reason,
"Flag phony_cycle_should_err mismatch, expected false vs true");
EXPECT_EQ("ninja -C build_dir -f input_file -wdupbuild=err -wphonycycle=err",
to_string(compat2.ToServerArgs("ninja")));
compat2 = compat;
compat2.SetNinjaVersionForTest("not_this_ninja");
ASSERT_FALSE(compat.IsCompatibleWith(compat2, &reason));
EXPECT_EQ(
reason,
"Ninja version mismatch, expected [this_ninja] vs [not_this_ninja]");
EXPECT_EQ("ninja -C build_dir -f input_file -wdupbuild=err -wphonycycle=warn",
to_string(compat2.ToServerArgs("ninja")));
}
// Unfortunately, this test, and PersistentMode in general do not work
// properly on Windows yet. The root of the problem is that StdioRedirector
// does not work because it is impossible to pass Win32 console handlers
// to other processes. Solving this requires an alternative implementation
// that relies on proxying the console handles through anonymous pipes on
// the client, which will be provided in a future fix.
#ifndef _WIN32
TEST(PersistentModeClient, DefaultMode) {
PersistentMode::Client client;
client.SetServerExecutable(persistent_mode_test::GetServerExecutable());
ScopedTempDir test_dir;
test_dir.CreateAndEnter("persistent_mode_test");
std::string build_dir = GetCurrentDir();
ASSERT_FALSE(client.IsServerRunning(build_dir));
auto compat = persistent_mode_test::GetClientCompatibility(build_dir);
std::string error;
int exit_code = -1;
auto query = persistent_mode_test::GetClientTestQuery1(build_dir);
AsyncLoop::ResetForTesting();
AsyncLoop& async_loop = AsyncLoop::Get();
{
StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
bool ret = client.RunQuery(compat, query, &exit_code, &error);
if (!ret)
fprintf(stderr, "\nERROR: %s\n", error.c_str());
std::string result;
EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
ASSERT_TRUE(ret);
EXPECT_EQ(0u, exit_code);
EXPECT_EQ(result, build_dir);
}
ASSERT_TRUE(client.IsServerRunning(build_dir));
ASSERT_TRUE(client.StopServer(build_dir, &error));
}
TEST(PersistentModeClient, EnvironmentVariables) {
PersistentMode::Client client;
client.SetServerExecutable(persistent_mode_test::GetServerExecutable());
ScopedTempDir test_dir;
test_dir.CreateAndEnter("persistent_mode_test");
std::string build_dir = test_dir.temp_dir_name_;
ASSERT_FALSE(client.IsServerRunning(build_dir));
auto compat = persistent_mode_test::GetClientCompatibility(build_dir);
std::string error;
int exit_code = -1;
std::vector<std::string> print_varnames;
print_varnames.emplace_back("bar");
print_varnames.emplace_back("foo");
print_varnames.emplace_back("undefined");
auto query = persistent_mode_test::GetClientTestQuery2(print_varnames);
query.config.environment.Insert("foo", "FOO");
query.config.environment.Insert("bar", "BAR");
query.config.environment.Remove("undefined");
AsyncLoop::ResetForTesting();
AsyncLoop& async_loop = AsyncLoop::Get();
// The first query should retrieve the variable values from the client
// process that started it.
{
StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
bool ret = client.RunQuery(compat, query, &exit_code, &error);
if (!ret)
fprintf(stderr, "\nERROR: %s\n", error.c_str());
std::string result;
EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
ASSERT_TRUE(ret);
EXPECT_EQ(0u, exit_code);
EXPECT_EQ(result, "bar=BAR\nfoo=FOO\nundefined=<UNSET>\n");
}
// Change the local values of 'foo' and 'undefined', and verify that only the
// ones listed by passthrough_varnames were modified on the server.
{
query.config.environment.Insert("foo", "NEW_FOO");
query.config.environment.Insert("undefined", "SURPRISE!");
StdioAsyncStringRedirector buffered_stdout(async_loop, stdout);
bool ret = client.RunQuery(compat, query, &exit_code, &error);
if (!ret)
fprintf(stderr, "\nERROR: %s\n", error.c_str());
std::string result;
EXPECT_TRUE(buffered_stdout.WaitForResult(&result));
ASSERT_TRUE(ret);
EXPECT_EQ(0u, exit_code);
EXPECT_EQ(result, "bar=BAR\nfoo=NEW_FOO\nundefined=SURPRISE!\n");
}
ASSERT_TRUE(client.IsServerRunning(build_dir));
ASSERT_TRUE(client.StopServer(build_dir, &error));
}
#endif // !_WIN32