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 {
// 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::string kNoContentType;
const std::string kBinaryContentType = "application/octet-stream";
@ -90,16 +78,15 @@ nlohmann::json HandleResponse(const FilePtr& file) {
// enough.
class HttpFileTest : public testing::Test {
protected:
void SetUp() override { ASSERT_TRUE(server_.Start(kTestServerPort)); }
void SetUp() override { ASSERT_TRUE(server_.Start()); }
private:
media::TestWebServer server_;
};
} // namespace
TEST_F(HttpFileTest, BasicGet) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerReflect,
FilePtr file(new HttpFile(HttpMethod::kGet, server_.ReflectUrl(),
kNoContentType, kNoHeaders, kDefaultTestTimeout));
ASSERT_TRUE(file);
ASSERT_TRUE(file->Open());
@ -112,7 +99,7 @@ TEST_F(HttpFileTest, BasicGet) {
TEST_F(HttpFileTest, CustomHeaders) {
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));
ASSERT_TRUE(file);
ASSERT_TRUE(file->Open());
@ -127,7 +114,7 @@ TEST_F(HttpFileTest, CustomHeaders) {
}
TEST_F(HttpFileTest, BasicPost) {
FilePtr file(new HttpFile(HttpMethod::kPost, kTestServerReflect,
FilePtr file(new HttpFile(HttpMethod::kPost, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders,
kDefaultTestTimeout));
ASSERT_TRUE(file);
@ -161,7 +148,7 @@ TEST_F(HttpFileTest, BasicPost) {
}
TEST_F(HttpFileTest, BasicPut) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect,
FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders,
kDefaultTestTimeout));
ASSERT_TRUE(file);
@ -195,7 +182,7 @@ TEST_F(HttpFileTest, BasicPut) {
}
TEST_F(HttpFileTest, MultipleWrites) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect,
FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders,
kDefaultTestTimeout));
ASSERT_TRUE(file);
@ -239,7 +226,7 @@ TEST_F(HttpFileTest, MultipleWrites) {
}
TEST_F(HttpFileTest, MultipleChunks) {
FilePtr file(new HttpFile(HttpMethod::kPut, kTestServerReflect,
FilePtr file(new HttpFile(HttpMethod::kPut, server_.ReflectUrl(),
kBinaryContentType, kNoHeaders,
kDefaultTestTimeout));
ASSERT_TRUE(file);
@ -286,8 +273,8 @@ TEST_F(HttpFileTest, MultipleChunks) {
}
TEST_F(HttpFileTest, Error404) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServer404, kNoContentType,
kNoHeaders, kDefaultTestTimeout));
FilePtr file(new HttpFile(HttpMethod::kGet, server_.StatusCodeUrl(404),
kNoContentType, kNoHeaders, kDefaultTestTimeout));
ASSERT_TRUE(file);
ASSERT_TRUE(file->Open());
@ -301,7 +288,7 @@ TEST_F(HttpFileTest, Error404) {
}
TEST_F(HttpFileTest, TimeoutTriggered) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerLongDelay,
FilePtr file(new HttpFile(HttpMethod::kGet, server_.DelayUrl(8),
kNoContentType, kNoHeaders,
1 /* timeout in seconds */));
ASSERT_TRUE(file);
@ -317,7 +304,7 @@ TEST_F(HttpFileTest, TimeoutTriggered) {
}
TEST_F(HttpFileTest, TimeoutNotTriggered) {
FilePtr file(new HttpFile(HttpMethod::kGet, kTestServerShortDelay,
FilePtr file(new HttpFile(HttpMethod::kGet, server_.DelayUrl(1),
kNoContentType, kNoHeaders,
5 /* timeout in seconds */));
ASSERT_TRUE(file);

View File

@ -12,18 +12,6 @@
#include "packager/media/test/test_web_server.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 media {
@ -35,37 +23,37 @@ namespace media {
// enough.
class HttpKeyFetcherTest : public testing::Test {
protected:
void SetUp() override { ASSERT_TRUE(server_.Start(kTestServerPort)); }
void SetUp() override { ASSERT_TRUE(server_.Start()); }
private:
TestWebServer server_;
};
TEST_F(HttpKeyFetcherTest, HttpGet) {
HttpKeyFetcher fetcher;
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\""));
}
TEST_F(HttpKeyFetcherTest, HttpPost) {
HttpKeyFetcher fetcher;
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\""));
}
TEST_F(HttpKeyFetcherTest, HttpFetchKeys) {
HttpKeyFetcher fetcher;
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\""));
}
TEST_F(HttpKeyFetcherTest, InvalidUrl) {
HttpKeyFetcher fetcher;
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_NE(std::string::npos, status.error_message().find("404"));
}
@ -74,7 +62,7 @@ TEST_F(HttpKeyFetcherTest, SmallTimeout) {
const int32_t kTimeoutInSeconds = 1;
HttpKeyFetcher fetcher(kTimeoutInSeconds);
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());
}
@ -82,7 +70,7 @@ TEST_F(HttpKeyFetcherTest, BigTimeout) {
const int32_t kTimeoutInSeconds = 5;
HttpKeyFetcher fetcher(kTimeoutInSeconds);
std::string response;
Status status = fetcher.FetchKeys(kTestUrlDelayTwoSecs, "", &response);
Status status = fetcher.FetchKeys(server_.DelayUrl(2), "", &response);
EXPECT_OK(status);
}

View File

@ -7,6 +7,7 @@
#include "packager/media/test/test_web_server.h"
#include <chrono>
#include <random>
#include <string_view>
#include "absl/strings/numbers.h"
@ -23,6 +24,12 @@
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.
std::string_view MongooseStringView(const mg_str& mg_string) {
return std::string_view(mg_string.ptr, mg_string.len);
@ -82,8 +89,8 @@ TestWebServer::~TestWebServer() {
thread_.reset();
}
bool TestWebServer::Start(int port) {
thread_.reset(new std::thread(&TestWebServer::ThreadCallback, this, port));
bool TestWebServer::Start() {
thread_.reset(new std::thread(&TestWebServer::ThreadCallback, this));
absl::MutexLock lock(&mutex_);
while (status_ == kNew) {
@ -93,12 +100,19 @@ bool TestWebServer::Start(int port) {
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.
// "127.0.0.1" is "localhost", and is not visible to other machines on the
// 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
// scope.
std::unique_ptr<struct mg_mgr, decltype(&mg_mgr_free)> manager(
@ -106,20 +120,29 @@ void TestWebServer::ThreadCallback(int port) {
// Then initialize it.
mg_mgr_init(manager.get());
auto connection =
mg_http_listen(manager.get(), http_address.c_str(),
&TestWebServer::HandleEvent, this /* callback_data */);
if (connection == NULL) {
// Failed to listen to the requested port. Mongoose has already printed an
// error message.
// Prepare to choose a random port.
std::random_device r;
std::default_random_engine e1(r());
std::uniform_int_distribution<int> uniform_dist(kMinPortNumber,
kMaxPortNumber);
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_);
if (!ok) {
// Failed to find a port to listen on. Mongoose has already printed an
// error message.
status_ = kFailed;
started_.Signal();
return;
}
{
absl::MutexLock lock(&mutex_);
status_ = kStarted;
started_.Signal();
}

View File

@ -17,6 +17,7 @@
// Forward declare mongoose struct types, used as pointers below.
struct mg_connection;
struct mg_http_message;
struct mg_mgr;
namespace shaka {
namespace media {
@ -26,7 +27,20 @@ class 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:
enum TestWebServerStatus {
@ -49,7 +63,12 @@ class TestWebServer {
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,
int event,