Add a unit test for HTTPRequest
Adding cpp_mock header only mocking library to making unit test that requires mocks easier to implement. Adding some HTTPRequest unit tests that makes use of cpp_mock to assess how useful could be to testing Godot.
This commit is contained in:
parent
e3213aaef5
commit
15e9008216
|
@ -0,0 +1,173 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* test_http_client_manual_mock.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TEST_HTTP_CLIENT_MANUAL_MOCK_H
|
||||||
|
#define TEST_HTTP_CLIENT_MANUAL_MOCK_H
|
||||||
|
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
|
||||||
|
#include "thirdparty/doctest/doctest.h"
|
||||||
|
|
||||||
|
class HTTPClientManualMock : public HTTPClient {
|
||||||
|
public:
|
||||||
|
static HTTPClientManualMock *current_instance;
|
||||||
|
|
||||||
|
static HTTPClient *_create_func(bool p_notify_postinitialize = true) {
|
||||||
|
current_instance = static_cast<HTTPClientManualMock *>(ClassDB::creator<HTTPClientManualMock>(p_notify_postinitialize));
|
||||||
|
return current_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HTTPClient *(*_old_create)(bool);
|
||||||
|
static void make_current() {
|
||||||
|
_old_create = HTTPClient::_create;
|
||||||
|
HTTPClient::_create = _create_func;
|
||||||
|
}
|
||||||
|
static void reset_current() {
|
||||||
|
if (_old_create) {
|
||||||
|
HTTPClient::_create = _old_create;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Status> get_status_return;
|
||||||
|
|
||||||
|
int set_read_chunk_size_p_size_parameter = 0;
|
||||||
|
int set_read_chunk_size_call_count = 0;
|
||||||
|
|
||||||
|
String connect_to_host_p_host_parameter;
|
||||||
|
int connect_to_host_p_port_parameter = 0;
|
||||||
|
Ref<TLSOptions> connect_to_host_p_tls_options_parameter = nullptr;
|
||||||
|
Error connect_to_host_return = Error::OK;
|
||||||
|
int connect_to_host_call_count = 0;
|
||||||
|
|
||||||
|
int close_call_count = 0;
|
||||||
|
|
||||||
|
int get_response_code_return = 0;
|
||||||
|
|
||||||
|
bool has_response_return = false;
|
||||||
|
|
||||||
|
Method request_p_method_parameter = Method::METHOD_GET;
|
||||||
|
String request_p_url_parameter;
|
||||||
|
Vector<String> request_p_headers_parameter;
|
||||||
|
uint8_t *request_p_body_parameter = nullptr;
|
||||||
|
int request_p_body_size_parameter = 0;
|
||||||
|
int request_call_count = 0;
|
||||||
|
Error request_return = Error::OK;
|
||||||
|
|
||||||
|
List<String> get_response_headers_r_response_parameter;
|
||||||
|
Error get_response_headers_return = Error::OK;
|
||||||
|
|
||||||
|
int64_t get_response_body_length_return;
|
||||||
|
|
||||||
|
PackedByteArray read_response_body_chunk_return;
|
||||||
|
#ifdef THREADS_ENABLED
|
||||||
|
Semaphore *read_response_body_chunk_semaphore = nullptr;
|
||||||
|
#endif // THREADS_ENABLED
|
||||||
|
|
||||||
|
Error poll_return;
|
||||||
|
|
||||||
|
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override {
|
||||||
|
request_p_method_parameter = p_method;
|
||||||
|
request_p_url_parameter = p_url;
|
||||||
|
request_p_headers_parameter = p_headers;
|
||||||
|
request_p_body_parameter = const_cast<uint8_t *>(p_body);
|
||||||
|
request_p_body_size_parameter = p_body_size;
|
||||||
|
request_call_count++;
|
||||||
|
return request_return;
|
||||||
|
}
|
||||||
|
Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) override {
|
||||||
|
connect_to_host_p_host_parameter = p_host;
|
||||||
|
connect_to_host_p_port_parameter = p_port;
|
||||||
|
connect_to_host_p_tls_options_parameter = p_tls_options;
|
||||||
|
connect_to_host_call_count++;
|
||||||
|
return connect_to_host_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_connection(const Ref<StreamPeer> &p_connection) override {}
|
||||||
|
Ref<StreamPeer> get_connection() const override { return Ref<StreamPeer>(); }
|
||||||
|
|
||||||
|
void close() override {
|
||||||
|
close_call_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status get_status() const override {
|
||||||
|
if (get_status_return.size() == 0) {
|
||||||
|
FAIL("Call to HTTPClient::get_status not set. Please set a return value.");
|
||||||
|
return Status();
|
||||||
|
}
|
||||||
|
|
||||||
|
Status status = get_status_return[get_status_return_current];
|
||||||
|
if (get_status_return_current + 1 < get_status_return.size()) {
|
||||||
|
get_status_return_current++;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_response() const override { return has_response_return; }
|
||||||
|
bool is_response_chunked() const override { return true; }
|
||||||
|
int get_response_code() const override { return get_response_code_return; }
|
||||||
|
Error get_response_headers(List<String> *r_response) override {
|
||||||
|
*r_response = get_response_headers_r_response_parameter;
|
||||||
|
(void)r_response;
|
||||||
|
return get_response_headers_return;
|
||||||
|
}
|
||||||
|
int64_t get_response_body_length() const override {
|
||||||
|
return get_response_body_length_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray read_response_body_chunk() override {
|
||||||
|
#ifdef THREADS_ENABLED
|
||||||
|
if (read_response_body_chunk_semaphore != nullptr) {
|
||||||
|
read_response_body_chunk_semaphore->post();
|
||||||
|
}
|
||||||
|
#endif // THREADS_ENABLED
|
||||||
|
return read_response_body_chunk_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_blocking_mode(bool p_enable) override {}
|
||||||
|
bool is_blocking_mode_enabled() const override { return true; }
|
||||||
|
|
||||||
|
void set_read_chunk_size(int p_size) override {
|
||||||
|
set_read_chunk_size_p_size_parameter = p_size;
|
||||||
|
set_read_chunk_size_call_count++;
|
||||||
|
}
|
||||||
|
int get_read_chunk_size() const override { return 0; }
|
||||||
|
|
||||||
|
Error poll() override {
|
||||||
|
return poll_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClientManualMock() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// This MUST be mutable because I need to update its value from a const method (the mock method)
|
||||||
|
mutable Vector<Status>::Size get_status_return_current = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEST_HTTP_CLIENT_MANUAL_MOCK_H
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* test_http_client_mock.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TEST_HTTP_CLIENT_MOCK_H
|
||||||
|
#define TEST_HTTP_CLIENT_MOCK_H
|
||||||
|
|
||||||
|
#include "core/io/http_client.h"
|
||||||
|
|
||||||
|
#include "thirdparty/cpp_mock/cpp_mock.h"
|
||||||
|
|
||||||
|
class HTTPClientMock : public HTTPClient {
|
||||||
|
public:
|
||||||
|
static HTTPClientMock *current_instance;
|
||||||
|
|
||||||
|
static HTTPClient *_create_func(bool p_notify_postinitialize = true) {
|
||||||
|
current_instance = static_cast<HTTPClientMock *>(ClassDB::creator<HTTPClientMock>(p_notify_postinitialize));
|
||||||
|
return current_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HTTPClient *(*_old_create)(bool);
|
||||||
|
static void make_current() {
|
||||||
|
_old_create = HTTPClient::_create;
|
||||||
|
HTTPClient::_create = _create_func;
|
||||||
|
}
|
||||||
|
static void reset_current() {
|
||||||
|
if (_old_create) {
|
||||||
|
HTTPClient::_create = _old_create;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MockMethod(Error, request, (Method, const String &, const Vector<String> &, const uint8_t *, int));
|
||||||
|
MockMethod(Error, connect_to_host, (const String &, int, Ref<TLSOptions>));
|
||||||
|
|
||||||
|
MockMethod(void, set_connection, (const Ref<StreamPeer> &));
|
||||||
|
MockConstMethod(Ref<StreamPeer>, get_connection, ());
|
||||||
|
|
||||||
|
MockMethod(void, close, ());
|
||||||
|
|
||||||
|
MockConstMethod(Status, get_status, ());
|
||||||
|
|
||||||
|
MockConstMethod(bool, has_response, ());
|
||||||
|
MockConstMethod(bool, is_response_chunked, ());
|
||||||
|
MockConstMethod(int, get_response_code, ());
|
||||||
|
MockMethod(Error, get_response_headers, (List<String> *));
|
||||||
|
MockConstMethod(int64_t, get_response_body_length, ());
|
||||||
|
|
||||||
|
MockMethod(PackedByteArray, read_response_body_chunk, ());
|
||||||
|
|
||||||
|
MockMethod(void, set_blocking_mode, (bool));
|
||||||
|
MockConstMethod(bool, is_blocking_mode_enabled, ());
|
||||||
|
|
||||||
|
MockMethod(void, set_read_chunk_size, (int));
|
||||||
|
MockConstMethod(int, get_read_chunk_size, ());
|
||||||
|
|
||||||
|
MockMethod(Error, poll, ());
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEST_HTTP_CLIENT_MOCK_H
|
|
@ -0,0 +1,493 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* test_http_request.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TEST_HTTP_REQUEST_H
|
||||||
|
#define TEST_HTTP_REQUEST_H
|
||||||
|
|
||||||
|
#include "scene/main/http_request.h"
|
||||||
|
#include "tests/core/io/test_http_client_mock.h"
|
||||||
|
#include "tests/test_macros.h"
|
||||||
|
|
||||||
|
#include "thirdparty/cpp_mock/cpp_mock.h"
|
||||||
|
|
||||||
|
namespace TestHTTPRequest {
|
||||||
|
|
||||||
|
static inline Array build_array() {
|
||||||
|
return Array();
|
||||||
|
}
|
||||||
|
template <typename... Targs>
|
||||||
|
static inline Array build_array(Variant item, Targs... Fargs) {
|
||||||
|
Array a = build_array(Fargs...);
|
||||||
|
a.push_front(item);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline PackedStringArray build_headers() {
|
||||||
|
return PackedStringArray();
|
||||||
|
}
|
||||||
|
template <typename... Targs>
|
||||||
|
static inline PackedStringArray build_headers(Variant item, Targs... Fargs) {
|
||||||
|
PackedStringArray psa = build_headers(Fargs...);
|
||||||
|
psa.push_back(item);
|
||||||
|
return psa;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest] Download chunk size is set when HTTP client is disconnected") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
int expected_value = 42;
|
||||||
|
|
||||||
|
When(http_client->get_status).Return(HTTPClient::STATUS_DISCONNECTED);
|
||||||
|
|
||||||
|
http_request->set_download_chunk_size(expected_value);
|
||||||
|
|
||||||
|
Verify(http_client->set_read_chunk_size).With(expected_value);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest] Download chunk size is not set when HTTP client is not disconnected") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
int expected_value = 42;
|
||||||
|
|
||||||
|
When(http_client->get_status).Return(HTTPClient::STATUS_CONNECTED);
|
||||||
|
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
http_request->set_download_chunk_size(expected_value);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
|
Verify(http_client->set_read_chunk_size).With(expected_value).Times(0);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest] ERR_UNCONFIGURED when HTTPRequest is not inside tree") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
|
CHECK(error == Error::ERR_UNCONFIGURED);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] Request when disconnected") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
|
||||||
|
When(http_client->get_status).Return(HTTPClient::STATUS_DISCONNECTED);
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
|
||||||
|
CHECK(http_request->is_processing_internal() == false);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] Parse URL errors") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
SUBCASE("URL is invalid") {
|
||||||
|
String url = "http://foo.com:8080:433";
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
|
CHECK(error == Error::ERR_INVALID_PARAMETER);
|
||||||
|
SIGNAL_CHECK_FALSE("request_completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("URL schema is invalid") {
|
||||||
|
String url = "ftp://foo.com";
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
|
CHECK(error == Error::ERR_INVALID_PARAMETER);
|
||||||
|
SIGNAL_CHECK_FALSE("request_completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] Port") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
|
||||||
|
SUBCASE("URLs are parse to get the port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
int port = 8080;
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "http://" + host + ":" + itos(port);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
Verify(http_client->connect_to_host).With(host, port, (Ref<TLSOptions>)(nullptr)).Times(1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("HTTP URLs default port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "http://" + host;
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
Verify(http_client->connect_to_host).With(host, 80, (Ref<TLSOptions>)(nullptr)).Times(1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("HTTPS URLs default port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
Ref<TLSOptions> tls_options = TLSOptions::client();
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "https://" + host;
|
||||||
|
|
||||||
|
http_request->set_tls_options(tls_options);
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
Verify(http_client->connect_to_host).With(host, 443, tls_options).Times(1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] Requests") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
String url = "http://foo.com";
|
||||||
|
|
||||||
|
SUBCASE("Just one at the same time") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
CHECK(error == Error::ERR_BUSY);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("Can be cancelled") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
http_request->cancel_request();
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
|
||||||
|
error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
Verify(http_client->close).Times(1);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("Are cancelled when HTTPRequest node is removed from SceneTree") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
CHECK(error == Error::ERR_BUSY);
|
||||||
|
|
||||||
|
// This will cancel the request.
|
||||||
|
SceneTree::get_singleton()->get_root()->remove_child(http_request);
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
|
||||||
|
// This is needed to create a new request.
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
Verify(http_client->close).Times(1);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] Timeout") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_RESOLVING });
|
||||||
|
When(http_client->poll).Return({ Error::OK });
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_timeout(1);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process with time greater than timeout.
|
||||||
|
SceneTree::get_singleton()->process(2);
|
||||||
|
|
||||||
|
Verify(http_client->request).Times(0);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] GET Request") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING,
|
||||||
|
// First STATUS_CONNECTED is to send the request, second STATUS_CONNECTED is to receive request.
|
||||||
|
HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_CONNECTED });
|
||||||
|
When(http_client->get_response_code).Return(HTTPClient::ResponseCode::RESPONSE_OK);
|
||||||
|
When(http_client->has_response).Return(true);
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Verify(http_client->request).With(HTTPClient::Method::METHOD_GET, String("/"), build_headers("Accept-Encoding: gzip, deflate"), (uint8_t *)nullptr, 0).Times(1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] GET Request with body and headers") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
PackedByteArray body = String("Godot Rules!!!").to_utf8_buffer();
|
||||||
|
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING, HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_BODY });
|
||||||
|
When(http_client->get_response_code).Return(HTTPClient::ResponseCode::RESPONSE_OK);
|
||||||
|
When(http_client->has_response).Return(true);
|
||||||
|
When(http_client->get_response_headers).Do([](List<String> *r_response) -> Error {
|
||||||
|
r_response->push_front("Server: Mock");
|
||||||
|
return Error::OK;
|
||||||
|
});
|
||||||
|
When(http_client->get_response_body_length).Return(body.size());
|
||||||
|
When(http_client->read_response_body_chunk).Return(body);
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Verify(http_client->request).With(HTTPClient::Method::METHOD_GET, String("/"), build_headers("Accept-Encoding: gzip, deflate"), (uint8_t *)nullptr, 0).Times(1);
|
||||||
|
SIGNAL_CHECK("request_completed",
|
||||||
|
build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, build_headers("Server: Mock"), body)));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree] POST Request with body and headers") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
String body("Godot Rules!!!");
|
||||||
|
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING,
|
||||||
|
// First STATUS_CONNECTED is to send the request, second STATUS_CONNECTED is to receive request.
|
||||||
|
HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_CONNECTED });
|
||||||
|
When(http_client->get_response_code).Return(HTTPClient::ResponseCode::RESPONSE_CREATED);
|
||||||
|
When(http_client->has_response).Return(true);
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url, build_headers("Accept: text/json"), HTTPClient::Method::METHOD_POST, body);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Verify(http_client->request).With(HTTPClient::Method::METHOD_POST, String("/"), build_headers("Accept-Encoding: gzip, deflate", "Accept: text/json"), cpp_mock::matching::any_matcher(), body.size() - 1).Times(1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_CREATED, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREADS_ENABLED
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][Threads] GET Request with body") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
PackedByteArray body = String("Godot Rules!!!").to_utf8_buffer();
|
||||||
|
Semaphore s;
|
||||||
|
|
||||||
|
// HTTPClient::STATUS_DISCONNECTED is needed by HTTPRequest::set_use_threads.
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_DISCONNECTED, HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING, HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_BODY });
|
||||||
|
When(http_client->get_response_code).Return(HTTPClient::ResponseCode::RESPONSE_OK);
|
||||||
|
When(http_client->has_response).Return(true);
|
||||||
|
When(http_client->get_response_body_length).Return(body.size());
|
||||||
|
When(http_client->read_response_body_chunk).Do([&s, body]() -> PackedByteArray {
|
||||||
|
s.post();
|
||||||
|
return body;
|
||||||
|
});
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_use_threads(true);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Let the thread do its job.
|
||||||
|
s.wait();
|
||||||
|
|
||||||
|
// This is needed to get defer calls processed.
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
|
||||||
|
Verify(http_client->request).With(HTTPClient::Method::METHOD_GET, String("/"), build_headers("Accept-Encoding: gzip, deflate"), (uint8_t *)nullptr, 0).Times(1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, PackedStringArray(), body)));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][Threads] Timeout") {
|
||||||
|
HTTPClientMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientMock *http_client = HTTPClientMock::current_instance;
|
||||||
|
|
||||||
|
// HTTPClient::STATUS_DISCONNECTED is needed by HTTPRequest::set_use_threads.
|
||||||
|
When(http_client->get_status).Return({ HTTPClient::STATUS_DISCONNECTED, HTTPClient::STATUS_RESOLVING });
|
||||||
|
When(http_client->poll).Return({ Error::OK });
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_use_threads(true);
|
||||||
|
http_request->set_timeout(1);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process with time greater than timeout.
|
||||||
|
SceneTree::get_singleton()->process(2);
|
||||||
|
|
||||||
|
// Let the thread do its job.
|
||||||
|
OS::get_singleton()->delay_usec(250000);
|
||||||
|
|
||||||
|
Verify(http_client->request).Times(0);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // THREADS_ENABLED
|
||||||
|
|
||||||
|
} // namespace TestHTTPRequest
|
||||||
|
|
||||||
|
#endif // TEST_HTTP_REQUEST_H
|
|
@ -0,0 +1,455 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* test_http_request_manual_mock.h */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#ifndef TEST_HTTP_REQUEST_MANUAL_MOCK_H
|
||||||
|
#define TEST_HTTP_REQUEST_MANUAL_MOCK_H
|
||||||
|
|
||||||
|
#include "scene/main/http_request.h"
|
||||||
|
#include "tests/core/io/test_http_client_manual_mock.h"
|
||||||
|
#include "tests/test_macros.h"
|
||||||
|
|
||||||
|
namespace TestHTTPRequestManualMock {
|
||||||
|
|
||||||
|
static inline Array build_array() {
|
||||||
|
return Array();
|
||||||
|
}
|
||||||
|
template <typename... Targs>
|
||||||
|
static inline Array build_array(Variant item, Targs... Fargs) {
|
||||||
|
Array a = build_array(Fargs...);
|
||||||
|
a.push_front(item);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline PackedStringArray build_headers() {
|
||||||
|
return PackedStringArray();
|
||||||
|
}
|
||||||
|
template <typename... Targs>
|
||||||
|
static inline PackedStringArray build_headers(Variant item, Targs... Fargs) {
|
||||||
|
PackedStringArray psa = build_headers(Fargs...);
|
||||||
|
psa.push_back(item);
|
||||||
|
return psa;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][ManualMock] Download chunk size is set when HTTP client is disconnected") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
int expected_value = 42;
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_DISCONNECTED });
|
||||||
|
|
||||||
|
http_request->set_download_chunk_size(expected_value);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->set_read_chunk_size_p_size_parameter, expected_value);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][ManualMock] Download chunk size is not set when HTTP client is not disconnected") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
int expected_value = 42;
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_CONNECTED });
|
||||||
|
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
http_request->set_download_chunk_size(expected_value);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->set_read_chunk_size_call_count, 0);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] Request when disconnected") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_DISCONNECTED });
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
|
||||||
|
CHECK(http_request->is_processing_internal() == false);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] Port") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
|
||||||
|
SUBCASE("URLs are parse to get the port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
int port = 8080;
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "http://" + host + ":" + itos(port);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_host_parameter, host);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_port_parameter, port);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_tls_options_parameter, (Ref<TLSOptions>)(nullptr));
|
||||||
|
CHECK_EQ(http_client->connect_to_host_call_count, 1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("HTTP URLs default port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "http://" + host;
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_host_parameter, host);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_port_parameter, 80);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_tls_options_parameter, (Ref<TLSOptions>)(nullptr));
|
||||||
|
CHECK_EQ(http_client->connect_to_host_call_count, 1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("HTTPS URLs default port") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
Ref<TLSOptions> tls_options = TLSOptions::client();
|
||||||
|
String host = "foo.com";
|
||||||
|
String url = "https://" + host;
|
||||||
|
|
||||||
|
http_request->set_tls_options(tls_options);
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_host_parameter, host);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_port_parameter, 443);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_p_tls_options_parameter, tls_options);
|
||||||
|
CHECK_EQ(http_client->connect_to_host_call_count, 1);
|
||||||
|
CHECK(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] Requests") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
String url = "http://foo.com";
|
||||||
|
|
||||||
|
SUBCASE("Can be cancelled") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
http_request->cancel_request();
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
|
||||||
|
error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
CHECK_EQ(http_client->close_call_count, 1);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("Are cancelled when HTTPRequest node is removed from SceneTree") {
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
ERR_PRINT_OFF;
|
||||||
|
error = http_request->request(url);
|
||||||
|
ERR_PRINT_ON;
|
||||||
|
CHECK(error == Error::ERR_BUSY);
|
||||||
|
|
||||||
|
// This will cancel the request.
|
||||||
|
SceneTree::get_singleton()->get_root()->remove_child(http_request);
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
|
||||||
|
// This is needed to create a new request.
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
error = http_request->request(url);
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
CHECK_EQ(http_client->close_call_count, 1);
|
||||||
|
|
||||||
|
memdelete(http_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] Timeout") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_RESOLVING });
|
||||||
|
http_client->poll_return = Error::OK;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_timeout(1);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process with time greater than timeout.
|
||||||
|
SceneTree::get_singleton()->process(2);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_call_count, 0);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] GET Request") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING,
|
||||||
|
// First STATUS_CONNECTED is to send the request, second STATUS_CONNECTED is to receive request.
|
||||||
|
HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_CONNECTED });
|
||||||
|
http_client->get_response_code_return = HTTPClient::ResponseCode::RESPONSE_OK;
|
||||||
|
http_client->has_response_return = true;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_p_method_parameter, HTTPClient::Method::METHOD_GET);
|
||||||
|
CHECK_EQ(http_client->request_p_url_parameter, String("/"));
|
||||||
|
CHECK_EQ(http_client->request_p_headers_parameter, build_headers("Accept-Encoding: gzip, deflate"));
|
||||||
|
CHECK_EQ(http_client->request_p_body_parameter, (uint8_t *)nullptr);
|
||||||
|
CHECK_EQ(http_client->request_p_body_size_parameter, 0);
|
||||||
|
CHECK_EQ(http_client->request_call_count, 1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] GET Request with body and headers") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
PackedByteArray body = String("Godot Rules!!!").to_utf8_buffer();
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING, HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_BODY, HTTPClient::STATUS_BODY });
|
||||||
|
http_client->get_response_code_return = HTTPClient::ResponseCode::RESPONSE_OK;
|
||||||
|
http_client->has_response_return = true;
|
||||||
|
List<String> headers;
|
||||||
|
headers.push_front("Server: Mock");
|
||||||
|
http_client->get_response_headers_r_response_parameter = headers;
|
||||||
|
http_client->get_response_headers_return = Error::OK;
|
||||||
|
http_client->get_response_body_length_return = body.size();
|
||||||
|
http_client->read_response_body_chunk_return = body;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_p_method_parameter, HTTPClient::Method::METHOD_GET);
|
||||||
|
CHECK_EQ(http_client->request_p_url_parameter, String("/"));
|
||||||
|
CHECK_EQ(http_client->request_p_headers_parameter, build_headers("Accept-Encoding: gzip, deflate"));
|
||||||
|
CHECK_EQ(http_client->request_p_body_parameter, (uint8_t *)nullptr);
|
||||||
|
CHECK_EQ(http_client->request_p_body_size_parameter, 0);
|
||||||
|
CHECK_EQ(http_client->request_call_count, 1);
|
||||||
|
SIGNAL_CHECK("request_completed",
|
||||||
|
build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, build_headers("Server: Mock"), body)));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][ManualMock] POST Request with body and headers") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
String body("Godot Rules!!!");
|
||||||
|
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING,
|
||||||
|
// First STATUS_CONNECTED is to send the request, second STATUS_CONNECTED is to receive request.
|
||||||
|
HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_CONNECTED });
|
||||||
|
http_client->get_response_code_return = HTTPClient::ResponseCode::RESPONSE_CREATED;
|
||||||
|
http_client->has_response_return = true;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url, build_headers("Accept: text/json"), HTTPClient::Method::METHOD_POST, body);
|
||||||
|
|
||||||
|
// Call process for each status.
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_p_method_parameter, HTTPClient::Method::METHOD_POST);
|
||||||
|
CHECK_EQ(http_client->request_p_url_parameter, String("/"));
|
||||||
|
CHECK_EQ(http_client->request_p_headers_parameter, build_headers("Accept-Encoding: gzip, deflate", "Accept: text/json"));
|
||||||
|
CHECK_EQ(http_client->request_p_body_size_parameter, body.size() - 1);
|
||||||
|
CHECK_EQ(http_client->request_call_count, 1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_CREATED, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREADS_ENABLED
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][Threads][ManualMock] GET Request with body") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
PackedByteArray body = String("Godot Rules!!!").to_utf8_buffer();
|
||||||
|
Semaphore *semaphore = new Semaphore();
|
||||||
|
|
||||||
|
// HTTPClient::STATUS_DISCONNECTED is needed by HTTPRequest::set_use_threads.
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_DISCONNECTED, HTTPClient::STATUS_RESOLVING, HTTPClient::STATUS_CONNECTING, HTTPClient::STATUS_CONNECTED, HTTPClient::STATUS_BODY, HTTPClient::STATUS_BODY });
|
||||||
|
http_client->get_response_code_return = HTTPClient::ResponseCode::RESPONSE_OK;
|
||||||
|
http_client->has_response_return = true;
|
||||||
|
http_client->get_response_headers_return = Error::OK;
|
||||||
|
http_client->get_response_body_length_return = body.size();
|
||||||
|
http_client->read_response_body_chunk_return = body;
|
||||||
|
http_client->read_response_body_chunk_semaphore = semaphore;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_use_threads(true);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Let the thread do its job.
|
||||||
|
semaphore->wait();
|
||||||
|
|
||||||
|
// This is needed to get defer calls processed.
|
||||||
|
SceneTree::get_singleton()->process(0);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_p_method_parameter, HTTPClient::Method::METHOD_GET);
|
||||||
|
CHECK_EQ(http_client->request_p_url_parameter, String("/"));
|
||||||
|
CHECK_EQ(http_client->request_p_headers_parameter, build_headers("Accept-Encoding: gzip, deflate"));
|
||||||
|
CHECK_EQ(http_client->request_p_body_parameter, (uint8_t *)nullptr);
|
||||||
|
CHECK_EQ(http_client->request_p_body_size_parameter, 0);
|
||||||
|
CHECK_EQ(http_client->request_call_count, 1);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_SUCCESS, HTTPClient::ResponseCode::RESPONSE_OK, PackedStringArray(), body)));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
http_client->read_response_body_chunk_semaphore = nullptr;
|
||||||
|
delete semaphore;
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Network][HTTPRequest][SceneTree][Threads][ManualMock] Timeout") {
|
||||||
|
HTTPClientManualMock::make_current();
|
||||||
|
HTTPRequest *http_request = memnew(HTTPRequest);
|
||||||
|
SceneTree::get_singleton()->get_root()->add_child(http_request);
|
||||||
|
HTTPClientManualMock *http_client = HTTPClientManualMock::current_instance;
|
||||||
|
|
||||||
|
// HTTPClient::STATUS_DISCONNECTED is needed by HTTPRequest::set_use_threads.
|
||||||
|
http_client->get_status_return = Vector<HTTPClient::Status>({ HTTPClient::STATUS_DISCONNECTED, HTTPClient::STATUS_RESOLVING });
|
||||||
|
http_client->poll_return = Error::OK;
|
||||||
|
SIGNAL_WATCH(http_request, "request_completed");
|
||||||
|
|
||||||
|
http_request->set_use_threads(true);
|
||||||
|
http_request->set_timeout(1);
|
||||||
|
String url = "http://foo.com";
|
||||||
|
Error error = http_request->request(url);
|
||||||
|
|
||||||
|
// Call process with time greater than timeout.
|
||||||
|
SceneTree::get_singleton()->process(2);
|
||||||
|
|
||||||
|
CHECK_EQ(http_client->request_call_count, 0);
|
||||||
|
SIGNAL_CHECK("request_completed", build_array(build_array(HTTPRequest::Result::RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray())));
|
||||||
|
CHECK_FALSE(http_request->is_processing_internal());
|
||||||
|
CHECK(error == Error::OK);
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(http_request, "request_completed");
|
||||||
|
memdelete(http_request);
|
||||||
|
HTTPClientManualMock::reset_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // THREADS_ENABLED
|
||||||
|
|
||||||
|
} // namespace TestHTTPRequestManualMock
|
||||||
|
|
||||||
|
#endif // TEST_HTTP_REQUEST_MANUAL_MOCK_H
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* test_http_client_mock.cpp */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/**************************************************************************/
|
||||||
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||||
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/**************************************************************************/
|
||||||
|
|
||||||
|
#include "tests/core/io/test_http_client_mock.h"
|
||||||
|
#include "tests/core/io/test_http_client_manual_mock.h"
|
||||||
|
|
||||||
|
HTTPClient *(*HTTPClientMock::_old_create)(bool) = nullptr;
|
||||||
|
HTTPClientMock *HTTPClientMock::current_instance = nullptr;
|
||||||
|
|
||||||
|
HTTPClient *(*HTTPClientManualMock::_old_create)(bool) = nullptr;
|
||||||
|
HTTPClientManualMock *HTTPClientManualMock::current_instance = nullptr;
|
|
@ -294,6 +294,15 @@ private:
|
||||||
_add_signal_entry(args, p_name);
|
_add_signal_entry(args, p_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _signal_callback_four(Variant p_arg1, Variant p_arg2, Variant p_arg3, Variant p_arg4, const String &p_name) {
|
||||||
|
Array args;
|
||||||
|
args.push_back(p_arg1);
|
||||||
|
args.push_back(p_arg2);
|
||||||
|
args.push_back(p_arg3);
|
||||||
|
args.push_back(p_arg4);
|
||||||
|
_add_signal_entry(args, p_name);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static SignalWatcher *get_singleton() { return singleton; }
|
static SignalWatcher *get_singleton() { return singleton; }
|
||||||
|
|
||||||
|
@ -313,6 +322,9 @@ public:
|
||||||
case 3: {
|
case 3: {
|
||||||
p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three).bind(p_signal));
|
p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three).bind(p_signal));
|
||||||
} break;
|
} break;
|
||||||
|
case 4: {
|
||||||
|
p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_four).bind(p_signal));
|
||||||
|
} break;
|
||||||
default: {
|
default: {
|
||||||
MESSAGE("Signal ", p_signal, " arg count not supported.");
|
MESSAGE("Signal ", p_signal, " arg count not supported.");
|
||||||
} break;
|
} break;
|
||||||
|
@ -335,6 +347,9 @@ public:
|
||||||
case 3: {
|
case 3: {
|
||||||
p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three));
|
p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three));
|
||||||
} break;
|
} break;
|
||||||
|
case 4: {
|
||||||
|
p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_four));
|
||||||
|
} break;
|
||||||
default: {
|
default: {
|
||||||
MESSAGE("Signal ", p_signal, " arg count not supported.");
|
MESSAGE("Signal ", p_signal, " arg count not supported.");
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -113,6 +113,8 @@
|
||||||
#include "tests/scene/test_curve_3d.h"
|
#include "tests/scene/test_curve_3d.h"
|
||||||
#include "tests/scene/test_gradient.h"
|
#include "tests/scene/test_gradient.h"
|
||||||
#include "tests/scene/test_gradient_texture.h"
|
#include "tests/scene/test_gradient_texture.h"
|
||||||
|
#include "tests/scene/test_http_request.h"
|
||||||
|
#include "tests/scene/test_http_request_manual_mock.h"
|
||||||
#include "tests/scene/test_image_texture.h"
|
#include "tests/scene/test_image_texture.h"
|
||||||
#include "tests/scene/test_image_texture_3d.h"
|
#include "tests/scene/test_image_texture_3d.h"
|
||||||
#include "tests/scene/test_instance_placeholder.h"
|
#include "tests/scene/test_instance_placeholder.h"
|
||||||
|
|
|
@ -117,6 +117,18 @@ Apply the patches in the `patches/` folder when syncing on newer upstream
|
||||||
commits.
|
commits.
|
||||||
|
|
||||||
|
|
||||||
|
## cpp_mock
|
||||||
|
|
||||||
|
- Upstream: https://github.com/samcragg/cpp_mock
|
||||||
|
- Version: 1.0.2 (e50d268a105a5c945599b9c184b0bb1dcb1c1f10, 2020)
|
||||||
|
- License: MIT
|
||||||
|
|
||||||
|
Files extracted from upstream source:
|
||||||
|
|
||||||
|
- `cpp_mock.h`
|
||||||
|
- `LICENSE`
|
||||||
|
|
||||||
|
|
||||||
## cvtt
|
## cvtt
|
||||||
|
|
||||||
- Upstream: https://github.com/elasota/ConvectionKernels
|
- Upstream: https://github.com/elasota/ConvectionKernels
|
||||||
|
@ -241,6 +253,7 @@ Files extracted from upstream source:
|
||||||
```
|
```
|
||||||
- `AUTHORS.txt` and `LICENSE.txt`
|
- `AUTHORS.txt` and `LICENSE.txt`
|
||||||
|
|
||||||
|
|
||||||
## fonts
|
## fonts
|
||||||
|
|
||||||
- `DroidSans*.woff2`:
|
- `DroidSans*.woff2`:
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Samuel Cragg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,684 @@
|
||||||
|
#ifndef CPP_MOCK_H
|
||||||
|
#define CPP_MOCK_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include "thirdparty/doctest/doctest.h"
|
||||||
|
|
||||||
|
namespace cpp_mock
|
||||||
|
{
|
||||||
|
namespace details
|
||||||
|
{
|
||||||
|
#if __cplusplus <= 201703L
|
||||||
|
template <class T>
|
||||||
|
struct remove_cvref
|
||||||
|
{
|
||||||
|
using type = typename std::remove_cv<
|
||||||
|
typename std::remove_reference<T>::type>::type;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
using std::remove_cvref;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus < 201703L
|
||||||
|
template <class R, class Fn, class Arg>
|
||||||
|
struct is_invocable_r : std::is_constructible<
|
||||||
|
std::function<R(Arg)>,
|
||||||
|
std::reference_wrapper<typename std::remove_reference<Fn>::type>>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
using std::is_invocable_r;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <std::size_t... I>
|
||||||
|
struct index_sequence
|
||||||
|
{
|
||||||
|
using type = index_sequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
struct make_index_sequence
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct make_index_sequence<0> : index_sequence<> { };
|
||||||
|
template<> struct make_index_sequence<1> : index_sequence<0> { };
|
||||||
|
template<> struct make_index_sequence<2> : index_sequence<0, 1> { };
|
||||||
|
template<> struct make_index_sequence<3> : index_sequence<0, 1, 2> { };
|
||||||
|
template<> struct make_index_sequence<4> : index_sequence<0, 1, 2, 3> { };
|
||||||
|
template<> struct make_index_sequence<5> : index_sequence<0, 1, 2, 3, 4> { };
|
||||||
|
template<> struct make_index_sequence<6> : index_sequence<0, 1, 2, 3, 4, 5> { };
|
||||||
|
template<> struct make_index_sequence<7> : index_sequence<0, 1, 2, 3, 4, 5, 6> { };
|
||||||
|
template<> struct make_index_sequence<8> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7> { };
|
||||||
|
template<> struct make_index_sequence<9> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8> { };
|
||||||
|
template<> struct make_index_sequence<10> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9> { };
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace invoking
|
||||||
|
{
|
||||||
|
using namespace ::cpp_mock::details;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct return_values
|
||||||
|
{
|
||||||
|
using storage_type = std::vector<T>;
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
T operator()(Args&&...)
|
||||||
|
{
|
||||||
|
if (index == values.size())
|
||||||
|
{
|
||||||
|
return values.back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return values[index++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_type values;
|
||||||
|
std::size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ignore_arg
|
||||||
|
{
|
||||||
|
explicit ignore_arg(const void*)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void save(const T&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct save_arg
|
||||||
|
{
|
||||||
|
explicit save_arg(T* arg) :
|
||||||
|
_arg(arg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(const T& value)
|
||||||
|
{
|
||||||
|
*_arg = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* _arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, typename = void>
|
||||||
|
struct sync_arg_save
|
||||||
|
{
|
||||||
|
using type = ignore_arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct sync_arg_save<T&, typename std::enable_if<!std::is_const<T>::value>::type>
|
||||||
|
{
|
||||||
|
using type = save_arg<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t I, class Args, class ArgSetters>
|
||||||
|
class sync_arg
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using element_type = typename std::tuple_element<I, Args>::type;
|
||||||
|
|
||||||
|
sync_arg(Args& args, ArgSetters& setters) :
|
||||||
|
_args(&args),
|
||||||
|
_setters(&setters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~sync_arg()
|
||||||
|
{
|
||||||
|
std::get<I>(*_setters).save(get());
|
||||||
|
}
|
||||||
|
|
||||||
|
element_type& get()
|
||||||
|
{
|
||||||
|
return std::get<I>(*_args);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Args* _args;
|
||||||
|
ArgSetters* _setters;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class R, class F, class Args, class ArgSetters, std::size_t... I>
|
||||||
|
static R invoke_indexes(const F& func, Args& args, ArgSetters& setters, index_sequence<I...>)
|
||||||
|
{
|
||||||
|
return func(sync_arg<I, Args, ArgSetters>(args, setters).get()...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class F, class Args, class ArgSetters>
|
||||||
|
static R invoke(F&& func, Args& args, ArgSetters& setters)
|
||||||
|
{
|
||||||
|
return invoke_indexes<R>(func, args, setters, typename make_index_sequence<std::tuple_size<Args>::value>::type{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace matching
|
||||||
|
{
|
||||||
|
using namespace ::cpp_mock::details;
|
||||||
|
|
||||||
|
struct any_matcher
|
||||||
|
{
|
||||||
|
template <class T>
|
||||||
|
bool operator()(const T&) const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class equals_matcher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
equals_matcher(T&& value) :
|
||||||
|
_expected(std::move(value))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
equals_matcher(const T& value) :
|
||||||
|
_expected(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const T& actual) const
|
||||||
|
{
|
||||||
|
return actual == _expected;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
T _expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Tuple>
|
||||||
|
struct method_arguments_matcher
|
||||||
|
{
|
||||||
|
virtual bool matches(const Tuple& args) = 0;
|
||||||
|
virtual ~method_arguments_matcher() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Tuple>
|
||||||
|
struct any_method_arguments_matcher : method_arguments_matcher<Tuple>
|
||||||
|
{
|
||||||
|
bool matches(const Tuple&) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Tuple, class MatcherTuple>
|
||||||
|
class match_arguments_wrapper : public method_arguments_matcher<Tuple>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit match_arguments_wrapper(MatcherTuple&& predicates) :
|
||||||
|
_predicates(std::move(predicates))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const Tuple& args) override
|
||||||
|
{
|
||||||
|
return match_arguments(_predicates, args);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
MatcherTuple _predicates;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||||
|
#elif defined(__clang__)
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunused-function"
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#pragma warning( push )
|
||||||
|
#pragma warning( disable : 4505 )
|
||||||
|
#endif
|
||||||
|
template <class Head>
|
||||||
|
static bool and_together(Head value)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#elif defined(__clang__)
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#pragma warning( pop )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <class Head, class... Tail>
|
||||||
|
static bool and_together(Head head, Tail... tail)
|
||||||
|
{
|
||||||
|
return head && and_together(tail...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P, class T, std::size_t... I>
|
||||||
|
static bool match_argument_indexes(const P& predicates, const T& args, index_sequence<I...>)
|
||||||
|
{
|
||||||
|
return and_together(std::get<I>(predicates)(std::get<I>(args))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P, class T>
|
||||||
|
static bool match_arguments(const P& predicates, const T& args)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
std::tuple_size<P>::value == std::tuple_size<T>::value,
|
||||||
|
"The number of predicates must match the number of arguments");
|
||||||
|
|
||||||
|
return match_argument_indexes(predicates, args, typename make_index_sequence<std::tuple_size<T>::value>::type{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
struct method_action
|
||||||
|
{
|
||||||
|
std::shared_ptr<matching::method_arguments_matcher<std::tuple<Args...>>> matcher;
|
||||||
|
std::function<R(Args& ...)> action;
|
||||||
|
};
|
||||||
|
|
||||||
|
// static matching::any_matcher _;
|
||||||
|
|
||||||
|
namespace mocking
|
||||||
|
{
|
||||||
|
using namespace ::cpp_mock::details;
|
||||||
|
|
||||||
|
template <class Fn, class Arg>
|
||||||
|
struct argument_matcher_wrapper :
|
||||||
|
std::conditional<is_invocable_r<bool, Fn, Arg>::value, Fn, matching::equals_matcher<Arg>>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... TupleArgs>
|
||||||
|
struct argument_wrapper
|
||||||
|
{
|
||||||
|
template <class... MatchArgs>
|
||||||
|
struct match_with
|
||||||
|
{
|
||||||
|
using type = std::tuple<
|
||||||
|
typename argument_matcher_wrapper<MatchArgs, TupleArgs>::type...>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
class method_action_builder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit method_action_builder(method_action<R, Args...>* action) :
|
||||||
|
_action(action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder(const method_action_builder&) = delete;
|
||||||
|
method_action_builder(method_action_builder&&) = default;
|
||||||
|
|
||||||
|
~method_action_builder()
|
||||||
|
{
|
||||||
|
if (_action->action == nullptr)
|
||||||
|
{
|
||||||
|
if (_values.empty())
|
||||||
|
{
|
||||||
|
_values.push_back(R());
|
||||||
|
}
|
||||||
|
|
||||||
|
_action->action = invoking::return_values<R> { std::move(_values), 0u };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_action->matcher == nullptr)
|
||||||
|
{
|
||||||
|
_action->matcher = std::make_shared<
|
||||||
|
matching::any_method_arguments_matcher<std::tuple<Args...>>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& operator=(const method_action_builder&) = delete;
|
||||||
|
method_action_builder& operator=(method_action_builder&&) = default;
|
||||||
|
|
||||||
|
template <class... MatchArgs>
|
||||||
|
method_action_builder& With(MatchArgs&&... args)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
sizeof...(MatchArgs) == sizeof...(Args),
|
||||||
|
"The number of matchers must match the number of arguments");
|
||||||
|
|
||||||
|
using matcher_tuple = typename argument_wrapper<Args...>
|
||||||
|
::template match_with<MatchArgs...>::type;
|
||||||
|
|
||||||
|
_action->matcher = std::make_shared<
|
||||||
|
matching::match_arguments_wrapper<std::tuple<Args...>, matcher_tuple>>(
|
||||||
|
matcher_tuple(std::forward<MatchArgs>(args)...));
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& Do(std::function<R(Args& ...)> function)
|
||||||
|
{
|
||||||
|
_action->action = std::move(function);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& Return(const R& value)
|
||||||
|
{
|
||||||
|
_values.push_back(value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& Return(std::initializer_list<R> values)
|
||||||
|
{
|
||||||
|
for (auto& value : values)
|
||||||
|
{
|
||||||
|
_values.push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
method_action<R, Args...>* _action;
|
||||||
|
typename invoking::return_values<R>::storage_type _values;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
class method_action_builder<void, Args...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit method_action_builder(method_action<void, Args...>* action) :
|
||||||
|
_action(action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder(const method_action_builder&) = delete;
|
||||||
|
method_action_builder(method_action_builder&&) = default;
|
||||||
|
|
||||||
|
~method_action_builder()
|
||||||
|
{
|
||||||
|
if (_action->action == nullptr)
|
||||||
|
{
|
||||||
|
_action->action = [](const Args& ...) { };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_action->matcher == nullptr)
|
||||||
|
{
|
||||||
|
_action->matcher = std::make_shared<
|
||||||
|
matching::any_method_arguments_matcher<std::tuple<Args...>>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& operator=(const method_action_builder&) = delete;
|
||||||
|
method_action_builder& operator=(method_action_builder&&) = default;
|
||||||
|
|
||||||
|
template <class... MatchArgs>
|
||||||
|
method_action_builder& With(MatchArgs&&... args)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
sizeof...(MatchArgs) == sizeof...(Args),
|
||||||
|
"The number of matchers must match the number of arguments");
|
||||||
|
|
||||||
|
using matcher_tuple = typename argument_wrapper<Args...>
|
||||||
|
::template match_with<MatchArgs...>::type;
|
||||||
|
|
||||||
|
_action->matcher = std::make_shared<
|
||||||
|
matching::match_arguments_wrapper<std::tuple<Args...>, matcher_tuple>>(
|
||||||
|
matcher_tuple(std::forward<MatchArgs>(args)...));
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
method_action_builder& Do(std::function<void(Args & ...)> function)
|
||||||
|
{
|
||||||
|
_action->action = std::move(function);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
method_action<void, Args...>* _action;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
class method_verify_builder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
method_verify_builder(
|
||||||
|
const char* method,
|
||||||
|
const char* file,
|
||||||
|
std::size_t line,
|
||||||
|
const std::vector<std::tuple<Args...>>* calls
|
||||||
|
) :
|
||||||
|
_calls(calls),
|
||||||
|
_count(std::numeric_limits<std::size_t>::max()),
|
||||||
|
_matched_count(calls->size()),
|
||||||
|
_method(method),
|
||||||
|
_file(file),
|
||||||
|
_line(line)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
method_verify_builder(const method_verify_builder&) = delete;
|
||||||
|
method_verify_builder(method_verify_builder&&) = default;
|
||||||
|
|
||||||
|
~method_verify_builder() noexcept(false)
|
||||||
|
{
|
||||||
|
if (_count == std::numeric_limits<std::size_t>::max())
|
||||||
|
{
|
||||||
|
if (_matched_count == 0)
|
||||||
|
{
|
||||||
|
std::ostringstream stream;
|
||||||
|
write_location(stream) << "Expecting a call to "
|
||||||
|
<< _method << " but none were received.";
|
||||||
|
FAIL(stream.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_count != _matched_count)
|
||||||
|
{
|
||||||
|
std::ostringstream stream;
|
||||||
|
write_location(stream) << "Expecting a call to " << _method << ' ';
|
||||||
|
write_times(stream, _count) << ", but it was invoked ";
|
||||||
|
write_times(stream, _matched_count) << '.';
|
||||||
|
FAIL(stream.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
method_verify_builder& operator=(const method_verify_builder&) = delete;
|
||||||
|
method_verify_builder& operator=(method_verify_builder&&) = default;
|
||||||
|
|
||||||
|
template <class... MatchArgs>
|
||||||
|
method_verify_builder& With(MatchArgs&&... matchers)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
sizeof...(MatchArgs) == sizeof...(Args),
|
||||||
|
"The number of matchers must match the number of arguments");
|
||||||
|
|
||||||
|
using argument_tuple = std::tuple<Args...>;
|
||||||
|
using matcher_tuple = typename argument_wrapper<Args...>
|
||||||
|
::template match_with<MatchArgs...>::type;
|
||||||
|
|
||||||
|
matcher_tuple matcher(std::forward<MatchArgs>(matchers)...);
|
||||||
|
_matched_count = std::count_if(
|
||||||
|
_calls->begin(),
|
||||||
|
_calls->end(),
|
||||||
|
[&](const argument_tuple& args) { return match_arguments(matcher, args); });
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
method_verify_builder& Times(std::size_t count)
|
||||||
|
{
|
||||||
|
_count = count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::ostream& write_location(std::ostream& stream)
|
||||||
|
{
|
||||||
|
return stream << _file << ':' << _line << ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& write_times(std::ostream& stream, std::size_t count)
|
||||||
|
{
|
||||||
|
return stream << count << " time" << ((count == 1) ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::tuple<Args...>>* _calls;
|
||||||
|
std::size_t _count;
|
||||||
|
std::size_t _matched_count;
|
||||||
|
const char* _method;
|
||||||
|
const char* _file;
|
||||||
|
std::size_t _line;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class>
|
||||||
|
class mock_method_types;
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
class mock_method_types<R(Args...)>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using method_action_t = method_action<R, typename remove_cvref<Args>::type ...>;
|
||||||
|
using tuple_t = typename std::tuple<typename remove_cvref<Args>::type ...>;
|
||||||
|
|
||||||
|
using action_t = typename std::vector<method_action_t>;
|
||||||
|
using record_t = typename std::vector<tuple_t>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class R, class... Args>
|
||||||
|
static method_action_builder<R, Args...> add_action(std::vector<method_action<R, Args...>>& actions)
|
||||||
|
{
|
||||||
|
actions.emplace_back(method_action<R, Args...>());
|
||||||
|
return method_action_builder<R, Args...>(&actions.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
static method_verify_builder<Args...> check_action(
|
||||||
|
const char* method,
|
||||||
|
const char* file,
|
||||||
|
std::size_t line,
|
||||||
|
const std::vector<std::tuple<Args...>>& invocations)
|
||||||
|
{
|
||||||
|
return method_verify_builder<Args...>(method, file, line, &invocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static T return_default()
|
||||||
|
{
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for VC++ to expand variadic macro arguments correctly
|
||||||
|
#define EXPAND_MACRO(macro, ...) macro
|
||||||
|
|
||||||
|
#define MAKE_FORWARD(arg) std::forward<decltype(arg)>(arg)
|
||||||
|
#define MAKE_SETTER(arg) ::cpp_mock::invoking::sync_arg_save<decltype(arg)>::type(&arg)
|
||||||
|
|
||||||
|
#define MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NameArgs, Transform, ...) \
|
||||||
|
Ret Name(NameArgs Args) Specs \
|
||||||
|
{ \
|
||||||
|
Name ## _Invocations.emplace_back(std::make_tuple( \
|
||||||
|
EXPAND_MACRO(Transform(MAKE_FORWARD, __VA_ARGS__)))); \
|
||||||
|
auto& args = Name ## _Invocations.back(); \
|
||||||
|
for (auto it = Name ## _Actions.rbegin(); it != Name ## _Actions.rend(); ++it) \
|
||||||
|
{ \
|
||||||
|
if (it->matcher->matches(args)) \
|
||||||
|
{ \
|
||||||
|
auto setters = std::make_tuple( \
|
||||||
|
EXPAND_MACRO(Transform(MAKE_SETTER, __VA_ARGS__))); \
|
||||||
|
return ::cpp_mock::invoking::invoke<Ret>(it->action, args, setters); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
return ::cpp_mock::mocking::return_default<Ret>(); \
|
||||||
|
} \
|
||||||
|
::cpp_mock::mocking::mock_method_types<Ret Args>::action_t Name ## _Actions; \
|
||||||
|
mutable ::cpp_mock::mocking::mock_method_types<Ret Args>::record_t Name ## _Invocations;
|
||||||
|
|
||||||
|
#define NAME_ARGS1(type) type a
|
||||||
|
#define NAME_ARGS2(type, ...) type b, EXPAND_MACRO(NAME_ARGS1(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS3(type, ...) type c, EXPAND_MACRO(NAME_ARGS2(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS4(type, ...) type d, EXPAND_MACRO(NAME_ARGS3(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS5(type, ...) type e, EXPAND_MACRO(NAME_ARGS4(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS6(type, ...) type f, EXPAND_MACRO(NAME_ARGS5(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS7(type, ...) type g, EXPAND_MACRO(NAME_ARGS6(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS8(type, ...) type h, EXPAND_MACRO(NAME_ARGS7(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS9(type, ...) type i, EXPAND_MACRO(NAME_ARGS8(__VA_ARGS__))
|
||||||
|
#define NAME_ARGS10(type, ...) type j, EXPAND_MACRO(NAME_ARGS9(__VA_ARGS__))
|
||||||
|
|
||||||
|
#define TRANSFORM1(Call, x) Call(x)
|
||||||
|
#define TRANSFORM2(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM1(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM3(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM2(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM4(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM3(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM5(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM4(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM6(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM5(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM7(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM6(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM8(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM7(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM9(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM8(Call, __VA_ARGS__))
|
||||||
|
#define TRANSFORM10(Call, x, ...) Call(x), EXPAND_MACRO(TRANSFORM9(Call, __VA_ARGS__))
|
||||||
|
|
||||||
|
// We need to handle () and (int) differently, however, when they get passed in
|
||||||
|
// via __VA_ARG__ then it looks like we received a single parameter for both
|
||||||
|
// cases. Use the fact that we're always appending an 'a' to the parameter to
|
||||||
|
// detect the difference between 'type a' and 'a'
|
||||||
|
#define CAT(a, b) a ## b
|
||||||
|
#define GET_SECOND_ARG(a, b, ...) b
|
||||||
|
#define DEFINE_EXISTS(...) EXPAND_MACRO(GET_SECOND_ARG(__VA_ARGS__, TRUE))
|
||||||
|
#define IS_TYPE_MISSING(x) DEFINE_EXISTS(CAT(TOKEN_IS_EMPTY_, x))
|
||||||
|
#define TOKEN_IS_EMPTY_a ignored, FALSE
|
||||||
|
#define GET_METHOD(method, suffix) CAT(method, suffix)
|
||||||
|
#define NOOP(...)
|
||||||
|
#define MOCK_METHOD_IS_SINGLE_FALSE(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NOOP, NOOP)
|
||||||
|
#define MOCK_METHOD_IS_SINGLE_TRUE(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS1, TRANSFORM1, a)
|
||||||
|
#define HANDLE_EMPTY_TYPE(...) GET_METHOD(MOCK_METHOD_IS_SINGLE_, IS_TYPE_MISSING(__VA_ARGS__ a))
|
||||||
|
|
||||||
|
#define MOCK_METHOD_1(Ret, Name, Args, Specs) HANDLE_EMPTY_TYPE Args (Ret, Name, Args, Specs)
|
||||||
|
#define MOCK_METHOD_2(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS2, TRANSFORM2, b, a)
|
||||||
|
#define MOCK_METHOD_3(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS3, TRANSFORM3, c, b, a)
|
||||||
|
#define MOCK_METHOD_4(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS4, TRANSFORM4, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_5(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS5, TRANSFORM5, e, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_6(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS6, TRANSFORM6, f, e, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_7(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS7, TRANSFORM7, g, f, e, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_8(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS8, TRANSFORM8, h, g, f, e, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_9(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS9, TRANSFORM9, i, h, g, f, e, d, c, b, a)
|
||||||
|
#define MOCK_METHOD_10(Ret, Name, Args, Specs) MOCK_METHOD_IMPL(Ret, Name, Args, Specs, NAME_ARGS10, TRANSFORM10, j, i, h, g, f, e, d, c, b, a)
|
||||||
|
|
||||||
|
#define GET_NTH_ARG(a, b, c, d, e, f, g, h, i, j, N, ...) N
|
||||||
|
|
||||||
|
#define GET_MOCK_METHOD(...) EXPAND_MACRO(GET_NTH_ARG(__VA_ARGS__, \
|
||||||
|
MOCK_METHOD_10, \
|
||||||
|
MOCK_METHOD_9, \
|
||||||
|
MOCK_METHOD_8, \
|
||||||
|
MOCK_METHOD_7, \
|
||||||
|
MOCK_METHOD_6, \
|
||||||
|
MOCK_METHOD_5, \
|
||||||
|
MOCK_METHOD_4, \
|
||||||
|
MOCK_METHOD_3, \
|
||||||
|
MOCK_METHOD_2, \
|
||||||
|
MOCK_METHOD_1))
|
||||||
|
|
||||||
|
#define INVALID_METHOD(...) static_assert(false, "Invalid usage. Call with return type, name, argument types and, optionally, specifiers.");
|
||||||
|
#define MOCK_METHOD_SPEC(Ret, Name, Args, Spec) GET_MOCK_METHOD Args (Ret, Name, Args, Spec)
|
||||||
|
#define MOCK_METHOD(Ret, Name, Args) MOCK_METHOD_SPEC(Ret, Name, Args, override)
|
||||||
|
|
||||||
|
#define MockMethod(...) EXPAND_MACRO(EXPAND_MACRO(GET_NTH_ARG(__VA_ARGS__, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
MOCK_METHOD_SPEC, \
|
||||||
|
MOCK_METHOD, \
|
||||||
|
INVALID_METHOD, \
|
||||||
|
INVALID_METHOD))(__VA_ARGS__))
|
||||||
|
|
||||||
|
#define MockConstMethod(Ret, Name, Args) MockMethod(Ret, Name, Args, const override)
|
||||||
|
#define When(call_method) ::cpp_mock::mocking::add_action(call_method ## _Actions)
|
||||||
|
#define Verify(call_method) ::cpp_mock::mocking::check_action(#call_method, __FILE__, __LINE__, call_method ## _Invocations)
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue