blob: 476b5e7ecc5496d395a592df84afd0a079307584 [file]
// Copyright 2026 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 "./centipede/stop.h"
#include <atomic>
#include <cstdlib>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "./centipede/thread_pool.h"
namespace fuzztest::internal {
namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Field;
using ::testing::StartsWith;
TEST(StopConditionTest, InitialState) {
StopCondition stop_condition;
EXPECT_FALSE(stop_condition.StopRequested());
EXPECT_FALSE(stop_condition.ShouldStop());
EXPECT_EQ(stop_condition.GetStopTime(), absl::InfiniteFuture());
}
TEST(StopConditionTest, RequestStopShortReason) {
StopCondition stop_condition;
stop_condition.RequestStop(EXIT_FAILURE, "test reason");
EXPECT_TRUE(stop_condition.StopRequested());
EXPECT_TRUE(stop_condition.ShouldStop());
StopCondition::StopRequest request;
EXPECT_TRUE(stop_condition.StopRequested(&request));
EXPECT_EQ(request.exit_code, EXIT_FAILURE);
EXPECT_EQ(request.reason, "test reason");
}
TEST(StopConditionTest, RequestStopLongReasonIsCapped) {
StopCondition stop_condition;
std::string long_reason(1000, 'a');
stop_condition.RequestStop(EXIT_FAILURE, long_reason);
StopCondition::StopRequest request;
EXPECT_TRUE(stop_condition.StopRequested(&request));
EXPECT_EQ(request.exit_code, EXIT_FAILURE);
// 100 is a reasonable size to check.
EXPECT_GE(request.reason.size(), 100);
EXPECT_THAT(long_reason, StartsWith(request.reason));
}
TEST(StopConditionTest, RequestStopOnlyFirstCallTakesEffect) {
StopCondition stop_condition;
stop_condition.RequestStop(EXIT_FAILURE, "first reason");
stop_condition.RequestStop(EXIT_SUCCESS, "second reason");
StopCondition::StopRequest request;
EXPECT_TRUE(stop_condition.StopRequested(&request));
EXPECT_EQ(request.exit_code, EXIT_FAILURE);
EXPECT_EQ(request.reason, "first reason");
}
TEST(StopConditionTest, ClearStopRequest) {
StopCondition stop_condition;
stop_condition.RequestStop(EXIT_FAILURE, "some reason");
EXPECT_TRUE(stop_condition.StopRequested());
stop_condition.ClearStopRequest();
EXPECT_FALSE(stop_condition.StopRequested());
StopCondition::StopRequest request;
EXPECT_FALSE(stop_condition.StopRequested(&request));
// Can request stop again after clearing
stop_condition.RequestStop(EXIT_SUCCESS, "new reason");
EXPECT_TRUE(stop_condition.StopRequested(&request));
EXPECT_EQ(request.exit_code, EXIT_SUCCESS);
EXPECT_EQ(request.reason, "new reason");
}
TEST(StopConditionTest, SetStopTime) {
StopCondition stop_condition;
absl::Time past_time = absl::Now() - absl::Seconds(10);
stop_condition.SetStopTime(past_time);
EXPECT_EQ(stop_condition.GetStopTime(), past_time);
EXPECT_TRUE(stop_condition.ShouldStop());
EXPECT_FALSE(stop_condition.StopRequested());
}
TEST(StopConditionTest, ConcurrentStopRequests) {
StopCondition stop_condition;
std::atomic<bool> stop_testing = false;
ThreadPool requesters(2);
requesters.Schedule([&] {
while (!stop_testing) {
stop_condition.RequestStop(/*exit_code=*/1234,
"stop request from thread 1");
}
});
requesters.Schedule([&] {
while (!stop_testing) {
stop_condition.RequestStop(/*exit_code=*/5678,
"stop request from thread 2");
}
});
bool got_stop_request_from_thread_1 = false;
bool got_stop_request_from_thread_2 = false;
const absl::Time start = absl::Now();
while (absl::Now() - start < absl::Seconds(3)) {
StopCondition::StopRequest stop_request;
if (stop_condition.StopRequested(&stop_request)) {
ASSERT_THAT(
stop_request,
AnyOf(AllOf(Field(&StopCondition::StopRequest::exit_code, 1234),
Field(&StopCondition::StopRequest::reason,
"stop request from thread 1")),
AllOf(Field(&StopCondition::StopRequest::exit_code, 5678),
Field(&StopCondition::StopRequest::reason,
"stop request from thread 2"))));
if (stop_request.exit_code == 1234) {
got_stop_request_from_thread_1 = true;
} else {
got_stop_request_from_thread_2 = true;
}
stop_condition.ClearStopRequest();
}
}
stop_testing = true;
EXPECT_TRUE(got_stop_request_from_thread_1);
EXPECT_TRUE(got_stop_request_from_thread_2);
// Requester threads would be joined at the end, so if they got stuck the test
// would time out.
}
} // namespace
} // namespace fuzztest::internal