| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 <gtest/gtest.h> |
| |
| #include <sys/wait.h> |
| |
| #include <string.h> |
| |
| #include <list> |
| #include <string> |
| |
| #include "docker/docker.hpp" |
| |
| #include <process/gmock.hpp> |
| #include <process/gtest.hpp> |
| |
| #include <stout/check.hpp> |
| #include <stout/error.hpp> |
| #include <stout/exit.hpp> |
| #include <stout/os.hpp> |
| #include <stout/strings.hpp> |
| |
| #ifdef __linux__ |
| #include "linux/cgroups.hpp" |
| #endif |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| #include "linux/routing/utils.hpp" |
| #endif |
| |
| #include "logging/logging.hpp" |
| |
| #include "tests/environment.hpp" |
| #include "tests/flags.hpp" |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| using namespace routing; |
| #endif |
| |
| using std::list; |
| using std::string; |
| |
| namespace mesos { |
| namespace internal { |
| namespace tests { |
| |
| // Storage for the global environment instance. |
| Environment* environment; |
| |
| |
| // Returns true if we should enable the provided test. Similar to how |
| // tests can be disabled using the 'DISABLED_' prefix on a test case |
| // name or test name, we use: |
| // |
| // 'ROOT_' : Disable test if current user isn't root. |
| // 'CGROUPS_' : Disable test if cgroups support isn't present. |
| // 'NOHIERARCHY_' : Disable test if there is already a cgroups |
| // hierarchy mounted. |
| // 'DOCKER_': Disable test if Docker is not supported. |
| // |
| // These flags can be composed in any order, but must come after |
| // 'DISABLED_'. In addition, we disable tests that attempt to use the |
| // CgroupsIsolator type parameter if the current user is not root or |
| // cgroups is not supported. |
| // TODO(benh): Provide a generic way to enable/disable tests by |
| // registering "filter" functions. |
| static bool enable(const ::testing::TestInfo& test) |
| { |
| // First check the test case name and test name. |
| list<string> names; |
| names.push_back(test.test_case_name()); |
| names.push_back(test.name()); |
| |
| Result<string> user = os::user(); |
| CHECK_SOME(user); |
| |
| foreach (const string& name, names) { |
| if (strings::contains(name, "ROOT_") && user.get() != "root") { |
| return false; |
| } |
| |
| if (strings::contains(name, "CGROUPS_")) { |
| #ifdef __linux__ |
| if (!cgroups::enabled()) { |
| return false; |
| } |
| #else |
| return false; |
| #endif |
| } |
| |
| #ifdef __linux__ |
| if (strings::contains(name, "NOHIERARCHY_")) { |
| Try<std::set<std::string> > hierarchies = cgroups::hierarchies(); |
| CHECK_SOME(hierarchies); |
| if (!hierarchies.get().empty()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "We cannot run any cgroups tests that require mounting\n" |
| << "hierarchies because you have the following hierarchies mounted:\n" |
| << strings::trim(stringify(hierarchies.get()), " {},") << "\n" |
| << "We'll disable the CgroupsNoHierarchyTest test fixture for now.\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| return false; |
| } |
| } |
| |
| // On Linux non-privileged users are limited to 64k of locked memory so we |
| // cannot run the MemIsolatorTest.Usage. |
| if (strings::contains(name, "MemIsolatorTest") && user.get() != "root") { |
| return false; |
| } |
| #endif |
| |
| // Filter out benchmark tests when we run 'make check'. |
| if (strings::contains(name, "BENCHMARK_") && !flags.benchmark) { |
| return false; |
| } |
| |
| if (strings::contains(name, "DOCKER_")) { |
| #ifdef __linux__ |
| Try<Docker> docker = Docker::create(flags.docker); |
| if (docker.isError()) { |
| std::cerr |
| << "-------------------------------------------------------------\n" |
| << "Skipping Docker tests because validation failed\n" |
| << "[Error] " + docker.error() + "\n" |
| << "-------------------------------------------------------------" |
| << std::endl; |
| |
| return false; |
| } |
| #else |
| return false; |
| #endif // __linux__ |
| } |
| } |
| |
| // Filter out regular tests when we run 'make bench', which |
| // requires us to check both the test case name and the test name |
| // at the same time. |
| if (flags.benchmark && |
| !strings::contains(test.test_case_name(), "BENCHMARK_") && |
| !strings::contains(test.name(), "BENCHMARK_")) { |
| return false; |
| } |
| |
| // Now check the type parameter. |
| if (test.type_param() != NULL) { |
| const string& type = test.type_param(); |
| if (strings::contains(type, "Cgroups")) { |
| #ifdef __linux__ |
| if (user.get() != "root" || !cgroups::enabled()) { |
| return false; |
| } |
| #else |
| return false; |
| #endif |
| } |
| } |
| |
| #ifdef WITH_NETWORK_ISOLATOR |
| // We can not run network isolator. |
| if (routing::check().isError() && |
| (strings::contains(test.name(), "PortMappingIsolatorTest") || |
| strings::contains(test.name(), "PortMappingMesosTest"))) { |
| return false; |
| } |
| |
| // Currently, the network isolator does not support multiple slaves. |
| if (strings::contains(test.name(), "MultipleSlaves")) { |
| return false; |
| } |
| #endif |
| |
| return true; |
| } |
| |
| |
| // We use the constructor to setup specific tests by updating the |
| // gtest filter. We do this so that we can selectively run tests that |
| // require root or specific OS support (e.g., cgroups). Note that this |
| // should not effect any other filters that have been put in place |
| // either on the command line or via an environment variable. |
| // N.B. This MUST be done _before_ invoking RUN_ALL_TESTS. |
| Environment::Environment(const Flags& _flags) : flags(_flags) |
| { |
| // First we split the current filter into enabled and disabled tests |
| // (which are separated by a '-'). |
| const string& filter = ::testing::GTEST_FLAG(filter); |
| |
| // An empty filter indicates no tests should be run. |
| if (filter.empty()) { |
| return; |
| } |
| |
| string enabled; |
| string disabled; |
| |
| size_t dash = filter.find('-'); |
| if (dash != string::npos) { |
| enabled = filter.substr(0, dash); |
| disabled = filter.substr(dash + 1); |
| } else { |
| enabled = filter; |
| } |
| |
| // Use universal filter if not specified. |
| if (enabled.empty()) { |
| enabled = "*"; |
| } |
| |
| // Ensure disabled tests end with ":" separator before we add more. |
| if (!disabled.empty() && !strings::endsWith(disabled, ":")) { |
| disabled += ":"; |
| } |
| |
| // Construct the filter string to handle system or platform specific tests. |
| ::testing::UnitTest* unitTest = ::testing::UnitTest::GetInstance(); |
| for (int i = 0; i < unitTest->total_test_case_count(); i++) { |
| const ::testing::TestCase* testCase = unitTest->GetTestCase(i); |
| for (int j = 0; j < testCase->total_test_count(); j++) { |
| const ::testing::TestInfo* testInfo = testCase->GetTestInfo(j); |
| |
| if (!enable(*testCase->GetTestInfo(j))) { |
| // Append 'TestCase.TestName:'. |
| disabled.append(testInfo->test_case_name()); |
| disabled.append("."); |
| disabled.append(testInfo->name()); |
| disabled.append(":"); |
| } |
| } |
| } |
| |
| // Now update the gtest flag. |
| ::testing::GTEST_FLAG(filter) = enabled + "-" + disabled; |
| |
| // Add our test event listeners. |
| ::testing::TestEventListeners& listeners = |
| ::testing::UnitTest::GetInstance()->listeners(); |
| |
| listeners.Append(process::FilterTestEventListener::instance()); |
| listeners.Append(process::ClockTestEventListener::instance()); |
| } |
| |
| |
| void Environment::SetUp() |
| { |
| // Clear any MESOS_ environment variables so they don't affect our tests. |
| char** environ = os::environ(); |
| for (int i = 0; environ[i] != NULL; i++) { |
| std::string variable = environ[i]; |
| if (variable.find("MESOS_") == 0) { |
| string key; |
| size_t eq = variable.find_first_of("="); |
| if (eq == string::npos) { |
| continue; // Not expecting a missing '=', but ignore anyway. |
| } |
| os::unsetenv(variable.substr(strlen("MESOS_"), eq - strlen("MESOS_"))); |
| } |
| } |
| |
| // Set the path to the native JNI library for running JVM tests. |
| // TODO(tillt): Adapt library towards JNI specific name once libmesos |
| // has been split. |
| if (!os::hasenv("MESOS_NATIVE_JAVA_LIBRARY")) { |
| string path = path::join(tests::flags.build_dir, "src", ".libs"); |
| #ifdef __APPLE__ |
| path = path::join(path, "libmesos-" VERSION ".dylib"); |
| #else |
| path = path::join(path, "libmesos-" VERSION ".so"); |
| #endif |
| os::setenv("MESOS_NATIVE_JAVA_LIBRARY", path); |
| } |
| |
| if (!GTEST_IS_THREADSAFE) { |
| EXIT(1) << "Testing environment is not thread safe, bailing!"; |
| } |
| } |
| |
| |
| void Environment::TearDown() |
| { |
| foreach (const string& directory, directories) { |
| Try<Nothing> rmdir = os::rmdir(directory); |
| if (rmdir.isError()) { |
| LOG(ERROR) << "Failed to remove '" << directory |
| << "': " << rmdir.error(); |
| } |
| } |
| directories.clear(); |
| |
| // Make sure we haven't left any child processes lying around. |
| // TODO(benh): Look for processes in the same group or session that |
| // might have been reparented. |
| Try<os::ProcessTree> pstree = os::pstree(0); |
| |
| if (pstree.isSome() && !pstree.get().children.empty()) { |
| FAIL() << "Tests completed with child processes remaining:\n" |
| << pstree.get(); |
| } |
| } |
| |
| |
| Try<string> Environment::mkdtemp() |
| { |
| const ::testing::TestInfo* const testInfo = |
| ::testing::UnitTest::GetInstance()->current_test_info(); |
| |
| if (testInfo == NULL) { |
| return Error("Failed to determine the current test information"); |
| } |
| |
| // We replace any slashes present in the test names (e.g. TYPED_TEST), |
| // to make sure the temporary directory resides under '/tmp/'. |
| const string& testCase = |
| strings::replace(testInfo->test_case_name(), "/", "_"); |
| |
| string testName = strings::replace(testInfo->name(), "/", "_"); |
| |
| // Adjust the test name to remove any 'DISABLED_' prefix (to make |
| // things easier to read). While this might seem alarming, if we are |
| // "running" a disabled test it must be the case that the test was |
| // explicitly enabled (e.g., via 'gtest_filter'). |
| if (strings::startsWith(testName, "DISABLED_")) { |
| testName = strings::remove(testName, "DISABLED_", strings::PREFIX); |
| } |
| |
| const string& path = |
| path::join("/tmp", strings::join("_", testCase, testName, "XXXXXX")); |
| |
| Try<string> mkdtemp = os::mkdtemp(path); |
| if (mkdtemp.isSome()) { |
| directories.push_back(mkdtemp.get()); |
| } |
| return mkdtemp; |
| } |
| |
| } // namespace tests { |
| } // namespace internal { |
| } // namespace mesos { |