| // Copyright 2022 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/sys/fuzzing/common/tests/integration-tests.h" | |
| #include "src/sys/fuzzing/common/artifact.h" | |
| #include "src/sys/fuzzing/common/async-socket.h" | |
| #include "src/sys/fuzzing/common/engine.h" | |
| #include "src/sys/fuzzing/common/testing/artifact.h" | |
| #include "src/sys/fuzzing/common/testing/component-context.h" | |
| namespace fuzzing { | |
| // Test fixtures. | |
| void EngineIntegrationTest::SetUp() { | |
| AsyncTest::SetUp(); | |
| context_ = ComponentContextForTest::Create(executor()); | |
| engine_ = std::make_unique<ChildProcess>(executor()); | |
| options_ = MakeOptions(); | |
| } | |
| void EngineIntegrationTest::AddArg(std::string_view arg) { cmdline_.emplace_back(arg); } | |
| ZxPromise<> EngineIntegrationTest::StartEngine() { | |
| registrar_ = std::make_unique<FakeRegistrar>(executor()); | |
| return fpromise::make_promise([this]() -> ZxResult<> { | |
| fidl::InterfaceHandle<Registrar> registrar = registrar_->NewBinding(); | |
| engine_->Reset(); | |
| for (const auto& arg : cmdline_) { | |
| if (auto status = engine_->AddArg(arg); status != ZX_OK) { | |
| return fpromise::error(status); | |
| } | |
| } | |
| if (auto status = engine_->AddChannel(ComponentContextForTest::kRegistrarId, | |
| registrar.TakeChannel()); | |
| status != ZX_OK) { | |
| return fpromise::error(status); | |
| } | |
| return fpromise::ok(); | |
| }) | |
| .and_then([this]() { return AsZxResult(engine_->Spawn()); }) | |
| .wrap_with(scope_); | |
| } | |
| ZxPromise<> EngineIntegrationTest::Connect() { | |
| FX_DCHECK(registrar_); | |
| Bridge<> bridge1; | |
| ZxBridge<> bridge2; | |
| return registrar_->TakeProvider() | |
| .then([this, completer = std::move(bridge1.completer)]( | |
| Result<ControllerProviderHandle>& result) mutable -> ZxResult<> { | |
| if (result.is_error()) { | |
| FX_LOGS(ERROR) << "Failed to get handle for the controller provider."; | |
| return fpromise::error(ZX_ERR_INTERNAL); | |
| } | |
| auto handle = result.take_value(); | |
| provider_ = handle.Bind(); | |
| provider_->Connect(controller_.NewRequest(executor()->dispatcher()), completer.bind()); | |
| return fpromise::ok(); | |
| }) | |
| .and_then(ConsumeBridge(bridge1)) | |
| .and_then([this, completer = std::move(bridge2.completer)]() mutable -> ZxResult<> { | |
| controller_->Configure(CopyOptions(*options_), ZxBind<>(std::move(completer))); | |
| return fpromise::ok(); | |
| }) | |
| .and_then(ConsumeBridge(bridge2)) | |
| .and_then([this]() { | |
| // Get the controller's current artifact, which should be empty. | |
| return WatchArtifact(executor(), controller_); | |
| }) | |
| .and_then([](Artifact& artifact) -> ZxResult<> { | |
| if (!artifact.is_empty()) { | |
| FX_LOGS(ERROR) << "Artifact is not empty upon connection: fuzz_result=" | |
| << artifact.fuzz_result(); | |
| return fpromise::error(ZX_ERR_BAD_STATE); | |
| } | |
| return fpromise::ok(); | |
| }) | |
| .wrap_with(scope_); | |
| } | |
| ZxPromise<> EngineIntegrationTest::GetArtifactAndStatus(Artifact* out_artifact, | |
| Status* out_status) { | |
| Bridge<Status> bridge; | |
| // Wait for an update to the controller's artifact. For this to be a "hanging get", a previous | |
| // call to `WatchArtifact` is needed to get the "baseline" value; see `Connect` above. | |
| return WatchArtifact(executor(), controller_) | |
| .and_then([this, out_artifact, completer = std::move(bridge.completer)]( | |
| Artifact& artifact) mutable -> ZxResult<> { | |
| *out_artifact = std::move(artifact); | |
| controller_->GetStatus(completer.bind()); | |
| return fpromise::ok(); | |
| }) | |
| .and_then(ConsumeBridge(bridge)) | |
| .and_then([out_status](Status& status) mutable -> ZxResult<> { | |
| *out_status = std::move(status); | |
| return fpromise::ok(); | |
| }) | |
| .wrap_with(scope_); | |
| } | |
| ZxPromise<int64_t> EngineIntegrationTest::WaitForEngine() { return engine_->Wait(); } | |
| void EngineIntegrationTest::TearDown() { | |
| Schedule(engine_->Kill()); | |
| RunUntilIdle(); | |
| AsyncTest::TearDown(); | |
| } | |
| // Integration tests. | |
| void EngineIntegrationTest::RunBounded() { | |
| options()->set_runs(100); | |
| options()->set_max_input_size(3); | |
| FUZZING_EXPECT_OK(StartEngine().and_then(Connect())); | |
| RunUntilIdle(); | |
| ZxBridge<> bridge; | |
| controller()->Fuzz(ZxBind<>(std::move(bridge.completer))); | |
| FUZZING_EXPECT_OK(ConsumeBridge(bridge)); | |
| RunUntilIdle(); | |
| Artifact artifact; | |
| Status status; | |
| FUZZING_EXPECT_OK(GetArtifactAndStatus(&artifact, &status)); | |
| RunUntilIdle(); | |
| EXPECT_EQ(artifact.fuzz_result(), FuzzResult::NO_ERRORS); | |
| EXPECT_FALSE(artifact.has_input()); | |
| // The use of a seed corpus and an input pipelining means we may go over the | |
| // number of runs by a bit. | |
| ASSERT_TRUE(status.has_runs()); | |
| EXPECT_GE(status.runs(), 100U); | |
| } | |
| void EngineIntegrationTest::TryCrashingInput() { | |
| FUZZING_EXPECT_OK(StartEngine().and_then(Connect())); | |
| RunUntilIdle(); | |
| Input input("FUZZ"); | |
| ZxBridge<> bridge; | |
| controller()->TryOne(AsyncSocketWrite(executor(), input), ZxBind<>(std::move(bridge.completer))); | |
| FUZZING_EXPECT_OK(ConsumeBridge(bridge)); | |
| RunUntilIdle(); | |
| Artifact artifact; | |
| Status status; | |
| FUZZING_EXPECT_OK(GetArtifactAndStatus(&artifact, &status)); | |
| RunUntilIdle(); | |
| EXPECT_EQ(artifact.fuzz_result(), FuzzResult::CRASH); | |
| EXPECT_TRUE(status.has_elapsed()); | |
| } | |
| void EngineIntegrationTest::RunAsTest() { | |
| // When run as a test, the fuzzer does not have a controller to connect to. | |
| int64_t exit_code = -1; | |
| FUZZING_EXPECT_OK(StartEngine().and_then(WaitForEngine()), &exit_code); | |
| RunUntilIdle(); | |
| EXPECT_EQ(exit_code, 0); | |
| } | |
| } // namespace fuzzing |