From 5ae175e918f2df2abe319c5e21c51478ac97c9f0 Mon Sep 17 00:00:00 2001 From: Bradley White <14679271+devbww@users.noreply.github.com> Date: Fri, 21 Feb 2020 10:07:44 -0500 Subject: [PATCH] feat: add new support for testing with environment variables (googleapis/google-cloud-cpp-common#180) `ScopedEnvironment` is an RAII object that will, upon destruction, restore the previous state of the environment variable it modified. For example: ``` // ${VAR} has some initial state. { ScopedEnvironment env("VAR", "value"); // ${VAR} now holds "value". } // The initial state of ${VAR} has been restored. ``` --- google/cloud/testing_util/CMakeLists.txt | 5 +- .../testing_util/google_cloud_cpp_testing.bzl | 2 + .../google_cloud_cpp_testing_unit_tests.bzl | 1 + .../cloud/testing_util/scoped_environment.cc | 38 ++++++++++++ .../cloud/testing_util/scoped_environment.h | 57 +++++++++++++++++ .../testing_util/scoped_environment_test.cc | 62 +++++++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 google/cloud/testing_util/scoped_environment.cc create mode 100644 google/cloud/testing_util/scoped_environment.h create mode 100644 google/cloud/testing_util/scoped_environment_test.cc diff --git a/google/cloud/testing_util/CMakeLists.txt b/google/cloud/testing_util/CMakeLists.txt index 32758372529f1..4b1ecf24fd523 100644 --- a/google/cloud/testing_util/CMakeLists.txt +++ b/google/cloud/testing_util/CMakeLists.txt @@ -32,6 +32,8 @@ if (BUILD_TESTING OR GOOGLE_CLOUD_CPP_TESTING_UTIL_ENABLE_INSTALL) expect_future_error.h init_google_mock.cc init_google_mock.h + scoped_environment.cc + scoped_environment.h testing_types.cc testing_types.h) target_link_libraries( @@ -41,7 +43,8 @@ if (BUILD_TESTING OR GOOGLE_CLOUD_CPP_TESTING_UTIL_ENABLE_INSTALL) create_bazel_config(google_cloud_cpp_testing YEAR 2019) - set(google_cloud_cpp_testing_unit_tests assert_ok_test.cc) + set(google_cloud_cpp_testing_unit_tests assert_ok_test.cc + scoped_environment_test.cc) # Export the list of unit tests so the Bazel BUILD file can pick it up. export_list_to_bazel("google_cloud_cpp_testing_unit_tests.bzl" diff --git a/google/cloud/testing_util/google_cloud_cpp_testing.bzl b/google/cloud/testing_util/google_cloud_cpp_testing.bzl index ade56b5c81401..de36af3d8418a 100644 --- a/google/cloud/testing_util/google_cloud_cpp_testing.bzl +++ b/google/cloud/testing_util/google_cloud_cpp_testing.bzl @@ -25,6 +25,7 @@ google_cloud_cpp_testing_hdrs = [ "expect_exception.h", "expect_future_error.h", "init_google_mock.h", + "scoped_environment.h", "testing_types.h", ] @@ -34,5 +35,6 @@ google_cloud_cpp_testing_srcs = [ "custom_google_mock_main.cc", "environment_variable_restore.cc", "init_google_mock.cc", + "scoped_environment.cc", "testing_types.cc", ] diff --git a/google/cloud/testing_util/google_cloud_cpp_testing_unit_tests.bzl b/google/cloud/testing_util/google_cloud_cpp_testing_unit_tests.bzl index a8121461fac1b..a3b6c9bcbc787 100644 --- a/google/cloud/testing_util/google_cloud_cpp_testing_unit_tests.bzl +++ b/google/cloud/testing_util/google_cloud_cpp_testing_unit_tests.bzl @@ -18,4 +18,5 @@ google_cloud_cpp_testing_unit_tests = [ "assert_ok_test.cc", + "scoped_environment_test.cc", ] diff --git a/google/cloud/testing_util/scoped_environment.cc b/google/cloud/testing_util/scoped_environment.cc new file mode 100644 index 0000000000000..f6d1b9664a362 --- /dev/null +++ b/google/cloud/testing_util/scoped_environment.cc @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// 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 "google/cloud/testing_util/scoped_environment.h" +#include "google/cloud/internal/getenv.h" +#include "google/cloud/internal/setenv.h" + +namespace google { +namespace cloud { +inline namespace GOOGLE_CLOUD_CPP_NS { +namespace testing_util { + +ScopedEnvironment::ScopedEnvironment(std::string variable, + optional const& value) + : variable_(std::move(variable)), + prev_value_(internal::GetEnv(variable_.c_str())) { + internal::SetEnv(variable_.c_str(), value); +} + +ScopedEnvironment::~ScopedEnvironment() { + internal::SetEnv(variable_.c_str(), std::move(prev_value_)); +} + +} // namespace testing_util +} // namespace GOOGLE_CLOUD_CPP_NS +} // namespace cloud +} // namespace google diff --git a/google/cloud/testing_util/scoped_environment.h b/google/cloud/testing_util/scoped_environment.h new file mode 100644 index 0000000000000..2fff1e6492986 --- /dev/null +++ b/google/cloud/testing_util/scoped_environment.h @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// 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. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_TESTING_UTIL_SCOPED_ENVIRONMENT_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_TESTING_UTIL_SCOPED_ENVIRONMENT_H + +#include "google/cloud/optional.h" +#include "google/cloud/version.h" +#include + +namespace google { +namespace cloud { +inline namespace GOOGLE_CLOUD_CPP_NS { +namespace testing_util { + +/** + * Helper class to (un)set and restore the value of an environment variable. + */ +class ScopedEnvironment { + public: + /** + * Set the @p variable environment variable to @p value. If @value is + * an unset optional then the variable is unset. The previous value of + * the variable will be restored upon destruction. + */ + ScopedEnvironment(std::string variable, optional const& value); + + ~ScopedEnvironment(); + + // Movable, but not copyable. + ScopedEnvironment(ScopedEnvironment const&) = delete; + ScopedEnvironment(ScopedEnvironment&&) = default; + ScopedEnvironment& operator=(ScopedEnvironment const&) = delete; + ScopedEnvironment& operator=(ScopedEnvironment&&) = default; + + private: + std::string variable_; + optional prev_value_; +}; + +} // namespace testing_util +} // namespace GOOGLE_CLOUD_CPP_NS +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_TESTING_UTIL_SCOPED_ENVIRONMENT_H diff --git a/google/cloud/testing_util/scoped_environment_test.cc b/google/cloud/testing_util/scoped_environment_test.cc new file mode 100644 index 0000000000000..e8fddf504c4f5 --- /dev/null +++ b/google/cloud/testing_util/scoped_environment_test.cc @@ -0,0 +1,62 @@ +// Copyright 2020 Google LLC +// +// 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 "google/cloud/testing_util/scoped_environment.h" +#include "google/cloud/internal/getenv.h" +#include "google/cloud/internal/setenv.h" +#include + +namespace google { +namespace cloud { +inline namespace GOOGLE_CLOUD_CPP_NS { +namespace testing_util { +namespace { + +constexpr auto kVarName = "SCOPED_ENVIRONMENT_TEST"; + +TEST(ScopedEnvironment, SetOverSet) { + ScopedEnvironment env_outer(kVarName, "foo"); + EXPECT_EQ(*internal::GetEnv(kVarName), "foo"); + { + ScopedEnvironment env_inner(kVarName, "bar"); + EXPECT_EQ(*internal::GetEnv(kVarName), "bar"); + } + EXPECT_EQ(*internal::GetEnv(kVarName), "foo"); +} + +TEST(ScopedEnvironment, SetOverUnset) { + ScopedEnvironment env_outer(kVarName, {}); + EXPECT_FALSE(internal::GetEnv(kVarName).has_value()); + { + ScopedEnvironment env_inner(kVarName, "bar"); + EXPECT_EQ(*internal::GetEnv(kVarName), "bar"); + } + EXPECT_FALSE(internal::GetEnv(kVarName).has_value()); +} + +TEST(ScopedEnvironment, UnsetOverSet) { + ScopedEnvironment env_outer(kVarName, "foo"); + EXPECT_EQ(*internal::GetEnv(kVarName), "foo"); + { + ScopedEnvironment env_inner(kVarName, {}); + EXPECT_FALSE(internal::GetEnv(kVarName).has_value()); + } + EXPECT_EQ(*internal::GetEnv(kVarName), "foo"); +} + +} // namespace +} // namespace testing_util +} // namespace GOOGLE_CLOUD_CPP_NS +} // namespace cloud +} // namespace google