diff --git a/.appveyor.yml b/.appveyor.yml index 91c6d65b..ce3e46ba 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,21 +9,17 @@ cache: environment: matrix: - cygwin_url: https://cygwin.com/setup-x86_64.exe - do_test2: no - cygwin_url: https://cygwin.com/setup-x86.exe - # For some reason pexpect fails on 32bit cygwin very often on appveyor, I don't know why - do_test2: no install: - ps: Invoke-WebRequest $env:cygwin_url -OutFile c:\cygwin-setup.exe # libcrypt-devel is needed only on x86_64 and only for modperl... probably some dependency problem. - - c:\cygwin-setup.exe --quiet-mode --no-shortcuts --no-startmenu --no-desktop --upgrade-also --only-site --site http://cygwin.mirror.constant.com/ --root c:\cygwin-root --local-package-dir c:\cygwin-setup-cache --packages automake,gcc-g++,make,pkg-config,wget,openssl-devel,libicu-devel,zlib-devel,libcrypt-devel,perl,python3,swig,libsasl2-devel,python3-setuptools,socat + - c:\cygwin-setup.exe --quiet-mode --no-shortcuts --no-startmenu --no-desktop --upgrade-also --only-site --site http://cygwin.mirror.constant.com/ --root c:\cygwin-root --local-package-dir c:\cygwin-setup-cache --packages automake,gcc-g++,make,pkg-config,wget,openssl-devel,libicu-devel,zlib-devel,libcrypt-devel,perl,python3,swig,libsasl2-devel,libQt5Core-devel - c:\cygwin-root\bin\sh -lc "echo Hi" - c:\cygwin-root\bin\sh -lc "uname -a" - c:\cygwin-root\bin\sh -lc "cat /proc/cpuinfo" - c:\cygwin-root\bin\sh -lc "cat /proc/meminfo" - c:\cygwin-root\bin\sh -lc "cygcheck -s -v > $APPVEYOR_BUILD_FOLDER/cygcheck.log 2>&1" - ps: Push-AppveyorArtifact cygcheck.log - - c:\cygwin-root\bin\sh -lc "/bin/easy_install* pexpect" # stdin is broken at AppVeyor, so we open it explicitly as /dev/null build_script: - git submodule update --init @@ -35,4 +31,4 @@ build_script: - c:\cygwin-root\bin\sh -lc "znc --version" test_script: - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; make VERBOSE=1 test < /dev/null" - - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; if [[ $do_test2 == yes ]]; then make VERBOSE=1 test2 < /dev/null; else true; fi" + - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; make VERBOSE=1 test2 < /dev/null" diff --git a/.travis.yml b/.travis.yml index e4a97edd..04e05052 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,10 @@ install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cat /proc/cpuinfo /proc/meminfo; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then lsb_release -a; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:teward/swig3.0; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:beineri/opt-qt551-trusty; fi # default qt5.2 from trusty doesn't support QByteArray::toStdString() - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install libperl-dev python3-dev tcl-dev libsasl2-dev libicu-dev swig3.0 doxygen graphviz python3-setuptools socat; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo easy_install3 pexpect; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -y libperl-dev python3-dev tcl-dev libsasl2-dev libicu-dev swig3.0 doxygen graphviz qt55base; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source /opt/qt55/bin/qt55-env.sh; fi - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "coverage" ]]; then # when travis upgrades ubuntu, install lcov from apt-get instead @@ -53,9 +54,9 @@ install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew config; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew list --versions; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install swig python3 icu4c jq openssl socat; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install swig python3 icu4c jq openssl qt5; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew info --json=v1 --installed | jq .; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip3 install pexpect; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PKG_CONFIG_PATH="$(brew --prefix qt5)/lib/pkgconfig:$PKG_CONFIG_PATH"; fi - "echo pkg-config path: [$PKG_CONFIG_PATH]" script: - ./bootstrap.sh @@ -63,9 +64,10 @@ script: - cd build - ../configure --enable-perl --enable-python --enable-tcl --enable-cyrus --enable-charset $CFGFLAGS CXXFLAGS="$CXXFLAGS $MYCXXFLAGS" LDFLAGS="$LDFLAGS $MYLDFLAGS" - cat config.log - - make V=1 - - make V=1 test + - make VERBOSE=1 + - make VERBOSE=1 test - sudo make install + - make VERBOSE=1 test2 - cd .. after_success: - test -r .travis_after_all.py && python .travis_after_all.py || echo No .travis_after_all.py found diff --git a/Makefile.in b/Makefile.in index 7dfd1909..38b6fde0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -39,6 +39,8 @@ endif ifeq "$(GMOCK_DIR)" "" GMOCK_DIR := $(srcdir)/third_party/googletest/googlemock endif +qt_CFLAGS := @qt_CFLAGS@ -fPIC -std=c++11 -pthread +qt_LIBS := @qt_LIBS@ -pthread # Force the simple internal regex engine to get consistent behavior on all platforms. # See https://code.google.com/p/chromium/issues/detail?id=317224 for more details. @@ -108,6 +110,10 @@ unittest: $(LIB_OBJS) test/gtest-all.o test/gmock-all.o test/gmock-main.o $(TEST $(E) Linking unit test... $(Q)$(CXX) $(LDFLAGS) -o $@ $(LIB_OBJS) test/gtest-all.o test/gmock-all.o test/gmock-main.o $(TESTS) $(LIBS) +inttest: test/Integration.o test/Int-gtest-all.o test/Int-gmock-all.o test/Int-gmock-main.o + $(E) Linking integration test... + $(Q)g++ -std=c++11 -o $@ test/Integration.o test/Int-gtest-all.o test/Int-gmock-all.o test/Int-gmock-main.o $(LIBS) $(qt_LIBS) + man: @$(MAKE) -C man $(C) @@ -147,6 +153,24 @@ test/gmock-main.o: $(GMOCK_DIR)/src/gmock_main.cc Makefile $(E) Building test object gmock-main... $(Q)$(CXX) $(CXXFLAGS) $(GTEST_FLAGS) -c -o $@ $< -MD -MF .depend/gmock-main.dep -MT $@ +# Qt fails under TSAN, so CXXFLAGS/LDFLAGS can't be used. +test/Integration.o: test/Integration.cpp Makefile + @mkdir -p .depend test + $(E) Building test object Integration... + $(Q)g++ $(qt_CFLAGS) $(GTEST_FLAGS) -c -o $@ $< -MD -MF .depend/Integration.test.dep -MT $@ +test/Int-gtest-all.o: $(GTEST_DIR)/src/gtest-all.cc Makefile + @mkdir -p .depend test + $(E) Building test object Int-gtest-all... + $(Q)g++ $(qt_CFLAGS) $(GTEST_FLAGS) -I$(GTEST_DIR) -c -o $@ $< -MD -MF .depend/Int-gtest-all.dep -MT $@ +test/Int-gmock-all.o: $(GMOCK_DIR)/src/gmock-all.cc Makefile + @mkdir -p .depend test + $(E) Building test object Int-gmock-all... + $(Q)g++ $(qt_CFLAGS) $(GTEST_FLAGS) -I$(GMOCK_DIR) -c -o $@ $< -MD -MF .depend/Int-gmock-all.dep -MT $@ +test/Int-gmock-main.o: $(GMOCK_DIR)/src/gmock_main.cc Makefile + @mkdir -p .depend test + $(E) Building test object Int-gmock-main... + $(Q)g++ $(qt_CFLAGS) $(GTEST_FLAGS) -c -o $@ $< -MD -MF .depend/Int-gmock-main.dep -MT $@ + ifneq "THIS_IS_NOT_TARBALL" "" # If git commit was changed since previous build, add a phony target to dependencies, forcing version.o to be recompiled # Nightlies have pregenerated version.cpp @@ -224,9 +248,9 @@ uninstall: test: unittest $(Q)./unittest -test2: - # This test uses files at /lib/znc, which is less than ideal, especially from build scripts of distros. - # That's why it's a separate make target. - $(Q)$(srcdir)/test/integration.py -v +# This test uses files at /lib/znc, which is less than ideal, especially from build scripts of distros. +# That's why it's a separate make target. +test2: inttest + $(Q)./inttest -include $(wildcard .depend/*.dep) diff --git a/configure.ac b/configure.ac index 1e90ceab..b1f7c04b 100644 --- a/configure.ac +++ b/configure.ac @@ -453,6 +453,9 @@ then ]) fi +# For integration test only +PKG_CHECK_MODULES([qt], [Qt5Network >= 5.4], [], [:]) + AC_ARG_WITH( [module-prefix], AS_HELP_STRING([--with-module-prefix], [module object code [LIBDIR/znc]]), [MODDIR=$withval], @@ -481,6 +484,8 @@ else # See https://cygwin.com/ml/cygwin-apps/2015-07/msg00108.html for the reasoning behind the name. LIBZNC="cygznc-${LIBZNC_VERSION}.dll" LIBZNCDIR="$bindir" + # See above about __STRICT_ANSI__ + qt_CFLAGS="$qt_CFLAGS -U__STRICT_ANSI__" fi if test -z "$ISDARWIN"; then @@ -683,6 +688,8 @@ AC_SUBST([PYTHON]) AC_SUBST([SWIG]) AC_SUBST([python_CFLAGS]) AC_SUBST([python_LIBS]) +AC_SUBST([qt_CFLAGS]) +AC_SUBST([qt_LIBS]) AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([znc-buildmod]) AC_CONFIG_FILES([man/Makefile]) diff --git a/test/Integration.cpp b/test/Integration.cpp new file mode 100644 index 00000000..ec82d6ba --- /dev/null +++ b/test/Integration.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2004-2015 ZNC, see the NOTICE file for details. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define Z if (::testing::Test::HasFatalFailure()) return + +namespace { + +class IO { +public: + IO(QIODevice* device, bool verbose = false) : m_device(device), m_verbose(verbose) {} + virtual ~IO() {} + void ReadUntil(QByteArray pattern) { + auto deadline = QDateTime::currentDateTime().addSecs(30); + while (true) { + int search = m_readed.indexOf(pattern); + if (search != -1) { + m_readed.remove(0, search + pattern.length()); + return; + } + if (m_readed.length() > pattern.length()) { + m_readed = m_readed.right(pattern.length()); + } + const int timeout_ms = QDateTime::currentDateTime().msecsTo(deadline); + ASSERT_GT(timeout_ms, 0) << pattern.toStdString(); + ASSERT_TRUE(m_device->waitForReadyRead(timeout_ms)) << pattern.toStdString(); + QByteArray chunk = m_device->readAll(); + if (m_verbose) { + std::cout << chunk.toStdString() << std::flush; + } + m_readed += chunk; + } + } + void Write(QString s = "") { + s += "\n"; + if (m_verbose) { + std::cout << s.toStdString() << std::flush; + } + QTextStream str(m_device); + str << s; + } + +private: + QIODevice* m_device; + bool m_verbose; + QByteArray m_readed; +}; + +class Process : public IO { +public: + Process(QString cmd, QStringList args, bool interactive) : IO(&m_proc, true) { + if (!interactive) { + m_proc.setProcessChannelMode(QProcess::ForwardedChannels); + } + m_proc.start(cmd, args); + } + ~Process() { + if (m_kill) m_proc.terminate(); + EXPECT_TRUE(m_proc.waitForFinished()); + m_proc.kill(); // for case if it didn't finish yet + } + void ShouldFinishItself() { + m_kill = false; + } + +private: + bool m_kill = true; + QProcess m_proc; +}; + +void WriteConfig(QString path) { + Process p("./znc", QStringList() << "--debug" << "--datadir" << path << "--makeconf", true); + p.ReadUntil("Listen on port");Z; p.Write("12345"); + p.ReadUntil("Listen using SSL");Z; p.Write(); + p.ReadUntil("IPv6");Z; p.Write(); + p.ReadUntil("Username");Z; p.Write("user"); + p.ReadUntil("password");Z; p.Write("hunter2"); + p.ReadUntil("Confirm");Z; p.Write("hunter2"); + p.ReadUntil("Nick [user]");Z; p.Write(); + p.ReadUntil("Alternate nick [user_]");Z; p.Write(); + p.ReadUntil("Ident [user]");Z; p.Write(); + p.ReadUntil("Real name");Z; p.Write(); + p.ReadUntil("Bind host");Z; p.Write(); + p.ReadUntil("Set up a network?");Z; p.Write(); + p.ReadUntil("Name [freenode]");Z; p.Write("test"); + p.ReadUntil("Server host (host only)");Z; p.Write("127.0.0.1"); + p.ReadUntil("Server uses SSL?");Z; p.Write(); + p.ReadUntil("6667");Z; p.Write(); + p.ReadUntil("password");Z; p.Write(); + p.ReadUntil("channels");Z; p.Write(); + p.ReadUntil("Launch ZNC now?");Z; p.Write("no"); + p.ShouldFinishItself(); +} + +TEST(ZNC, Connect) { + QTemporaryDir dir; + WriteConfig(dir.path());Z; + QTcpServer ser; + ASSERT_TRUE(ser.listen(QHostAddress::LocalHost, 6667)) << ser.errorString().toStdString(); + Process znc("./znc", QStringList() << "--debug" << "--datadir" << dir.path(), false); + ASSERT_TRUE(ser.waitForNewConnection(30000 /* msec */)); + IO ircd(ser.nextPendingConnection()); + ircd.ReadUntil("CAP LS");Z; + QTcpSocket cli; + cli.connectToHost("127.0.0.1", 12345); + ASSERT_TRUE(cli.waitForConnected()) << cli.errorString().toStdString(); + IO cl(&cli); + cl.Write("PASS :hunter2"); + cl.Write("NICK nick"); + cl.Write("USER user/test x x :x"); + cl.ReadUntil("Welcome");Z; +} + +} // namespace diff --git a/test/integration.py b/test/integration.py deleted file mode 100755 index aa56ff60..00000000 --- a/test/integration.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2004-2015 ZNC, see the NOTICE file for details. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -import pexpect -import subprocess -import sys -import tempfile -import unittest - -from contextlib import contextmanager - - -def write_config(config_dir): - znc = pexpect.spawnu('./znc', ['--debug', '--datadir', config_dir, '--makeconf']) - znc.timeout = 180 - try: - znc.logfile_read = sys.stdout - znc.expect_exact('Listen on port'); znc.sendline('12345') - znc.expect_exact('Listen using SSL'); znc.sendline() - znc.expect_exact('IPv6'); znc.sendline() - znc.expect_exact('Username'); znc.sendline('user') - znc.expect_exact('password'); znc.sendline('hunter2') - znc.expect_exact('Confirm'); znc.sendline('hunter2') - znc.expect_exact('Nick [user]'); znc.sendline() - znc.expect_exact('Alternate nick [user_]'); znc.sendline() - znc.expect_exact('Ident [user]'); znc.sendline() - znc.expect_exact('Real name'); znc.sendline() - znc.expect_exact('Bind host'); znc.sendline() - znc.expect_exact('Set up a network?'); znc.sendline() - znc.expect_exact('Name [freenode]'); znc.sendline('test') - znc.expect_exact('Server host (host only)'); znc.sendline('::1') - znc.expect_exact('Server uses SSL?'); znc.sendline() - znc.expect_exact('6667'); znc.sendline() - znc.expect_exact('password'); znc.sendline() - znc.expect_exact('channels'); znc.sendline() - znc.expect_exact('Launch ZNC now?'); znc.sendline('no') - znc.expect_exact(pexpect.EOF) - finally: - znc.terminate() - - -class TestZNC(unittest.TestCase): - def setUp(self): - config = tempfile.TemporaryDirectory() - self.addCleanup(config.cleanup) - write_config(config.name) - self.config = config.name - - - @contextmanager - def run_znc(self): - znc = subprocess.Popen(['./znc', '--debug', '--datadir', self.config]) - yield - znc.terminate() - # TODO: bump python requirements to 3.3 and use znc.wait(timeout=30) instead. - # Ubuntu Precise on Travis has too old python. - self.assertEqual(0, znc.wait()) - - - @contextmanager - def run_ircd(self): - ircd = pexpect.spawnu('socat', ['stdio', 'tcp6-listen:6667,reuseaddr']) - ircd.timeout = 180 - yield ircd - ircd.terminate() - - - @contextmanager - def run_client(self): - # if server didn't start yet, try again, up to 30 times. - client = pexpect.spawnu('socat', ['stdio', 'tcp6:[::1]:12345,retry=30']) - client.timeout = 180 - yield client - client.terminate() - - - def test_connect(self): - with self.run_ircd() as ircd: - with self.run_znc(): - ircd.expect_exact('CAP LS') - with self.run_client() as client: - client.sendline('PASS :hunter2') - client.sendline('NICK :nick') - client.sendline('USER user/test x x :x') - client.expect_exact('Welcome') - - -if __name__ == '__main__': - unittest.main() -