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:
Alexey Sokolov
2015-10-16 23:22:13 +01:00
parent f3762e8b05
commit 90ae78533f
6 changed files with 181 additions and 121 deletions
+136
View File
@@ -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
-105
View File
@@ -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()