/* * Copyright 2020 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Kyle Ambroff-Kao, kyle@ambroffkao.com */ #include "TestServer.h" #include #include #include #include #include #include #include #include #include #include #include namespace { template std::string to_string(T value) { std::ostringstream s; s << value; return s.str(); } void exec(const std::vector& args) { const char** argv = new const char*[args.size() + 1]; ArrayDeleter _(argv); for (size_t i = 0; i < args.size(); ++i) { argv[i] = args[i].c_str(); } argv[args.size()] = NULL; execv(args[0].c_str(), const_cast(argv)); } // Return the path of a file path relative to this source file. std::string TestFilePath(const std::string& relativePath) { char *testFileSource = strdup(__FILE__); MemoryDeleter _(testFileSource); std::string testSrcDir(::dirname(testFileSource)); return testSrcDir + "/" + relativePath; } } RandomTCPServerPort::RandomTCPServerPort() : fInitStatus(B_NOT_INITIALIZED), fSocketFd(-1), fServerPort(0) { // Create socket with port 0 to get an unused one selected by the // kernel. int socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { fprintf( stderr, "ERROR: Unable to create socket: %s\n", strerror(errno)); fInitStatus = B_ERROR; return; } fSocketFd = socket_fd; // We may quickly reclaim the same socket between test runs, so allow // for reuse. { int reuse = 1; int result = ::setsockopt( socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); if (result == -1) { fInitStatus = errno; fprintf( stderr, "ERROR: Unable to set socket options on fd %d: %s\n", socket_fd, strerror(fInitStatus)); return; } } // Bind to loopback 127.0.0.1 struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); int bind_result = ::bind( socket_fd, reinterpret_cast(&server_address), sizeof(server_address)); if (bind_result == -1) { fInitStatus = errno; fprintf( stderr, "ERROR: Unable to bind to loopback interface: %s\n", strerror(fInitStatus)); return; } // Listen is apparently required before getsockname will work. if (::listen(socket_fd, 32) == -1) { fInitStatus = errno; fprintf(stderr, "ERROR: listen() failed: %s\n", strerror(fInitStatus)); return; } // Now get the port from the socket. socklen_t server_address_length = sizeof(server_address); ::getsockname( socket_fd, reinterpret_cast(&server_address), &server_address_length); fServerPort = ntohs(server_address.sin_port); fInitStatus = B_OK; } RandomTCPServerPort::~RandomTCPServerPort() { if (fSocketFd != -1) { ::close(fSocketFd); fSocketFd = -1; fInitStatus = B_NOT_INITIALIZED; } } status_t RandomTCPServerPort::InitCheck() const { return fInitStatus; } int RandomTCPServerPort::FileDescriptor() const { return fSocketFd; } uint16_t RandomTCPServerPort::Port() const { return fServerPort; } ChildProcess::ChildProcess() : fChildPid(-1) { } ChildProcess::~ChildProcess() { if (fChildPid != -1) { ::kill(fChildPid, SIGTERM); pid_t result = -1; while (result != fChildPid) { result = ::waitpid(fChildPid, NULL, 0); } } } // The job of this method is to spawn a child process that will later be killed // by the destructor. status_t ChildProcess::Start(const std::vector& args) { if (fChildPid != -1) { return B_ALREADY_RUNNING; } pid_t child = ::fork(); if (child < 0) return B_ERROR; if (child > 0) { fChildPid = child; return B_OK; } // This is the child process. We can exec image provided in args. exec(args); // If we reach this point we failed to load the Python image. std::ostringstream ostr; for (std::vector::const_iterator iter = args.begin(); iter != args.end(); ++iter) { ostr << " " << *iter; } fprintf( stderr, "Unable to spawn `%s': %s\n", ostr.str().c_str(), strerror(errno)); exit(1); } TestServer::TestServer(TestServerMode mode) : fMode(mode) { } // Start a child testserver.py process with the random TCP port chosen by // fPort. status_t TestServer::Start() { if (fPort.InitCheck() != B_OK) { return fPort.InitCheck(); } // This is the child process. We can exec the server process. std::vector child_process_args; child_process_args.push_back("/bin/python3"); child_process_args.push_back(TestFilePath("testserver.py")); child_process_args.push_back("--port"); child_process_args.push_back(to_string(fPort.Port())); child_process_args.push_back("--fd"); child_process_args.push_back(to_string(fPort.FileDescriptor())); if (fMode == TEST_SERVER_MODE_HTTPS) { child_process_args.push_back("--use-tls"); } // After this the child process has started. It may take a short amount of // time before the child process is ready to call accept(), but that's OK. // // Since the socket has already been created above, the tests will not // get ECONNREFUSED and will block until the child process calls // accept(). So we don't have to busy loop here waiting for a // connection to the child. return fChildProcess.Start(child_process_args); } BUrl TestServer::BaseUrl() const { std::string scheme; switch(fMode) { case TEST_SERVER_MODE_HTTP: scheme = "http://"; break; case TEST_SERVER_MODE_HTTPS: scheme = "https://"; break; } std::string port_string = to_string(fPort.Port()); std::string baseUrl = scheme + "127.0.0.1:" + port_string + "/"; return BUrl(baseUrl.c_str()); } // Start a child proxy.py process using the random TCP port chosen by fPort. status_t TestProxyServer::Start() { if (fPort.InitCheck() != B_OK) { return fPort.InitCheck(); } std::vector child_process_args; child_process_args.push_back("/bin/python3"); child_process_args.push_back(TestFilePath("proxy.py")); child_process_args.push_back("--port"); child_process_args.push_back(to_string(fPort.Port())); child_process_args.push_back("--fd"); child_process_args.push_back(to_string(fPort.FileDescriptor())); // After this the child process has started. It may take a short amount of // time before the child process is ready to call accept(), but that's OK. // // Since the socket has already been created above, the tests will not // get ECONNREFUSED and will block until the child process calls // accept(). So we don't have to busy loop here waiting for a // connection to the child. return fChildProcess.Start(child_process_args); } uint16_t TestProxyServer::Port() const { return fPort.Port(); }