Vinzenz Feenstra has uploaded a new change for review. Change subject: Adding Unit Tests and Functional Tests ......................................................................
Adding Unit Tests and Functional Tests The functional tests so far are testing the retrieval of data from the underlying system and ensures the structure of the data. It does not however validate the content. It ensures that the messages sent over the VirtIO Serial channel have the format defined. If we change the format it will break the unit tests. Additionally a check for the unicode filtering. Change-Id: I4c1d740c948e33aed8a04a1013b22276cf7d85e3 Signed-off-by: Vinzenz Feenstra <vfeen...@redhat.com> --- M Makefile.am M configure.ac M ovirt-guest-agent/VirtIoChannel.py A tests/Makefile.am A tests/encoding_test.py A tests/guest_agent_test.py A tests/message_validator.py A tests/test_port.py A tests/testrunner.py A tests/unittest.bat 10 files changed, 493 insertions(+), 12 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-guest-agent refs/changes/52/19052/1 diff --git a/Makefile.am b/Makefile.am index b571b90..d13b408 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = \ ovirt-guest-agent \ GinaSSO \ + tests \ windows-credprov \ $(NULL) @@ -52,7 +53,8 @@ ovirt-guest-agent/VirtIoChannel.py \ ovirt-guest-agent/WinFile.py \ ovirt-guest-agent/ovirt-guest-agent.py \ - ovirt-guest-agent/version.py + ovirt-guest-agent/version.py \ + tests/*.py PEP8_BLACKLIST = ovirt-guest-agent/setup.py diff --git a/configure.ac b/configure.ac index 2c5f67c..9cce1ed 100644 --- a/configure.ac +++ b/configure.ac @@ -210,6 +210,7 @@ ovirt-guest-agent/ovirt-guest-agent ovirt-guest-agent/consoleapps/Makefile ovirt-guest-agent/pam/Makefile + tests/Makefile windows-credprov/Makefile ]) if test "x$sso" == "xyes"; then diff --git a/ovirt-guest-agent/VirtIoChannel.py b/ovirt-guest-agent/VirtIoChannel.py index dd68668..f6edd9a 100644 --- a/ovirt-guest-agent/VirtIoChannel.py +++ b/ovirt-guest-agent/VirtIoChannel.py @@ -111,25 +111,48 @@ return filt(obj) -class VirtIoChannel: - +class VirtIoStream(object): # Python on Windows 7 returns 'Microsoft' rather than 'Windows' as # documented. is_windows = platform.system() in ['Windows', 'Microsoft'] + is_test = False def __init__(self, vport_name): - if self.is_windows: + if self.is_test: + from test_port import get_test_port + self._vport = get_test_port(vport_name) + self._read = self._vport.read + self._write = self._vport.write + elif self.is_windows: from WinFile import WinFile self._vport = WinFile(vport_name) + self._read = self._vport.read + self._write = self._vport.write else: self._vport = os.open(vport_name, os.O_RDWR) + self._read = self._os_read + self._write = self._os_write + + def _os_read(self, size): + return os.read(self._vport, size) + + def _os_write(self, buffer): + return os.write(self._vport, buffer) + + def read(self, size): + return self._read(size) + + def write(self, buffer): + return self._write(buffer) + + +class VirtIoChannel: + def __init__(self, vport_name): + self._stream = VirtIoStream(vport_name) self._buffer = '' def _readbuffer(self): - if self.is_windows: - buffer = self._vport.read(4096) - else: - buffer = os.read(self._vport, 4096) + buffer = self._stream.read(4096) if buffer: self._buffer += buffer else: @@ -173,10 +196,7 @@ args = _filter_object(args) message = (json.dumps(args) + '\n').encode('utf8') while len(message) > 0: - if self.is_windows: - written = self._vport.write(message) - else: - written = os.write(self._vport, message) + written = self._stream.write(message) message = message[written:] diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..55fb55a --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,9 @@ +AM_TESTS_ENVIRONMENT=PYTHONPATH=$(PYTHONPATH):$(top_srcdir)/ovirt-guest-agent:$(top_srcdir)/tests; export PYTHONPATH; +LOG_COMPILER=$(PYTHON) +AM_LOG_FLAGS=$(top_srcdir)/tests/testrunner.py + +TESTS=\ + encoding_test.py \ + guest_agent_test.py \ + $(NULL) + diff --git a/tests/encoding_test.py b/tests/encoding_test.py new file mode 100644 index 0000000..a158730 --- /dev/null +++ b/tests/encoding_test.py @@ -0,0 +1,51 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 + +from testrunner import GuestAgentTestCase as TestCaseBase +from VirtIoChannel import _filter_object + + +class EncodingTest(TestCaseBase): + + def testNonUnicodeKeyInput(self): + non_unicode_key = {'non-unicode-key': u'unicode value'} + self.assertEquals({u'non-unicode-key': u'unicode value'}, + _filter_object(non_unicode_key)) + + def testNonUnicodeValueInput(self): + non_unicode_value = {u'unicode-key': 'non-unicode value'} + self.assertEquals({u'unicode-key': u'non-unicode value'}, + _filter_object(non_unicode_value)) + + def testNullChar(self): + non_unicode_value = {u'unicode-key': '\x00'} + self.assertEquals({u'unicode-key': u'\ufffd'}, + _filter_object(non_unicode_value)) + + def testIllegalInput(self): + ILLEGAL_DATA = {u'foo': '\x00data\x00test\xef\xbf\xbf\xef\xbf\xbe\xed' + '\xb1\xb9\xed\xa0\x80'} + EXPECTED = {u'foo': u'\ufffddata\ufffdtest\ufffd\ufffd\ufffd\ufffd'} + self.assertEqual(EXPECTED, _filter_object(ILLEGAL_DATA)) + + def testIllegalUnicodeInput(self): + ILLEGAL_DATA = {u'foo': u'\x00data\x00test\uffff\ufffe\udc79\ud800'} + EXPECTED = {u'foo': u'\ufffddata\ufffdtest\ufffd\ufffd\ufffd\ufffd'} + self.assertEqual(EXPECTED, _filter_object(ILLEGAL_DATA)) + + def testLegalUnicodeInput(self): + LEGAL_DATA = {u'foo': u'?data?test\U00010000'} + self.assertEqual(LEGAL_DATA, _filter_object(LEGAL_DATA)) + + def testIllegalUnicodeCharacters(self): + INVALID = (u'\u0000', u'\ufffe', u'\uffff', u'\ud800', u'\udc79', + u'\U00000000', '\x00', '\x01', '\x02', '\x03', '\x04', + '\x05') + for invchar in INVALID: + self.assertEqual(u'\ufffd', _filter_object(invchar)) + + def testLegalUnicodeCharacters(self): + LEGAL = (u'\u2122', u'Hello World') + for legalchar in LEGAL: + self.assertEqual(legalchar, _filter_object(legalchar)) diff --git a/tests/guest_agent_test.py b/tests/guest_agent_test.py new file mode 100644 index 0000000..b3939ad --- /dev/null +++ b/tests/guest_agent_test.py @@ -0,0 +1,93 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 + + +from ConfigParser import ConfigParser +import platform + +from message_validator import MessageValidator +from testrunner import GuestAgentTestCase + +import test_port + + +def _linux_setup_test(conf): + port_name = 'linux-functional-test-port' + conf.set('general', 'applications_list', + 'kernel ovirt-guest-agent xorg-x11-drv-qxl ' + 'linux-image xserver-xorg-video-qxl') + conf.set('general', 'ignored_fs', + 'rootfs tmpfs autofs cgroup selinuxfs udev mqueue ' + 'nfds proc sysfs devtmpfs hugetlbfs rpc_pipefs devpts ' + 'securityfs debugfs binfmt_misc fuse.gvfsd-fuse ' + 'fuse.gvfs-fuse-daemon fusectl usbfs') + from GuestAgentLinux2 import LinuxVdsAgent + return port_name, LinuxVdsAgent + + +def _win32_setup_test(conf): + port_name = "windows-functional-test-port" + from GuestAgentWin32 import WinVdsAgent + return port_name, WinVdsAgent + + +class FunctionalTest(GuestAgentTestCase): + def setUp(self): + self._config = ConfigParser() + self._config.add_section('general') + self._config.add_section('virtio') + + agent_class = None + if platform.system() in ['Windows', 'Microsoft']: + self._vport_name, agent_class = _win32_setup_test(self._config) + else: + self._vport_name, agent_class = _linux_setup_test(self._config) + + self._validator = MessageValidator(self._vport_name) + self._vport = self._validator.port() + test_port.add_test_port(self._vport_name, self._vport) + + self._config.set('general', 'heart_beat_rate', '5') + self._config.set('general', 'report_user_rate', '10') + self._config.set('general', 'report_application_rate', '120') + self._config.set('general', 'report_disk_usage', '300') + self._config.set('virtio', 'device', self._vport_name) + + self.vdsAgent = agent_class(self._config) + + def testSendInfo(self): + self._validator.verifySendInfo(self.vdsAgent) + + def testSendAppList(self): + self._validator.verifySendAppList(self.vdsAgent) + + def testSendDisksUsages(self): + self._validator.verifySendDisksUsages(self.vdsAgent) + + def testSendMemoryStats(self): + self._validator.verifySendMemoryStats(self.vdsAgent) + + def testSendFQDN(self): + self._validator.verifySendFQDN(self.vdsAgent) + + def testSendUserInfo(self): + self._validator.verifySendUserInfo(self.vdsAgent) + + def testSessionLogon(self): + self._validator.verifySessionLogon(self.vdsAgent) + + def testSessionLogoff(self): + self._validator.verifySessionLogon(self.vdsAgent) + + def testSessionLock(self): + self._validator.verifySessionLock(self.vdsAgent) + + def testSessionUnlock(self): + self._validator.verifySessionUnlock(self.vdsAgent) + + def testSessionStartup(self): + self._validator.verifySessionStartup(self.vdsAgent) + + def testSessionShutdown(self): + self._validator.verifySessionShutdown(self.vdsAgent) diff --git a/tests/message_validator.py b/tests/message_validator.py new file mode 100644 index 0000000..f188efe --- /dev/null +++ b/tests/message_validator.py @@ -0,0 +1,206 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 + +import test_port +import json +import logging + + +class TestPortWriteBuffer(test_port.TestPort): + def __init__(self, vport_name, *args, **kwargs): + test_port.TestPort.__init__(self, vport_name, *args, **kwargs) + self._buffer = '' + + def write(self, buffer): + self._buffer = self._buffer + buffer + return len(buffer) + + def read(self, size): + return '' + + def clear(self): + self._buffer = '' + + +def _ensure_messages(*messages): + def wrapped(f): + def fun(self, *args, **kwargs): + result = f(self, *args, **kwargs) + names = [] + parsed = self._get_messages() + assert(len(parsed) == len(messages)) + for m in parsed: + assert('__name__' in m) + names.append(m['__name__']) + self._check(m) + for m in messages: + assert(m in names) + return result + return fun + return wrapped + + +def _name_only(n): + def wrapped(o): + assert(len(o) == 1) + assert(o['__name__'] == n) + return wrapped + + +def assert_string_param(o, n): + assert(n in o) + assert(isinstance(o[n], basestring)) + + +def assert_integral_param(o, n): + assert(n in o) + assert(isinstance(o[n], (int, long))) + + +def _name_and_one_str_param(msg_name, param_name): + def wrapped(o): + assert(o['__name__'] == msg_name) + assert_string_param(o, param_name) + return wrapped + + +def assert_is_string_list(o): + assert(isinstance(o, list)) + for s in o: + assert(isinstance(s, basestring)) + + +def _name_and_one_string_list_param(msg_name, param_name): + def wrapped(o): + assert(o['__name__'] == msg_name) + assert(param_name in o) + assert_is_string_list(o[param_name]) + return wrapped + + +def validate_network_interfaces(msg): + assert(msg['__name__'] == 'network-interfaces') + assert('interfaces' in msg) + assert(isinstance(msg['interfaces'], list)) + for obj in msg['interfaces']: + assert_string_param(obj, 'hw') + assert_string_param(obj, 'name') + assert('inet' in obj) + assert_is_string_list(obj['inet']) + assert('inet6' in obj) + assert_is_string_list(obj['inet6']) + + +def validate_disks_usage(msg): + for disk in msg['disks']: + assert_string_param(disk, 'fs') + assert_string_param(disk, 'path') + assert('total' in disk) + assert_integral_param(disk, 'total') + assert('used' in disk) + assert_integral_param(disk, 'used') + + +def validate_memory_stats(msg): + assert('memory' in msg) + mem = msg['memory'] + assert_integral_param(mem, 'majflt') + assert_integral_param(mem, 'mem_free') + assert_integral_param(mem, 'mem_total') + assert_integral_param(mem, 'mem_unused') + assert_integral_param(mem, 'pageflt') + assert_integral_param(mem, 'swap_in') + assert_integral_param(mem, 'swap_out') + + +_MSG_VALIDATORS = { + 'active-user': _name_and_one_str_param('active-user', 'name'), + 'applications': _name_and_one_string_list_param('applications', + 'applications'), + 'disks-usage': validate_disks_usage, + 'fqdn': _name_and_one_str_param('fqdn', 'fqdn'), + 'host-name': _name_and_one_str_param('host-name', 'name'), + 'memory-stats': validate_memory_stats, + 'network-interfaces': validate_network_interfaces, + 'os-version': _name_and_one_str_param('os-version', 'version'), + 'session-lock': _name_only('session-lock'), + 'session-logoff': _name_only('session-logoff'), + 'session-logon': _name_only('session-logon'), + 'session-shutdown': _name_only('session-shutdown'), + 'session-startup': _name_only('session-startup'), + 'session-unlock': _name_only('session-unlock'), +} + + +def _check_fun(msg): + logging.debug("Message: %s", str(msg)) + assert(msg['__name__'] in _MSG_VALIDATORS) + _MSG_VALIDATORS[msg['__name__']](msg) + + +class MessageValidator(object): + def __init__(self, vport_name): + self._port = TestPortWriteBuffer(vport_name) + + def port(self): + return self._port + + def _get_messages(self): + result = [] + for line in self._port._buffer.split('\n'): + line = line.strip() + if line: + result.append(json.loads(line)) + return result + + def _check(self, msg): + _check_fun(msg) + + @_ensure_messages('host-name', 'os-version', 'network-interfaces') + def verifySendInfo(self, agent): + agent.sendInfo() + + @_ensure_messages('applications') + def verifySendAppList(self, agent): + agent.sendAppList() + + @_ensure_messages('disks-usage') + def verifySendDisksUsages(self, agent): + agent.sendDisksUsages() + + @_ensure_messages('memory-stats') + def verifySendMemoryStats(self, agent): + agent.sendMemoryStats() + + @_ensure_messages('active-user') + def verifySendUserInfo(self, agent): + agent.sendUserInfo() + + @_ensure_messages('fqdn') + def verifySendFQDN(self, agent): + agent.sendFQDN() + + @_ensure_messages('active-user', 'session-logon') + def verifySessionLogon(self, agent): + agent.sessionLogon() + + @_ensure_messages('active-user', 'session-logoff') + def verifySessionLogoff(self, agent): + agent.sessionLogoff() + + @_ensure_messages('session-lock') + def verifySessionLock(self, agent): + agent.sessionLock() + + @_ensure_messages('session-unlock') + def verifySessionUnlock(self, agent): + agent.sessionUnlock() + + @_ensure_messages('session-startup') + def verifySessionStartup(self, agent): + agent.sessionStartup() + + @_ensure_messages('session-shutdown') + def verifySessionShutdown(self, agent): + agent.sessionShutdown() diff --git a/tests/test_port.py b/tests/test_port.py new file mode 100644 index 0000000..4cb9576 --- /dev/null +++ b/tests/test_port.py @@ -0,0 +1,26 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 + + +class TestPort(object): + def __init__(self, vport_name, *args, **kwargs): + self._vport_name = vport_name + + def write(buffer): + return len(buffer) + + def read(size): + return "" + + +_registered_ports = {} + + +def get_test_port(vport_name): + return _registered_ports.get(vport_name, TestPort(vport_name)) + + +def add_test_port(vport_name, port): + assert(isinstance(port, TestPort)) + _registered_ports[vport_name] = port diff --git a/tests/testrunner.py b/tests/testrunner.py new file mode 100644 index 0000000..6c7f822 --- /dev/null +++ b/tests/testrunner.py @@ -0,0 +1,68 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import os +import sys +import unittest + +from nose import config +from nose import core +from nose import result + +from VirtIoChannel import VirtIoStream + + +class GuestAgentTestCase(unittest.TestCase): + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + self.log = logging.getLogger(self.__class__.__name__) + + def assertRaises(self, exceptions, callable, *args, **kwargs): + passed = False + try: + callable(*args, **kwargs) + except exceptions: + passed = True + self.assertTrue(passed) + + +class GuestAgentTestRunner(core.TextTestRunner): + def __init__(self, *args, **kwargs): + core.TextTestRunner.__init__(self, *args, **kwargs) + + def _makeResult(self): + return result.TextTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + def run(self, test): + result_ = core.TextTestRunner.run(self, test) + return result_ + + +def run(): + argv = sys.argv + stream = sys.stdout + verbosity = 3 + testdir = os.path.dirname(os.path.abspath(__file__)) + + conf = config.Config(stream=stream, + env=os.environ, + verbosity=verbosity, + workingDir=testdir, + plugins=core.DefaultPluginManager()) + + runner = GuestAgentTestRunner(stream=conf.stream, + verbosity=conf.verbosity, + config=conf) + + sys.exit(not core.run(config=conf, testRunner=runner, argv=argv)) + + +if __name__ == '__main__': + # We're ensuring VirtIoStream is monkey patched to unit test output mode + # which requires no VirtIO Channel to be present + VirtIoStream.is_test = True + run() diff --git a/tests/unittest.bat b/tests/unittest.bat new file mode 100644 index 0000000..7bca3ca --- /dev/null +++ b/tests/unittest.bat @@ -0,0 +1,5 @@ +@echo off +REM Run unittests for Windows + +set PYTHONPATH=%PYTHONPATH%;../ovirt-guest-agent;.; +python testrunner.py win32_guest_agent_test.py encoding_test.py -- To view, visit http://gerrit.ovirt.org/19052 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I4c1d740c948e33aed8a04a1013b22276cf7d85e3 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-guest-agent Gerrit-Branch: master Gerrit-Owner: Vinzenz Feenstra <vfeen...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches