mirror of
https://github.com/znc/znc.git
synced 2026-05-10 07:14:43 +02:00
Rewrite integration test.
Pexpect was failing too often, even when starting a new process. Now the test is using Qt and C++. Fix #772
This commit is contained in:
+2
-6
@@ -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"
|
||||
|
||||
+8
-6
@@ -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
|
||||
|
||||
+28
-4
@@ -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 <prefix>/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 <prefix>/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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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 <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QProcess>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTextStream>
|
||||
|
||||
#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
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user