diff --git a/sdk/core/azure-core/test/ut/CMakeLists.txt b/sdk/core/azure-core/test/ut/CMakeLists.txt index 9d3ddc893..7c707fb1f 100644 --- a/sdk/core/azure-core/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core/test/ut/CMakeLists.txt @@ -21,6 +21,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) if(BUILD_TRANSPORT_CURL) SET(CURL_OPTIONS_TESTS curl_options.cpp) + SET(CURL_TRANSPORT_TESTS transport_adapter_curl.cpp) endif() include(GoogleTest) @@ -39,8 +40,8 @@ add_executable ( simplified_header.cpp string.cpp telemetry_policy.cpp - transport_adapter.cpp - transport_adapter_file_upload.cpp + transport_adapter_base.cpp + ${CURL_TRANSPORT_TESTS} url.cpp uuid.cpp ) diff --git a/sdk/core/azure-core/test/ut/transport_adapter.cpp b/sdk/core/azure-core/test/ut/transport_adapter.cpp deleted file mode 100644 index 2e42a7e2d..000000000 --- a/sdk/core/azure-core/test/ut/transport_adapter.cpp +++ /dev/null @@ -1,508 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#include "transport_adapter.hpp" -#include -#include -#include -#include -#include - -namespace Azure { namespace Core { namespace Test { - - static std::vector> CreatePolicies() - { - std::vector> p; - Azure::Core::Http::RetryOptions opt; - opt.RetryDelay = std::chrono::milliseconds(10); - - // Retry policy will help to prevent server-occasionally-errors - p.push_back(std::make_unique(opt)); - p.push_back(std::make_unique()); - return p; - } - - std::vector> TransportAdapter::policies - = CreatePolicies(); - - Azure::Core::Http::HttpPipeline TransportAdapter::pipeline(policies); - Azure::Core::Context TransportAdapter::context = Azure::Core::GetApplicationContext(); - -// Connection pool feature is curl-implementation only. No other transport adapter would have the -// connection pool -#ifdef BUILD_CURL_HTTP_TRANSPORT_ADAPTER - // connectionPoolTest requires `ConnectionsOnPool` hook which is only available when building - // BUILD_TESTING. This test is only built when that case is true. - TEST_F(TransportAdapter, connectionPoolTest) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - Azure::Core::Http::CurlConnectionPool::ClearIndex(); - - auto threadRoutine = [host]() { - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - }; - - std::thread t1(threadRoutine); - std::thread t2(threadRoutine); - t1.join(); - t2.join(); - - // 2 connections must be available at this point - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2); - - std::thread t3(threadRoutine); - std::thread t4(threadRoutine); - std::thread t5(threadRoutine); - t3.join(); - t4.join(); - t5.join(); - - // Two connections re-used plus one connection created - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 3); - -#ifdef RUN_LONG_UNIT_TESTS - { - // Test pool clean routine - std::cout << "Running Connection Pool Cleaner Test. This test takes more than 3 minutes to " - "complete." - << std::endl - << "Add compiler option -DRUN_LONG_UNIT_TESTS=OFF when building if you want to " - "skip this " - "test." - << std::endl; - - // Wait for 180 secs to make sure any previous connection is removed by the cleaner - std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 180)); - - std::cout << "First wait time done. Validating state." << std::endl; - - // index is not affected by cleaner. It does not remove index - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1); - // cleaner should have removed connections - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 0); - - std::thread t1(threadRoutine); - std::thread t2(threadRoutine); - t1.join(); - t2.join(); - - // wait for connection to be moved back to pool - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - - // 2 connections must be available at this point and one index - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1); - EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2); - } -#endif - } -#endif - - TEST_F(TransportAdapter, get) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - - // Need to init request again, since retry would be on after it is sent - request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - // Add a header and send again. RawResponse should return that header in the body - request.AddHeader("123", "456"); - response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - // header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,` - CheckBodyFromBuffer(*response, expectedResponseBodySize + 6 + 13); - } - - TEST_F(TransportAdapter, get204) - { - Azure::Core::Http::Url host("http://mt3.google.com/generate_204"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, getLoop) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - - // loop sending request - for (auto i = 0; i < 50; i++) - { - auto response = pipeline.Send(context, request); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - checkResponseCode(response->GetStatusCode()); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - } - - TEST_F(TransportAdapter, head) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - auto expectedResponseBodySize = 0; - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - - // Check content-length header to be greater than 0 - int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length")); - EXPECT_TRUE(contentLengthHeader > 0); - } - - TEST_F(TransportAdapter, put) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - - // PUT 1K - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, deleteRequest) - { - Azure::Core::Http::Url host("http://httpbin.org/delete"); - - // Delete with 1k payload - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, patch) - { - Azure::Core::Http::Url host("http://httpbin.org/patch"); - - // Patch with 1kb payload - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, getChunk) - { - Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked"); - auto expectedResponseBodySize = -1; // chunked will return unknown body length - auto expectedChunkResponse = std::string( - "\r\n\r\n\r\n\r\nChunked " - "transfer encoding test\r\n\r\n

Chunked transfer encoding " - "test

This is a chunked response after 100 ms.
This is a chunked " - "response after 1 second. The server should not close the stream before all chunks are " - "sent to a client.
"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - auto response = pipeline.Send(context, request); - - checkResponseCode(response->GetStatusCode()); - CheckBodyFromBuffer(*response, expectedResponseBodySize, expectedChunkResponse); - } - - TEST_F(TransportAdapter, putErrorResponse) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - - // Try to make a PUT to a GET url. This will return an error code from server. - // This test makes sure that the connection is not re-used (because it gets closed by server) - // and next request is not hang - for (auto i = 0; i < 10; i++) - { - auto requestBodyVector = std::vector(10, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); - auto response = pipeline.Send(context, request); - } - } - - // ********************** - // ***Same tests but getting stream to pull from socket, simulating the Download Op - // ********************** - - TEST_F(TransportAdapter, getWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromStream(*response, expectedResponseBodySize); - - request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); - // Add a header and send again. Response should return that header in the body - request.AddHeader("123", "456"); - response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - // header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,` - CheckBodyFromStream(*response, expectedResponseBodySize + 6 + 13); - } - - TEST_F(TransportAdapter, getLoopWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); - - // loop sending request - for (auto i = 0; i < 50; i++) - { - auto response = pipeline.Send(context, request); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - checkResponseCode(response->GetStatusCode()); - CheckBodyFromStream(*response, expectedResponseBodySize); - } - } - - TEST_F(TransportAdapter, headWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - auto expectedResponseBodySize = 0; - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host, true); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - CheckBodyFromStream(*response, expectedResponseBodySize); - - // Check content-length header to be greater than 0 - int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length")); - EXPECT_TRUE(contentLengthHeader > 0); - } - - TEST_F(TransportAdapter, putWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - - // PUT 1k - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromStream(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, deleteRequestWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/delete"); - - // Delete with 1k payload - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest, true); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromStream(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, patchWithStream) - { - Azure::Core::Http::Url host("http://httpbin.org/patch"); - - // Patch with 1kb payload - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest, true); - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromStream(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, getChunkWithStream) - { - Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked"); - auto expectedResponseBodySize = -1; // chunked will return unknown body length - auto expectedChunkResponse = std::string( - "\r\n\r\n\r\n\r\nChunked " - "transfer encoding test\r\n\r\n

Chunked transfer encoding " - "test

This is a chunked response after 100 ms.
This is a chunked " - "response after 1 second. The server should not close the stream before all chunks are " - "sent to a client.
"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); - auto response = pipeline.Send(context, request); - - checkResponseCode(response->GetStatusCode()); - CheckBodyFromStream(*response, expectedResponseBodySize, expectedChunkResponse); - } - - TEST_F(TransportAdapter, createResponseT) - { - Azure::Core::Http::Url host("http://httpbin.org/get"); - std::string expectedType("This is the Response Type"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, false); - auto response = pipeline.Send(context, request); - - Azure::Core::Response responseT(expectedType, std::move(response)); - auto& r = responseT.GetRawResponse(); - - EXPECT_TRUE(r.GetStatusCode() == Azure::Core::Http::HttpStatusCode::Ok); - auto expectedResponseBodySize = std::stoull(r.GetHeaders().at("content-length")); - CheckBodyFromBuffer(r, expectedResponseBodySize); - - // Direct access - EXPECT_STREQ((*responseT).data(), expectedType.data()); - EXPECT_STREQ(responseT->data(), expectedType.data()); - // extracting T out of response - auto result = responseT.ExtractValue(); - EXPECT_STREQ(result.data(), expectedType.data()); - // Test that calling getValue again will return empty - result = responseT.ExtractValue(); - EXPECT_STREQ(result.data(), std::string("").data()); - } - - TEST_F(TransportAdapter, customSizePut) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - - // PUT 1MB - auto requestBodyVector = std::vector(1024 * 1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); - // Make transport adapter to read all stream content for uploading instead of chunks - request.SetUploadChunkSize(1024 * 1024); - { - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - } - - TEST_F(TransportAdapter, putWithStreamOnFail) - { - // point to bad address pah to generate server MethodNotAllowed error - Azure::Core::Http::Url host("http://httpbin.org/get"); - - // PUT 1k - auto requestBodyVector = std::vector(1024, 'x'); - auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true); - auto response = pipeline.Send(context, request); - checkResponseCode( - response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::MethodNotAllowed); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromBuffer(*response, expectedResponseBodySize); - } - - TEST_F(TransportAdapter, cancelTransferUpload) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - Azure::Core::Context cancelThis; - - auto threadRoutine = [host, cancelThis]() { - // Start a big upload and expect it to throw cancelation - std::vector bigBuffer(1024 * 1024 * 200, 'x'); // upload 200 Mb - auto stream = Azure::Core::Http::MemoryBodyStream(bigBuffer); - auto request - = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &stream); - - // Request will be canceled from main thread throwing the exception - EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::OperationCanceledException); - }; - - // Start request - std::thread t1(threadRoutine); - - // Wait 100 ms so we know upload has started - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - cancelThis.Cancel(); - t1.join(); - } - - TEST_F(TransportAdapter, cancelTransferDownload) - { - // public big blob (321MB) - Azure::Core::Http::Url host("https://bigtestfiles.blob.core.windows.net/cpptestfiles/321MB"); - Azure::Core::Context cancelThis; - - auto threadRoutine = [host, cancelThis]() { - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - - // Request will be canceled from main thread throwing the exception - EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::OperationCanceledException); - }; - - // Start request - std::thread t1(threadRoutine); - - // Wait 100 ms so we know download has started - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - cancelThis.Cancel(); - t1.join(); - } - - TEST_F(TransportAdapter, requestFailedException) - { - Azure::Core::Http::Url host("http://unresolvedHost.org/get"); - - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - EXPECT_THROW(pipeline.Send(context, request), Azure::Core::RequestFailedException); - } - - TEST_F(TransportAdapter, dynamicCast) - { - Azure::Core::Http::Url host("http://unresolvedHost.org/get"); - auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); - - // test dynamic cast - try - { - auto result = pipeline.Send(context, request); - } - catch (Azure::Core::RequestFailedException& err) - { - // if ref can't be cast, it throws - EXPECT_NO_THROW(dynamic_cast(err)); - EXPECT_NO_THROW(dynamic_cast(err)); - EXPECT_THROW(dynamic_cast(err), std::bad_cast); - } - } - -}}} // namespace Azure::Core::Test diff --git a/sdk/core/azure-core/test/ut/transport_adapter_base.cpp b/sdk/core/azure-core/test/ut/transport_adapter_base.cpp new file mode 100644 index 000000000..865e90b4c --- /dev/null +++ b/sdk/core/azure-core/test/ut/transport_adapter_base.cpp @@ -0,0 +1,597 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#ifdef POSIX +#include +#endif // Posix + +#ifdef WINDOWS +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif // Windows + +#include "transport_adapter_base.hpp" +#include +#include +#include +#include +#include + +namespace Azure { namespace Core { namespace Test { + + namespace Datails { + constexpr int64_t c_fileSize = 1024 * 100; + } + + TEST_P(TransportAdapter, get) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + + // Need to init request again, since retry would be on after it is sent + request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + // Add a header and send again. RawResponse should return that header in the body + request.AddHeader("123", "456"); + response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + // header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,` + CheckBodyFromBuffer(*response, expectedResponseBodySize + 6 + 13); + } + + TEST_P(TransportAdapter, get204) + { + Azure::Core::Http::Url host("http://mt3.google.com/generate_204"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, getLoop) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + + // loop sending request + for (auto i = 0; i < 50; i++) + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + checkResponseCode(response->GetStatusCode()); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + } + + TEST_P(TransportAdapter, head) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + auto expectedResponseBodySize = 0; + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + + // Check content-length header to be greater than 0 + int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length")); + EXPECT_TRUE(contentLengthHeader > 0); + } + + TEST_P(TransportAdapter, put) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + + // PUT 1K + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, deleteRequest) + { + Azure::Core::Http::Url host("http://httpbin.org/delete"); + + // Delete with 1k payload + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, patch) + { + Azure::Core::Http::Url host("http://httpbin.org/patch"); + + // Patch with 1kb payload + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, getChunk) + { + Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked"); + auto expectedResponseBodySize = -1; // chunked will return unknown body length + auto expectedChunkResponse = std::string( + "\r\n\r\n\r\n\r\nChunked " + "transfer encoding test\r\n\r\n

Chunked transfer encoding " + "test

This is a chunked response after 100 ms.
This is a chunked " + "response after 1 second. The server should not close the stream before all chunks are " + "sent to a client.
"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + + checkResponseCode(response->GetStatusCode()); + CheckBodyFromBuffer(*response, expectedResponseBodySize, expectedChunkResponse); + } + + TEST_P(TransportAdapter, putErrorResponse) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + + // Try to make a PUT to a GET url. This will return an error code from server. + // This test makes sure that the connection is not re-used (because it gets closed by server) + // and next request is not hang + for (auto i = 0; i < 10; i++) + { + auto requestBodyVector = std::vector(10, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + } + } + + // ********************** + // ***Same tests but getting stream to pull from socket, simulating the Download Op + // ********************** + + TEST_P(TransportAdapter, getWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromStream(*response, expectedResponseBodySize); + + request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); + // Add a header and send again. Response should return that header in the body + request.AddHeader("123", "456"); + response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + // header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,` + CheckBodyFromStream(*response, expectedResponseBodySize + 6 + 13); + } + + TEST_P(TransportAdapter, getLoopWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); + + // loop sending request + for (auto i = 0; i < 50; i++) + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + checkResponseCode(response->GetStatusCode()); + CheckBodyFromStream(*response, expectedResponseBodySize); + } + } + + TEST_P(TransportAdapter, headWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + auto expectedResponseBodySize = 0; + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + CheckBodyFromStream(*response, expectedResponseBodySize); + + // Check content-length header to be greater than 0 + int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length")); + EXPECT_TRUE(contentLengthHeader > 0); + } + + TEST_P(TransportAdapter, putWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + + // PUT 1k + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromStream(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, deleteRequestWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/delete"); + + // Delete with 1k payload + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromStream(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, patchWithStream) + { + Azure::Core::Http::Url host("http://httpbin.org/patch"); + + // Patch with 1kb payload + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromStream(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, getChunkWithStream) + { + Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked"); + auto expectedResponseBodySize = -1; // chunked will return unknown body length + auto expectedChunkResponse = std::string( + "\r\n\r\n\r\n\r\nChunked " + "transfer encoding test\r\n\r\n

Chunked transfer encoding " + "test

This is a chunked response after 100 ms.
This is a chunked " + "response after 1 second. The server should not close the stream before all chunks are " + "sent to a client.
"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + + checkResponseCode(response->GetStatusCode()); + CheckBodyFromStream(*response, expectedResponseBodySize, expectedChunkResponse); + } + + TEST_P(TransportAdapter, createResponseT) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + std::string expectedType("This is the Response Type"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, false); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + + Azure::Core::Response responseT(expectedType, std::move(response)); + auto& r = responseT.GetRawResponse(); + + EXPECT_TRUE(r.GetStatusCode() == Azure::Core::Http::HttpStatusCode::Ok); + auto expectedResponseBodySize = std::stoull(r.GetHeaders().at("content-length")); + CheckBodyFromBuffer(r, expectedResponseBodySize); + + // Direct access + EXPECT_STREQ((*responseT).data(), expectedType.data()); + EXPECT_STREQ(responseT->data(), expectedType.data()); + // extracting T out of response + auto result = responseT.ExtractValue(); + EXPECT_STREQ(result.data(), expectedType.data()); + // Test that calling getValue again will return empty + result = responseT.ExtractValue(); + EXPECT_STREQ(result.data(), std::string("").data()); + } + + TEST_P(TransportAdapter, customSizePut) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + + // PUT 1MB + auto requestBodyVector = std::vector(1024 * 1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest); + // Make transport adapter to read all stream content for uploading instead of chunks + request.SetUploadChunkSize(1024 * 1024); + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + } + + TEST_P(TransportAdapter, putWithStreamOnFail) + { + // point to bad address pah to generate server MethodNotAllowed error + Azure::Core::Http::Url host("http://httpbin.org/get"); + + // PUT 1k + auto requestBodyVector = std::vector(1024, 'x'); + auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector); + auto request + = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode( + response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::MethodNotAllowed); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromBuffer(*response, expectedResponseBodySize); + } + + TEST_P(TransportAdapter, cancelTransferUpload) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + Azure::Core::Context cancelThis; + + auto threadRoutine = [&]() { + // Start a big upload and expect it to throw cancelation + std::vector bigBuffer(1024 * 1024 * 200, 'x'); // upload 200 Mb + auto stream = Azure::Core::Http::MemoryBodyStream(bigBuffer); + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &stream); + + // Request will be canceled from main thread throwing the exception + EXPECT_THROW(m_pipeline->Send(cancelThis, request), Azure::Core::OperationCanceledException); + }; + + // Start request + std::thread t1(threadRoutine); + + // Wait 100 ms so we know upload has started + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + cancelThis.Cancel(); + t1.join(); + } + + TEST_P(TransportAdapter, cancelTransferDownload) + { + // public big blob (321MB) + Azure::Core::Http::Url host("https://bigtestfiles.blob.core.windows.net/cpptestfiles/321MB"); + Azure::Core::Context cancelThis; + + auto threadRoutine = [&]() { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + + // Request will be canceled from main thread throwing the exception + EXPECT_THROW(m_pipeline->Send(cancelThis, request), Azure::Core::OperationCanceledException); + }; + + // Start request + std::thread t1(threadRoutine); + + // Wait 100 ms so we know download has started + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + cancelThis.Cancel(); + t1.join(); + } + + TEST_P(TransportAdapter, requestFailedException) + { + Azure::Core::Http::Url host("http://unresolvedHost.org/get"); + + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + EXPECT_THROW( + m_pipeline->Send(Azure::Core::GetApplicationContext(), request), + Azure::Core::RequestFailedException); + } + + TEST_P(TransportAdapter, dynamicCast) + { + Azure::Core::Http::Url host("http://unresolvedHost.org/get"); + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + + // test dynamic cast + try + { + auto result = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + } + catch (Azure::Core::RequestFailedException& err) + { + // if ref can't be cast, it throws + EXPECT_NO_THROW(dynamic_cast(err)); + EXPECT_NO_THROW(dynamic_cast(err)); + EXPECT_THROW(dynamic_cast(err), std::bad_cast); + } + } + + TEST_P(TransportAdapter, SizePutFromFile) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + std::string testDataPath(AZURE_TEST_DATA_PATH); + +#ifdef POSIX + testDataPath.append("/fileData"); + int f = open(testDataPath.data(), O_RDONLY); +#endif +#ifdef WINDOWS + testDataPath.append("\\fileData"); + HANDLE f = CreateFile( + testDataPath.data(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); +#endif + auto requestBodyStream + = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); + // Make transport adapter to read all stream content for uploading instead of chunks + request.SetUploadChunkSize(Azure::Core::Test::Datails::c_fileSize); + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromStream(*response, expectedResponseBodySize); + } + } + + TEST_P(TransportAdapter, SizePutFromFileDefault) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + std::string testDataPath(AZURE_TEST_DATA_PATH); + +#ifdef POSIX + testDataPath.append("/fileData"); + int f = open(testDataPath.data(), O_RDONLY); +#endif +#ifdef WINDOWS + testDataPath.append("\\fileData"); + HANDLE f = CreateFile( + testDataPath.data(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); +#endif + auto requestBodyStream + = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); + // Make transport adapter to read default chunk size + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromStream(*response, expectedResponseBodySize); + } + } + + TEST_P(TransportAdapter, SizePutFromFileBiggerPage) + { + Azure::Core::Http::Url host("http://httpbin.org/put"); + std::string testDataPath(AZURE_TEST_DATA_PATH); + +#ifdef POSIX + testDataPath.append("/fileData"); + int f = open(testDataPath.data(), O_RDONLY); +#endif +#ifdef WINDOWS + testDataPath.append("\\fileData"); + HANDLE f = CreateFile( + testDataPath.data(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); +#endif + auto requestBodyStream + = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); + auto request = Azure::Core::Http::Request( + Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); + // Make transport adapter to read more than file size (5Mb) + request.SetUploadChunkSize(Azure::Core::Test::Datails::c_fileSize * 5); + { + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + + CheckBodyFromStream(*response, expectedResponseBodySize); + } + } + + /***************** Test Utils *************************/ + void TransportAdapter::checkResponseCode( + Azure::Core::Http::HttpStatusCode code, + Azure::Core::Http::HttpStatusCode expectedCode) + { + EXPECT_PRED2( + [](Azure::Core::Http::HttpStatusCode a, Azure::Core::Http::HttpStatusCode b) { + return a == b; + }, + code, + expectedCode); + } + + void TransportAdapter::CheckBodyFromBuffer( + Azure::Core::Http::RawResponse& response, + int64_t size, + std::string expectedBody) + { + auto body = response.GetBodyStream(); + EXPECT_EQ(body, nullptr); + std::vector bodyVector = response.GetBody(); + int64_t bodySize = bodyVector.size(); + + if (size > 0) + { // only for known body size + EXPECT_EQ(bodySize, size); + } + + if (expectedBody.size() > 0) + { + auto bodyString = std::string(bodyVector.begin(), bodyVector.end()); + EXPECT_STREQ(expectedBody.data(), bodyString.data()); + } + } + + void TransportAdapter::CheckBodyFromStream( + Azure::Core::Http::RawResponse& response, + int64_t size, + std::string expectedBody) + { + auto body = response.GetBodyStream(); + EXPECT_NE(body, nullptr); + + std::vector bodyVector + = Azure::Core::Http::BodyStream::ReadToEnd(Azure::Core::GetApplicationContext(), *body); + int64_t bodySize = body->Length(); + EXPECT_EQ(bodySize, size); + bodySize = bodyVector.size(); + + if (size > 0) + { // only for known body size + EXPECT_EQ(bodyVector.size(), size); + } + + if (expectedBody.size() > 0) + { + auto bodyString = std::string(bodyVector.begin(), bodyVector.end()); + EXPECT_STREQ(expectedBody.data(), bodyString.data()); + } + } + +}}} // namespace Azure::Core::Test diff --git a/sdk/core/azure-core/test/ut/transport_adapter.hpp b/sdk/core/azure-core/test/ut/transport_adapter_base.hpp similarity index 56% rename from sdk/core/azure-core/test/ut/transport_adapter.hpp rename to sdk/core/azure-core/test/ut/transport_adapter_base.hpp index 04e6aebf6..a5448fa05 100644 --- a/sdk/core/azure-core/test/ut/transport_adapter.hpp +++ b/sdk/core/azure-core/test/ut/transport_adapter_base.hpp @@ -20,11 +20,26 @@ namespace Azure { namespace Core { namespace Test { - class TransportAdapter : public ::testing::Test { + class TransportAdapter + : public testing::TestWithParam { protected: - static Azure::Core::Http::HttpPipeline pipeline; - static std::vector> policies; - static Azure::Core::Context context; + std::unique_ptr m_pipeline; + + // Befor each test, create pipeline + virtual void SetUp() override + { + std::vector> policies; + Azure::Core::Http::RetryOptions opt; + opt.RetryDelay = std::chrono::milliseconds(10); + + // Retry policy will help to prevent server-occasionally-errors + policies.push_back(std::make_unique(opt)); + // Will get transport policy options from test param + // auto param = GetParam(); + policies.push_back(std::make_unique(GetParam())); + + m_pipeline = std::make_unique(policies); + } static void CheckBodyFromBuffer( Azure::Core::Http::RawResponse& response, diff --git a/sdk/core/azure-core/test/ut/transport_adapter_curl.cpp b/sdk/core/azure-core/test/ut/transport_adapter_curl.cpp new file mode 100644 index 000000000..a52d0d093 --- /dev/null +++ b/sdk/core/azure-core/test/ut/transport_adapter_curl.cpp @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "transport_adapter_base.hpp" +#include +#include +#include +#include +#include + +using testing::ValuesIn; + +namespace Azure { namespace Core { namespace Test { + + /********************** Define the parameters for the base test and a suffix ***************/ + namespace { + static Azure::Core::Http::TransportPolicyOptions GetTransportOptions() + { + Azure::Core::Http::TransportPolicyOptions options; + options.Transport = std::make_shared(); + return options; + } + + // When adding more than one parameter, this function should return a unique string. + // But since we are only using one parameter (the libcurl transport adapter) this is fine. + static std::string GetSuffix(const testing::TestParamInfo& info) + { + // Can't use empty spaces or underscores (_) as per google test documentation + // https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#specifying-names-for-value-parameterized-test-parameters + (void)(info); + std::string suffix("curlImplementation"); + return suffix; + } + } // namespace + + /*********************** Unique Tests for Libcurl ********************************/ + TEST_P(TransportAdapter, connectionPoolTest) + { + Azure::Core::Http::Url host("http://httpbin.org/get"); + Azure::Core::Http::CurlConnectionPool::ClearIndex(); + + auto threadRoutine = [&]() { + auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host); + auto response = m_pipeline->Send(Azure::Core::GetApplicationContext(), request); + checkResponseCode(response->GetStatusCode()); + auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); + CheckBodyFromBuffer(*response, expectedResponseBodySize); + }; + + std::thread t1(threadRoutine); + std::thread t2(threadRoutine); + t1.join(); + t2.join(); + + // 2 connections must be available at this point + EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2); + + std::thread t3(threadRoutine); + std::thread t4(threadRoutine); + std::thread t5(threadRoutine); + t3.join(); + t4.join(); + t5.join(); + + // Two connections re-used plus one connection created + EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 3); + +#ifdef RUN_LONG_UNIT_TESTS + { + // Test pool clean routine + std::cout << "Running Connection Pool Cleaner Test. This test takes more than 3 minutes to " + "complete." + << std::endl + << "Add compiler option -DRUN_LONG_UNIT_TESTS=OFF when building if you want to " + "skip this test." + << std::endl; + + // Wait for 180 secs to make sure any previous connection is removed by the cleaner + std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 180)); + + std::cout << "First wait time done. Validating state." << std::endl; + + // index is not affected by cleaner. It does not remove index + EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1); + // cleaner should have removed connections + EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 0); + + std::thread t1(threadRoutine); + std::thread t2(threadRoutine); + t1.join(); + t2.join(); + + // wait for connection to be moved back to pool + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // 2 connections must be available at this point and one index + EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1); + // Depending on how fast the previous requests are sent, there could be one or more + // connections in the pool. If first request is too fast, the second request will reuse the + // same connection. + EXPECT_PRED1( + [](int currentConnections) { return currentConnections > 1; }, + Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org")); + } +#endif + } + + /*********************** Base Transpoer Adapter Tests ******************************/ + INSTANTIATE_TEST_SUITE_P( + TransportAdapterCurlImpl, + TransportAdapter, + testing::Values(GetTransportOptions()), + GetSuffix); + +}}} // namespace Azure::Core::Test diff --git a/sdk/core/azure-core/test/ut/transport_adapter_file_upload.cpp b/sdk/core/azure-core/test/ut/transport_adapter_file_upload.cpp deleted file mode 100644 index 7ab858ad3..000000000 --- a/sdk/core/azure-core/test/ut/transport_adapter_file_upload.cpp +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -#ifdef POSIX -#include -#endif // Posix - -#ifdef WINDOWS -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include -#endif // Windows - -#include - -#include "transport_adapter.hpp" - -#include - -namespace Azure { namespace Core { namespace Test { - namespace Datails { - constexpr int64_t c_fileSize = 1024 * 100; - } - - void TransportAdapter::checkResponseCode( - Azure::Core::Http::HttpStatusCode code, - Azure::Core::Http::HttpStatusCode expectedCode) - { - EXPECT_PRED2( - [](Azure::Core::Http::HttpStatusCode a, Azure::Core::Http::HttpStatusCode b) { - return a == b; - }, - code, - expectedCode); - } - - void TransportAdapter::CheckBodyFromBuffer( - Azure::Core::Http::RawResponse& response, - int64_t size, - std::string expectedBody) - { - auto body = response.GetBodyStream(); - EXPECT_EQ(body, nullptr); - std::vector bodyVector = response.GetBody(); - int64_t bodySize = bodyVector.size(); - - if (size > 0) - { // only for known body size - EXPECT_EQ(bodySize, size); - } - - if (expectedBody.size() > 0) - { - auto bodyString = std::string(bodyVector.begin(), bodyVector.end()); - EXPECT_STREQ(expectedBody.data(), bodyString.data()); - } - } - - void TransportAdapter::CheckBodyFromStream( - Azure::Core::Http::RawResponse& response, - int64_t size, - std::string expectedBody) - { - auto body = response.GetBodyStream(); - EXPECT_NE(body, nullptr); - - std::vector bodyVector = Azure::Core::Http::BodyStream::ReadToEnd(context, *body); - int64_t bodySize = body->Length(); - EXPECT_EQ(bodySize, size); - bodySize = bodyVector.size(); - - if (size > 0) - { // only for known body size - EXPECT_EQ(bodyVector.size(), size); - } - - if (expectedBody.size() > 0) - { - auto bodyString = std::string(bodyVector.begin(), bodyVector.end()); - EXPECT_STREQ(expectedBody.data(), bodyString.data()); - } - } - - TEST_F(TransportAdapter, SizePutFromFile) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - std::string testDataPath(AZURE_TEST_DATA_PATH); - -#ifdef POSIX - testDataPath.append("/fileData"); - int f = open(testDataPath.data(), O_RDONLY); -#endif -#ifdef WINDOWS - testDataPath.append("\\fileData"); - HANDLE f = CreateFile( - testDataPath.data(), - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN, - NULL); -#endif - auto requestBodyStream - = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); - // Make transport adapter to read all stream content for uploading instead of chunks - request.SetUploadChunkSize(Azure::Core::Test::Datails::c_fileSize); - { - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromStream(*response, expectedResponseBodySize); - } - } - - TEST_F(TransportAdapter, SizePutFromFileDefault) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - std::string testDataPath(AZURE_TEST_DATA_PATH); - -#ifdef POSIX - testDataPath.append("/fileData"); - int f = open(testDataPath.data(), O_RDONLY); -#endif -#ifdef WINDOWS - testDataPath.append("\\fileData"); - HANDLE f = CreateFile( - testDataPath.data(), - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN, - NULL); -#endif - auto requestBodyStream - = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); - // Make transport adapter to read default chunk size - { - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromStream(*response, expectedResponseBodySize); - } - } - - TEST_F(TransportAdapter, SizePutFromFileBiggerPage) - { - Azure::Core::Http::Url host("http://httpbin.org/put"); - std::string testDataPath(AZURE_TEST_DATA_PATH); - -#ifdef POSIX - testDataPath.append("/fileData"); - int f = open(testDataPath.data(), O_RDONLY); -#endif -#ifdef WINDOWS - testDataPath.append("\\fileData"); - HANDLE f = CreateFile( - testDataPath.data(), - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_FLAG_SEQUENTIAL_SCAN, - NULL); -#endif - auto requestBodyStream - = Azure::Core::Http::FileBodyStream(f, 0, Azure::Core::Test::Datails::c_fileSize); - auto request = Azure::Core::Http::Request( - Azure::Core::Http::HttpMethod::Put, host, &requestBodyStream, true); - // Make transport adapter to read more than file size (5Mb) - request.SetUploadChunkSize(Azure::Core::Test::Datails::c_fileSize * 5); - { - auto response = pipeline.Send(context, request); - checkResponseCode(response->GetStatusCode()); - auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length")); - - CheckBodyFromStream(*response, expectedResponseBodySize); - } - } - -}}} // namespace Azure::Core::Test