blob: 75f2141e39df84d1ad72fcb614787e2a9bc959d0 [file] [log] [blame] [edit]
// Copyright 2024 The Centipede Authors.
//
// 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
//
// https://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 "./common/bazel.h"
#include <algorithm>
#include <cstdlib>
#include <string>
#include "absl/status/status.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "./common/logging.h"
namespace fuzztest::internal {
namespace {
absl::Duration GetBazelTestTimeout() {
const char *test_timeout_env = std::getenv("TEST_TIMEOUT");
if (test_timeout_env == nullptr) return absl::InfiniteDuration();
// When using `bazel run`, `TEST_TIMEOUT` is set but not used, and we should
// ignore the timeout. We detect this by the presence of
// `BUILD_WORKSPACE_DIRECTORY`
// (https://bazel.build/docs/user-manual#running-executables).
if (std::getenv("BUILD_WORKSPACE_DIRECTORY") != nullptr) {
return absl::InfiniteDuration();
}
int timeout_s = 0;
FUZZTEST_CHECK(absl::SimpleAtoi(test_timeout_env, &timeout_s))
<< "Failed to parse TEST_TIMEOUT: \"" << test_timeout_env << "\"";
return absl::Seconds(timeout_s);
}
} // namespace
TestShard GetBazelTestShard() {
static TestShard cached_test_shard = [] {
TestShard test_shard;
if (const char *test_total_shards_env = std::getenv("TEST_TOTAL_SHARDS");
test_total_shards_env != nullptr) {
FUZZTEST_CHECK(
absl::SimpleAtoi(test_total_shards_env, &test_shard.total_shards))
<< "Failed to parse TEST_TOTAL_SHARDS as an integer: \""
<< test_total_shards_env << "\"";
FUZZTEST_CHECK_GT(test_shard.total_shards, 0)
<< "TEST_TOTAL_SHARDS must be greater than 0.";
}
if (const char *test_shard_index_env = std::getenv("TEST_SHARD_INDEX");
test_shard_index_env != nullptr) {
FUZZTEST_CHECK(absl::SimpleAtoi(test_shard_index_env, &test_shard.index))
<< "Failed to parse TEST_SHARD_INDEX as an integer: \""
<< test_shard_index_env << "\"";
FUZZTEST_CHECK(0 <= test_shard.index &&
test_shard.index < test_shard.total_shards)
<< "TEST_SHARD_INDEX must be in the range [0, "
<< test_shard.total_shards << ").";
}
return test_shard;
}();
return cached_test_shard;
}
absl::Status VerifyBazelHasEnoughTimeToRunTest(absl::Time target_start_time,
absl::Duration test_time_limit,
int executed_tests_in_shard,
int fuzz_test_count) {
static const absl::Duration bazel_test_timeout = GetBazelTestTimeout();
const int shard_count = GetBazelTestShard().total_shards;
constexpr float kTimeoutSafetyFactor = 1.2;
const auto required_test_time = kTimeoutSafetyFactor * test_time_limit;
const auto remaining_duration =
bazel_test_timeout - (absl::Now() - target_start_time);
if (required_test_time <= remaining_duration) return absl::OkStatus();
std::string error =
"Cannot fuzz a fuzz test within the given timeout. Please ";
if (executed_tests_in_shard == 0) {
// Increasing number of shards won't help.
const absl::Duration suggested_timeout =
required_test_time * ((fuzz_test_count - 1) / shard_count + 1);
absl::StrAppend(&error, "set the `timeout` to ", suggested_timeout,
" or reduce the fuzzing time, ");
} else {
constexpr int kMaxShardCount = 50;
const int suggested_shard_count = std::min(
(fuzz_test_count - 1) / executed_tests_in_shard + 1, kMaxShardCount);
const int suggested_tests_per_shard =
(fuzz_test_count - 1) / suggested_shard_count + 1;
if (suggested_tests_per_shard > executed_tests_in_shard) {
// We wouldn't be able to execute the suggested number of tests without
// timeout. This case can only happen if we would in fact need more than
// `kMaxShardCount` shards, indicating that there are simply too many fuzz
// tests in a binary.
FUZZTEST_CHECK_EQ(suggested_shard_count, kMaxShardCount);
absl::StrAppend(&error,
"split the fuzz tests into several test binaries where "
"each binary has at most ",
executed_tests_in_shard * kMaxShardCount, "tests ",
"with `shard_count` = ", kMaxShardCount, ", ");
} else {
// In this case, `suggested_shard_count` must be greater than
// `shard_count`, otherwise we would have already executed all the tests
// without a timeout.
FUZZTEST_CHECK_GT(suggested_shard_count, shard_count);
absl::StrAppend(&error, "increase the `shard_count` to ",
suggested_shard_count, ", ");
}
}
absl::StrAppend(&error, "to avoid this issue. ");
absl::StrAppend(&error,
"(https://bazel.build/reference/be/"
"common-definitions#common-attributes-tests)");
return absl::ResourceExhaustedError(error);
}
} // namespace fuzztest::internal