blob: c76218288e0fd0f22323d84ff12d192adb41a969 [file] [log] [blame]
// Copyright 2018 The Chromium 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 <memory>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "components/cronet/native/test/test_url_request_callback.h"
#include "components/cronet/native/test/test_util.h"
#include "components/cronet/test/test_server.h"
#include "cronet_c.h"
#include "net/cert/mock_cert_verifier.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using cronet::test::TestUrlRequestCallback;
namespace {
class UrlRequestTest : public ::testing::Test {
protected:
UrlRequestTest() {}
~UrlRequestTest() override {}
void SetUp() override { cronet::TestServer::Start(); }
void TearDown() override { cronet::TestServer::Shutdown(); }
std::unique_ptr<TestUrlRequestCallback> StartAndWaitForComplete(
const std::string& url,
std::unique_ptr<TestUrlRequestCallback> test_callback) {
Cronet_EnginePtr engine = cronet::test::CreateTestEngine(0);
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params =
Cronet_UrlRequestParams_Create();
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback->GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback->CreateUrlRequestCallback();
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor);
Cronet_UrlRequest_Start(request);
test_callback->WaitForDone();
// Wait for all posted tasks to be executed to ensure there is no unhandled
// exception.
test_callback->ShutdownExecutor();
EXPECT_TRUE(test_callback->IsDone());
EXPECT_TRUE(Cronet_UrlRequest_IsDone(request));
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestCallback_Destroy(callback);
Cronet_Engine_Destroy(engine);
return test_callback;
}
std::unique_ptr<TestUrlRequestCallback> StartAndWaitForComplete(
const std::string& url) {
return StartAndWaitForComplete(url,
std::make_unique<TestUrlRequestCallback>());
}
void CheckResponseInfo(
const TestUrlRequestCallback::UrlResponseInfo& response_info,
const std::string& expected_url,
int expected_http_status_code,
const std::string& expected_http_status_text) {
EXPECT_EQ(expected_url, response_info.url);
EXPECT_EQ(expected_url, response_info.url_chain.back());
EXPECT_EQ(expected_http_status_code, response_info.http_status_code);
EXPECT_EQ(expected_http_status_text, response_info.http_status_text);
EXPECT_FALSE(response_info.was_cached);
}
void CheckResponseInfoHeader(
const TestUrlRequestCallback::UrlResponseInfo& response_info,
const std::string& header_name,
const std::string& header_value) {
for (const auto& header : response_info.all_headers) {
if (header.first == header_name) {
EXPECT_EQ(header.second, header_value);
return;
}
}
NOTREACHED();
}
void ExpectResponseInfoEquals(
const TestUrlRequestCallback::UrlResponseInfo& expected,
const TestUrlRequestCallback::UrlResponseInfo& actual) {
EXPECT_EQ(expected.url, actual.url);
EXPECT_EQ(expected.url_chain, actual.url_chain);
EXPECT_EQ(expected.http_status_code, actual.http_status_code);
EXPECT_EQ(expected.http_status_text, actual.http_status_text);
EXPECT_EQ(expected.all_headers, actual.all_headers);
EXPECT_EQ(expected.was_cached, actual.was_cached);
EXPECT_EQ(expected.negotiated_protocol, actual.negotiated_protocol);
EXPECT_EQ(expected.proxy_server, actual.proxy_server);
EXPECT_EQ(expected.received_byte_count, actual.received_byte_count);
}
void TestCancel(TestUrlRequestCallback::FailureType failure_type,
TestUrlRequestCallback::ResponseStep failure_step,
bool expect_response_info,
bool expect_error);
protected:
// Provide a message loop for use by TestExecutor instances.
base::MessageLoop message_loop_;
private:
DISALLOW_COPY_AND_ASSIGN(UrlRequestTest);
};
TEST_F(UrlRequestTest, InitChecks) {
Cronet_EngineParamsPtr engine_params = Cronet_EngineParams_Create();
Cronet_EnginePtr engine = Cronet_Engine_Create();
// Disable runtime CHECK of the result, so it could be verified.
Cronet_EngineParams_enable_check_result_set(engine_params, false);
EXPECT_EQ(Cronet_RESULT_SUCCESS,
Cronet_Engine_StartWithParams(engine, engine_params));
Cronet_EngineParams_Destroy(engine_params);
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params = Cronet_UrlRequestParams_Create();
const std::string url = cronet::TestServer::GetEchoMethodURL();
TestUrlRequestCallback test_callback;
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback.GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback.CreateUrlRequestCallback();
EXPECT_EQ(Cronet_RESULT_NULL_POINTER_URL,
Cronet_UrlRequest_InitWithParams(
request, engine, /* url = */ nullptr,
/* request_params = */ nullptr, /* callback = */ nullptr,
/* executor = */ nullptr));
EXPECT_EQ(Cronet_RESULT_NULL_POINTER_PARAMS,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
/* request_params = */ nullptr,
/* callback = */ nullptr,
/* executor = */ nullptr));
EXPECT_EQ(Cronet_RESULT_NULL_POINTER_CALLBACK,
Cronet_UrlRequest_InitWithParams(
request, engine, url.c_str(), request_params,
/* callback = */ nullptr, /* executor = */ nullptr));
EXPECT_EQ(Cronet_RESULT_NULL_POINTER_EXECUTOR,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback,
/* executor = */ nullptr));
EXPECT_EQ(Cronet_RESULT_NULL_POINTER_EXECUTOR,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback,
/* executor = */ nullptr));
Cronet_UrlRequestParams_http_method_set(request_params, "bad:method");
EXPECT_EQ(
Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_METHOD,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor));
Cronet_UrlRequestParams_http_method_set(request_params, "HEAD");
// Check header validation
Cronet_HttpHeaderPtr http_header = Cronet_HttpHeader_Create();
Cronet_UrlRequestParams_request_headers_add(request_params, http_header);
EXPECT_EQ(
Cronet_RESULT_NULL_POINTER_HEADER_NAME,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor));
Cronet_UrlRequestParams_request_headers_clear(request_params);
Cronet_HttpHeader_name_set(http_header, "bad:name");
Cronet_UrlRequestParams_request_headers_add(request_params, http_header);
EXPECT_EQ(
Cronet_RESULT_NULL_POINTER_HEADER_VALUE,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor));
Cronet_UrlRequestParams_request_headers_clear(request_params);
Cronet_HttpHeader_value_set(http_header, "header value");
Cronet_UrlRequestParams_request_headers_add(request_params, http_header);
EXPECT_EQ(
Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_HEADER,
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor));
Cronet_UrlRequestParams_request_headers_clear(request_params);
Cronet_HttpHeader_name_set(http_header, "header-name");
Cronet_UrlRequestParams_request_headers_add(request_params, http_header);
EXPECT_EQ(Cronet_RESULT_SUCCESS, Cronet_UrlRequest_InitWithParams(
request, engine, url.c_str(),
request_params, callback, executor));
Cronet_HttpHeader_Destroy(http_header);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequestCallback_Destroy(callback);
Cronet_Engine_Destroy(engine);
}
TEST_F(UrlRequestTest, SimpleGet) {
const std::string url = cronet::TestServer::GetEchoMethodURL();
auto callback = StartAndWaitForComplete(url);
EXPECT_EQ(200, callback->response_info_->http_status_code);
// Default method is 'GET'.
EXPECT_EQ("GET", callback->response_as_string_);
EXPECT_EQ(0, callback->redirect_count_);
EXPECT_EQ(callback->response_step_, callback->ON_SUCCEEDED);
CheckResponseInfo(*callback->response_info_, url, 200, "OK");
TestUrlRequestCallback::UrlResponseInfo expected_response_info(
std::vector<std::string>({url}), "OK", 200, 86,
std::vector<std::string>({"Connection", "close", "Content-Length", "3",
"Content-Type", "text/plain"}));
ExpectResponseInfoEquals(expected_response_info, *callback->response_info_);
}
TEST_F(UrlRequestTest, SimpleRequest) {
Cronet_EnginePtr engine = cronet::test::CreateTestEngine(0);
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params = Cronet_UrlRequestParams_Create();
std::string url = cronet::TestServer::GetSimpleURL();
TestUrlRequestCallback test_callback;
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback.GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback.CreateUrlRequestCallback();
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(), request_params,
callback, executor);
Cronet_UrlRequest_Start(request);
test_callback.WaitForDone();
EXPECT_TRUE(test_callback.IsDone());
ASSERT_EQ("The quick brown fox jumps over the lazy dog.",
test_callback.response_as_string_);
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestCallback_Destroy(callback);
Cronet_Engine_Destroy(engine);
}
TEST_F(UrlRequestTest, MultiRedirect) {
const std::string url = cronet::TestServer::GetMultiRedirectURL();
auto callback = StartAndWaitForComplete(url);
EXPECT_EQ(2, callback->redirect_count_);
EXPECT_EQ(200, callback->response_info_->http_status_code);
EXPECT_EQ(2ul, callback->redirect_response_info_list_.size());
// Check first redirect (multiredirect.html -> redirect.html).
TestUrlRequestCallback::UrlResponseInfo first_expected_response_info(
std::vector<std::string>({url}), "Found", 302, 76,
std::vector<std::string>(
{"Location", GURL(cronet::TestServer::GetRedirectURL()).path(),
"redirect-header0", "header-value"}));
ExpectResponseInfoEquals(first_expected_response_info,
*callback->redirect_response_info_list_.front());
// Check second redirect (redirect.html -> success.txt).
TestUrlRequestCallback::UrlResponseInfo second_expected_response_info(
std::vector<std::string>({cronet::TestServer::GetMultiRedirectURL(),
cronet::TestServer::GetRedirectURL()}),
"Found", 302, 149,
std::vector<std::string>(
{"Location", GURL(cronet::TestServer::GetSuccessURL()).path(),
"redirect-header", "header-value"}));
ExpectResponseInfoEquals(second_expected_response_info,
*callback->redirect_response_info_list_.back());
// Check final response (success.txt).
TestUrlRequestCallback::UrlResponseInfo final_expected_response_info(
std::vector<std::string>({cronet::TestServer::GetMultiRedirectURL(),
cronet::TestServer::GetRedirectURL(),
cronet::TestServer::GetSuccessURL()}),
"OK", 200, 334,
std::vector<std::string>(
{"Content-Type", "text/plain", "Access-Control-Allow-Origin", "*",
"header-name", "header-value", "multi-header-name", "header-value1",
"multi-header-name", "header-value2"}));
ExpectResponseInfoEquals(final_expected_response_info,
*callback->response_info_);
EXPECT_NE(0, callback->response_data_length_);
EXPECT_EQ(callback->ON_SUCCEEDED, callback->response_step_);
}
TEST_F(UrlRequestTest, CancelRequest) {
Cronet_EnginePtr engine = cronet::test::CreateTestEngine(0);
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params = Cronet_UrlRequestParams_Create();
std::string url = cronet::TestServer::GetSimpleURL();
TestUrlRequestCallback test_callback;
test_callback.set_failure(test_callback.CANCEL_SYNC,
test_callback.ON_RESPONSE_STARTED);
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback.GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback.CreateUrlRequestCallback();
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(), request_params,
callback, executor);
Cronet_UrlRequest_Start(request);
test_callback.WaitForDone();
EXPECT_TRUE(test_callback.IsDone());
EXPECT_TRUE(test_callback.on_canceled_called_);
ASSERT_FALSE(test_callback.on_error_called_);
EXPECT_TRUE(test_callback.response_as_string_.empty());
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestCallback_Destroy(callback);
Cronet_Engine_Destroy(engine);
}
TEST_F(UrlRequestTest, FailedRequestHostNotFound) {
Cronet_EnginePtr engine = cronet::test::CreateTestEngine(0);
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params = Cronet_UrlRequestParams_Create();
std::string url = "https://notfound.example.com";
TestUrlRequestCallback test_callback;
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback.GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback.CreateUrlRequestCallback();
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(), request_params,
callback, executor);
Cronet_UrlRequest_Start(request);
test_callback.WaitForDone();
EXPECT_TRUE(test_callback.IsDone());
EXPECT_TRUE(test_callback.on_error_called_);
ASSERT_FALSE(test_callback.on_canceled_called_);
EXPECT_TRUE(test_callback.response_as_string_.empty());
ASSERT_EQ(nullptr, test_callback.response_info_);
ASSERT_NE(nullptr, test_callback.last_error_);
ASSERT_EQ(Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED,
Cronet_Error_error_code_get(test_callback.last_error_));
ASSERT_FALSE(
Cronet_Error_immediately_retryable_get(test_callback.last_error_));
ASSERT_STREQ("net::ERR_NAME_NOT_RESOLVED",
Cronet_Error_message_get(test_callback.last_error_));
ASSERT_EQ(-105,
Cronet_Error_internal_error_code_get(test_callback.last_error_));
ASSERT_EQ(
0, Cronet_Error_quic_detailed_error_code_get(test_callback.last_error_));
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestCallback_Destroy(callback);
Cronet_Engine_Destroy(engine);
}
void UrlRequestTest::TestCancel(
TestUrlRequestCallback::FailureType failure_type,
TestUrlRequestCallback::ResponseStep failure_step,
bool expect_response_info,
bool expect_error) {
auto callback = std::make_unique<TestUrlRequestCallback>();
callback->set_failure(failure_type, failure_step);
const std::string url = cronet::TestServer::GetRedirectURL();
callback = StartAndWaitForComplete(url, std::move(callback));
EXPECT_EQ(1, callback->redirect_count_);
EXPECT_EQ(1ul, callback->redirect_response_info_list_.size());
if (failure_type == TestUrlRequestCallback::CANCEL_SYNC ||
failure_type == TestUrlRequestCallback::CANCEL_ASYNC) {
EXPECT_EQ(TestUrlRequestCallback::ON_CANCELED, callback->response_step_);
}
EXPECT_EQ(expect_response_info, callback->response_info_ != nullptr);
EXPECT_EQ(expect_error, callback->last_error_ != nullptr);
EXPECT_EQ(expect_error, callback->on_error_called_);
// When |failure_type| is CANCEL_ASYNC_WITHOUT_PAUSE and |failure_step|
// is ON_READ_COMPLETED, there might be an onSucceeded() task
// already posted. If that's the case, onCanceled() will not be invoked. See
// crbug.com/657415.
if (!(failure_type == TestUrlRequestCallback::CANCEL_ASYNC_WITHOUT_PAUSE &&
failure_step == TestUrlRequestCallback::ON_READ_COMPLETED)) {
EXPECT_TRUE(callback->on_canceled_called_);
}
}
TEST_F(UrlRequestTest, TestCancel) {
TestCancel(TestUrlRequestCallback::CANCEL_SYNC,
TestUrlRequestCallback::ON_RECEIVED_REDIRECT, false, false);
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC,
TestUrlRequestCallback::ON_RECEIVED_REDIRECT, false, false);
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC_WITHOUT_PAUSE,
TestUrlRequestCallback::ON_RECEIVED_REDIRECT, false, false);
TestCancel(TestUrlRequestCallback::CANCEL_SYNC,
TestUrlRequestCallback::ON_RESPONSE_STARTED, true, false);
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC,
TestUrlRequestCallback::ON_RESPONSE_STARTED, true, false);
// https://crbug.com/812334 - If request is canceled asynchronously, the
// 'OnReadCompleted' callback may arrive AFTER 'OnCanceled'.
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC_WITHOUT_PAUSE,
TestUrlRequestCallback::ON_RESPONSE_STARTED, true, false);
TestCancel(TestUrlRequestCallback::CANCEL_SYNC,
TestUrlRequestCallback::ON_READ_COMPLETED, true, false);
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC,
TestUrlRequestCallback::ON_READ_COMPLETED, true, false);
TestCancel(TestUrlRequestCallback::CANCEL_ASYNC_WITHOUT_PAUSE,
TestUrlRequestCallback::ON_READ_COMPLETED, true, false);
}
TEST_F(UrlRequestTest, PerfTest) {
const int kTestIterations = 10;
const int kDownloadSize = 19307439; // used for internal server only
Cronet_EnginePtr engine = Cronet_Engine_Create();
Cronet_EngineParamsPtr engine_params = Cronet_EngineParams_Create();
Cronet_Engine_StartWithParams(engine, engine_params);
std::string url = cronet::TestServer::PrepareBigDataURL(kDownloadSize);
base::Time start = base::Time::Now();
for (int i = 0; i < kTestIterations; ++i) {
Cronet_UrlRequestPtr request = Cronet_UrlRequest_Create();
Cronet_UrlRequestParamsPtr request_params =
Cronet_UrlRequestParams_Create();
TestUrlRequestCallback test_callback;
test_callback.set_accumulate_response_data(false);
// Executor provided by the application is owned by |test_callback|.
Cronet_ExecutorPtr executor = test_callback.GetExecutor(false);
// Callback provided by the application.
Cronet_UrlRequestCallbackPtr callback =
test_callback.CreateUrlRequestCallback();
Cronet_UrlRequest_InitWithParams(request, engine, url.c_str(),
request_params, callback, executor);
Cronet_UrlRequest_Start(request);
test_callback.WaitForDone();
EXPECT_TRUE(test_callback.IsDone());
ASSERT_EQ(kDownloadSize, test_callback.response_data_length_);
Cronet_UrlRequestParams_Destroy(request_params);
Cronet_UrlRequest_Destroy(request);
Cronet_UrlRequestCallback_Destroy(callback);
}
base::Time end = base::Time::Now();
base::TimeDelta delta = end - start;
LOG(INFO) << "Total time " << delta.InMillisecondsF() << " ms";
LOG(INFO) << "Single Iteration time "
<< delta.InMillisecondsF() / kTestIterations << " ms";
double bytes_per_ms =
kDownloadSize * kTestIterations / delta.InMillisecondsF();
double megabytes_per_ms = bytes_per_ms / 1000000;
double megabits_per_second = megabytes_per_ms * 8 * 1000;
LOG(INFO) << "Average Throughput: " << megabits_per_second << " mbps";
Cronet_EngineParams_Destroy(engine_params);
Cronet_Engine_Destroy(engine);
cronet::TestServer::ReleaseBigDataURL();
}
} // namespace