Exit peacefully when custom mutator fails.

PiperOrigin-RevId: 768049765
diff --git a/centipede/centipede.cc b/centipede/centipede.cc
index 66499d0..e30d548 100644
--- a/centipede/centipede.cc
+++ b/centipede/centipede.cc
@@ -832,6 +832,8 @@
 
     const std::vector<ByteArray> mutants =
         user_callbacks_.Mutate(mutation_inputs, batch_size);
+    if (ShouldStop()) break;
+
     bool gained_new_coverage =
         RunBatch(mutants, corpus_file.get(), features_file.get(), nullptr);
     new_runs += mutants.size();
diff --git a/centipede/centipede_default_callbacks.cc b/centipede/centipede_default_callbacks.cc
index 559812d..20b2efa 100644
--- a/centipede/centipede_default_callbacks.cc
+++ b/centipede/centipede_default_callbacks.cc
@@ -107,8 +107,9 @@
       // Returning whatever mutants we got before the failure.
       return std::move(result).mutants();
     } else {
-      // TODO(b/398261908): Exit with failure instead of crashing.
-      LOG(FATAL) << "Test binary failed when asked to mutate inputs.";
+      LOG(ERROR) << "Test binary failed when asked to mutate inputs - exiting.";
+      RequestEarlyStop(EXIT_FAILURE);
+      return {};
     }
   }
   // Fall back to the internal mutator.
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc
index 948159a..59826f4 100644
--- a/centipede/centipede_test.cc
+++ b/centipede/centipede_test.cc
@@ -1144,9 +1144,11 @@
   const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
   // Previous stop condition could interfere here.
   ClearEarlyStopRequestAndSetStopTime(absl::InfiniteFuture());
-  EXPECT_DEATH(callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs),
-                                inputs.size()),
-               "Custom mutator failed");
+  EXPECT_THAT(callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs),
+                               inputs.size()),
+              IsEmpty());
+  EXPECT_TRUE(EarlyStopRequested());
+  EXPECT_EQ(ExitCode(), EXIT_FAILURE);
 }
 
 TEST_F(CentipedeWithTemporaryLocalDir,
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 4dc7432..9ee676e 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -480,8 +480,7 @@
 
 TEST_F(UnitTestModeTest, DetectsRecursiveStructureIfOptionalsSetByDefault) {
   auto [status, std_out, std_err] = Run("MySuite.FailsIfCantInitializeProto");
-  // TODO: b/398261908 - Change to `ExpectTargetAbort` once the bug is fixed.
-  EXPECT_THAT(status, Eq(Signal(SIGABRT)));
+  ExpectTargetAbort(status, std_err);
   EXPECT_THAT(std_err, HasSubstr("recursive fields"));
 }
 
@@ -516,8 +515,7 @@
 TEST_F(UnitTestModeTest, FailsWhenRepeatedFieldsSizeRangeIsInvalid) {
   auto [status, std_out, std_err] =
       Run("MySuite.FailsToInitializeIfRepeatedFieldsSizeRangeIsInvalid");
-  // TODO: b/398261908 - Change to `ExpectTargetAbort` once the bug is fixed.
-  EXPECT_THAT(status, Eq(Signal(SIGABRT)));
+  ExpectTargetAbort(status, std_err);
   EXPECT_THAT(std_err, HasSubstr("size range is not valid"));
 }
 
@@ -530,8 +528,7 @@
 TEST_F(UnitTestModeTest, ChecksTypeOfProvidedDefaultDomainForProtos) {
   auto [status, std_out, std_err] =
       Run("MySuite.FailsWhenWrongDefaultProtobufDomainIsProvided");
-  // TODO: b/398261908 - Change to `ExpectTargetAbort` once the bug is fixed.
-  EXPECT_THAT(status, Eq(Signal(SIGABRT)));
+  ExpectTargetAbort(status, std_err);
   EXPECT_THAT(std_err, HasSubstr("does not match the expected message type"));
 }