| // Copyright 2017 The Bazel Authors. 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 <sys/types.h> |
| #include <sys/resource.h> |
| #include <sys/wait.h> |
| |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <cstdarg> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| |
| #include "src/main/cpp/blaze_util.h" |
| #include "src/main/cpp/blaze_util_platform.h" |
| #include "googletest/include/gtest/gtest.h" |
| |
| namespace blaze { |
| |
| // Test fixture for the UnlimitResources function. |
| // |
| // The test cases in this fixture are special because the setup forks a |
| // subprocess and the actual testing is supposed to happen in such subprocess. |
| // This is because resource limits are process-wide so we must ensure that our |
| // testing does not interfere with other tests in this fixture or with other |
| // tests in the whole test program. |
| // |
| // What this means is that each test case function must check if IsChild() is |
| // false first. If it is, the function must return immediately. If it is not, |
| // then the function can proceed to execute the test but care must be taken: the |
| // test function cannot use any of the gunit functions, nor it can use std::exit |
| // to terminate. Instead, the function must use Die() to exit on a failure. |
| class UnlimitResourcesTest : public testing::Test { |
| protected: |
| UnlimitResourcesTest() { |
| pid_ = fork(); |
| EXPECT_NE(-1, pid_); |
| } |
| |
| virtual ~UnlimitResourcesTest() { |
| if (IsChild()) { |
| _exit(EXIT_SUCCESS); |
| } else { |
| int status; |
| EXPECT_NE(-1, waitpid(pid_, &status, 0)); |
| EXPECT_TRUE(WIFEXITED(status)); |
| EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); |
| } |
| } |
| |
| // Returns true if the test function is running in the child subprocess. |
| bool IsChild() { |
| return pid_ == 0; |
| } |
| |
| // Description of the resource limits to test for. |
| static struct limits_spec { |
| const char* name; |
| const int resource; |
| } limits_[]; |
| |
| // Aborts execution with the given message and fails the test case. |
| // This can only be called when IsChild() is true. |
| static void Die(const char* fmt, ...) ATTRIBUTE_NORETURN { |
| va_list ap; |
| va_start(ap, fmt); |
| std::vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| _exit(EXIT_FAILURE); |
| } |
| |
| // Version of getrlimit(3) that fails the test on error. |
| // This can only be called when IsChild() is true. |
| static struct rlimit GetrlimitOrDie(const int resource) { |
| struct rlimit rl; |
| if (getrlimit(resource, &rl) == -1) { |
| Die("getrlimit(%d) failed: %s\n", resource, strerror(errno)); |
| } |
| return rl; |
| } |
| |
| // Version of setrlimit(3) that fails the test on error. |
| // This can only be called when IsChild() is true. |
| static void SetrlimitOrDie(const int resource, struct rlimit rl) { |
| if (setrlimit(resource, &rl) == -1) { |
| Die("setrlimit(%d) failed with cur=%" PRIdMAX ", max=%" PRIdMAX ": %s\n", |
| resource, static_cast<intmax_t>(rl.rlim_cur), |
| static_cast<intmax_t>(rl.rlim_max), strerror(errno)); |
| } |
| } |
| |
| private: |
| // PID of the test subprocess, or 0 if we are the subprocess. |
| pid_t pid_; |
| }; |
| |
| struct UnlimitResourcesTest::limits_spec UnlimitResourcesTest::limits_[] = { |
| { "RLIMIT_NOFILE", RLIMIT_NOFILE }, |
| { "RLIMIT_NPROC", RLIMIT_NPROC }, |
| { nullptr, 0 }, |
| }; |
| |
| TEST_F(UnlimitResourcesTest, SuccessWithExplicitLimits) { |
| if (!IsChild()) return; |
| // The rest of this test runs in a subprocess. See the fixture's docstring |
| // for details on what this implies. |
| |
| // Lower the limits to very low values that should always work. |
| for (struct limits_spec* limit = limits_; limit->name != nullptr; limit++) { |
| struct rlimit rl = GetrlimitOrDie(limit->resource); |
| rl.rlim_cur = 1; |
| rl.rlim_max = 8; |
| SetrlimitOrDie(limit->resource, rl); |
| } |
| |
| if (!blaze::UnlimitResources()) { |
| Die("UnlimitResources returned error; see output for diagnostics\n"); |
| } |
| |
| // Check that the soft limits were raised to the explicit hard limits we set. |
| for (struct limits_spec* limit = limits_; limit->name != nullptr; limit++) { |
| const struct rlimit rl = GetrlimitOrDie(limit->resource); |
| if (rl.rlim_cur != rl.rlim_max) { |
| Die("UnlimitResources did not increase the soft %s to its hard limit\n", |
| limit->name); |
| } |
| } |
| } |
| |
| TEST_F(UnlimitResourcesTest, SuccessWithPossiblyInfiniteLimits) { |
| if (!IsChild()) return; |
| // The rest of this test runs in a subprocess. See the fixture's docstring |
| // for details on what this implies. |
| |
| if (GetExplicitSystemLimit(-1) == -1) { |
| fprintf(stderr, "GetExplicitSystemLimit not implemented for this platform; " |
| "cannot verify the behavior of UnlimitResources\n"); |
| return; |
| } |
| |
| // Lower only the soft limits to very low values and assume that the hard |
| // limits are set to infinity; otherwise, there is nothing we can do because |
| // we may not have permissions to increase them. |
| for (struct limits_spec* limit = limits_; limit->name != nullptr; limit++) { |
| struct rlimit rl = GetrlimitOrDie(limit->resource); |
| if (rl.rlim_max != RLIM_INFINITY) { |
| fprintf(stderr, "Hard resource limit for %s is not infinity; will not " |
| "be able to meaningfully test anything\n", limit->name); |
| } |
| rl.rlim_cur = 1; |
| SetrlimitOrDie(limit->resource, rl); |
| } |
| |
| if (!blaze::UnlimitResources()) { |
| Die("UnlimitResources returned error; see output for diagnostics\n"); |
| } |
| |
| // Check that the soft limits were increased to a higher explicit number. |
| for (struct limits_spec* limit = limits_; limit->name != nullptr; limit++) { |
| const struct rlimit rl = GetrlimitOrDie(limit->resource); |
| if (rl.rlim_cur == 1 || rl.rlim_cur == RLIM_INFINITY) { |
| Die("UnlimitResources did not increase the soft %s to the system limit\n", |
| limit->name); |
| } |
| } |
| } |
| |
| TEST_F(UnlimitResourcesTest, Coredumps) { |
| if (!IsChild()) return; |
| // The rest of this test runs in a subprocess. See the fixture's docstring |
| // for details on what this implies. |
| |
| // Lower only the soft limit to a very low value and assume that the hard |
| // limit is non-zero. |
| struct rlimit rl = GetrlimitOrDie(RLIMIT_CORE); |
| if (rl.rlim_max <= 1) { |
| fprintf(stderr, |
| "Hard resource limit for RLIMIT_CORE is %" PRIuMAX |
| "; cannot test anything meaningful\n", |
| static_cast<uintmax_t>(rl.rlim_max)); |
| return; |
| } |
| rl.rlim_cur = 1; |
| SetrlimitOrDie(RLIMIT_CORE, rl); |
| |
| if (!blaze::UnlimitCoredumps()) { |
| Die("UnlimitCoredumps returned error; see output for diagnostics\n"); |
| } |
| |
| // Check that the soft limits were increased to a higher explicit number. |
| rl = GetrlimitOrDie(RLIMIT_CORE); |
| if (rl.rlim_cur == 1) { |
| Die("UnlimitCoredumps did not increase the soft RLIMIT_CORE to the system " |
| "limit\n"); |
| } |
| } |
| |
| } // namespace blaze |