Use stop time to cap the execution deadline and skip reporting.

After this we don't need to set batch timeout to the test time limit - it was a quick workaround for the same purpose.

Make some e2e test fuzzing duration to 10s to reduce the flakiness.

PiperOrigin-RevId: 818809280
diff --git a/centipede/BUILD b/centipede/BUILD
index 59982a3..f22646d 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -669,6 +669,7 @@
         ":runner_request",
         ":runner_result",
         ":shared_memory_blob_sequence",
+        ":stop",
         ":util",
         ":workdir",
         "@abseil-cpp//absl/base:nullability",
diff --git a/centipede/centipede.cc b/centipede/centipede.cc
index 97120d3..1b8c53d 100644
--- a/centipede/centipede.cc
+++ b/centipede/centipede.cc
@@ -363,8 +363,15 @@
                                       const std::vector<ByteArray> &input_vec,
                                       BatchResult &batch_result) {
   bool success = user_callbacks_.Execute(binary, input_vec, batch_result);
-  if (!success) ReportCrash(binary, input_vec, batch_result);
-  return success || batch_result.IsIgnoredFailure();
+  if (success) return true;
+  if (ShouldStop()) {
+    FUZZTEST_LOG_FIRST_N(WARNING, 1)
+        << "Crash found but the stop condition is met - not reporting further "
+           "possibly related crashes.";
+    return true;
+  }
+  ReportCrash(binary, input_vec, batch_result);
+  return batch_result.IsIgnoredFailure();
 }
 
 // *** Highly experimental and risky. May not scale well for large targets. ***
diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc
index 8110dce..d628972 100644
--- a/centipede/centipede_callbacks.cc
+++ b/centipede/centipede_callbacks.cc
@@ -50,6 +50,7 @@
 #include "./centipede/mutation_input.h"
 #include "./centipede/runner_request.h"
 #include "./centipede/runner_result.h"
+#include "./centipede/stop.h"
 #include "./centipede/util.h"
 #include "./centipede/workdir.h"
 #include "./common/blob_file.h"
@@ -479,7 +480,8 @@
       env_.timeout_per_batch == 0
           ? absl::InfiniteDuration()
           : absl::Seconds(env_.timeout_per_batch) + absl::Seconds(5);
-  const auto deadline = absl::Now() + amortized_timeout;
+  const auto deadline =
+      std::min(absl::Now() + amortized_timeout, GetStopTime());
   int exit_code = EXIT_SUCCESS;
   const bool should_clean_up = [&] {
     if (!cmd.is_executing() && !cmd.ExecuteAsync()) {
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index 321f89a..7b428d2 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -692,6 +692,8 @@
           << "Skip updating corpus database due to early stop requested.";
       continue;
     }
+    // The test time limit does not apply for the rest of the steps.
+    ClearEarlyStopRequestAndSetStopTime(/*stop_time=*/absl::InfiniteFuture());
 
     // TODO(xinhaoyuan): Have a separate flag to skip corpus updating instead
     // of checking whether workdir is specified or not.
diff --git a/centipede/environment.cc b/centipede/environment.cc
index 4cd2be6..bed7922 100644
--- a/centipede/environment.cc
+++ b/centipede/environment.cc
@@ -285,17 +285,6 @@
   timeout_per_input = time_limit_per_input_sec;
   UpdateTimeoutPerBatchIfEqualTo(autocomputed_timeout_per_batch);
 
-  // Adjust `timeout_per_batch` to never exceed the test time limit.
-  if (const auto test_time_limit = config.GetTimeLimitPerTest();
-      test_time_limit < absl::InfiniteDuration()) {
-    const size_t test_time_limit_seconds =
-        convert_to_seconds(test_time_limit, "Test time limit");
-    timeout_per_batch =
-        timeout_per_batch == 0
-            ? test_time_limit_seconds
-            : std::min(timeout_per_batch, test_time_limit_seconds);
-  }
-
   // Convert bytes to MB by rounding up.
   constexpr auto bytes_to_mb = [](size_t bytes) {
     return bytes == 0 ? 0 : (bytes - 1) / 1024 / 1024 + 1;
diff --git a/centipede/environment_test.cc b/centipede/environment_test.cc
index a906b99..9776192 100644
--- a/centipede/environment_test.cc
+++ b/centipede/environment_test.cc
@@ -147,30 +147,6 @@
   EXPECT_EQ(env.timeout_per_batch, 0);
 }
 
-TEST(Environment, UpdatesTimeoutPerBatchFromTargetConfigTimeLimit) {
-  Environment env;
-  fuzztest::internal::Configuration config;
-  config.time_limit = absl::Seconds(123);
-  config.time_budget_type = fuzztest::internal::TimeBudgetType::kPerTest;
-  FUZZTEST_CHECK(config.GetTimeLimitPerTest() == absl::Seconds(123));
-  env.UpdateWithTargetConfig(config);
-  EXPECT_EQ(env.timeout_per_batch, 123)
-      << "`timeout_per_batch` should be set to the test time limit when it was "
-         "previously unset";
-
-  env.timeout_per_batch = 456;
-  env.UpdateWithTargetConfig(config);
-  EXPECT_EQ(env.timeout_per_batch, 123)
-      << "`timeout_per_batch` should be set to test time limit when it is "
-         "shorter than the previous value";
-
-  env.timeout_per_batch = 56;
-  env.UpdateWithTargetConfig(config);
-  EXPECT_EQ(env.timeout_per_batch, 56)
-      << "`timeout_per_batch` should not be updated with the test time limit "
-         "when it is longer than the previous value";
-}
-
 TEST(Environment, UpdatesRssLimitMbFromTargetConfigRssLimit) {
   Environment env;
   env.rss_limit_mb = Environment::Default().rss_limit_mb;
diff --git a/centipede/stop.cc b/centipede/stop.cc
index 20f85bd..6a76c17 100644
--- a/centipede/stop.cc
+++ b/centipede/stop.cc
@@ -46,6 +46,8 @@
   early_stop.store({exit_code, true}, std::memory_order_release);
 }
 
+absl::Time GetStopTime() { return stop_time; }
+
 bool ShouldStop() { return EarlyStopRequested() || stop_time < absl::Now(); }
 
 int ExitCode() { return early_stop.load(std::memory_order_acquire).exit_code; }
diff --git a/centipede/stop.h b/centipede/stop.h
index f4244f8..d3e255d 100644
--- a/centipede/stop.h
+++ b/centipede/stop.h
@@ -46,6 +46,13 @@
 // ENSURES: Thread-safe.
 bool ShouldStop();
 
+// Returns the stop time set from the recent
+// `ClearEarlyStopRequestAndSetStopTime()`, or `absl::InfiniteFuture()` it was
+// not set.
+//
+// ENSURES: Thread-safe.
+absl::Time GetStopTime();
+
 // Returns the value most recently passed to `RequestEarlyStop()` or 0 if
 // `RequestEarlyStop()` was not called since the most recent call to
 // `ClearEarlyStopRequestAndSetStopTime()` (if any).
diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc
index c41f7d4..c369b84 100644
--- a/e2e_tests/corpus_database_test.cc
+++ b/e2e_tests/corpus_database_test.cc
@@ -280,11 +280,11 @@
       RunBinaryMaybeWithCentipede(GetCorpusDatabaseTestingBinaryPath(),
                                   fst_run_options);
 
-  // Adjust the fuzzing time so that only 1s remains.
+  // Adjust the fuzzing time so that only 10s remains.
   const absl::StatusOr<std::string> fuzzing_time_file =
       FindFile(corpus_database.path().c_str(), "fuzzing_time");
   ASSERT_TRUE(fuzzing_time_file.ok()) << fst_std_err;
-  ASSERT_TRUE(WriteFile(*fuzzing_time_file, "299s"));
+  ASSERT_TRUE(WriteFile(*fuzzing_time_file, "290s"));
 
   // 2nd run that should resume due to the same execution ID.
   RunOptions snd_run_options;
@@ -301,7 +301,7 @@
       snd_std_err,
       // The resumed fuzz test is the first one defined in the binary.
       AllOf(HasSubstr("Resuming running the fuzz test FuzzTest.FailsInTwoWays"),
-            HasSubstr("Fuzzing FuzzTest.FailsInTwoWays for 1s"),
+            HasSubstr("Fuzzing FuzzTest.FailsInTwoWays for 10s"),
             // Make sure that FailsInTwoWays finished.
             HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow")));
 
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 4bd3874..ae86a6a 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -124,6 +124,9 @@
       absl::flat_hash_map<std::string, std::string> fuzzer_flags = {}) {
     fuzzer_flags["print_subprocess_log"] = "true";
     fuzzer_flags["unguided"] = "true";
+    if (!fuzzer_flags.contains("fuzz_for")) {
+      fuzzer_flags["fuzz_for"] = "10s";
+    }
     RunOptions run_options;
     run_options.flags = {
         {GTEST_FLAG_PREFIX_ "filter", std::string(test_filter)},
@@ -1177,7 +1180,7 @@
 // to restrict the filter to only fuzz tests.
 TEST_F(FuzzingModeCommandLineInterfaceTest, RunsOnlyFuzzTests) {
   auto [status, std_out, std_err] =
-      RunWith({{"fuzz_for", "1ns"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
+      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
               "testdata/unit_test_and_fuzz_tests");
 
   EXPECT_THAT_LOG(std_out,
@@ -1191,7 +1194,7 @@
 TEST_F(FuzzingModeCommandLineInterfaceTest,
        AllowsSpecifyingFilterWithFuzzForDuration) {
   auto [status, std_out, std_err] =
-      RunWith({{"fuzz_for", "1ns"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
+      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
               "testdata/unit_test_and_fuzz_tests",
               {{GTEST_FLAG_PREFIX_ "filter",
                 "UnitTest.AlwaysPasses:FuzzTest.AlwaysPasses"}});