Permalink
Browse files

Proper unit tests for the application

  • Loading branch information...
1 parent 1b6a724 commit 6547d91ec865141d3297e475115efb4a636bfd12 @hluk committed Feb 7, 2013
Showing with 798 additions and 278 deletions.
  1. +23 −1 CMakeLists.txt
  2. +14 −2 src/app.cpp
  3. +4 −2 src/clipboardmonitor.cpp
  4. +1 −1 src/clipboardserver.cpp
  5. +37 −6 src/main.cpp
  6. +6 −1 src/scriptable.cpp
  7. +7 −0 src/src.pro
  8. +460 −0 src/tests/tests.cpp
  9. +73 −0 src/tests/tests.h
  10. +173 −265 tests/test.sh
View
24 CMakeLists.txt
@@ -2,6 +2,7 @@ project(copyq)
OPTION(WITH_WEBKIT "WebKit support" ON)
OPTION(WITH_QT5 "Qt5 support" OFF)
+OPTION(WITH_TESTS "Run test cases from command line" OFF)
if (WITH_QT5)
cmake_minimum_required(VERSION 2.8.8)
@@ -34,7 +35,6 @@ else()
endif(WITH_WEBKIT)
set(QT_USE_QTWEBKIT ${HAS_WEBKIT})
- include(${QT_USE_FILE})
set(USE_QXT TRUE)
endif()
@@ -144,6 +144,26 @@ endif(USE_QXT)
# Be more strict while compiling debugging version
if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wextra -Wall -pedantic")
+ set(WITH_TESTS ON)
+endif()
+
+# Compile with tests?
+if (WITH_TESTS)
+ message(STATUS "Building with tests.")
+ add_definitions( -DHAS_TESTS )
+
+ file(GLOB copyq_SOURCES ${copyq_SOURCES}
+ src/tests/*.cpp
+ )
+ file(GLOB copyq_MOCABLE ${copyq_MOCABLE}
+ src/tests/*.h
+ )
+
+ if (WITH_QT5)
+ set(copyq_Qt5_Modules ${copyq_Qt5_Modules} Test)
+ else()
+ set(QT_USE_QTTEST TRUE)
+ endif()
endif()
set(copyq_LIBRARIES ${copyq_LIBRARIES} ${X11_LIBRARIES} ${X11_Xfixes_LIB})
@@ -187,6 +207,8 @@ add_executable(copyq ${copyq_SOURCES}
if (WITH_QT5)
qt5_use_modules(copyq ${copyq_Qt5_Modules})
+else()
+ include(${QT_USE_FILE})
endif()
target_link_libraries(copyq ${QT_LIBRARIES} ${copyq_LIBRARIES})
View
16 src/app.cpp
@@ -24,6 +24,9 @@
#include <QLibraryInfo>
#include <QLocale>
#include <QTranslator>
+#ifdef HAS_TESTS
+# include <QProcessEnvironment>
+#endif
#ifdef Q_OS_UNIX
# include <QSocketNotifier>
@@ -54,8 +57,17 @@ App::App(int &argc, char **argv)
, m_exitCode(0)
, m_closed(false)
{
- QCoreApplication::setOrganizationName("copyq");
- QCoreApplication::setApplicationName("copyq");
+#ifdef HAS_TESTS
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ if ( env.value("COPYQ_TESTING") == "1" ) {
+ QCoreApplication::setOrganizationName("copyq-test");
+ QCoreApplication::setApplicationName("copyq-test");
+ } else
+#endif
+ {
+ QCoreApplication::setOrganizationName("copyq");
+ QCoreApplication::setApplicationName("copyq");
+ }
const QString locale = QLocale::system().name();
QTranslator *translator = new QTranslator(this);
View
6 src/clipboardmonitor.cpp
@@ -21,7 +21,6 @@
#include "client_server.h"
#include "clipboarditem.h"
-#include "clipboardserver.h"
#include <QMimeData>
#include <QTimer>
@@ -165,7 +164,10 @@ ClipboardMonitor::ClipboardMonitor(int &argc, char **argv)
this, SLOT(readyRead()), Qt::DirectConnection );
connect( m_socket, SIGNAL(disconnected()),
this, SLOT(quit()) );
- m_socket->connectToServer( ClipboardServer::monitorServerName() );
+
+ QStringList args = QCoreApplication::instance()->arguments();
+ Q_ASSERT(args.size() == 3);
+ m_socket->connectToServer(args.at(2));
if ( !m_socket->waitForConnected(2000) ) {
log( tr("Cannot connect to server!"), LogError );
exit(1);
View
2 src/clipboardserver.cpp
@@ -196,7 +196,7 @@ void ClipboardServer::startMonitoring()
connect( m_monitor, SIGNAL(readyReadStandardError()),
this, SLOT(monitorStandardError()) );
m_monitor->start( QApplication::arguments().at(0),
- QStringList() << "monitor",
+ QStringList("monitor") << monitorServerName(),
QProcess::ReadOnly );
if ( !m_monitor->waitForStarted(2000) ) {
log( tr("Cannot start clipboard monitor!"), LogError );
View
43 src/main.cpp
@@ -33,6 +33,17 @@
#include <windows.h>
#endif
+#ifdef HAS_TESTS
+# include "tests/tests.h"
+# include <QTest>
+
+namespace tests {
+
+QTEST_MAIN(Tests)
+
+}
+#endif // HAS_TESTS
+
Q_DECLARE_METATYPE(QByteArray*)
namespace {
@@ -86,6 +97,13 @@ int startClient(int argc, char *argv[])
return app.exec();
}
+#ifdef HAS_TESTS
+int startTests(int argc, char *argv[])
+{
+ return tests::main(argc, argv);
+}
+#endif
+
bool needsHelp(const char *arg)
{
return strcmp("-h",arg) == 0 ||
@@ -100,11 +118,19 @@ bool needsVersion(const char *arg)
strcmp("version",arg) == 0;
}
+#ifdef HAS_TESTS
+bool needsTests(const char *arg)
+{
+ return strcmp("--tests", arg) == 0 ||
+ strcmp("tests", arg) == 0;
+}
+#endif
+
} // namespace
int main(int argc, char *argv[])
{
- // print version
+ // print version, help or run tests
if (argc == 2 || argc == 3) {
const char *arg = argv[1];
if ( argc == 2 && needsVersion(arg) ) {
@@ -113,20 +139,25 @@ int main(int argc, char *argv[])
} else if ( needsHelp(arg) ) {
evaluate("help", argc == 3 ? argv[2] : NULL);
return 0;
+#ifdef HAS_TESTS
+ } else if ( needsTests(arg) ) {
+ // Skip the "tests" argument and pass the rest to tests.
+ return startTests(argc - 1, argv + 1);
+#endif
}
}
if (argc == 1) {
// if server hasn't been run yet and no argument were specified
- // then run this as server
+ // then run this process as server
return startServer(argc, argv);
- } else if (argc == 2 && strcmp(argv[1], "monitor") == 0) {
- // if argument specified and server is running
- // then run this as client
+ } else if (argc == 3 && strcmp(argv[1], "monitor") == 0) {
+ // if first argument is monitor (second is monitor server name/ID)
+ // then run this process as monitor
return startMonitor(argc, argv);
} else {
// if argument specified and server is running
- // then run this as client
+ // then run this process as client
return startClient(argc, argv);
}
}
View
7 src/scriptable.cpp
@@ -190,7 +190,12 @@ QList<CommandHelp> commandHelp()
Scriptable::tr("\nPrint help for COMMAND or all commands."))
.addArg("[" + Scriptable::tr("COMMAND") + "]")
<< CommandHelp("version, -v, --version",
- Scriptable::tr("\nPrint version of program and libraries."));
+ Scriptable::tr("\nPrint version of program and libraries."))
+#ifdef HAS_TESTS
+ << CommandHelp("tests, --tests",
+ Scriptable::tr("Run tests."))
+#endif
+ ;
}
QString helpHead()
View
7 src/src.pro
@@ -81,6 +81,13 @@ SOURCES += \
QT += core gui xml network script
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+debug {
+ DEFINES += HAS_TESTS
+ QT += testlib
+ SOURCES += tests/tests.cpp
+ HEADERS += tests/tests.h
+}
+
WITH_WEBKIT {
SOURCES += itemweb.cpp
HEADERS += include/itemweb.h
View
460 src/tests/tests.cpp
@@ -0,0 +1,460 @@
+/*
+ Copyright (c) 2013, Lukas Holecek <hluk@email.cz>
+
+ This file is part of CopyQ.
+
+ CopyQ 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.
+
+ CopyQ 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 CopyQ. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "tests.h"
+#include "client_server.h"
+#include "clipboarditem.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QProcess>
+#include <QTemporaryFile>
+#include <QTest>
+
+using QTest::qSleep;
+
+#define VERIFY_SERVER_OUTPUT() \
+do {\
+ QByteArray stdout = m_server->readAllStandardError(); \
+ QVERIFY2( !stdout.contains("warning") && !stdout.contains("ERROR"), stdout ); \
+} while (0)
+
+#define RUN(arguments, stdoutExpected) \
+do {\
+ QVERIFY( isServerRunning() ); \
+ QByteArray stdoutActual; \
+ QByteArray stderrActual; \
+ QCOMPARE( run(arguments, &stdoutActual, &stderrActual), 0 ); \
+ QCOMPARE( stdoutActual.data(), stdoutExpected ); \
+ QCOMPARE( stderrActual.data(), "" ); \
+ VERIFY_SERVER_OUTPUT(); \
+ QVERIFY( isServerRunning() ); \
+} while (0)
+
+namespace {
+
+/// Naming scheme for test tabs in application.
+const QString testTabs = "TEST_%1";
+
+/// Interval to wait (in ms) until an action is completed and items from stdout are created.
+const int waitMsAction = 200;
+
+/// Interval to wait (in ms) until new clipboard content is propagated to items or monitor.
+const int waitMsClipboard = 200;
+
+typedef QStringList Args;
+
+QByteArray getClipboard(const QString &mime = QString("text/plain"))
+{
+ QApplication::processEvents();
+ const QMimeData *data = QApplication::clipboard()->mimeData();
+ Q_ASSERT(data != NULL);
+ return data->data(mime);
+}
+
+int run(const Args &arguments = Args(), QByteArray *stdoutData = NULL, QByteArray *stderrData = NULL,
+ const QByteArray &in = QByteArray())
+{
+ QProcess p;
+ p.start( QApplication::applicationFilePath(), arguments );
+
+ p.write(in);
+ p.closeWriteChannel();
+
+ if ( !p.waitForFinished(100) ) {
+ // Process events in case we own clipboard and the new process requests the contens.
+ QApplication::processEvents();
+ if ( !p.waitForFinished(200) ) {
+ QApplication::processEvents();
+
+ if ( !p.waitForFinished(4000) ) {
+ p.terminate();
+ if ( !p.waitForFinished(1000) )
+ p.kill();
+
+ return -1;
+ }
+ }
+ }
+
+ if (stdoutData != NULL)
+ *stdoutData = p.readAllStandardOutput();
+ if (stderrData != NULL)
+ *stderrData = p.readAllStandardError();
+
+ return p.exitCode();
+}
+
+bool isAnyServerRunning()
+{
+ return run(Args("size")) == 0;
+}
+
+bool hasTab(const QString &tabName)
+{
+ QByteArray out;
+ run(Args("tab"), &out);
+ return out.split('\n').contains(tabName.toLatin1());
+}
+
+} // namespace
+
+Tests::Tests(QObject *parent)
+ : QObject(parent)
+ , m_server(NULL)
+ , m_monitor(NULL)
+ , m_monitorServer(NULL)
+ , m_monitorSocket(NULL)
+{
+}
+
+void Tests::initTestCase()
+{
+ if ( isAnyServerRunning() ) {
+ run(Args("exit"));
+
+ // Wait for client/server communication is closed.
+ int tries = 0;
+ while( !startServer() && ++tries <= 100 )
+ qSleep(100);
+
+ QVERIFY( isServerRunning() );
+ } else {
+ QVERIFY( startServer() );
+ }
+
+ cleanup();
+}
+
+void Tests::cleanupTestCase()
+{
+ if (m_server != NULL) {
+ QVERIFY( stopServer() );
+ if ( m_server->state() != QProcess::NotRunning ) {
+ m_server->terminate();
+ if ( !m_server->waitForFinished(1000) )
+ m_server->kill();
+ }
+ }
+
+ delete m_monitorServer;
+ if (m_monitor != NULL) {
+ if ( !m_monitor->waitForFinished(1000) ) {
+ m_monitor->terminate();
+ if ( !m_monitor->waitForFinished(1000) )
+ m_monitor->kill();
+ }
+ }
+}
+
+void Tests::init()
+{
+ QVERIFY( isAnyServerRunning() );
+ QVERIFY( isServerRunning() );
+ VERIFY_SERVER_OUTPUT();
+}
+
+void Tests::cleanup()
+{
+ // Remove test tabs
+ for (int i = 0; i < 10; ++i) {
+ QString tab = testTabs.arg(i);
+ if ( hasTab(tab.toLatin1()) )
+ RUN(Args("removetab") << tab, "");
+ }
+}
+
+void Tests::clipboardToItem()
+{
+ setClipboard("TEST1");
+ QCOMPARE( getClipboard().data(), "TEST1" );
+ RUN(Args("clipboard"), "TEST1");
+ RUN(Args("read") << "0", "TEST1");
+
+ setClipboard("TEST2");
+ QCOMPARE( getClipboard().data(), "TEST2" );
+ RUN(Args("clipboard"), "TEST2");
+ RUN(Args("read") << "0", "TEST2");
+}
+
+void Tests::itemToClipboard()
+{
+ RUN(Args("add") << "TESTING1" << "TESTING2", "");
+ RUN(Args("read") << "0", "TESTING2");
+ RUN(Args("read") << "1", "TESTING1");
+
+ qSleep(waitMsClipboard);
+ RUN(Args("clipboard"), "TESTING2");
+ QCOMPARE( getClipboard().data(), "TESTING2" );
+
+ RUN(Args("select") << "1", "");
+ RUN(Args("read") << "0", "TESTING1");
+ RUN(Args("read") << "1", "TESTING2");
+
+ qSleep(waitMsClipboard);
+ RUN(Args("clipboard"), "TESTING1");
+ QCOMPARE( getClipboard().data(), "TESTING1" );
+}
+
+void Tests::tabAddRemove()
+{
+ const Args args = Args("tab") << testTabs.arg(1);
+
+ RUN(args, "");
+ RUN(Args(args) << "size", "0\n");
+ RUN(Args(args) << "add" << "abc", "");
+ RUN(Args(args) << "add" << "def" << "ghi", "");
+ RUN(Args(args) << "size", "3\n");
+ RUN(Args(args) << "read" << "0", "ghi");
+ RUN(Args(args) << "read" << "1", "def");
+ RUN(Args(args) << "read" << "2", "abc");
+ RUN(Args(args) << "read" << "0" << "2" << "1", "ghi\nabc\ndef");
+
+ // Restart server.
+ QVERIFY( stopServer() );
+ QVERIFY( startServer() );
+
+ RUN(Args(args) << "size", "3\n");
+ RUN(Args(args) << "read" << "0" << "2" << "1", "ghi\nabc\ndef");
+}
+
+void Tests::action()
+{
+ const Args args = Args("tab") << testTabs.arg(1);
+ const Args argsAction = Args(args) << "action";
+ const QString action = QString("%1 %2 %3").arg(QApplication::applicationFilePath())
+ .arg(args.join(" "));
+
+ // action with size
+ RUN(Args(argsAction) << action.arg("size"), "");
+ qSleep(waitMsAction);
+ RUN(Args(args) << "size", "1\n");
+ RUN(Args(args) << "read" << "0", "0");
+
+ // action with size
+ RUN(Args(argsAction) << action.arg("size"), "");
+ qSleep(waitMsAction);
+ RUN(Args(args) << "size", "2\n");
+ RUN(Args(args) << "read" << "0", "1");
+
+ // action with eval print
+ RUN(Args(argsAction) << action.arg("eval 'print(\"A,B,C\")'"), "");
+ qSleep(waitMsAction);
+ RUN(Args(args) << "size", "3\n");
+ RUN(Args(args) << "read" << "0", "A,B,C");
+
+ // action with read and comma separator for new items
+ RUN(Args(argsAction) << action.arg("read") << ",", "");
+ qSleep(waitMsAction);
+ RUN(Args(args) << "size", "6\n");
+ RUN(Args(args) << "read" << "0", "C");
+ RUN(Args(args) << "read" << "1", "B");
+ RUN(Args(args) << "read" << "2", "A");
+}
+
+void Tests::insertRemoveItems()
+{
+ const Args args = Args("tab") << testTabs.arg(1);
+
+ RUN(Args(args) << "add" << "abc" << "ghi", "");
+ RUN(Args(args) << "read" << "0", "ghi");
+ RUN(Args(args) << "read" << "1", "abc");
+
+ RUN(Args(args) << "insert" << "1" << "def", "");
+ RUN(Args(args) << "read" << "0", "ghi");
+ RUN(Args(args) << "read" << "1", "def");
+ RUN(Args(args) << "read" << "2", "abc");
+
+ RUN(Args(args) << "insert" << "0" << "012", "");
+ RUN(Args(args) << "read" << "0", "012");
+ RUN(Args(args) << "read" << "1", "ghi");
+ RUN(Args(args) << "read" << "2", "def");
+ RUN(Args(args) << "read" << "3", "abc");
+
+ RUN(Args(args) << "remove" << "0" << "2", "");
+ RUN(Args(args) << "read" << "0", "ghi");
+ RUN(Args(args) << "read" << "1", "abc");
+
+ QByteArray in("ABC");
+ QCOMPARE( run(Args(args) << "insert" << "1" << "-", NULL, NULL, in), 0);
+ RUN(Args(args) << "read" << "0", "ghi");
+ RUN(Args(args) << "read" << "1", "ABC");
+ RUN(Args(args) << "read" << "2", "abc");
+
+ RUN(Args(args) << "read" << "3", "");
+}
+
+void Tests::renameTab()
+{
+ const QString tab1 = testTabs.arg(1);
+ const QString tab2 = testTabs.arg(2);
+ const QString tab3 = testTabs.arg(3);
+
+ RUN(Args("tab") << tab1 << "add" << "abc" << "def" << "ghi", "");
+ RUN(Args("tab") << tab1 << "size", "3\n");
+ RUN(Args("tab") << tab1 << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+
+ RUN(Args("renametab") << tab1 << tab2, "");
+ RUN(Args("tab") << tab2 << "size", "3\n");
+ RUN(Args("tab") << tab2 << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+ QVERIFY( !hasTab(tab1) );
+
+ QByteArray stderrData;
+ // Rename non-existing tab.
+ QCOMPARE( run(Args("renametab") << tab1 << tab2, NULL, &stderrData), 1 );
+ QVERIFY( !stderrData.isEmpty() );
+ // Rename to same name.
+ QCOMPARE( run(Args("renametab") << tab2 << tab2, NULL, &stderrData), 1 );
+ QVERIFY( !stderrData.isEmpty() );
+ // Rename to empty name.
+ QCOMPARE( run(Args("renametab") << tab2 << "", NULL, &stderrData), 1 );
+ QVERIFY( !stderrData.isEmpty() );
+ // Rename to existing tab.
+ RUN(Args("tab") << tab3 << "add" << "xxx", "");
+ QCOMPARE( run(Args("renametab") << tab2 << tab3, NULL, &stderrData), 1 );
+ QVERIFY( !stderrData.isEmpty() );
+
+ QVERIFY( !hasTab(tab1) );
+ QVERIFY( hasTab(tab2) );
+ QVERIFY( hasTab(tab3) );
+
+ RUN(Args("renametab") << tab2 << tab1, "");
+ RUN(Args("tab") << tab1 << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+
+ QVERIFY( hasTab(tab1) );
+ QVERIFY( !hasTab(tab2) );
+ QVERIFY( hasTab(tab3) );
+}
+
+void Tests::importExportTab()
+{
+ const QString tab = testTabs.arg(1);
+ const Args args = Args("tab") << tab;
+
+ RUN(Args(args) << "add" << "abc" << "def" << "ghi", "");
+ RUN(Args(args) << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+
+ QTemporaryFile tmp;
+ QVERIFY(tmp.open());
+ RUN(Args(args) << "exporttab" << tmp.fileName(), "");
+
+ RUN(Args("removetab") << tab, "");
+ QVERIFY( !hasTab(tab) );
+
+ RUN(Args(args) << "importtab" << tmp.fileName(), "");
+ RUN(Args(args) << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+}
+
+void Tests::separator()
+{
+ const QString tab = testTabs.arg(1);
+ const Args args = Args("tab") << tab;
+
+ RUN(Args(args) << "add" << "abc" << "def" << "ghi", "");
+ RUN(Args(args) << "read" << "0" << "1" << "2", "ghi\ndef\nabc");
+ RUN(Args(args) << "separator" << "," << "read" << "0" << "1" << "2", "ghi,def,abc");
+ RUN(Args(args) << "separator" << "---" << "read" << "0" << "1" << "2", "ghi---def---abc");
+}
+
+bool Tests::startServer()
+{
+ if (m_server != NULL)
+ m_server->deleteLater();
+ m_server = new QProcess(this);
+
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("COPYQ_TESTING", "1");
+ m_server->setProcessEnvironment(env);
+
+ m_server->start( QApplication::applicationFilePath(), QIODevice::ReadOnly );
+ m_server->waitForStarted();
+
+ if (m_server->state() != QProcess::Running)
+ return false;
+
+ // Wait for client/server communication is established.
+ int tries = 0;
+ while( !isServerRunning() && ++tries <= 50 )
+ qSleep(100);
+
+ return isServerRunning();
+}
+
+bool Tests::stopServer()
+{
+ if (m_server == NULL)
+ return !isServerRunning();
+
+ run(Args("exit"));
+ m_server->waitForFinished(5000);
+
+ return !isAnyServerRunning();
+}
+
+bool Tests::isServerRunning()
+{
+ return m_server != NULL && m_server->state() == QProcess::Running && ::run(Args("size")) == 0;
+}
+
+void Tests::startMonitorServer()
+{
+ if (m_monitor != NULL)
+ return;
+
+ const QString monitorServerName = "CopyQmonitorTest";
+ m_monitorServer = newServer(monitorServerName, m_monitor);
+
+ m_monitor = new QProcess(this);
+ m_monitor->start( QApplication::applicationFilePath(),
+ QStringList("monitor") << monitorServerName );
+ QVERIFY( m_monitor->waitForStarted(2000) );
+
+ QVERIFY( m_monitorServer->waitForNewConnection(2000) );
+ m_monitorSocket = m_monitorServer->nextPendingConnection();
+}
+
+void Tests::setClipboard(const QByteArray &bytes, const QString &mime)
+{
+ if (m_monitorServer == NULL)
+ startMonitorServer();
+
+ // Create item.
+ ClipboardItem item;
+ item.setData(mime, bytes);
+
+ // Send item.
+ QByteArray msg;
+ QDataStream out(&msg, QIODevice::WriteOnly);
+ out << item;
+
+ QVERIFY(m_monitorSocket != NULL);
+ QVERIFY(m_monitorSocket->isWritable());
+ writeMessage(m_monitorSocket, msg);
+ while ( m_monitorSocket->bytesToWrite() > 0 )
+ QVERIFY( m_monitorSocket->waitForBytesWritten() );
+
+ qSleep(waitMsClipboard);
+ QVERIFY(m_monitor->state() == QProcess::Running);
+ QByteArray stderrData = m_monitor->readAllStandardError();
+ QVERIFY2(stderrData.isEmpty(), stderrData);
+ QByteArray stdoutData = m_monitor->readAllStandardOutput();
+ QVERIFY2(stdoutData.isEmpty(), stdoutData);
+}
View
73 src/tests/tests.h
@@ -0,0 +1,73 @@
+/*
+ Copyright (c) 2013, Lukas Holecek <hluk@email.cz>
+
+ This file is part of CopyQ.
+
+ CopyQ 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.
+
+ CopyQ 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 CopyQ. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TESTS_H
+#define TESTS_H
+
+#include <QObject>
+#include <QStringList>
+
+class QProcess;
+class QByteArray;
+class QLocalServer;
+class QLocalSocket;
+
+/**
+ * Tests for the application.
+ */
+class Tests : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Tests(QObject *parent = NULL);
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void init();
+ void cleanup();
+
+ void clipboardToItem();
+ void itemToClipboard();
+ void tabAddRemove();
+ void action();
+ void insertRemoveItems();
+ void renameTab();
+ void importExportTab();
+ void separator();
+
+private:
+ bool startServer();
+ bool stopServer();
+ bool isServerRunning();
+
+ /** Create clipboard monitor process if it doesn't exist. */
+ void startMonitorServer();
+
+ /** Set clipboard through monitor process. */
+ void setClipboard(const QByteArray &bytes, const QString &mime = QString("text/plain"));
+
+ QProcess *m_server;
+ QProcess *m_monitor;
+ QLocalServer *m_monitorServer;
+ QLocalSocket *m_monitorSocket;
+};
+
+#endif // TESTS_H
View
438 tests/test.sh
@@ -1,33 +1,23 @@
#!/bin/bash
-# path to copyq binary
-COPYQ=${1:-copyq}
-# command to read clipboard
-READ=${2:-"xclip -o"}
-# command to write to clipboard
-WRITE=${3:-"xclip"}
-
-# count failed tests
-FAILED=0
-# result of last perf() call
-T=0
-# result if last testpl() call
-LASTPL=0
-# log filename
+# Testing suite for CopyQ application.
+copyq=${1:-copyq}
+
+# log file for copyq server
LOG="$0.log"
-# last copyq exit code
-EXIT=0
-# testing tab name
-TAB=test
+>"$LOG"
+
+TEMP="$0.out"
-# redirect to log file
-> "$LOG"
+if [ -t 1 -a "$NOCOLOR" != "1" ]; then
+ export HAS_COLOR=1
+fi
# colorize output if possible
# usage: color {color_code} [format] {message}
color () {
c=""
c_end=""
- if [ -t 1 -a -z "$NOCOLOR" ]; then
+ if [ "$HAS_COLOR" = "1" ] ; then
case "$1" in
r) c="\e[0;31m" ;;
g) c="\e[0;32m" ;;
@@ -44,284 +34,202 @@ color () {
fmt=$1
shift
else
- fmt="%s\n"
+ fmt="%s"
fi
fmt="$c$fmt$c_end"
- printf "$fmt" "$@"
+ printf "$fmt" "$@" 1>&2
}
-log () {
- color y "%-50s " "* $@"
- echo -e "\n\n* $@" >> "$LOG"
-}
+assert() {
+ local expected_exit=$1
+ local expected_output=$2
+ shift 2
-ok () {
- if [ $EXIT -eq 0 ]; then
- color g "OK $@"
- echo "** OK" >> "$LOG"
- else
- fail
+ # Keyword "local" eats exit status!
+ actual_output=$("$@")
+ actual_exit=$?
+
+ local msg_exit=""
+ local msg_output=""
+
+ if [ "$actual_exit" != "$expected_exit" ]; then
+ msg_exit="Exit status of \"$@\" is $actual_exit but should be $expected_exit!"
+ fi
+
+ if [ "$IGNORE_OUTPUT" != "1" -a "$actual_output" != "$expected_output" ]; then
+ msg_output="Unexpected output of \"$@\"!"
+ # TODO: Also print expected and actual outputs.
+ fi
+
+ if [ -n "$msg_exit" -o -n "$msg_output" ]; then
+ lineno=$(caller 1 | cut -d ' ' -f 1)
+ color "r" " Line $lineno: "
+ [ -z "$msg_exit" ] || color "w" "%s\n" "$msg_exit"
+ [ -z "$msg_output" ] || color "w" "%s\n expected: %s\n actual: %s\n" \
+ "$msg_output" "$expected_output" "$actual_output"
+ exit 1
fi
}
-fail () {
- FAILED=$((FAILED+1))
- color r "FAILED! $@"
- [ -z "$ASSERTMSG" ] || color w " $ASSERTMSG"
- [ $EXIT -eq 0 ] || color r " exit code $EXIT"
- echo "** FAILED: $ASSERTMSG $@ (exit code $EXIT)" >> "$LOG"
- EXIT=0
- ASSERTMSG=""
- return 1
+assert_true() {
+ IGNORE_OUTPUT=1 assert 0 "" "$@"
}
-run () {
- args=${TAB:+tab $TAB}
- echo "*** $COPYQ $args $@" >> "$LOG"
- OUT=`"$COPYQ" $args "$@" 2>> "$LOG"`
- EXIT=$?
- [ $EXIT -eq 0 ] || echo "** exit code $EXIT" >> "$LOG"
- [ $EXIT -eq 0 ] || return $EXIT
- printf "%s" "$OUT"
+assert_false() {
+ IGNORE_OUTPUT=1 assert 1 "" "$@"
}
-perf () {
- T=`{ time -p "$COPYQ" tab "$TAB" "$@" >/dev/null || exit 1; } 2>&1|awk '{print $2;exit}'` || return 1
+assert_equals() {
+ assert 0 "$@"
}
-testpl () {
- echo "exit(!($@))" | perl
- LASTPL=$?
- return $LASTPL
+# print label
+print_label() {
+ color "y" "%32s" "$@ ... "
}
-lastpl () {
- return $LASTPL
+# print OK or FAILED
+print_status() {
+ local t=$1
+ [ -n "$t" ] && color "g" "OK (in $t s)" || color "r" "FAILED!"
+ echo
}
-# usage: assert {command} {value} [description]
-assert () {
- result=$(eval "$1")
- exit_code=$?
- if [ $exit_code -ne 0 ]; then
- ASSERTMSG="$3: \"$1\" exit code is $exit_code)"
- return 1
- elif [ "$result" != "$2" ]; then
- ASSERTMSG="$3: \"$1\" returned \"$result\" != \"$2\")"
- return 1
- fi
+# print current test number and number of tests
+print_counter() {
+ color "b" "[$1/$2] "
}
-start_server () {
- "$COPYQ" 2>> "$LOG" &
- pidof -s copyq 2>&1 > /dev/null
+# run copyq
+run() {
+ "$copyq" "$@"
}
-terminate_server () {
- "$COPYQ" exit &> /dev/null
- while pidof -s copyq 2>&1 > /dev/null; do
- sleep 1
+is_server_running() {
+ run size &>/dev/null
+}
+
+start_server() {
+ nohup "$copyq" &>> "$LOG" &
+
+ tries=20
+ while [ $((--tries)) -ge 0 ] && ! is_server_running; do
+ sleep 0.1
done
- return 0
+
+ assert_true is_server_running
+}
+
+stop_server() {
+ assert_true run exit >/dev/null
+ assert_false is_server_running
+}
+
+restart_server() {
+ is_server_running && stop_server
+ start_server
+}
+
+has_tab() {
+ { run tab || assert false; } | grep -q '^'"$1"'$'
+}
+
+# Called before all tests are executed.
+init_tests() {
+ restart_server
}
-restart_server ()
-{
- log "restarting copyq server"
- { terminate_server && start_server; } 2>&1 > /dev/null &&
- ok || fail
+# Called after all tests are executed.
+finish_tests() {
+ has_tab "test" &&
+ assert_true run removetab "test"
+ assert_false has_tab "test"
+ assert_true stop_server
}
-clipboard ()
-{
- sleep 0.5
- $READ
+# Called befor each test is executed.
+init_test() {
+ assert_true is_server_running
+ has_tab "test" &&
+ assert_true run removetab "test"
+ assert_false has_tab "test"
}
-set_clipboard ()
-{
- printf "%s" "$1" | $WRITE
+test_add_remove_test_tab() {
+ assert_true run tab "test" add 0
+ assert_true has_tab "test"
+ assert_true run removetab "test"
+ assert_false has_tab "test"
}
-ulimit -c unlimited
+test_restore_tab() {
+ assert_true run tab "test" add a b c d
+ assert_true has_tab "test"
-restart_server || exit 1
+ restart_server
-for tab in test test2 test3; do
- log "remove tab \"$tab\" if exists"
- tabs=$(run tab) || { fail; continue; }
- if echo "$tabs" | grep -q '^'"$tab"'$'; then
- run removetab "$tab" && ok || fail
+ assert_true has_tab "test"
+ assert_equals "4" run tab "test" size
+ assert_equals "d" run tab "test" read 0
+ assert_equals "c" run tab "test" read 1
+ assert_equals "b" run tab "test" read 2
+ assert_equals "a" run tab "test" read 3
+
+ assert_true run removetab "test"
+
+ restart_server
+
+ assert_false has_tab "test"
+}
+
+run_test() {
+ local test_fn=$1
+ if [ "$test_fn" = "init_tests" -o "$test_fn" = "finish_tests" ]; then
+ "$test_fn"
+ else
+ init_test && "$test_fn"
+ fi 2>&1
+}
+
+run_tests() {
+ local tests=($(compgen -A function | grep '^test_'))
+ count=${#tests[*]}
+
+ i=0
+ failed=0
+ for test_fn in init_tests "${tests[@]}" finish_tests; do
+ if [ $i -eq 0 -o $i -gt $count ]; then
+ print_counter "*" "*"
else
- ok
+ print_counter $i $count
fi
-done
-
-content="A B C D!"
-log "clipboard"
- set_clipboard "$content" &&
- assert "run clipboard" "$(clipboard)" &&
- ok || fail
-
-log "set clipboard"
- run add "$content" && sleep 0.25 &&
- assert "run clipboard" "$content" &&
- assert "clipboard" "$content" &&
- ok || fail
-
-log "set clipboard with MIME"
- run write text/html "$content" && sleep 0.25 &&
- assert "run clipboard text/html" "$content" &&
- ok || fail
-
-log "add items"
-run add 3 2 1 0 &&
- assert "run read 0" "0" "first item" &&
- assert "clipboard" "0" "first item in clipboard" &&
- assert "run read 1" "1" "second item" &&
- assert "run read 2" "2" "third item" &&
- assert "run read 3" "3" "fourth item" &&
- ok || fail
-
-log "remove items"
-run remove &&
- assert "run read 0" "1" "first item after first removal" &&
- assert "clipboard" "1" "clipboard after first removal" &&
-run remove &&
- assert "run read 0" "2" "first item after second removal" &&
- assert "clipboard" "2" "clipboard after second removal" &&
- ok || fail
-
-log "move second item to clipboard"
-i1=`run read 1`
-run "select" 1
-assert "run read 0" "$i1" && assert "clipboard" "$i1" &&
- ok || fail
-
-log "read past end of list"
-SIZE=`run size` &&
- X=`run read $SIZE` &&
- assert "$X" "" &&
- ok || fail
-
-log "time - \"copyq size\" in 0.1 seconds"
-{ for _ in {1..10}; do
- perf size &&
- testpl "$T <= 0.1" || break
-done } && lastpl &&
- ok || fail "($T seconds)"
-
-log "time - \"copyq select 1\" in 0.3 seconds"
-{ for _ in {1..10}; do
- perf "select" 1 &&
- testpl "$T <= 0.3" || break
-done } && lastpl &&
- ok || fail "($T seconds)"
-
-log "adding 30 items (each in 0.1 seconds)"
-str=""
-{ for x in {1..30}; do
- perf add "$x" &&
- str=$x${str:+", $str"} &&
- testpl "$T <= 0.1" || break
-done } && lastpl &&
- assert "run separator ', ' read $(echo {0..29})" "$str" &&
- ok || fail "($T seconds)"
-
-log "removing 30 items (each in 0.1 seconds)"
-{ for _ in {0..29}; do
- perf remove 0 &&
- testpl "$T <= 0.1" || break
-done } && lastpl &&
- ok || fail "($T seconds)"
-
-log "adding 100 items at once in 0.3 seconds"
-perf add {1..100} && testpl "$T <= 0.3" &&
- ok || fail "($T seconds)"
-
-log "reading mime of 100 items at once in 0.1 seconds"
-perf read "?" {1..100} && testpl "$T <= 0.1" &&
- ok || fail "($T seconds)"
-
-log "removing 100 items at once in 0.3 seconds"
-perf remove {0..99} && testpl "$T <= 0.3" &&
- ok || fail "($T seconds)"
-
-mime="application/copyq-test"
-log "adding huge amount of data in 1.0 seconds"
-echo {0..99999}|perf write "$mime" - && testpl "$T <= 1.0" &&
- ok || fail "($T seconds)"
-
-log "reading huge amount of data"
-[ "`run read "$mime" 0`" = "`echo {0..99999}`" ] &&
- ok || fail
-
-mime="text/plain"
-log "adding huge amount of text in 1.0 seconds"
-echo {0..99999}|perf write "$mime" - && testpl "$T <= 1.0" &&
- ok || fail "($T seconds)"
-
-log "reading huge amount of text"
-[ "`run read "$mime" 0`" = "`echo {0..99999}`" ] &&
- ok || fail
-
-log "reading huge amount of text from clipboard"
-[ "`clipboard`" = "`echo {0..99999}`" ] &&
- ok || fail
-
-log "removing huge amount of data in 0.1 seconds"
-perf remove 0 && testpl "$T < 0.1" &&
- ok || fail "($T seconds)"
-
-TAB=test2
-log "creating items in a new tab"
-perf add 1 2 3 && testpl "$T < 0.1" &&
- ok || fail "($T seconds)"
-
-restart_server || exit 1
-
-log "checking items in the new tab"
- assert "run read 0" "3" "first item" &&
- assert "run read 1" "2" "second item" &&
- assert "run read 2" "1" "third item" &&
- ok || fail
-
-log "exporting"
-tmp=`mktemp`
-trap "rm -f \"$tmp\"" INT TERM EXIT
-run exporttab "$tmp" &&
- ok || fail
-
-TAB=""
-log "clipboard content"
-for x in 1 2 3; do set_clipboard "... $x ..."; done && sleep 0.25 &&
- assert "run read" "... 3 ..." && run remove &&
-for x in 4 5 6; do set_clipboard "... $x ..."; done && sleep 0.25 &&
- assert "run read" "... 6 ..." && run remove &&
- ok || fail
-
-log "rename tab"
-run renametab test2 test3 &&
- ok || fail
-
-log "import tab"
-run importtab "$tmp" &&
- ok || fail
-
-log "checking imported tab content"
- assert "run eval 'for (i = 0; i < 1000; ++i) {tab(\"test2\");a=read(i);tab(\"test3\");b=read(i);if(str(a)!=str(b))break;}; print(i)'" 1000 &&
- ok || fail
-
-TAB=test2
-log "checking items in the imported tab"
- assert "run read 0" "3" "first item" &&
- assert "run read 1" "2" "second item" &&
- assert "run read 2" "1" "third item" &&
- ok || fail
-
-log "summary"
-[ $FAILED -eq 0 ] && ok "(0 failed)" || fail "($FAILED failed)"
-
-terminate_server || fail
+
+ print_label "$test_fn"
+
+ t=$({ time -p run_test "$test_fn" > "$TEMP" || exit 1; } 2>&1 | awk '{print $2;exit}')
+
+ if [ -z "$t" ]; then
+ failed=$((failed+1))
+ fi
+
+ print_status "$t"
+
+ output=$(cat "$TEMP")
+ if [ -n "$output" ]; then
+ echo "$output"
+ fi
+
+ i=$((i + 1))
+ done
+
+ if [ $failed = 0 ]; then
+ color "g" "All OK."
+ else
+ color "r" "Failed tests: $failed"
+ fi
+ echo
+}
+
+run_tests

0 comments on commit 6547d91

Please sign in to comment.