Skip to content

Commit

Permalink
Testlib enhancements
Browse files Browse the repository at this point in the history
GitHub: closes xapi-project#172 on xapi-project/sm
  • Loading branch information
chandrikas committed Jun 10, 2014
2 parents a5c0da5 + b90da6e commit b56821c
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 17 deletions.
3 changes: 1 addition & 2 deletions drivers/xs_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ class XenError(Exception):
def __init__(self, key, opterr=None):
# Check the XML definition file exists
if not os.path.exists(XML_DEFS):
print "No XML def file found"
raise Exception.__init__(self, '')
raise Exception("No XML def file found")

# Read the definition list
self._fromxml('SM-errorcodes')
Expand Down
3 changes: 0 additions & 3 deletions tests/install_prerequisites_for_python_unittests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,3 @@ else
apt-get update
apt-get -qy install python-dev python-virtualenv
fi

# Install other dependences
apt-get -qy install libxen-dev make
2 changes: 1 addition & 1 deletion tests/run_python_unittests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ set -u

(
cd "$SMROOT"
PYTHONPATH="$SMROOT/snapwatchd:$SMROOT/drivers/" \
PYTHONPATH="$SMROOT/drivers/" \
coverage run $(which nosetests) \
--with-xunit \
--xunit-file=nosetests.xml \
Expand Down
7 changes: 0 additions & 7 deletions tests/setup_env_for_python_unittests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,3 @@ pip install nose
pip install coverage
pip install xenapi
pip install mock

TEMPDIR=$(mktemp -d)
# build xslib.py
# I need -fPIC otherwise I get "relocation R_X86_64_32 against" type errors
PYTHONLIBS=$(dirname $(find /usr/include/ -maxdepth 2 -path \*/python\*/Python.h -type f | head -1))
make -C "$SMROOT/snapwatchd" DESTDIR=$TEMPDIR CFLAGS="-O2 -I${PYTHONLIBS}/ -I/usr/include -shared -fPIC"
rm -rf "$TEMPDIR"
141 changes: 141 additions & 0 deletions tests/test_testlib.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
import os
import mock

import testlib

Expand Down Expand Up @@ -135,6 +136,7 @@ def test_exists_returns_true_for_root(self, context):

@testlib.with_context
def test_error_codes_read(self, context):
context.setup_error_codes()
errorcodes_file = open('/opt/xensource/sm/XE_SR_ERRORCODES.xml', 'rb')
errorcodes = errorcodes_file.read()
errorcodes_file.close()
Expand Down Expand Up @@ -186,6 +188,132 @@ def test_modinfo(self, context):
self.assertEquals('somemodule-description', out)
self.assertEquals('', err)

@testlib.with_context
def test_makedirs_mocked_out(self, context):
import os

os.makedirs('/blah/subdir')

self.assertTrue(os.path.exists('/blah/subdir'))

@testlib.with_context
def test_makedirs_raises_if_exists(self, context):
import os

os.makedirs('/blah/subdir')

self.assertRaises(OSError, os.makedirs, '/blah/subdir')

@testlib.with_context
def test_setup_error_codes(self, context):
context.setup_error_codes()

self.assertTrue(
os.path.exists('/opt/xensource/sm/XE_SR_ERRORCODES.xml'))

@testlib.with_context
def test_write_a_file(self, context):
import os

os.makedirs('/blah/subdir')

f = open('/blah/subdir/somefile', 'w+')
f.write('hello')
f.close()

self.assertTrue(
('/blah/subdir/somefile', 'hello')
in list(context.generate_path_content()))

@testlib.with_context
def test_file_returns_an_object_with_fileno_callable(self, context):
f = file('/file', 'w+')

self.assertTrue(hasattr(f, 'fileno'))
self.assertTrue(callable(f.fileno))

@testlib.with_context
def test_filenos_are_unique(self, context):
import os

os.makedirs('/blah/subdir')

file_1 = file('/blah/subdir/somefile', 'w+')
fileno_1 = file_1.fileno()

file_2 = file('/blah/subdir/somefile2', 'w+')
fileno_2 = file_2.fileno()

self.assertTrue(fileno_1 != fileno_2)

def test_get_created_directories(self):
context = testlib.TestContext()

context.fake_makedirs('/some/path')

self.assertEquals([
'/',
'/some',
'/some/path'],
context.get_created_directories())

def test_popen_raises_error(self):
import subprocess
context = testlib.TestContext()

self.assertRaises(
testlib.ContextSetupError,
context.fake_popen,
['something'],
subprocess.PIPE,
subprocess.PIPE,
subprocess.PIPE,
True
)

def test_glob_requests_logged(self):
context = testlib.TestContext()
context.log = mock.Mock()

context.fake_glob('/dir/*')

self.assertEquals(
[
mock.call('no glob', '/dir/*'),
],
context.log.mock_calls
)

def test_fake_open_logged(self):
context = testlib.TestContext()
context.log = mock.Mock()

try:
context.fake_open('/nonexisting_file', 'r')
except:
pass

self.assertEquals(
[
mock.call('tried to open file', '/nonexisting_file'),
],
context.log.mock_calls
)

def test_context_stops_mocking_on_failures(self):
original_open = os.open

@testlib.with_context
def somefunction(firstparam, context):
raise Exception()

try:
somefunction(None)
except:
pass

self.assertEquals(original_open, os.open)


class TestFilesystemFor(unittest.TestCase):
def test_returns_single_item_for_root(self):
Expand All @@ -197,3 +325,16 @@ def test_returns_multiple_items_for_path(self):
fs = testlib.filesystem_for('/somedir')

self.assertEquals(['/', '/somedir'], fs)


class TestXmlMixIn(unittest.TestCase, testlib.XmlMixIn):

def test_assertXML_doesn_t_care_about_spaces(self):
self.assertXML(
"""
<?xml version="1.0" ?>
<something/>
""",
'<?xml version="1.0" ?><something/>')
29 changes: 29 additions & 0 deletions tests/test_xs_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import unittest

import testlib

import xs_errors


class TestXenError(unittest.TestCase):
@testlib.with_context
def test_without_xml_defs(self, context):
raised_exception = None
try:
xs_errors.XenError('blah')
except Exception, e:
raised_exception = e

self.assertTrue("No XML def file found" in str(e))

@testlib.with_context
def test_xml_defs(self, context):
context.setup_error_codes()

raised_exception = None
try:
xs_errors.XenError('SRInUse')
except Exception, e:
raised_exception = e

self.assertTrue("The SR device is currently in use" in str(e))
66 changes: 62 additions & 4 deletions tests/testlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
PATHSEP = '/'


class ContextSetupError(Exception):
pass


def get_error_codes():
this_dir = os.path.dirname(__file__)
drivers_dir = os.path.join(this_dir, '..', 'drivers')
Expand Down Expand Up @@ -72,6 +76,14 @@ def __init__(self):
self.scsi_adapters = []
self.kernel_version = '3.1'
self.executables = {}
self._created_directories = []
self._path_content = {}
self._next_fileno = 0

def _get_inc_fileno(self):
result = self._next_fileno
self._next_fileno += 1
return result

def add_executable(self, fpath, funct):
self.executables[fpath] = Executable(funct)
Expand All @@ -87,7 +99,9 @@ def generate_inventory_contents(self):
def start(self):
self.patchers = [
mock.patch('__builtin__.open', new=self.fake_open),
mock.patch('__builtin__.file', new=self.fake_open),
mock.patch('os.path.exists', new=self.fake_exists),
mock.patch('os.makedirs', new=self.fake_makedirs),
mock.patch('os.listdir', new=self.fake_listdir),
mock.patch('glob.glob', new=self.fake_glob),
mock.patch('os.uname', new=self.fake_uname),
Expand All @@ -96,9 +110,20 @@ def start(self):
map(lambda patcher: patcher.start(), self.patchers)
self.setup_modinfo()

def fake_makedirs(self, path):
if path in self.get_filesystem():
raise OSError(path + " Already exists")
self._created_directories.append(path)
self.log("Recursively created directory", path)

def setup_modinfo(self):
self.add_executable('/sbin/modinfo', self.fake_modinfo)

def setup_error_codes(self):
self._path_content['/opt/xensource/sm/XE_SR_ERRORCODES.xml'] = (
self.error_codes
)

def fake_modinfo(self, args, stdin_data):
assert len(args) == 3
assert args[1] == '-d'
Expand All @@ -114,7 +139,7 @@ def fake_popen(self, args, stdin, stdout, stderr, close_fds):
path_to_executable = args[0]

if path_to_executable not in self.executables:
raise AssertionError(
raise ContextSetupError(
path_to_executable
+ ' was not found. Set it up using add_executable.'
+ ' was called with: ' + str(args))
Expand All @@ -135,13 +160,15 @@ def fake_open(self, fname, mode='r'):
if fname == '/etc/xensource-inventory':
return StringIO.StringIO(self.generate_inventory_contents())

elif fname == '/opt/xensource/sm/XE_SR_ERRORCODES.xml':
return StringIO.StringIO(self.error_codes)

for fpath, contents in self.generate_path_content():
if fpath == fname:
return StringIO.StringIO(contents)

if mode == 'w+':
if os.path.dirname(fname) in self.get_created_directories():
self._path_content[fname] = ''
return WriteableFile(self, fname, self._get_inc_fileno())

self.log('tried to open file', fname)
raise IOError(fname)

Expand Down Expand Up @@ -169,6 +196,16 @@ def get_filesystem(self):
for path in filesystem_for(executable_path):
result.add(path)

for directory in self.get_created_directories():
result.add(directory)

return sorted(result)

def get_created_directories(self):
result = set(['/'])
for created_directory in self._created_directories:
for path in filesystem_for(created_directory):
result.add(path)
return sorted(result)

def generate_path_content(self):
Expand All @@ -179,6 +216,9 @@ def generate_path_content(self):
host_class, host_id, key)
yield (path, value)

for path, value in self._path_content.iteritems():
yield (path, value)

def generate_device_paths(self):
actual_disk_letter = 'a'
for host_id, adapter in enumerate(self.scsi_adapters):
Expand Down Expand Up @@ -292,3 +332,21 @@ def assertXML(self, expected, actual):
marshalled(expected_dom),
marshalled(actual_dom)
)


class WriteableFile(object):
def __init__(self, context, fname, fileno, data=None):
self._context = context
self._fname = fname
self._file = StringIO.StringIO(data)
self._fileno = fileno

def fileno(self):
return self._fileno

def write(self, data):
return self._file.write(data)

def close(self):
self._context._path_content[self._fname] = self._file.getvalue()
self._file.close()

0 comments on commit b56821c

Please sign in to comment.