| // 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 |