test: Use a random HTTP port for web server tests (#1248)

This will pick a random HTTP port for the test web server, and retry up
to 10 times if the chosen port number is in use.
This commit is contained in:
Joey Parrish 2023-07-18 16:19:52 -07:00 committed by GitHub
parent 5a2571b9bc
commit 86a183a847
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 60 deletions

View File

@ -24,18 +24,6 @@ namespace shaka {
namespace { namespace {
// A completely arbitrary port number, unlikely to be in use.
const int kTestServerPort = 58405;
// Reflects back the method, body, and headers of the request as JSON.
const std::string kTestServerReflect = "http://localhost:58405/reflect";
// Returns the requested HTTP status code.
const std::string kTestServer404 = "http://localhost:58405/status?code=404";
// Returns after the requested delay.
const std::string kTestServerLongDelay =
"http://localhost:58405/delay?seconds=8";
const std::string kTestServerShortDelay =
"http://localhost:58405/delay?seconds=1";
const std::vector<std::string> kNoHeaders; const std::vector<std::string> kNoHeaders;
const std::string kNoContentType; const std::string kNoContentType;
const std::string kBinaryContentType = "application/octet-stream"; const std::string kBinaryContentType = "application/octet-stream";
@ -90,16 +78,15 @@ nlohmann::json HandleResponse(const FilePtr& file) {
// enough. // enough.
class HttpFileTest : public testing::Test { class HttpFileTest : public testing::Test {
protected: protected:
void SetUp() override { ASSERT_TRUE(server_.Start(kTestServerPort)); } void SetUp() override { ASSERT_TRUE(server_.Start()); }
private:
media::TestWebServer server_; media::TestWebServer server_;
}; };
} // namespace } // namespace
TEST_F(HttpFileTest, BasicGet) { TEST_F(HttpFileTest, BasicGet) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kGet, server_.ReflectUrl(),
kNoContentType, kNoHeaders, kDefaultTestTimeout)); kNoContentType, kNoHeaders, kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
ASSERT_TRUE(file->Open()); ASSERT_TRUE(file->Open());
@ -112,7 +99,7 @@ TEST_F(HttpFileTest, BasicGet) {
TEST_F(HttpFileTest, CustomHeaders) { TEST_F(HttpFileTest, CustomHeaders) {
std::vector<std::string> headers{"Host: foo", "X-My-Header: Something"}; std::vector<std::string> headers{"Host: foo", "X-My-Header: Something"};
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kGet, server_.ReflectUrl(),
kNoContentType, headers, kDefaultTestTimeout)); kNoContentType, headers, kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
ASSERT_TRUE(file->Open()); ASSERT_TRUE(file->Open());
@ -127,7 +114,7 @@ TEST_F(HttpFileTest, CustomHeaders) {
} }
TEST_F(HttpFileTest, BasicPost) { TEST_F(HttpFileTest, BasicPost) {
FilePtr file(new HttpFile(HttpMethod::kPost, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kPost, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders, kBinaryContentType, kNoHeaders,
kDefaultTestTimeout)); kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
@ -161,7 +148,7 @@ TEST_F(HttpFileTest, BasicPost) {
} }
TEST_F(HttpFileTest, BasicPut) { TEST_F(HttpFileTest, BasicPut) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders, kBinaryContentType, kNoHeaders,
kDefaultTestTimeout)); kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
@ -195,7 +182,7 @@ TEST_F(HttpFileTest, BasicPut) {
} }
TEST_F(HttpFileTest, MultipleWrites) { TEST_F(HttpFileTest, MultipleWrites) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders, kBinaryContentType, kNoHeaders,
kDefaultTestTimeout)); kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
@ -239,7 +226,7 @@ TEST_F(HttpFileTest, MultipleWrites) {
} }
TEST_F(HttpFileTest, MultipleChunks) { TEST_F(HttpFileTest, MultipleChunks) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect, FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders, kBinaryContentType, kNoHeaders,
kDefaultTestTimeout)); kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
@ -286,8 +273,8 @@ TEST_F(HttpFileTest, MultipleChunks) {
} }
TEST_F(HttpFileTest, Error404) { TEST_F(HttpFileTest, Error404) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServer404, kNoContentType, FilePtr file(new HttpFile(HttpMethod::kGet, server_.StatusCodeUrl(404),
kNoHeaders, kDefaultTestTimeout)); kNoContentType, kNoHeaders, kDefaultTestTimeout));
ASSERT_TRUE(file); ASSERT_TRUE(file);
ASSERT_TRUE(file->Open()); ASSERT_TRUE(file->Open());
@ -301,7 +288,7 @@ TEST_F(HttpFileTest, Error404) {
} }
TEST_F(HttpFileTest, TimeoutTriggered) { TEST_F(HttpFileTest, TimeoutTriggered) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerLongDelay, FilePtr file(new HttpFile(HttpMethod::kGet, server_.DelayUrl(8),
kNoContentType, kNoHeaders, kNoContentType, kNoHeaders,
1 /* timeout in seconds */)); 1 /* timeout in seconds */));
ASSERT_TRUE(file); ASSERT_TRUE(file);
@ -317,7 +304,7 @@ TEST_F(HttpFileTest, TimeoutTriggered) {
} }
TEST_F(HttpFileTest, TimeoutNotTriggered) { TEST_F(HttpFileTest, TimeoutNotTriggered) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerShortDelay, FilePtr file(new HttpFile(HttpMethod::kGet, server_.DelayUrl(1),
kNoContentType, kNoHeaders, kNoContentType, kNoHeaders,
5 /* timeout in seconds */)); 5 /* timeout in seconds */));
ASSERT_TRUE(file); ASSERT_TRUE(file);

View File

@ -12,18 +12,6 @@
#include "packager/media/test/test_web_server.h" #include "packager/media/test/test_web_server.h"
#include "packager/status/status_test_util.h" #include "packager/status/status_test_util.h"
namespace {
// A completely arbitrary port number, unlikely to be in use.
const int kTestServerPort = 58405;
// Reflects back the method, body, and headers of the request as JSON.
const char kTestUrl[] = "http://localhost:58405/reflect";
// Returns the requested HTTP status code.
const char kTestUrl404[] = "http://localhost:58405/status?code=404";
// Returns after the requested delay.
const char kTestUrlDelayTwoSecs[] = "http://localhost:58405/delay?seconds=2";
} // namespace
namespace shaka { namespace shaka {
namespace media { namespace media {
@ -35,37 +23,37 @@ namespace media {
// enough. // enough.
class HttpKeyFetcherTest : public testing::Test { class HttpKeyFetcherTest : public testing::Test {
protected: protected:
void SetUp() override { ASSERT_TRUE(server_.Start(kTestServerPort)); } void SetUp() override { ASSERT_TRUE(server_.Start()); }
private:
TestWebServer server_; TestWebServer server_;
}; };
TEST_F(HttpKeyFetcherTest, HttpGet) { TEST_F(HttpKeyFetcherTest, HttpGet) {
HttpKeyFetcher fetcher; HttpKeyFetcher fetcher;
std::string response; std::string response;
ASSERT_OK(fetcher.Get(kTestUrl, &response)); ASSERT_OK(fetcher.Get(server_.ReflectUrl(), &response));
EXPECT_NE(std::string::npos, response.find("\"method\":\"GET\"")); EXPECT_NE(std::string::npos, response.find("\"method\":\"GET\""));
} }
TEST_F(HttpKeyFetcherTest, HttpPost) { TEST_F(HttpKeyFetcherTest, HttpPost) {
HttpKeyFetcher fetcher; HttpKeyFetcher fetcher;
std::string response; std::string response;
ASSERT_OK(fetcher.Post(kTestUrl, "", &response)); ASSERT_OK(fetcher.Post(server_.ReflectUrl(), "", &response));
EXPECT_NE(std::string::npos, response.find("\"method\":\"POST\"")); EXPECT_NE(std::string::npos, response.find("\"method\":\"POST\""));
} }
TEST_F(HttpKeyFetcherTest, HttpFetchKeys) { TEST_F(HttpKeyFetcherTest, HttpFetchKeys) {
HttpKeyFetcher fetcher; HttpKeyFetcher fetcher;
std::string response; std::string response;
ASSERT_OK(fetcher.FetchKeys(kTestUrl, "foo=62&type=mp4", &response)); ASSERT_OK(
fetcher.FetchKeys(server_.ReflectUrl(), "foo=62&type=mp4", &response));
EXPECT_NE(std::string::npos, response.find("\"foo=62&type=mp4\"")); EXPECT_NE(std::string::npos, response.find("\"foo=62&type=mp4\""));
} }
TEST_F(HttpKeyFetcherTest, InvalidUrl) { TEST_F(HttpKeyFetcherTest, InvalidUrl) {
HttpKeyFetcher fetcher; HttpKeyFetcher fetcher;
std::string response; std::string response;
Status status = fetcher.FetchKeys(kTestUrl404, "", &response); Status status = fetcher.FetchKeys(server_.StatusCodeUrl(404), "", &response);
EXPECT_EQ(error::HTTP_FAILURE, status.error_code()); EXPECT_EQ(error::HTTP_FAILURE, status.error_code());
EXPECT_NE(std::string::npos, status.error_message().find("404")); EXPECT_NE(std::string::npos, status.error_message().find("404"));
} }
@ -74,7 +62,7 @@ TEST_F(HttpKeyFetcherTest, SmallTimeout) {
const int32_t kTimeoutInSeconds = 1; const int32_t kTimeoutInSeconds = 1;
HttpKeyFetcher fetcher(kTimeoutInSeconds); HttpKeyFetcher fetcher(kTimeoutInSeconds);
std::string response; std::string response;
Status status = fetcher.FetchKeys(kTestUrlDelayTwoSecs, "", &response); Status status = fetcher.FetchKeys(server_.DelayUrl(2), "", &response);
EXPECT_EQ(error::TIME_OUT, status.error_code()); EXPECT_EQ(error::TIME_OUT, status.error_code());
} }
@ -82,7 +70,7 @@ TEST_F(HttpKeyFetcherTest, BigTimeout) {
const int32_t kTimeoutInSeconds = 5; const int32_t kTimeoutInSeconds = 5;
HttpKeyFetcher fetcher(kTimeoutInSeconds); HttpKeyFetcher fetcher(kTimeoutInSeconds);
std::string response; std::string response;
Status status = fetcher.FetchKeys(kTestUrlDelayTwoSecs, "", &response); Status status = fetcher.FetchKeys(server_.DelayUrl(2), "", &response);
EXPECT_OK(status); EXPECT_OK(status);
} }

View File

@ -7,6 +7,7 @@
#include "packager/media/test/test_web_server.h" #include "packager/media/test/test_web_server.h"
#include <chrono> #include <chrono>
#include <random>
#include <string_view> #include <string_view>
#include "absl/strings/numbers.h" #include "absl/strings/numbers.h"
@ -23,6 +24,12 @@
namespace { namespace {
// A random HTTP port will be chosen, and if there is a collision, we will try
// again up to |kMaxPortTries| times.
const int kMinPortNumber = 58000;
const int kMaxPortNumber = 58999;
const int kMaxPortTries = 10;
// Get a string_view on mongoose's mg_string, which may not be nul-terminated. // Get a string_view on mongoose's mg_string, which may not be nul-terminated.
std::string_view MongooseStringView(const mg_str& mg_string) { std::string_view MongooseStringView(const mg_str& mg_string) {
return std::string_view(mg_string.ptr, mg_string.len); return std::string_view(mg_string.ptr, mg_string.len);
@ -82,8 +89,8 @@ TestWebServer::~TestWebServer() {
thread_.reset(); thread_.reset();
} }
bool TestWebServer::Start(int port) { bool TestWebServer::Start() {
thread_.reset(new std::thread(&TestWebServer::ThreadCallback, this, port)); thread_.reset(new std::thread(&TestWebServer::ThreadCallback, this));
absl::MutexLock lock(&mutex_); absl::MutexLock lock(&mutex_);
while (status_ == kNew) { while (status_ == kNew) {
@ -93,12 +100,19 @@ bool TestWebServer::Start(int port) {
return status_ == kStarted; return status_ == kStarted;
} }
void TestWebServer::ThreadCallback(int port) { bool TestWebServer::TryListenOnPort(struct mg_mgr* manager, int port) {
// Mongoose needs an HTTP server address in string format. // Mongoose needs an HTTP server address in string format.
// "127.0.0.1" is "localhost", and is not visible to other machines on the // "127.0.0.1" is "localhost", and is not visible to other machines on the
// network. // network.
std::string http_address = absl::StrFormat("http://127.0.0.1:%d", port); base_url_ = absl::StrFormat("http://127.0.0.1:%d", port);
auto connection =
mg_http_listen(manager, base_url_.c_str(), &TestWebServer::HandleEvent,
this /* callback_data */);
return connection != NULL;
}
void TestWebServer::ThreadCallback() {
// Set up the manager structure to be automatically cleaned up when it leaves // Set up the manager structure to be automatically cleaned up when it leaves
// scope. // scope.
std::unique_ptr<struct mg_mgr, decltype(&mg_mgr_free)> manager( std::unique_ptr<struct mg_mgr, decltype(&mg_mgr_free)> manager(
@ -106,20 +120,29 @@ void TestWebServer::ThreadCallback(int port) {
// Then initialize it. // Then initialize it.
mg_mgr_init(manager.get()); mg_mgr_init(manager.get());
auto connection = // Prepare to choose a random port.
mg_http_listen(manager.get(), http_address.c_str(), std::random_device r;
&TestWebServer::HandleEvent, this /* callback_data */); std::default_random_engine e1(r());
if (connection == NULL) { std::uniform_int_distribution<int> uniform_dist(kMinPortNumber,
// Failed to listen to the requested port. Mongoose has already printed an kMaxPortNumber);
// error message.
bool ok = false;
for (int i = 0; !ok && i < kMaxPortTries; ++i) {
// Choose a random port in the range.
int port = uniform_dist(e1);
ok = TryListenOnPort(manager.get(), port);
}
{
absl::MutexLock lock(&mutex_); absl::MutexLock lock(&mutex_);
if (!ok) {
// Failed to find a port to listen on. Mongoose has already printed an
// error message.
status_ = kFailed; status_ = kFailed;
started_.Signal(); started_.Signal();
return; return;
} }
{
absl::MutexLock lock(&mutex_);
status_ = kStarted; status_ = kStarted;
started_.Signal(); started_.Signal();
} }

View File

@ -17,6 +17,7 @@
// Forward declare mongoose struct types, used as pointers below. // Forward declare mongoose struct types, used as pointers below.
struct mg_connection; struct mg_connection;
struct mg_http_message; struct mg_http_message;
struct mg_mgr;
namespace shaka { namespace shaka {
namespace media { namespace media {
@ -26,7 +27,20 @@ class TestWebServer {
TestWebServer(); TestWebServer();
~TestWebServer(); ~TestWebServer();
bool Start(int port); bool Start();
// Reflects back the request characteristics as a JSON response.
std::string ReflectUrl() { return base_url_ + "/reflect"; }
// Responds with a specific HTTP status code.
std::string StatusCodeUrl(int code) {
return base_url_ + "/status?code=" + std::to_string(code);
}
// Responds with HTTP 200 after a delay.
std::string DelayUrl(int seconds) {
return base_url_ + "/delay?seconds=" + std::to_string(seconds);
}
private: private:
enum TestWebServerStatus { enum TestWebServerStatus {
@ -49,7 +63,12 @@ class TestWebServer {
std::unique_ptr<std::thread> thread_; std::unique_ptr<std::thread> thread_;
void ThreadCallback(int port); std::string base_url_;
// Sets base_url_.
bool TryListenOnPort(struct mg_mgr* manager, int port);
void ThreadCallback();
static void HandleEvent(struct mg_connection* connection, static void HandleEvent(struct mg_connection* connection,
int event, int event,