Add a few simple unit tests to boost coverage (#3163)

This commit is contained in:
Anton Kolesnyk 2021-12-13 14:50:30 -08:00 committed by GitHub
parent 12a2ca31bc
commit a2b2b74080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 396 additions and 15 deletions

View File

@ -915,7 +915,7 @@ void CurlConnection::Shutdown()
#elif defined(AZ_PLATFORM_WINDOWS)
::shutdown(m_curlSocket, SD_BOTH);
#endif
m_isShutDown = true;
CurlNetworkConnection::Shutdown();
}
// Read from socket and return the number of bytes taken from socket

View File

@ -57,7 +57,7 @@ namespace Azure { namespace Core { namespace Http {
*
*/
class CurlNetworkConnection {
protected:
private:
bool m_isShutDown = false;
public:

View File

@ -46,7 +46,7 @@ add_executable (
${CURL_OPTIONS_TESTS}
${CURL_SESSION_TESTS}
datetime_test.cpp
environmentLogLevelListener_test.cpp
environment_log_level_listener_test.cpp
etag_test.cpp
http_test.cpp
http_test.hpp

View File

@ -161,6 +161,16 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_EQ(values->second.begin()->get()->GetConnectionKey(), expectedConnectionKey);
}
{
Azure::Core::Http::CurlSession::ResponseBufferParser responseParser;
EXPECT_EQ(responseParser.ExtractResponse(), nullptr);
const uint8_t responseBuf[] = "HTTP/1.1 200 OK\r\n\r\n";
static_cast<void>(responseParser.Parse(responseBuf, sizeof(responseBuf) - 1));
EXPECT_NE(responseParser.ExtractResponse(), nullptr);
EXPECT_EQ(responseParser.ExtractResponse(), nullptr);
}
// Test re-using same custom config
{
// Creating a new connection with default options

View File

@ -6,6 +6,7 @@
#include <azure/core/datetime.hpp>
#include <chrono>
#include <ctime>
#include <limits>
using namespace Azure;
@ -373,6 +374,18 @@ TEST(DateTime, ParseTimeRfc3339BoundaryCases)
"2038-01-19T03:13:07-00:01",
"2038-01-19T03:14:07Z"); // INT_MAX after subtacting 1
TestDateTimeRoundtrip("2038-01-19T03:14:07-00:00", "2038-01-19T03:14:07Z");
// No ':' in time zone offset
EXPECT_THROW(
DateTime::Parse("2001-01-01T00:00:00+12345", DateTime::DateFormat::Rfc3339),
std::invalid_argument);
}
TEST(DateTime, ParseUnrecognizedFormat)
{
EXPECT_THROW(
DateTime::Parse("2001-01-01T00:00:00", static_cast<DateTime::DateFormat>(42)),
std::invalid_argument);
}
TEST(DateTime, ParseTimeRfc3339UsesEachTimezoneDigit)
@ -819,3 +832,66 @@ TEST(DateTime, ParseRoundUpInvalidDate)
static_cast<void>(DateTime::Parse("9999-12-31T23:59:60", DateTime::DateFormat::Rfc3339)),
std::invalid_argument);
}
TEST(DateTime, ToSystemClock)
{
if (DateTime(std::chrono::system_clock::time_point::min())
> DateTime(DateTime::time_point::min()))
{
EXPECT_THROW(
static_cast<void>(static_cast<std::chrono::system_clock::time_point>(
DateTime(DateTime::time_point::min()))),
std::invalid_argument);
}
if (DateTime(std::chrono::system_clock::time_point::max())
> DateTime(DateTime::time_point::max()))
{
EXPECT_THROW(
static_cast<void>(static_cast<std::chrono::system_clock::time_point>(
DateTime(DateTime::time_point::max()))),
std::invalid_argument);
}
{
auto const tt = std::chrono::system_clock::to_time_t(
static_cast<std::chrono::system_clock::time_point>(DateTime(2021, 7, 8, 15, 34, 56)));
#ifdef _MSC_VER
#pragma warning(push)
// warning C4996: 'gmtime': This function or variable may be unsafe. Consider using gmtime_s
// instead.
#pragma warning(disable : 4996)
#endif
auto const tm = std::gmtime(&tt);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// https://en.cppreference.com/w/cpp/chrono/c/tm
EXPECT_EQ(tm->tm_year, (2021 - 1900)); // std::tm::tm_year is 1900-based.
EXPECT_EQ(tm->tm_mon, 6); // std::tm::tm_mon is 0-based.
EXPECT_EQ(tm->tm_mday, 8);
EXPECT_EQ(tm->tm_hour, 15);
EXPECT_EQ(tm->tm_min, 34);
EXPECT_EQ(tm->tm_sec, 56);
}
}
TEST(DateTime, OutOfToStringRange)
{
using namespace std::literals::chrono_literals;
const DateTime underflow(DateTime(0001) - 1s);
const DateTime overflow(DateTime(9999, 12, 31, 23, 59, 59) + 1s);
EXPECT_THROW(static_cast<void>(underflow.ToString()), std::invalid_argument);
EXPECT_THROW(static_cast<void>(overflow.ToString()), std::invalid_argument);
}
TEST(DateTime, LeapYear)
{
EXPECT_NO_THROW(static_cast<void>(DateTime(2021, 1, 29)));
EXPECT_NO_THROW(static_cast<void>(DateTime(2021, 2, 28)));
EXPECT_THROW(static_cast<void>(DateTime(2021, 2, 29)), std::invalid_argument);
}

View File

@ -200,3 +200,20 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerInformational)
EXPECT_NE(buffer.str().find("INFO : message"), std::string::npos);
std::cerr.rdbuf(old);
}
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerUnknown)
{
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
listener(static_cast<Logger::Level>(42), "message");
EXPECT_NE(listener, nullptr);
EXPECT_NE(buffer.str().find("????? : message"), std::string::npos);
std::cerr.rdbuf(old);
}

View File

@ -32,6 +32,8 @@ TEST(RequestFailedException, JSONError)
EXPECT_EQ(exception.ErrorCode, "503");
EXPECT_EQ(exception.RequestId, "1");
EXPECT_EQ(exception.ClientRequestId, "2");
EXPECT_EQ(exception.ReasonPhrase, "retry please :");
EXPECT_EQ(exception.what(), std::string("Received an HTTP unsuccessful status code."));
}
TEST(RequestFailedException, JSONErrorNoError)
@ -55,4 +57,49 @@ TEST(RequestFailedException, JSONErrorNoError)
EXPECT_EQ(exception.ErrorCode, "");
EXPECT_EQ(exception.RequestId, "1");
EXPECT_EQ(exception.ClientRequestId, "2");
EXPECT_EQ(exception.ReasonPhrase, "retry please :");
EXPECT_EQ(exception.what(), std::string("Received an HTTP unsuccessful status code."));
}
TEST(RequestFailedException, EmptyValues)
{
auto response = std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::None, std::string());
auto exception = Azure::Core::RequestFailedException(response);
EXPECT_EQ(exception.StatusCode, Azure::Core::Http::HttpStatusCode::None);
EXPECT_EQ(exception.Message, std::string());
EXPECT_EQ(exception.ErrorCode, std::string());
EXPECT_EQ(exception.RequestId, std::string());
EXPECT_EQ(exception.ClientRequestId, std::string());
EXPECT_EQ(exception.ReasonPhrase, std::string());
EXPECT_EQ(exception.what(), std::string("Received an HTTP unsuccessful status code."));
}
TEST(RequestFailedException, Message)
{
auto response = std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::ServiceUnavailable, "retry please :");
static constexpr uint8_t const responseBody[]
= "{\"error\":{ \"code\":\"503\", \"message\":\"JT\"}}";
static constexpr uint8_t const responseBodyStream[]
= "{\"error\":{ \"code\":\"503\", \"message\":\"JT\"}}";
response->SetHeader(HttpShared::ContentType, "application/json");
response->SetHeader(HttpShared::MsRequestId, "1");
response->SetHeader(HttpShared::MsClientRequestId, "2");
response->SetBody(std::vector<uint8_t>(responseBody, responseBody + sizeof(responseBody)));
response->SetBodyStream(std::make_unique<Azure::Core::IO::MemoryBodyStream>(
responseBodyStream, sizeof(responseBodyStream) - 1));
auto exception = Azure::Core::RequestFailedException("Msg", std::move(response));
EXPECT_EQ(exception.StatusCode, Azure::Core::Http::HttpStatusCode::ServiceUnavailable);
EXPECT_EQ(exception.Message, "Msg");
EXPECT_EQ(exception.ErrorCode, "");
EXPECT_EQ(exception.RequestId, "1");
EXPECT_EQ(exception.ClientRequestId, "2");
EXPECT_EQ(exception.ReasonPhrase, "retry please :");
EXPECT_EQ(exception.what(), std::string("Msg"));
}

View File

@ -13,7 +13,7 @@ using Azure::Core::Http::Policies::LogOptions;
// cspell:ignore qparam
namespace {
void SendRequest(LogOptions const& logOptions)
void SendRequest(LogOptions const& logOptions, std::string const& portAndPath = "")
{
using namespace Azure::Core;
using namespace Azure::Core::IO;
@ -51,13 +51,12 @@ void SendRequest(LogOptions const& logOptions)
Request request(
HttpMethod::Get,
Url("https://"
"www.microsoft.com"
"?qparam1=qVal1"
"&Qparam2=Qval2"
"&qParam3=qval3"
"&qparam%204=qval%204"
"&qparam%25204=QVAL%25204"),
Url(std::string("https://www.microsoft.com") + portAndPath
+ "?qparam1=qVal1"
"&Qparam2=Qval2"
"&qParam3=qval3"
"&qparam%204=qval%204"
"&qparam%25204=QVAL%25204"),
bodyStream.get());
request.SetHeader("hEaDeR1", "HvAlUe1");
@ -154,6 +153,35 @@ TEST(LogPolicy, Default)
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
}
TEST(LogPolicy, PortAndPath)
{
TestLogger const Log;
SendRequest(LogOptions(), ":8080/path");
EXPECT_EQ(Log.Entries.size(), 2);
auto const entry1 = Log.Entries.at(0);
auto const entry2 = Log.Entries.at(1);
EXPECT_EQ(entry1.Level, Logger::Level::Informational);
EXPECT_EQ(entry2.Level, Logger::Level::Informational);
EXPECT_EQ(
entry1.Message,
"HTTP Request : GET https://www.microsoft.com:8080/path"
"?Qparam2=REDACTED"
"&qParam3=REDACTED"
"&qparam%204=REDACTED"
"&qparam%25204=REDACTED"
"&qparam1=REDACTED"
"\nheader1 : REDACTED"
"\nheader2 : REDACTED"
"\nx-ms-request-id : 6c536700-4c36-4e22-9161-76e7b3bf8269");
EXPECT_TRUE(StartsWith(entry2.Message, "HTTP Response ("));
EXPECT_TRUE(EndsWith(entry2.Message, "ms) : 200 OKAY"));
}
TEST(LogPolicy, Headers)
{
TestLogger const Log;

View File

@ -51,3 +51,71 @@ TEST(Pipeline, refrefEmptyPipeline)
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>>(0)),
std::invalid_argument);
}
TEST(Pipeline, AdditionalPolicies)
{
class TestPolicy : public Azure::Core::Http::Policies::HttpPolicy {
int* m_cloneCount;
public:
TestPolicy(int* cloneCount) : m_cloneCount(cloneCount) {}
std::unique_ptr<HttpPolicy> Clone() const override
{
++(*m_cloneCount);
return std::make_unique<TestPolicy>(*this);
}
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Http::Request& request,
Azure::Core::Http::Policies::NextHttpPolicy nextPolicy,
Azure::Core::Context const& context) const override
{
return nextPolicy.Send(request, context);
}
};
int perCallPolicyCloneCount = 0;
int perCallClientPolicyCloneCount = 0;
int perRetryPolicyCloneCount = 0;
int perRetryClientPolicyCloneCount = 0;
auto options = Azure::Core::_internal::ClientOptions();
using PolicyVector = std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>>;
PolicyVector perCallPolicies;
PolicyVector perRetryPolicies;
{
struct InitHelper
{
PolicyVector* Policies;
int* Counter;
};
std::vector<InitHelper> const initializations = {
{&perCallPolicies, &perCallPolicyCloneCount},
{&options.PerOperationPolicies, &perCallClientPolicyCloneCount},
{&perRetryPolicies, &perRetryPolicyCloneCount},
{&options.PerRetryPolicies, &perRetryClientPolicyCloneCount},
};
const int size = static_cast<int>(initializations.size());
for (int i = 0; i < size; ++i)
{
for (int j = 0; j < i + 2; ++j)
{
initializations[i].Policies->emplace_back(
std::make_unique<TestPolicy>(initializations[i].Counter));
}
}
}
EXPECT_NO_THROW(static_cast<void>(Azure::Core::Http::_internal::HttpPipeline(
options, "Test", "1.0.0", std::move(perRetryPolicies), std::move(perCallPolicies))));
EXPECT_EQ(perCallPolicyCloneCount, 2);
EXPECT_EQ(perCallClientPolicyCloneCount, 3);
EXPECT_EQ(perRetryPolicyCloneCount, 4);
EXPECT_EQ(perRetryClientPolicyCloneCount, 5);
}

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/diagnostics/logger.hpp"
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/internal/http/pipeline.hpp"
@ -54,8 +55,24 @@ public:
int32_t,
std::chrono::milliseconds&,
double)> shouldRetryOnResponse)
: RetryPolicy(retryOptions), m_shouldRetryOnTransportFailure(shouldRetryOnTransportFailure),
m_shouldRetryOnResponse(shouldRetryOnResponse)
: RetryPolicy(retryOptions),
m_shouldRetryOnTransportFailure(
shouldRetryOnTransportFailure != nullptr
? shouldRetryOnTransportFailure
: [&](auto options, auto attempt, auto retryAfter, auto jitter) {
retryAfter = std::chrono::milliseconds(0);
auto ignore = decltype(retryAfter)();
return RetryPolicy::ShouldRetryOnTransportFailure(options, attempt, ignore, jitter);
}),
m_shouldRetryOnResponse(
shouldRetryOnResponse != nullptr
? shouldRetryOnResponse
: [&](RawResponse const& response, auto options, auto attempt, auto retryAfter, auto jitter) {
retryAfter = std::chrono::milliseconds(0);
auto ignore = decltype(retryAfter)();
return RetryPolicy::ShouldRetryOnResponse(
response, options, attempt, ignore, jitter);
})
{
}
@ -717,3 +734,79 @@ TEST(RetryPolicy, RetryAfter)
EXPECT_EQ(retryAfter, 90s);
}
}
TEST(RetryPolicy, LogMessages)
{
using Azure::Core::Diagnostics::Logger;
struct Log
{
struct Entry
{
Logger::Level Level;
std::string Message;
};
std::vector<Entry> Entries;
Log()
{
Logger::SetLevel(Logger::Level::Informational);
Logger::SetListener([&](auto lvl, auto msg) { Entries.emplace_back(Entry{lvl, msg}); });
}
~Log()
{
Logger::SetListener(nullptr);
Logger::SetLevel(Logger::Level::Warning);
}
} log;
{
using namespace std::chrono_literals;
RetryOptions const retryOptions{5, 10s, 5min, {HttpStatusCode::InternalServerError}};
auto requestNumber = 0;
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
policies.emplace_back(std::make_unique<RetryPolicyTest>(retryOptions, nullptr, nullptr));
policies.emplace_back(std::make_unique<TestTransportPolicy>([&]() {
++requestNumber;
if (requestNumber == 1)
{
throw TransportException("Cable Unplugged");
}
return std::make_unique<RawResponse>(
1,
1,
requestNumber == 2 ? HttpStatusCode::InternalServerError
: HttpStatusCode::ServiceUnavailable,
"Test");
}));
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
pipeline.Send(request, Azure::Core::Context());
}
EXPECT_EQ(log.Entries.size(), 5);
EXPECT_EQ(log.Entries[0].Level, Logger::Level::Warning);
EXPECT_EQ(log.Entries[0].Message, "HTTP Transport error: Cable Unplugged");
EXPECT_EQ(log.Entries[1].Level, Logger::Level::Informational);
EXPECT_EQ(log.Entries[1].Message, "HTTP Retry attempt #1 will be made in 0ms.");
EXPECT_EQ(log.Entries[2].Level, Logger::Level::Informational);
EXPECT_EQ(log.Entries[2].Message, "HTTP status code 500 will be retried.");
EXPECT_EQ(log.Entries[3].Level, Logger::Level::Informational);
EXPECT_EQ(log.Entries[3].Message, "HTTP Retry attempt #2 will be made in 0ms.");
EXPECT_EQ(log.Entries[4].Level, Logger::Level::Warning);
EXPECT_EQ(log.Entries[4].Message, "HTTP status code 503 won't be retried.");
}

View File

@ -18,3 +18,27 @@ TEST(SHA, SHA256Test)
for (size_t i = 0; i != shaResult.size(); i++)
printf("%02x", shaResult[i]);
}
TEST(SHA, SHA384Test)
{
Sha384Hash sha;
Sha384Hash sha2;
uint8_t data[] = "A";
auto shaResult = sha.Final(data, sizeof(data));
auto shaResult2 = sha2.Final(data, sizeof(data));
EXPECT_EQ(shaResult, shaResult2);
for (size_t i = 0; i != shaResult.size(); i++)
printf("%02x", shaResult[i]);
}
TEST(SHA, SHA512Test)
{
Sha512Hash sha;
Sha512Hash sha2;
uint8_t data[] = "A";
auto shaResult = sha.Final(data, sizeof(data));
auto shaResult2 = sha2.Final(data, sizeof(data));
EXPECT_EQ(shaResult, shaResult2);
for (size_t i = 0; i != shaResult.size(); i++)
printf("%02x", shaResult[i]);
}

View File

@ -312,4 +312,22 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_EQ(url1.GetPath(), "x/y");
EXPECT_EQ(url2.GetPath(), "x/y");
}
TEST(URL, Decode)
{
EXPECT_EQ(Core::Url::Decode("+%61b"), " ab");
EXPECT_THROW(Core::Url::Decode("%"), std::runtime_error);
EXPECT_THROW(Core::Url::Decode("%GA"), std::runtime_error);
EXPECT_THROW(Core::Url::Decode("%AG"), std::runtime_error);
}
TEST(URL, AppendQueryParameters)
{
Core::Url url("http://www.microsoft.com??param=value");
auto params = url.GetQueryParameters();
EXPECT_EQ(params.size(), 1);
EXPECT_NE(params.find("param"), params.end());
EXPECT_EQ(params["param"], "value");
}
}}} // namespace Azure::Core::Test

View File

@ -43,8 +43,8 @@ stages:
CtestExcludeRegex: azure-storage|azure-identity-livetest
LiveTestCtestRegex: '"azure-core.|json-test"'
LiveTestTimeoutInMinutes: 90 # default is 60 min. We need a little longer on worst case for Win+jsonTests
LineCoverageTarget: 81
BranchCoverageTarget: 43
LineCoverageTarget: 93
BranchCoverageTarget: 55
Artifacts:
- Name: azure-core
Path: azure-core