/// /// 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 /// #include #include #include #include #include #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(this) = t; free(t); } ~TempDir() { fs::remove_all(*this); } }; class MockLaminar : public LaminarInterface { public: LaminarClient* client = nullptr; ~MockLaminar() {} 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; } // MOCK_METHOD does not seem to work with return values whose destructors have noexcept(false) kj::Own getArtefact(std::string path) override { return kj::Own(nullptr, kj::NullDisposer()); } MOCK_METHOD2(queueJob, std::shared_ptr(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_METHOD0(getCustomCss, std::string()); MOCK_METHOD0(abortAll, void()); MOCK_METHOD0(notifyConfigChanged, void()); }; 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(); } kj::WaitScope& ws() const { return server->ioContext.waitScope; } void waitForHttpReady() { server->httpReady.promise.wait(server->ioContext.waitScope); } kj::Network& network() { return server->ioContext.provider->getNetwork(); } TempDir tempDir; MockLaminar mockLaminar; Server* server; }; TEST_F(ServerTest, RpcQueue) { auto req = client().queueRequest(); req.setJobName("foo"); EXPECT_CALL(mockLaminar, queueJob("foo", ParamMap())).Times(testing::Exactly(1)); req.send().wait(ws()); } // 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&& addr){ return addr->connect().attach(kj::mv(addr)).then([this](kj::Own&& stream){ kj::AsyncIoStream* s = stream.get(); return s->write(WS, strlen(WS)).then([this,s](){ // Read the websocket header response, ensure the client has been registered return s->tryRead(buffer, 64, 256).then([this,s](size_t sz){ 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; s->setsockopt(SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); return kj::Promise(kj::READY_NOW); }); }).attach(kj::mv(stream)); }); }).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); }