blob: 1a03f3013632ebf63759d42d08b4f91e4f3f7bb6 [file] [log] [blame]
// Copyright 2018 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 <lib/async/cpp/task.h>
#include "garnet/bin/cobalt/utils/fuchsia_http_client.h"
#include "gtest/gtest.h"
#include "lib/gtest/test_loop_fixture.h"
#include "lib/network_wrapper/fake_network_wrapper.h"
namespace cobalt {
namespace utils {
namespace http = ::fuchsia::net::oldhttp;
using clearcut::HTTPRequest;
using clearcut::HTTPResponse;
using network_wrapper::FakeNetworkWrapper;
using network_wrapper::NetworkWrapper;
using tensorflow_statusor::StatusOr;
class CVBool {
public:
CVBool() : set_(false) {}
void Notify() {
{
std::unique_lock<std::mutex> lock(mutex_);
set_ = true;
}
cv_.notify_all();
}
template <class Rep, class Period>
bool Wait(const std::chrono::duration<Rep, Period>& duration) {
std::unique_lock<std::mutex> lock(mutex_);
if (set_) {
return true;
}
return cv_.wait_for(lock, duration, [this] { return set_; });
}
bool Check() {
std::unique_lock<std::mutex> lock(mutex_);
return set_;
}
private:
std::mutex mutex_;
std::condition_variable cv_;
bool set_;
};
class TestFuchsiaHTTPClient : public FuchsiaHTTPClient {
public:
TestFuchsiaHTTPClient(NetworkWrapper* network_wrapper,
async_dispatcher_t* dispatcher)
: FuchsiaHTTPClient(network_wrapper, dispatcher) {}
void HandleResponse(fxl::RefPtr<NetworkRequest> req,
http::URLResponse fx_response) override {
FuchsiaHTTPClient::HandleResponse(req, std::move(fx_response));
response_handled_.Notify();
}
void HandleDeadline(fxl::RefPtr<NetworkRequest> req) override {
deadline_triggered_.Notify();
FuchsiaHTTPClient::HandleDeadline(req);
}
bool CheckResponseHandled() { return response_handled_.Check(); }
bool CheckDeadlineTriggered() { return deadline_triggered_.Check(); }
CVBool response_handled_;
CVBool deadline_triggered_;
};
class FuchsiaHTTPClientTest : public ::gtest::TestLoopFixture {
public:
FuchsiaHTTPClientTest()
: ::gtest::TestLoopFixture(),
network_wrapper_(dispatcher()),
delete_after_post_(false),
http_client(
new TestFuchsiaHTTPClient(&network_wrapper_, dispatcher())) {}
void PrepareResponse(const std::string& body, uint32_t status_code = 200) {
network_wrapper_.SetStringResponse(body, status_code);
}
void PrepareResponse(zx::socket body, uint32_t status_code = 200) {
network_wrapper_.SetSocketResponse(std::move(body), status_code);
}
std::future<StatusOr<HTTPResponse>> PostString(const std::string& body) {
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
auto future = std::async(std::launch::async, [this, body, deadline] {
auto post_future =
http_client->Post(HTTPRequest("http://www.test.com", body), deadline);
if (delete_after_post_) {
delete_after_post_ = false;
http_client.reset(nullptr);
}
post_sent_.Notify();
return post_future.get();
});
// Wait up to 10 second for std::async to run. This should happen almost
// immediately.
EXPECT_TRUE(post_sent_.Wait(std::chrono::seconds(10)));
return future;
}
template <class F>
StatusOr<F> RunUntilReady(std::future<StatusOr<F>>* future,
zx::duration max_wait,
zx::duration increment = zx::msec(100)) {
zx::duration elapsed;
while (elapsed < max_wait) {
elapsed += increment;
RunLoopFor(increment);
if (std::future_status::ready ==
future->wait_for(std::chrono::milliseconds(1))) {
return future->get();
}
}
return util::Status(util::StatusCode::CANCELLED, "Ran out of time");
}
void DeleteHttpClientAfterPost() { delete_after_post_ = true; }
private:
FakeNetworkWrapper network_wrapper_;
bool delete_after_post_;
CVBool post_sent_;
public:
std::unique_ptr<TestFuchsiaHTTPClient> http_client;
};
TEST_F(FuchsiaHTTPClientTest, MakePostAndGet) {
PrepareResponse("Response");
auto response_future = PostString("Request");
RunLoopUntilIdle();
EXPECT_TRUE(http_client->CheckResponseHandled());
auto response_or = response_future.get();
EXPECT_TRUE(response_or.ok());
auto response = response_or.ConsumeValueOrDie();
EXPECT_EQ(response.response, "Response");
}
TEST_F(FuchsiaHTTPClientTest, TestTimeout) {
auto response_future = PostString("Request");
RunLoopFor(zx::msec(100));
EXPECT_FALSE(http_client->CheckDeadlineTriggered());
RunLoopFor(zx::sec(1));
EXPECT_TRUE(http_client->CheckDeadlineTriggered());
auto response_or = response_future.get();
ASSERT_FALSE(response_or.ok());
EXPECT_EQ(response_or.status().error_code(),
util::StatusCode::DEADLINE_EXCEEDED);
}
TEST_F(FuchsiaHTTPClientTest, WaitAfterRelease) {
zx::socket socket_in, socket_out;
ASSERT_EQ(zx::socket::create(0u, &socket_in, &socket_out), ZX_OK);
PrepareResponse(std::move(socket_in));
DeleteHttpClientAfterPost();
auto response_future = PostString("Request");
const char message[] = "Response";
for (const char* it = message; *it; ++it) {
RunLoopFor(zx::sec(1));
size_t bytes_written;
socket_out.write(0u, it, 1, &bytes_written);
EXPECT_EQ(1u, bytes_written);
EXPECT_NE(std::future_status::ready,
response_future.wait_for(std::chrono::milliseconds(1)));
}
socket_out.reset();
auto response_or = RunUntilReady(&response_future, zx::sec(1));
EXPECT_EQ(response_or.status().error_code(), util::StatusCode::OK);
auto response = response_or.ConsumeValueOrDie();
EXPECT_EQ(response.response, "Response");
}
} // namespace utils
} // namespace cobalt