[netemul] Refactor error codes

- Sandbox was using fuchsia::sys::TerminationReason for its termination
codes, refactored termination process to return a netemul-specific
status code.
- Unit tests now check for expected status codes.

Change-Id: I7bc48a34da311d8de1f5a5d6caab27f600e3c5ba
diff --git a/src/connectivity/network/testing/netemul/runner/main.cc b/src/connectivity/network/testing/netemul/runner/main.cc
index cd75919..8e99495 100644
--- a/src/connectivity/network/testing/netemul/runner/main.cc
+++ b/src/connectivity/network/testing/netemul/runner/main.cc
@@ -57,14 +57,10 @@
     }
 
     Sandbox sandbox(std::move(args));
-    sandbox.SetTerminationCallback([](int64_t exit_code,
-                                      Sandbox::TerminationReason reason) {
-      FXL_LOG(INFO) << "Sandbox terminated with (" << exit_code << ") reason: "
-                    << sys::HumanReadableTerminationReason(reason);
-      if (reason != Sandbox::TerminationReason::EXITED) {
-        exit_code = 1;
-      }
-      zx_process_exit(exit_code);
+    sandbox.SetTerminationCallback([](SandboxResult result) {
+      FXL_LOG(INFO) << "Sandbox terminated with status: " << result;
+      int64_t exit = result.is_success() ? 0 : 1;
+      zx_process_exit(exit);
     });
 
     sandbox.Start(loop.dispatcher());
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox.cc b/src/connectivity/network/testing/netemul/runner/sandbox.cc
index ce7fd45..35907c1 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox.cc
+++ b/src/connectivity/network/testing/netemul/runner/sandbox.cc
@@ -25,8 +25,6 @@
 namespace netemul {
 
 static const char* kEndpointMountPath = "class/ethernet/";
-constexpr int64_t kFailureTerminationCode = -1;
-constexpr int64_t kTimeoutTerminationCode = 1;
 
 #define STATIC_MSG_STRUCT(name, msgv) \
   struct name {                       \
@@ -75,19 +73,19 @@
   test_spawned_ = false;
 
   if (!parent_env_ || !loader_) {
-    Terminate(TerminationReason::INTERNAL_ERROR);
+    Terminate(SandboxResult::Status::INTERNAL_ERROR,
+              "Missing parent environment or loader");
     return;
   } else if (env_config_.disabled()) {
-    FXL_LOG(INFO) << "test is disabled, skipping.";
-    Terminate(0, TerminationReason::EXITED);
+    Terminate(SandboxResult::Status::SUCCESS, "Test is disabled");
     return;
   }
 
   helper_loop_ =
       std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
   if (helper_loop_->StartThread("helper-thread") != ZX_OK) {
-    FXL_LOG(ERROR) << "Can't start config thread";
-    Terminate(TerminationReason::INTERNAL_ERROR);
+    Terminate(SandboxResult::Status::INTERNAL_ERROR,
+              "Can't start config thread");
     return;
   }
   helper_executor_ =
@@ -99,8 +97,10 @@
                                             TerminationReason reason) {
     if (helper_loop_ &&
         (reason != TerminationReason::EXITED || exit_code != 0)) {
-      async::PostTask(helper_loop_->dispatcher(), [this]() {
-        PostTerminate(TerminationReason::INTERNAL_ERROR);
+      async::PostTask(helper_loop_->dispatcher(), [this, service]() {
+        std::stringstream ss;
+        ss << service << " terminated prematurely";
+        PostTerminate(SandboxResult::Status::SERVICE_EXITED, ss.str());
       });
     }
   };
@@ -114,7 +114,7 @@
   StartEnvironments();
 }
 
-void Sandbox::Terminate(int64_t exit_code, Sandbox::TerminationReason reason) {
+void Sandbox::Terminate(SandboxResult result) {
   // all processes must have been emptied to call callback
   ASSERT_MAIN_DISPATCHER;
   ZX_ASSERT(procs_.empty());
@@ -125,7 +125,8 @@
     helper_loop_ = nullptr;
   }
 
-  if (exit_code != 0 || env_config_.capture() == config::CaptureMode::ALWAYS) {
+  if (!result.is_success() ||
+      env_config_.capture() == config::CaptureMode::ALWAYS) {
     // check if any of the network dumps have data, and just dump them to
     // stdout:
     if (net_dumps_ && net_dumps_->HasData()) {
@@ -138,27 +139,28 @@
   }
 
   if (termination_callback_) {
-    termination_callback_(exit_code, reason);
+    termination_callback_(std::move(result));
   }
 }
 
-void Sandbox::PostTerminate(TerminationReason reason) {
-  ASSERT_HELPER_DISPATCHER;
-  PostTerminate(kFailureTerminationCode, reason);
+void Sandbox::Terminate(netemul::SandboxResult::Status status,
+                        std::string description) {
+  Terminate(SandboxResult(status, std::move(description)));
 }
 
-void Sandbox::PostTerminate(int64_t exit_code, TerminationReason reason) {
+void Sandbox::PostTerminate(SandboxResult result) {
   ASSERT_HELPER_DISPATCHER;
   // kill all component controllers before posting termination
   procs_.clear();
-  async::PostTask(main_dispatcher_, [this, exit_code, reason]() {
-    Terminate(exit_code, reason);
-  });
+  async::PostTask(main_dispatcher_,
+                  [this, result = std::move(result)]() mutable {
+                    Terminate(std::move(result));
+                  });
 }
 
-void Sandbox::Terminate(Sandbox::TerminationReason reason) {
-  ASSERT_MAIN_DISPATCHER;
-  Terminate(kFailureTerminationCode, reason);
+void Sandbox::PostTerminate(netemul::SandboxResult::Status status,
+                            std::string description) {
+  PostTerminate(SandboxResult(status, std::move(description)));
 }
 
 void Sandbox::StartEnvironments() {
@@ -166,13 +168,15 @@
 
   async::PostTask(helper_loop_->dispatcher(), [this]() {
     if (!ConfigureNetworks()) {
-      PostTerminate(TerminationReason::INTERNAL_ERROR);
+      PostTerminate(
+          SandboxResult(SandboxResult::Status::NETWORK_CONFIG_FAILED));
       return;
     }
 
     ManagedEnvironment::Options root_options;
     if (!CreateEnvironmentOptions(env_config_.environment(), &root_options)) {
-      PostTerminate(TerminationReason::INTERNAL_ERROR);
+      PostTerminate(SandboxResult::Status::ENVIRONMENT_CONFIG_FAILED,
+                    "Root environment can't load options");
       return;
     }
 
@@ -360,26 +364,29 @@
   fit::schedule_for_consumer(
       helper_executor_.get(),
       ConfigureEnvironment(std::move(svc), &env_config_.environment(), true)
-          .or_else(
-              [this]() { PostTerminate(TerminationReason::INTERNAL_ERROR); }));
+          .or_else([this](SandboxResult& result) {
+            PostTerminate(std::move(result));
+          }));
 }
 
-fit::promise<> Sandbox::StartChildEnvironment(
+Sandbox::Promise Sandbox::StartChildEnvironment(
     ConfiguringEnvironmentPtr parent, const config::Environment* config) {
   ASSERT_HELPER_DISPATCHER;
 
   return fit::make_promise(
-             [this, parent,
-              config]() -> fit::result<ConfiguringEnvironmentPtr> {
+             [this, parent, config]()
+                 -> fit::result<ConfiguringEnvironmentPtr, SandboxResult> {
                ManagedEnvironment::Options options;
                if (!CreateEnvironmentOptions(*config, &options)) {
-                 return fit::error();
+                 return fit::error(SandboxResult(
+                     SandboxResult::Status::ENVIRONMENT_CONFIG_FAILED));
                }
                auto child_env =
                    std::make_shared<environment::ManagedEnvironmentSyncPtr>();
                if ((*parent)->CreateChildEnvironment(
                        child_env->NewRequest(), std::move(options)) != ZX_OK) {
-                 return fit::error();
+                 return fit::error(SandboxResult(
+                     SandboxResult::Status::ENVIRONMENT_CONFIG_FAILED));
                }
 
                return fit::ok(std::move(child_env));
@@ -389,11 +396,11 @@
       });
 }
 
-fit::promise<> Sandbox::StartEnvironmentSetup(
+Sandbox::Promise Sandbox::StartEnvironmentSetup(
     const config::Environment* config,
     ConfiguringEnvironmentLauncher launcher) {
   return fit::make_promise([this, config, launcher = std::move(launcher)] {
-    auto prom = fit::make_ok_promise().box();
+    auto prom = fit::make_result_promise(PromiseResult(fit::ok())).box();
     for (const auto& setup : config->setup()) {
       prom = prom.and_then([this, setup = &setup, launcher]() {
                    return LaunchSetup(
@@ -407,43 +414,49 @@
   });
 }
 
-fit::promise<> Sandbox::StartEnvironmentAppsAndTests(
+Sandbox::Promise Sandbox::StartEnvironmentAppsAndTests(
     const netemul::config::Environment* config,
     netemul::Sandbox::ConfiguringEnvironmentLauncher launcher) {
-  return fit::make_promise([this, config,
-                            launcher = std::move(launcher)]() -> fit::result<> {
-    for (const auto& app : config->apps()) {
-      if (!LaunchProcess<kMsgApp>(
-              launcher.get(), app.GetUrlOrDefault(sandbox_env_->default_name()),
-              app.arguments(), false)) {
-        return fit::error();
-      }
-    }
+  return fit::make_promise(
+      [this, config, launcher = std::move(launcher)]() -> PromiseResult {
+        for (const auto& app : config->apps()) {
+          auto& url = app.GetUrlOrDefault(sandbox_env_->default_name());
+          if (!LaunchProcess<kMsgApp>(launcher.get(), url, app.arguments(),
+                                      false)) {
+            std::stringstream ss;
+            ss << "Failed to launch app " << url;
+            return fit::error(
+                SandboxResult(SandboxResult::Status::INTERNAL_ERROR, ss.str()));
+          }
+        }
 
-    for (const auto& test : config->test()) {
-      if (!LaunchProcess<kMsgTest>(
-              launcher.get(),
-              test.GetUrlOrDefault(sandbox_env_->default_name()),
-              test.arguments(), true)) {
-        return fit::error();
-      }
-      // save that at least one test was spawned.
-      test_spawned_ = true;
-    }
+        for (const auto& test : config->test()) {
+          auto& url = test.GetUrlOrDefault(sandbox_env_->default_name());
+          if (!LaunchProcess<kMsgTest>(launcher.get(), url, test.arguments(),
+                                       true)) {
+            std::stringstream ss;
+            ss << "Failed to launch test " << url;
+            return fit::error(
+                SandboxResult(SandboxResult::Status::INTERNAL_ERROR, ss.str()));
+          }
+          // save that at least one test was spawned.
+          test_spawned_ = true;
+        }
 
-    return fit::ok();
-  });
+        return fit::ok();
+      });
 }
 
-fit::promise<> Sandbox::StartEnvironmentInner(
+Sandbox::Promise Sandbox::StartEnvironmentInner(
     ConfiguringEnvironmentPtr env, const config::Environment* config) {
   ASSERT_HELPER_DISPATCHER;
   auto launcher = std::make_shared<fuchsia::sys::LauncherSyncPtr>();
-  return fit::make_promise([this, launcher, env]() -> fit::result<> {
+  return fit::make_promise([this, launcher, env]() -> PromiseResult {
            // get launcher
            if ((*env)->GetLauncher(launcher->NewRequest()) != ZX_OK) {
-             FXL_LOG(ERROR) << "Can't get environment launcher";
-             return fit::error();
+             return fit::error(
+                 SandboxResult(SandboxResult::Status::INTERNAL_ERROR,
+                               "Can't get environment launcher"));
            }
            return fit::ok();
          })
@@ -451,12 +464,12 @@
       .and_then(StartEnvironmentAppsAndTests(config, launcher));
 }
 
-fit::promise<> Sandbox::ConfigureEnvironment(ConfiguringEnvironmentPtr env,
-                                             const config::Environment* config,
-                                             bool root) {
+Sandbox::Promise Sandbox::ConfigureEnvironment(
+    ConfiguringEnvironmentPtr env, const config::Environment* config,
+    bool root) {
   ASSERT_HELPER_DISPATCHER;
 
-  std::vector<fit::promise<>> promises;
+  std::vector<Sandbox::Promise> promises;
 
   // iterate on children
   for (const auto& child : config->children()) {
@@ -469,21 +482,25 @@
   if (root) {
     // if root, after everything is set up, enable observing
     // test returns.
-    promises.emplace_back(
-        self_start.and_then([this]() { EnableTestObservation(); }));
+    promises.emplace_back(self_start.and_then([this]() -> PromiseResult {
+      EnableTestObservation();
+      return fit::ok();
+    }));
   } else {
     promises.emplace_back(std::move(self_start));
   }
 
   return fit::join_promise_vector(std::move(promises))
-      .and_then([](std::vector<fit::result<>>& results) -> fit::result<> {
-        for (const auto& r : results) {
-          if (r.is_error()) {
-            return fit::error();
-          }
-        }
-        return fit::ok();
-      });
+      .then(
+          [](fit::result<std::vector<PromiseResult>>& result) -> PromiseResult {
+            auto results = result.take_value();
+            for (auto& r : results) {
+              if (r.is_error()) {
+                return r;
+              }
+            }
+            return fit::ok();
+          });
 }
 
 template <typename T>
@@ -505,8 +522,11 @@
     RegisterTest(ticket);
   }
 
-  proc.set_error_handler([this](zx_status_t status) {
-    PostTerminate(TerminationReason::INTERNAL_ERROR);
+  proc.set_error_handler([this, url](zx_status_t status) {
+    std::stringstream ss;
+    ss << "Component controller for " << url << " reported error "
+       << zx_status_get_string(status);
+    PostTerminate(SandboxResult::Status::COMPONENT_FAILURE, ss.str());
   });
 
   // we observe test processes return code
@@ -518,12 +538,19 @@
     // remove the error handler:
     procs_[ticket].set_error_handler(nullptr);
     if (is_test) {
-      if (code != 0 || reason != TerminationReason::EXITED) {
-        // test failed, early bail
-        PostTerminate(code, reason);
+      if (reason == TerminationReason::EXITED) {
+        if (code != 0) {
+          // test failed, early bail
+          PostTerminate(SandboxResult::Status::TEST_FAILED, url);
+        } else {
+          // unregister test ticket
+          UnregisterTest(ticket);
+        }
       } else {
-        // unregister test ticket
-        UnregisterTest(ticket);
+        std::stringstream ss;
+        ss << "Test component " << url
+           << " failure: " << sys::HumanReadableTerminationReason(reason);
+        PostTerminate(SandboxResult::Status::COMPONENT_FAILURE, ss.str());
       }
     }
   };
@@ -537,12 +564,12 @@
   return true;
 }
 
-fit::promise<> Sandbox::LaunchSetup(fuchsia::sys::LauncherSyncPtr* launcher,
-                                    const std::string& url,
-                                    const std::vector<std::string>& arguments) {
+Sandbox::Promise Sandbox::LaunchSetup(
+    fuchsia::sys::LauncherSyncPtr* launcher, const std::string& url,
+    const std::vector<std::string>& arguments) {
   ASSERT_HELPER_DISPATCHER;
 
-  fit::bridge<> bridge;
+  fit::bridge<void, SandboxResult> bridge;
 
   fuchsia::sys::LaunchInfo linfo;
   linfo.url = url;
@@ -554,11 +581,16 @@
 
   if ((*launcher)->CreateComponent(std::move(linfo), proc.NewRequest()) !=
       ZX_OK) {
-    FXL_LOG(ERROR) << "couldn't launch setup: " << url;
-    bridge.completer.complete_error();
+    std::stringstream ss;
+    ss << "Failed to launch setup " << url;
+    bridge.completer.complete_error(
+        SandboxResult(SandboxResult::Status::INTERNAL_ERROR, ss.str()));
   } else {
-    proc.set_error_handler([this](zx_status_t status) {
-      PostTerminate(TerminationReason::INTERNAL_ERROR);
+    proc.set_error_handler([this, url](zx_status_t status) {
+      std::stringstream ss;
+      ss << "Component controller for " << url << " reported error "
+         << zx_status_get_string(status);
+      PostTerminate(SandboxResult::Status::COMPONENT_FAILURE, ss.str());
     });
 
     // we observe test processes return code
@@ -573,7 +605,8 @@
           if (code == 0 && reason == TerminationReason::EXITED) {
             completer.complete_ok();
           } else {
-            completer.complete_error();
+            completer.complete_error(
+                SandboxResult(SandboxResult::Status::SETUP_FAILED, url));
           }
         };
   }
@@ -590,13 +623,13 @@
   // consider it a failure.
   if (!test_spawned_) {
     FXL_LOG(ERROR) << "No tests were specified";
-    PostTerminate(TerminationReason::INTERNAL_ERROR);
+    PostTerminate(SandboxResult::EMPTY_TEST_SET);
     return;
   }
 
   if (tests_.empty()) {
     // all tests finished successfully
-    PostTerminate(0, TerminationReason::EXITED);
+    PostTerminate(SandboxResult::SUCCESS);
     return;
   }
 
@@ -606,7 +639,7 @@
         helper_loop_->dispatcher(),
         [this]() {
           FXL_LOG(ERROR) << "Test timed out.";
-          PostTerminate(kTimeoutTerminationCode, TerminationReason::EXITED);
+          PostTerminate(SandboxResult::TIMEOUT);
         },
         env_config_.timeout());
   }
@@ -624,7 +657,7 @@
   tests_.erase(ticket);
   if (setup_done_ && tests_.empty()) {
     // all tests finished successfully
-    PostTerminate(0, TerminationReason::EXITED);
+    PostTerminate(SandboxResult::SUCCESS);
   }
 }
 
@@ -661,4 +694,47 @@
   return ParseFromJSON(cmx.GetFacet(config::Config::Facet), &json_parser);
 }
 
+std::ostream& operator<<(std::ostream& os, const SandboxResult& result) {
+  switch (result.status_) {
+    case SandboxResult::Status::SUCCESS:
+      os << "Success";
+      break;
+    case SandboxResult::Status::NETWORK_CONFIG_FAILED:
+      os << "Network configuration failed";
+      break;
+    case SandboxResult::Status::SERVICE_EXITED:
+      os << "Service exited";
+      break;
+    case SandboxResult::Status::ENVIRONMENT_CONFIG_FAILED:
+      os << "Environment configuration failed";
+      break;
+    case SandboxResult::Status::TEST_FAILED:
+      os << "Test failed";
+      break;
+    case SandboxResult::Status::COMPONENT_FAILURE:
+      os << "Component failure";
+      break;
+    case SandboxResult::Status::SETUP_FAILED:
+      os << "Setup failed";
+      break;
+    case SandboxResult::Status::EMPTY_TEST_SET:
+      os << "Test set is empty";
+      break;
+    case SandboxResult::Status::TIMEOUT:
+      os << "Timeout";
+      break;
+    case SandboxResult::Status::INTERNAL_ERROR:
+      os << "Internal Error";
+      break;
+    case SandboxResult::Status::UNSPECIFIED:
+      os << "Unspecified error";
+      break;
+    default:
+      os << "Undefined(" << static_cast<uint32_t>(result.status_) << ")";
+  }
+  if (!result.description_.empty()) {
+    os << ": " << result.description_;
+  }
+  return os;
+}
 }  // namespace netemul
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox.h b/src/connectivity/network/testing/netemul/runner/sandbox.h
index 9504f72..248b6c0 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox.h
+++ b/src/connectivity/network/testing/netemul/runner/sandbox.h
@@ -29,11 +29,46 @@
   config::Config config;
 };
 
+class SandboxResult {
+ public:
+  enum Status {
+    SUCCESS,
+    NETWORK_CONFIG_FAILED,
+    SERVICE_EXITED,
+    ENVIRONMENT_CONFIG_FAILED,
+    TEST_FAILED,
+    SETUP_FAILED,
+    COMPONENT_FAILURE,
+    EMPTY_TEST_SET,
+    TIMEOUT,
+    INTERNAL_ERROR,
+    UNSPECIFIED
+  };
+
+  SandboxResult() : status_(SUCCESS) {}
+  explicit SandboxResult(Status status) : status_(status) {}
+
+  SandboxResult(Status status, std::string description)
+      : status_(status), description_(std::move(description)) {}
+
+  bool is_success() const { return status_ == Status::SUCCESS; }
+
+  Status status() const { return status_; }
+
+  const std::string& description() const { return description_; }
+
+  friend std::ostream& operator<<(std::ostream& os,
+                                  const SandboxResult& result);
+
+ private:
+  Status status_;
+  std::string description_;
+};
+
 class Sandbox {
  public:
   using TerminationReason = fuchsia::sys::TerminationReason;
-  using TerminationCallback =
-      fit::function<void(int64_t code, TerminationReason reason)>;
+  using TerminationCallback = fit::function<void(SandboxResult)>;
   using ServicesCreatedCallback = fit::function<void()>;
   using RootEnvironmentCreatedCallback =
       fit::function<void(ManagedEnvironment*)>;
@@ -62,12 +97,16 @@
       fidl::SynchronousInterfacePtr<ManagedEnvironment::FManagedEnvironment>>;
   using ConfiguringEnvironmentLauncher =
       std::shared_ptr<fidl::SynchronousInterfacePtr<fuchsia::sys::Launcher>>;
+  using Promise = fit::promise<void, SandboxResult>;
+  using PromiseResult = fit::result<void, SandboxResult>;
 
   void StartEnvironments();
-  void Terminate(TerminationReason reason);
-  void Terminate(int64_t exit_code, TerminationReason reason);
-  void PostTerminate(TerminationReason reason);
-  void PostTerminate(int64_t exit_code, TerminationReason reason);
+  void Terminate(SandboxResult result);
+  void Terminate(SandboxResult::Status status,
+                 std::string description = std::string());
+  void PostTerminate(SandboxResult result);
+  void PostTerminate(SandboxResult::Status status,
+                     std::string description = std::string());
 
   void EnableTestObservation();
   void RegisterTest(size_t ticket);
@@ -78,26 +117,25 @@
                      const std::string& url,
                      const std::vector<std::string>& arguments, bool is_test);
 
-  fit::promise<> LaunchSetup(fuchsia::sys::LauncherSyncPtr* launcher,
-                             const std::string& url,
-                             const std::vector<std::string>& arguments);
+  Promise LaunchSetup(fuchsia::sys::LauncherSyncPtr* launcher,
+                      const std::string& url,
+                      const std::vector<std::string>& arguments);
 
-  fit::promise<> StartChildEnvironment(ConfiguringEnvironmentPtr parent,
-                                       const config::Environment* config);
-  fit::promise<> StartEnvironmentInner(ConfiguringEnvironmentPtr environment,
-                                       const config::Environment* config);
-  fit::promise<> StartEnvironmentSetup(const config::Environment* config,
+  Promise StartChildEnvironment(ConfiguringEnvironmentPtr parent,
+                                const config::Environment* config);
+  Promise StartEnvironmentInner(ConfiguringEnvironmentPtr environment,
+                                const config::Environment* config);
+  Promise StartEnvironmentSetup(const config::Environment* config,
+                                ConfiguringEnvironmentLauncher launcher);
+  Promise StartEnvironmentAppsAndTests(const config::Environment* config,
                                        ConfiguringEnvironmentLauncher launcher);
-  fit::promise<> StartEnvironmentAppsAndTests(
-      const config::Environment* config,
-      ConfiguringEnvironmentLauncher launcher);
 
   bool CreateEnvironmentOptions(const config::Environment& config,
                                 ManagedEnvironment::Options* options);
   void ConfigureRootEnvironment();
-  fit::promise<> ConfigureEnvironment(ConfiguringEnvironmentPtr env,
-                                      const config::Environment* config,
-                                      bool root = false);
+  Promise ConfigureEnvironment(ConfiguringEnvironmentPtr env,
+                               const config::Environment* config,
+                               bool root = false);
   bool ConfigureNetworks();
 
   async_dispatcher_t* main_dispatcher_;
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc b/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
index 2e5c752..db668ee 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
+++ b/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
@@ -34,11 +34,10 @@
  protected:
   using TerminationReason = Sandbox::TerminationReason;
 
-  void RunSandbox(bool expect_success, TerminationReason expect_reason) {
+  void RunSandbox(SandboxResult::Status expect) {
     Sandbox sandbox(std::move(sandbox_args_));
     bool done = false;
-    int64_t o_exit_code;
-    TerminationReason o_term_reason;
+    SandboxResult o_result;
 
     sandbox.SetServicesCreatedCallback([this, &sandbox]() {
       if (connect_to_network_) {
@@ -49,13 +48,9 @@
       }
     });
 
-    sandbox.SetTerminationCallback([&done, &o_exit_code, &o_term_reason](
-                                       int64_t exit_code,
-                                       TerminationReason reason) {
-      FXL_LOG(INFO) << "Sandbox terminated with (" << exit_code << ") reason: "
-                    << sys::HumanReadableTerminationReason(reason);
-      o_exit_code = exit_code;
-      o_term_reason = reason;
+    sandbox.SetTerminationCallback([&done, &o_result](SandboxResult result) {
+      FXL_LOG(INFO) << "Sandbox terminated with status: " << result;
+      o_result = std::move(result);
       done = true;
     });
 
@@ -70,17 +65,19 @@
     // last chance to read any events pending.
     RunLoopUntilIdle();
 
-    EXPECT_EQ(o_exit_code == 0, expect_success);
-    EXPECT_EQ(o_term_reason, expect_reason);
+    // If we're expecting unspecified status, we will just check for expected
+    // failure. That's for failure cases that can have races on different
+    // failure points.
+    if (expect == SandboxResult::Status::UNSPECIFIED) {
+      EXPECT_FALSE(o_result.is_success());
+    } else {
+      EXPECT_EQ(o_result.status(), expect);
+    }
   }
 
-  void RunSandboxSuccess() { RunSandbox(true, TerminationReason::EXITED); }
+  void RunSandboxSuccess() { RunSandbox(SandboxResult::SUCCESS); }
 
-  void RunSandboxInternalError() {
-    RunSandbox(false, TerminationReason::INTERNAL_ERROR);
-  }
-
-  void RunSandboxFailure() { RunSandbox(false, TerminationReason::EXITED); }
+  void RunSandboxFailure() { RunSandbox(SandboxResult::TEST_FAILED); }
 
   void SetCmx(const std::string& cmx) {
     ASSERT_TRUE(sandbox_args_.ParseFromString(cmx));
@@ -294,7 +291,7 @@
   }
   )");
   EnableEventCollection();
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::SETUP_FAILED);
   // root proc should not have run, so events should be empty
   EXPECT_TRUE(events().empty());
 }
@@ -481,7 +478,7 @@
     ]
   }
   )");
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::NETWORK_CONFIG_FAILED);
 }
 
 TEST_F(SandboxTest, DuplicateEndpointNameFails) {
@@ -503,7 +500,7 @@
     ]
   }
   )");
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::NETWORK_CONFIG_FAILED);
 }
 
 TEST_F(SandboxTest, ValidNetworkSetup) {
@@ -649,7 +646,7 @@
     }
   }
   )");
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::EMPTY_TEST_SET);
 }
 
 TEST_F(SandboxTest, DisabledTestSucceeds) {
@@ -676,7 +673,7 @@
    }
 }
 )");
-  RunSandbox(false, TerminationReason::PACKAGE_NOT_FOUND);
+  RunSandbox(SandboxResult::Status::COMPONENT_FAILURE);
 }
 
 TEST_F(SandboxTest, TimeoutFires) {
@@ -691,7 +688,7 @@
 )");
   // expect that we'll fail due to the timeout of 1s < 10s of wait in the dummy
   // proc:
-  RunSandbox(false, TerminationReason::EXITED);
+  RunSandbox(SandboxResult::Status::TIMEOUT);
 }
 
 TEST_F(SandboxTest, ProcessSucceedsBeforeTimeoutFires) {
@@ -722,7 +719,7 @@
    }
 }
 )");
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::SERVICE_EXITED);
 }
 
 TEST_F(SandboxTest, ServiceExittingCausesFailure) {
@@ -742,7 +739,7 @@
    }
 }
 )");
-  RunSandboxInternalError();
+  RunSandbox(SandboxResult::Status::SERVICE_EXITED);
 }
 
 TEST_F(SandboxTest, DestructorRunsCleanly) {
@@ -764,9 +761,7 @@
 )");
   auto sandbox = std::make_unique<Sandbox>(TakeArgs());
   sandbox->SetTerminationCallback(
-      [](int64_t exit_code, TerminationReason reason) {
-        FAIL() << "Shouldn't exit";
-      });
+      [](SandboxResult result) { FAIL() << "Shouldn't exit"; });
   sandbox->Start(dispatcher());
   // Give enough time for the process to actually open the file:
   RunLoopWithTimeout(zx::msec(15));
@@ -790,7 +785,11 @@
       ]
    }
 })");
-  RunSandboxInternalError();
+  // Failures for environment with same name can come
+  // from different sources and is racy, to prevent
+  // test flakiness we just check that it'll fail cleanly,
+  // and not expect a specific return code.
+  RunSandbox(SandboxResult::Status::UNSPECIFIED);
 }
 
 }  // namespace testing