/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helpers.hpp" #include "countloolkits.hpp" using namespace helpers; /// Tests the HTTP WebSocket API of loolwsd. The server has to be started manually before running this test. class HTTPCrashTest : public CPPUNIT_NS::TestFixture { const Poco::URI _uri; Poco::Net::HTTPResponse _response; static int InitialLoolKitCount; CPPUNIT_TEST_SUITE(HTTPCrashTest); CPPUNIT_TEST(testBarren); CPPUNIT_TEST(testCrashKit); CPPUNIT_TEST_SUITE_END(); void testCountHowManyLoolkits(); void testBarren(); void testCrashKit(); void testNoExtraLoolKitsLeft(); static void killLoKitProcesses(); public: HTTPCrashTest() : _uri(helpers::getTestServerURI()) { #if ENABLE_SSL Poco::Net::initializeSSL(); // Just accept the certificate anyway for testing purposes Poco::SharedPtr invalidCertHandler = new Poco::Net::AcceptCertificateHandler(false); Poco::Net::Context::Params sslParams; Poco::Net::Context::Ptr sslContext = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, sslParams); Poco::Net::SSLManager::instance().initializeClient(0, invalidCertHandler, sslContext); #endif } #if ENABLE_SSL ~HTTPCrashTest() { Poco::Net::uninitializeSSL(); } #endif void setUp() { testCountHowManyLoolkits(); } void tearDown() { testNoExtraLoolKitsLeft(); } }; int HTTPCrashTest::InitialLoolKitCount = 1; void HTTPCrashTest::testCountHowManyLoolkits() { InitialLoolKitCount = countLoolKitProcesses(InitialLoolKitCount); CPPUNIT_ASSERT(InitialLoolKitCount > 0); } void HTTPCrashTest::testNoExtraLoolKitsLeft() { const auto countNow = countLoolKitProcesses(InitialLoolKitCount); CPPUNIT_ASSERT_EQUAL(InitialLoolKitCount, countNow); } void HTTPCrashTest::testBarren() { // Kill all kit processes and try loading a document. try { killLoKitProcesses(); countLoolKitProcesses(0); std::cerr << "Loading after kill." << std::endl; // Load a document and get its status. std::string documentPath, documentURL; getDocumentPathAndURL("hello.odt", documentPath, documentURL); Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL); auto socket = connectLOKit(_uri, request, _response); // First load should fail. sendTextFrame(socket, "load url=" + documentURL); SocketProcessor("Barren ", socket, [&](const std::string& msg) { const std::string prefix = "status: "; if (msg.find(prefix) == 0) { const auto status = msg.substr(prefix.length()); CPPUNIT_ASSERT_EQUAL(std::string("type=text parts=1 current=0 width=12808 height=16408 viewid=0"), status); return false; } else if (msg.find("Service") == 0) { // Service unavailable. Try again. auto socket2 = loadDocAndGetSocket(_uri, documentURL); sendTextFrame(socket2, "status"); const auto status = getResponseString(socket2, "status"); CPPUNIT_ASSERT_EQUAL(std::string("type=text parts=1 current=0 width=12808 height=16408"), status); return false; } return true; }); } catch (const Poco::Exception& exc) { CPPUNIT_FAIL(exc.displayText()); } } void HTTPCrashTest::testCrashKit() { const auto testname = "crashKit "; try { auto socket = *loadDocAndGetSocket("empty.odt", _uri, testname); killLoKitProcesses(); // We expect the client connection to close. // In the future we might restore the kit, but currently we don't. std::cerr << "Reading after kill." << std::endl; // Drain the socket. getResponseMessage(socket, "", testname, 1000); // 5 seconds timeout socket.setReceiveTimeout(5000000); // receive close frame handshake int bytes; int flags; char buffer[READ_BUFFER_SIZE]; do { bytes = socket.receiveFrame(buffer, sizeof(buffer), flags); std::cerr << testname << "Got " << bytes << " bytes: " << LOOLProtocol::getAbbreviatedMessage(buffer, bytes) << std::endl; } while ((flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE); // respond close frame socket.shutdown(); // no more messages is received. bytes = socket.receiveFrame(buffer, sizeof(buffer), flags); CPPUNIT_ASSERT_MESSAGE("Expected no more data", bytes <= 2); // The 2-byte marker is ok. CPPUNIT_ASSERT_EQUAL(0x88, flags); } catch (const Poco::Exception& exc) { CPPUNIT_FAIL(exc.displayText()); } } void HTTPCrashTest::killLoKitProcesses() { // Crash all lokit processes. for (auto it = Poco::DirectoryIterator(std::string("/proc")); it != Poco::DirectoryIterator(); ++it) { try { Poco::Path procEntry = it.path(); const std::string& fileName = procEntry.getFileName(); int pid; std::size_t endPos = 0; try { pid = std::stoi(fileName, &endPos); } catch (const std::invalid_argument&) { pid = 0; } if (pid > 1 && endPos == fileName.length()) { Poco::FileInputStream stat(procEntry.toString() + "/stat"); std::string statString; Poco::StreamCopier::copyToString(stat, statString); Poco::StringTokenizer tokens(statString, " "); if (tokens.count() > 3 && tokens[1] == "(loolkit)") { std::cerr << "Killing " << pid << std::endl; if (kill(pid, SIGKILL) == -1) { std::cerr << "kill(" << pid << ",SIGKILL) failed: " << std::strerror(errno) << std::endl; } } } } catch (const Poco::Exception&) { } } countLoolKitProcesses(0); } CPPUNIT_TEST_SUITE_REGISTRATION(HTTPCrashTest); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */