2018-01-27 10:59:39 +00:00
|
|
|
///
|
|
|
|
/// Copyright 2018 Oliver Giles
|
|
|
|
///
|
|
|
|
/// This file is part of Laminar
|
|
|
|
///
|
|
|
|
/// Laminar is free software: you can redistribute it and/or modify
|
|
|
|
/// it under the terms of the GNU General Public License as published by
|
|
|
|
/// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
/// (at your option) any later version.
|
|
|
|
///
|
|
|
|
/// Laminar is distributed in the hope that it will be useful,
|
|
|
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
/// GNU General Public License for more details.
|
|
|
|
///
|
|
|
|
/// You should have received a copy of the GNU General Public License
|
|
|
|
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
///
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <thread>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include "server.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "interface.h"
|
|
|
|
#include "laminar.capnp.h"
|
|
|
|
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
|
|
|
class TempDir : public fs::path {
|
|
|
|
public:
|
|
|
|
TempDir(const char* tmpl) {
|
|
|
|
char* t = strdup(tmpl);
|
|
|
|
mkdtemp(t);
|
|
|
|
*static_cast<fs::path*>(this) = t;
|
|
|
|
free(t);
|
|
|
|
}
|
|
|
|
~TempDir() {
|
|
|
|
fs::remove_all(*this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class MockLaminar : public LaminarInterface {
|
|
|
|
public:
|
2018-01-27 11:11:40 +00:00
|
|
|
LaminarClient* client = nullptr;
|
|
|
|
virtual void registerClient(LaminarClient* c) override {
|
|
|
|
ASSERT_EQ(nullptr, client);
|
|
|
|
client = c;
|
|
|
|
EXPECT_CALL(*this, sendStatus(client)).Times(testing::Exactly(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void deregisterClient(LaminarClient* c) override {
|
|
|
|
ASSERT_EQ(client, c);
|
|
|
|
client = nullptr;
|
|
|
|
}
|
|
|
|
|
2018-01-27 10:59:39 +00:00
|
|
|
MOCK_METHOD2(queueJob, std::shared_ptr<Run>(std::string name, ParamMap params));
|
|
|
|
MOCK_METHOD1(registerWaiter, void(LaminarWaiter* waiter));
|
|
|
|
MOCK_METHOD1(deregisterWaiter, void(LaminarWaiter* waiter));
|
|
|
|
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
|
|
|
|
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
|
|
|
|
MOCK_METHOD2(getArtefact, bool(std::string path, std::string& result));
|
|
|
|
MOCK_METHOD0(getCustomCss, std::string());
|
|
|
|
};
|
|
|
|
|
|
|
|
class ServerTest : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
ServerTest() :
|
|
|
|
tempDir("/tmp/laminar-test-XXXXXX")
|
|
|
|
{
|
|
|
|
}
|
|
|
|
void SetUp() override {
|
|
|
|
EXPECT_CALL(mockLaminar, registerWaiter(testing::_));
|
|
|
|
EXPECT_CALL(mockLaminar, deregisterWaiter(testing::_));
|
|
|
|
server = new Server(mockLaminar, "unix:"+fs::path(tempDir/"rpc.sock").string(), "127.0.0.1:8080");
|
|
|
|
}
|
|
|
|
void TearDown() override {
|
|
|
|
delete server;
|
|
|
|
}
|
|
|
|
|
|
|
|
LaminarCi::Client client() const {
|
|
|
|
return server->rpcInterface.castAs<LaminarCi>();
|
|
|
|
}
|
|
|
|
kj::WaitScope& ws() const {
|
|
|
|
return server->ioContext.waitScope;
|
|
|
|
}
|
2018-01-27 11:11:40 +00:00
|
|
|
void waitForHttpReady() {
|
|
|
|
server->httpReady.promise.wait(server->ioContext.waitScope);
|
|
|
|
}
|
|
|
|
|
|
|
|
kj::Network& network() { return server->ioContext.provider->getNetwork(); }
|
2018-01-27 10:59:39 +00:00
|
|
|
TempDir tempDir;
|
|
|
|
MockLaminar mockLaminar;
|
|
|
|
Server* server;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(ServerTest, RpcTrigger) {
|
|
|
|
auto req = client().triggerRequest();
|
|
|
|
req.setJobName("foo");
|
|
|
|
EXPECT_CALL(mockLaminar, queueJob("foo", ParamMap())).Times(testing::Exactly(1));
|
|
|
|
req.send().wait(ws());
|
|
|
|
}
|
2018-01-27 11:11:40 +00:00
|
|
|
|
|
|
|
// Tests that agressively closed websockets are properly removed
|
|
|
|
// and will not be attempted to be contacted again
|
|
|
|
TEST_F(ServerTest, HttpWebsocketRST) {
|
|
|
|
waitForHttpReady();
|
|
|
|
|
|
|
|
// TODO: generalize
|
|
|
|
constexpr const char* WS =
|
|
|
|
"GET / HTTP/1.1\r\n"
|
|
|
|
"Host: localhost:8080\r\n"
|
|
|
|
"Connection: Upgrade\r\n"
|
|
|
|
"Upgrade: websocket\r\n"
|
|
|
|
"Sec-WebSocket-Key: GTFmrUCM9N6B32LdDE3Rzw==\r\n"
|
|
|
|
"Sec-WebSocket-Version: 13\r\n\r\n";
|
|
|
|
|
|
|
|
static char buffer[256];
|
|
|
|
network().parseAddress("localhost:8080").then([this](kj::Own<kj::NetworkAddress>&& addr){
|
|
|
|
return addr->connect().attach(kj::mv(addr)).then([this](kj::Own<kj::AsyncIoStream>&& stream){
|
2018-01-27 17:13:53 +00:00
|
|
|
kj::AsyncIoStream* s = stream.get();
|
|
|
|
return s->write(WS, strlen(WS)).then([this,s](){
|
2018-01-27 11:11:40 +00:00
|
|
|
// Read the websocket header response, ensure the client has been registered
|
2018-01-27 17:13:53 +00:00
|
|
|
return s->tryRead(buffer, 64, 256).then([this,s](size_t sz){
|
2018-01-27 11:11:40 +00:00
|
|
|
EXPECT_LE(64, sz);
|
|
|
|
EXPECT_NE(nullptr, mockLaminar.client);
|
|
|
|
// agressively abort the connection
|
|
|
|
struct linger so_linger;
|
|
|
|
so_linger.l_onoff = 1;
|
|
|
|
so_linger.l_linger = 0;
|
2018-01-27 17:13:53 +00:00
|
|
|
s->setsockopt(SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
|
2018-01-27 11:11:40 +00:00
|
|
|
return kj::Promise<void>(kj::READY_NOW);
|
2018-01-27 17:13:53 +00:00
|
|
|
});
|
|
|
|
}).attach(kj::mv(stream));
|
2018-01-27 11:11:40 +00:00
|
|
|
});
|
|
|
|
}).wait(ws());
|
|
|
|
ws().poll();
|
|
|
|
// Expect that the client has been cleared. If it has not, Laminar could
|
|
|
|
// try to write to the closed file descriptor, causing an exception
|
|
|
|
EXPECT_EQ(nullptr, mockLaminar.client);
|
|
|
|
}
|