Skip to content
This repository has been archived by the owner on Aug 19, 2019. It is now read-only.

Add unit tests for environment with fake HTTP server. #139

Merged
merged 7 commits into from
Jul 17, 2018

Conversation

davidbtucker
Copy link
Contributor

No description provided.

@@ -83,7 +80,7 @@ Environment::Environment(const Configuration& config)
std::string Environment::GetMetadataString(const std::string& path) const {
http::client::options options;
http::client client(options.timeout(2));
http::client::request request(kGceMetadataServerAddress + path);
http::client::request request(config_.GceMetadataServerAddress() + path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not make this a configuration parameter. There is no reason for anything but our tests to override this. You could instead make this an instance field of the Environment object, and either add a private constructor that injects it, or a private test-only setter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to not making this a config parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) {
// Start a server in a separate thread, and allow it to choose an
// available port.
FakeHandler handler(std::map<std::string, std::string>({{"/a/b/c", "hello"}}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nicer to follow the style of Python testing and add a SetResponse(path, response) method (maybe even SetResponse(path, code, response))... Then you can fold the handler and the options object into the FakeServer implementation, rather than expose them to clients.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (did not include code yet).

FakeServer::options options(handler);
FakeServer server(options.address("127.0.0.1").port(""));
server.listen();
std::thread t1([&server] { server.run(); });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your fake server is always single-threaded, so you can fold the thread into the server object, regardless (calling join() in the destructor).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"{\"client_email\":\"[email protected]\",\"private_key\":\"some_key\"}");
std::string cfg;
Configuration config(std::istringstream(
"CredentialsFile: '" + credentials_file.FullPath().native() + "'\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary for testing a fake server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, it's not! Removed.

std::string cfg;
Configuration config(std::istringstream(
"CredentialsFile: '" + credentials_file.FullPath().native() + "'\n"
"GceMetadataServerAddress: 'http://invalidaddress'\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fragile, and breaks in environments where an ISP intercepts invalid lookups:

environment_unittest.cc:171: Failure
Expected equality of these values:
  ""
  environment.GetMetadataString("/a/b/c")
    Which is: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><meta http-equiv=\"refresh\" content=\"0;url=http://searchassist.verizon.com/main?ParticipantID=euekiz39ksg8nwp7iqj2fp5wzfwi5q76&FailedURI=http%3A%2F%2Finvalidaddress%2Fa%2Fb%2Fc&FailureMode=1&Implementation=&AddInType=4&Version=pywr1.0&ClientLocation=us\"/><script type=\"text/javascript\">url=\"http://searchassist.verizon.com/main?ParticipantID=euekiz39ksg8nwp7iqj2fp5wzfwi5q76&FailedURI=http%3A%2F%2Finvalidaddress%2Fa%2Fb%2Fc&FailureMode=1&Implementation=&AddInType=4&Version=pywr1.0&ClientLocation=us\";if(top.location!=location){var w=window,d=document,e=d.documentElement,b=d.body,x=w.innerWidth||e.clientWidth||b.clientWidth,y=w.innerHeight||e.clientHeight||b.clientHeight;url+=\"&w=\"+x+\"&h=\"+y;}window.location.replace(url);</script></head><body></body></html>"

We should instead make the fake server throw boost::system::system_error on simulated resolution failures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this test altogether, as the other test already exercises this catch clause.

Copy link
Contributor Author

@davidbtucker davidbtucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks PTAL

@@ -83,7 +80,7 @@ Environment::Environment(const Configuration& config)
std::string Environment::GetMetadataString(const std::string& path) const {
http::client::options options;
http::client client(options.timeout(2));
http::client::request request(kGceMetadataServerAddress + path);
http::client::request request(config_.GceMetadataServerAddress() + path);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) {
// Start a server in a separate thread, and allow it to choose an
// available port.
FakeHandler handler(std::map<std::string, std::string>({{"/a/b/c", "hello"}}));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (did not include code yet).

FakeServer::options options(handler);
FakeServer server(options.address("127.0.0.1").port(""));
server.listen();
std::thread t1([&server] { server.run(); });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"{\"client_email\":\"[email protected]\",\"private_key\":\"some_key\"}");
std::string cfg;
Configuration config(std::istringstream(
"CredentialsFile: '" + credentials_file.FullPath().native() + "'\n"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, it's not! Removed.

std::string cfg;
Configuration config(std::istringstream(
"CredentialsFile: '" + credentials_file.FullPath().native() + "'\n"
"GceMetadataServerAddress: 'http://invalidaddress'\n"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this test altogether, as the other test already exercises this catch clause.

Copy link
Contributor

@supriyagarg supriyagarg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@igorpeshansky igorpeshansky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some naming/location nits.

} else {
// Note: We have to set headers; otherwise, an exception is thrown.
connection->set_status(FakeServer::connection::not_found);
connection->set_headers(std::map<std::string, std::string>({}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that this bit is necessary, but we can deal with this later.

@@ -78,12 +78,14 @@ constexpr const char kGceInstanceResourceType[] = "gce_instance";
}

Environment::Environment(const Configuration& config)
: config_(config), application_default_credentials_read_(false) {}
: config_(config),
gce_metadata_server_address_(kGceMetadataServerAddress),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just call it metadata_server_url_.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const Configuration& config_;

std::string gce_metadata_server_address_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const Configuration& config_;

std::string gce_metadata_server_address_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically cached data as well. Let's put this just before application_default_credentials_read_.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -48,8 +48,13 @@ class Environment {

void ReadApplicationDefaultCredentials() const;

// Used to override server address in tests.
void SetGceMetadataServerAddress(const std::string& address);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetMetadataServerUrlForTest, and you can get rid of the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

class FakeServerThread {
public:
FakeServerThread()
: server_(Server::options(handler_).address("127.0.0.1").port("")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "empty port" behavior seems to be undocumented. Sigh.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged.

handler_.path_responses[path] = response;
}

std::string HostPort() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why HostPort and not GetUrl?

Copy link
Contributor Author

@davidbtucker davidbtucker May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to GetUrl.

connection->set_headers(std::map<std::string, std::string>());
}
}
std::map<std::string, std::string> path_responses;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this map live in the FakeServer class instead?

Copy link
Contributor Author

@davidbtucker davidbtucker May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd have to pass in a FakeServer reference to the Handler, which seems more complicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also just pass a const reference to the map... But it's also fine the way it is.


Handler handler_;
Server server_;
std::thread thread_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server_thread_?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Starts a server in a separate thread, allowing it to choose an
// available port.
class FakeServerThread {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pull this out to a separate header/.cc file now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

@davidbtucker davidbtucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL, thanks!

@@ -78,12 +78,14 @@ constexpr const char kGceInstanceResourceType[] = "gce_instance";
}

Environment::Environment(const Configuration& config)
: config_(config), application_default_credentials_read_(false) {}
: config_(config),
gce_metadata_server_address_(kGceMetadataServerAddress),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -48,8 +48,13 @@ class Environment {

void ReadApplicationDefaultCredentials() const;

// Used to override server address in tests.
void SetGceMetadataServerAddress(const std::string& address);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const Configuration& config_;

std::string gce_metadata_server_address_;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const Configuration& config_;

std::string gce_metadata_server_address_;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -12,9 +13,15 @@ class EnvironmentTest : public ::testing::Test {
static void ReadApplicationDefaultCredentials(const Environment& environment) {
environment.ReadApplicationDefaultCredentials();
}

static void SetGceMetadataServerAddress(Environment* environment,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


Handler handler_;
Server server_;
std::thread thread_;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Starts a server in a separate thread, allowing it to choose an
// available port.
class FakeServerThread {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Starts a server in a separate thread, allowing it to choose an
// available port.
class FakeServerThread {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

connection->set_headers(std::map<std::string, std::string>());
}
}
std::map<std::string, std::string> path_responses;
Copy link
Contributor Author

@davidbtucker davidbtucker May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd have to pass in a FakeServer reference to the Handler, which seems more complicated.

@@ -48,8 +48,13 @@ class Environment {

void ReadApplicationDefaultCredentials() const;

// Used to override server address in tests.
void SetGceMetadataServerAddress(const std::string& address);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

public:
FakeServerThread()
: server_(Server::options(handler_).address("127.0.0.1").port("")) {
server_.listen();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, can you please add a comment saying exactly that?

connection->set_headers(std::map<std::string, std::string>());
}
}
std::map<std::string, std::string> path_responses;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also just pass a const reference to the map... But it's also fine the way it is.

@@ -48,6 +48,10 @@ class Environment {

void ReadApplicationDefaultCredentials() const;

void SetMetadataServerUrlForTest(const std::string& url) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add a comment saying that the url must end in a '/'...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -98,4 +105,17 @@ TEST_F(EnvironmentTest, ReadApplicationDefaultCredentialsCaches) {
);
EXPECT_EQ("some_key", environment.CredentialsPrivateKey());
}

TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this one before... I'd just call it GetMetadataStringFakeServer is an implementation detail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

namespace google {
namespace testing {

FakeServer::FakeServer()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a comment along the lines of "an empty port selects a random available port (undocumented)"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a class comment — let's move it to where the empty port is passed in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

std::string FakeServer::GetUrl() {
return std::string("http://") + server_.address() + ":" + server_.port() + "/";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Optional]

  network::uri_builder builder;
  builder.scheme("http").host(server_.address()).port(server_.port()).path("/");
  return builder.uri().to_string();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

handler_.path_responses[path] = response;
}

void FakeServer::Handler::operator() (Server::request const &request,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No space after operator().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Handler that maps paths to response strings.
struct Handler {
void operator() (Server::request const &request,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No space after operator().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@igorpeshansky igorpeshansky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One remaining comment.

namespace google {
namespace testing {

FakeServer::FakeServer()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a class comment — let's move it to where the empty port is passed in.

@@ -48,6 +48,11 @@ class Environment {

void ReadApplicationDefaultCredentials() const;

// The url must end in a '/'.
void SetMetadataServerUrlForTest(const std::string& url) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only "ForTest"? It seems like a reasonable enough setter function to remove that suffix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make it clear that the method only exists because we need to override the URL in test code. If we ever need to use it in production code, we can rename the method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

Copy link
Contributor

@bmoyles0117 bmoyles0117 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@igorpeshansky igorpeshansky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :shipit:

@davidbtucker davidbtucker merged commit 8ec2e47 into master Jul 17, 2018
@davidbtucker davidbtucker deleted the davidbtucker-http-mocks branch July 17, 2018 20:34
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants