Skip to content

Commit 569a535

Browse files
committed
Embellish fixture assertions with the path and carve out fixture support.
1 parent d00442f commit 569a535

6 files changed

Lines changed: 175 additions & 93 deletions

tests/support/__init__.py

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import difflib
3333
import errno
3434
import io
35-
import json
3635
import logging
3736
import os
3837
import shutil
@@ -41,7 +40,7 @@
4140
from unittest import TestCase, main as testmain
4241

4342
from tests.support.configsupp import FakeConfiguration
44-
from tests.support.suppconst import MIG_BASE, TEST_BASE, TEST_FIXTURE_DIR, \
43+
from tests.support.suppconst import MIG_BASE, TEST_BASE, \
4544
TEST_DATA_DIR, TEST_OUTPUT_DIR, ENVHELP_OUTPUT_DIR
4645

4746
from tests.support._env import MIG_ENV, PY2
@@ -237,6 +236,9 @@ def assert_over(self, values=None, _AssertOver=AssertOver):
237236
self._register_check(check_callable)
238237
return assert_over
239238

239+
def temppath(self, relative_path, **kwargs):
240+
return temppath(relative_path, self, **kwargs)
241+
240242
# custom assertions available for common use
241243

242244
def assertFileContentIdentical(self, file_actual, file_expected):
@@ -316,81 +318,6 @@ def ensure_dirs_exist(absolute_dir):
316318
return absolute_dir
317319

318320

319-
def fixturefile(relative_path, fixture_format=None, include_path=False):
320-
"""Support function for loading fixtures from their serialised format.
321-
322-
Doing so is a little more involved than it may seem because serialisation
323-
formats may not capture various nuances of the python data they represent.
324-
For this reason each supported format defers to a format specific function
325-
which can then, for example, load hints about deserialization.
326-
"""
327-
328-
assert fixture_format is not None, "fixture format must be specified"
329-
assert not os.path.isabs(
330-
relative_path), "fixture is not relative to fixture folder"
331-
relative_path_with_ext = "%s.%s" % (relative_path, fixture_format)
332-
tmp_path = os.path.join(TEST_FIXTURE_DIR, relative_path_with_ext)
333-
assert os.path.isfile(tmp_path), \
334-
"fixture file for format is not present: %s" % \
335-
(relative_path_with_ext,)
336-
#_, extension = os.path.splitext(os.path.basename(tmp_path))
337-
#assert fixture_format == extension, "fixture file does not match format"
338-
339-
data = None
340-
341-
if fixture_format == 'binary':
342-
with open(tmp_path, 'rb') as binfile:
343-
data = binfile.read()
344-
elif fixture_format == 'json':
345-
data = _fixturefile_json(tmp_path)
346-
else:
347-
raise AssertionError(
348-
"unsupported fixture format: %s" % (fixture_format,))
349-
350-
return (data, tmp_path) if include_path else data
351-
352-
353-
def fixturefile_normname(relative_path, prefix=''):
354-
"""Grab normname from relative_path and optionally add a path prefix"""
355-
normname, _ = relative_path.split('--')
356-
if prefix:
357-
return os.path.join(prefix, normname)
358-
return normname
359-
360-
361-
_FIXTUREFILE_HINTAPPLIERS = {
362-
'array_of_tuples': lambda value: [tuple(x) for x in value]
363-
}
364-
365-
366-
def _fixturefile_json(json_path):
367-
hints = ConfigParser()
368-
369-
# let's see if there are loading hints
370-
try:
371-
hints_path = "%s.ini" % (json_path,)
372-
with open(hints_path) as hints_file:
373-
hints.read_file(hints_file)
374-
except FileNotFoundError:
375-
pass
376-
377-
with io.open(json_path) as json_file:
378-
json_object = json.load(json_file)
379-
380-
for item_name, item_hint in hints['DEFAULT'].items():
381-
loaded_value = json_object[item_name]
382-
value_from_loaded_value = _FIXTUREFILE_HINTAPPLIERS[item_hint]
383-
json_object[item_name] = value_from_loaded_value(loaded_value)
384-
385-
return json_object
386-
387-
388-
def fixturepath(relative_path):
389-
"""Get absolute fixture path for relative_path"""
390-
tmp_path = os.path.join(TEST_FIXTURE_DIR, relative_path)
391-
return tmp_path
392-
393-
394321
def temppath(relative_path, test_case, ensure_dir=False, skip_clean=False):
395322
"""Register relative_path as a temp path and schedule automatic clean up
396323
after unit tests unless skip_clean is set. Anchors the temp path in

tests/support/fixturesupp.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# --- BEGIN_HEADER ---
5+
#
6+
# configsupp - configuration helpers for unit tests
7+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
8+
#
9+
# This file is part of MiG.
10+
#
11+
# MiG is free software: you can redistribute it and/or modify
12+
# it under the terms of the GNU General Public License as published by
13+
# the Free Software Foundation; either version 2 of the License, or
14+
# (at your option) any later version.
15+
#
16+
# MiG is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program; if not, write to the Free Software
23+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24+
#
25+
# -- END_HEADER ---
26+
#
27+
28+
"""Fixture related details within the test support library."""
29+
30+
from configparser import ConfigParser
31+
import io
32+
import json
33+
import os
34+
import shutil
35+
36+
from tests.support.suppconst import MIG_BASE, TEST_FIXTURE_DIR
37+
38+
39+
_FIXTUREFILE_HINTAPPLIERS = {
40+
'array_of_tuples': lambda value: [tuple(x) for x in value]
41+
}
42+
43+
44+
def _to_display_path(value):
45+
display_path = os.path.relpath(value, MIG_BASE)
46+
if not display_path.startswith('.'):
47+
return "./" + display_path
48+
return display_path
49+
50+
51+
def _fixturefile(relative_path, fixture_format=None):
52+
"""Support function for loading fixtures from their serialised format.
53+
54+
Doing so is a little more involved than it may seem because serialisation
55+
formats may not capture various nuances of the python data they represent.
56+
For this reason each supported format defers to a format specific function
57+
which can then, for example, load hints about deserialization.
58+
"""
59+
60+
assert fixture_format is not None, "fixture format must be specified"
61+
assert not os.path.isabs(
62+
relative_path), "fixture is not relative to fixture folder"
63+
relative_path_with_ext = "%s.%s" % (relative_path, fixture_format)
64+
tmp_path = os.path.join(TEST_FIXTURE_DIR, relative_path_with_ext)
65+
assert os.path.isfile(tmp_path), \
66+
"fixture file for format is not present: %s" % \
67+
(relative_path_with_ext,)
68+
69+
data = None
70+
71+
if fixture_format == 'binary':
72+
with open(tmp_path, 'rb') as binfile:
73+
data = binfile.read()
74+
elif fixture_format == 'json':
75+
data = _fixturefile_json(tmp_path)
76+
else:
77+
raise AssertionError(
78+
"unsupported fixture format: %s" % (fixture_format,))
79+
80+
return data, tmp_path
81+
82+
83+
def _fixturefile_json(json_path):
84+
hints = ConfigParser()
85+
86+
# let's see if there are loading hints
87+
try:
88+
hints_path = "%s.ini" % (json_path,)
89+
with open(hints_path) as hints_file:
90+
hints.read_file(hints_file)
91+
except FileNotFoundError:
92+
pass
93+
94+
with io.open(json_path) as json_file:
95+
json_object = json.load(json_file)
96+
97+
for item_name, item_hint in hints['DEFAULT'].items():
98+
loaded_value = json_object[item_name]
99+
value_from_loaded_value = _FIXTUREFILE_HINTAPPLIERS[item_hint]
100+
json_object[item_name] = value_from_loaded_value(loaded_value)
101+
102+
return json_object
103+
104+
105+
def _fixturefile_normname(relative_path, prefix=''):
106+
"""Grab normname from relative_path and optionally add a path prefix"""
107+
normname, _ = relative_path.split('--')
108+
if prefix:
109+
return os.path.join(prefix, normname)
110+
return normname
111+
112+
113+
class _AssertAgainstFixture:
114+
def __init__(self, testcase, fixture_format, fixture_data, fixture_path):
115+
self._testcase = testcase
116+
self._fixture_format = fixture_format
117+
self._fixture_data = fixture_data
118+
self._fixture_path = fixture_path
119+
120+
def assertAgainstFixture(self, value=None):
121+
assert value is not None
122+
originalMaxDiff = self._testcase.maxDiff
123+
self._testcase.maxDiff = None
124+
try:
125+
self._testcase.assertEqual(value, self._fixture_data)
126+
except AssertionError as diffexc:
127+
message = "value differed from fixture stored at %s\n\n%s" % (
128+
_to_display_path(self._fixture_path), diffexc)
129+
raise AssertionError(message)
130+
finally:
131+
self._testcase.maxDiff = originalMaxDiff
132+
133+
def copy_as_temp(self, prefix=None):
134+
assert prefix is not None
135+
fixture_basename = os.path.basename(self._fixture_path)
136+
fixture_name = fixture_basename[0:-len(self._fixture_format) - 1]
137+
normalised_path = _fixturefile_normname(fixture_name, prefix=prefix)
138+
copied_fixture_file = self._testcase.temppath(normalised_path)
139+
shutil.copyfile(self._fixture_path, copied_fixture_file)
140+
return copied_fixture_file
141+
142+
143+
class FixtureAssertMixin:
144+
def prepareFixtureAssert(self, fixture_relpath, fixture_format=None):
145+
fixture_data, fixture_path = _fixturefile(
146+
fixture_relpath, fixture_format)
147+
return _AssertAgainstFixture(self, fixture_format, fixture_data, fixture_path)
148+
149+
150+
def fixturepath(relative_path):
151+
"""Get absolute fixture path for relative_path"""
152+
tmp_path = os.path.join(TEST_FIXTURE_DIR, relative_path)
153+
return tmp_path

tests/test_mig_shared_configuration.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
import os
3232
import unittest
3333

34-
from tests.support import MigTestCase, TEST_DATA_DIR, PY2, testmain, fixturefile
34+
from tests.support import MigTestCase, TEST_DATA_DIR, PY2, testmain
35+
from tests.support.fixturesupp import FixtureAssertMixin
3536
from mig.shared.configuration import Configuration
3637

3738

@@ -44,7 +45,7 @@ def _to_dict(obj):
4445
if not (k.startswith('__') or _is_method(v))}
4546

4647

47-
class MigSharedConfiguration(MigTestCase):
48+
class MigSharedConfiguration(MigTestCase, FixtureAssertMixin):
4849
"""Wrap unit tests for the corresponding module"""
4950

5051
def test_argument_storage_protocols(self):
@@ -71,7 +72,7 @@ def test_argument_wwwserve_max_bytes(self):
7172

7273
@unittest.skipIf(PY2, "Python 3 only")
7374
def test_default_object(self):
74-
expected_values = fixturefile(
75+
prepared_fixture = self.prepareFixtureAssert(
7576
'mig_shared_configuration--new', fixture_format='json')
7677

7778
configuration = Configuration(None)
@@ -84,8 +85,7 @@ def test_default_object(self):
8485

8586
actual_values = _to_dict(configuration)
8687

87-
self.maxDiff = None
88-
self.assertEqual(actual_values, expected_values)
88+
prepared_fixture.assertAgainstFixture(value=actual_values)
8989

9090
def test_object_isolation(self):
9191
configuration_1 = Configuration(None)

tests/test_mig_shared_functionality_cat.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
import unittest
3636

3737
from tests.support import MIG_BASE, PY2, TEST_DATA_DIR, MigTestCase, testmain, \
38-
fixturefile, fixturefile_normname, ensure_dirs_exist, temppath
38+
temppath, ensure_dirs_exist
39+
from tests.support.fixturesupp import FixtureAssertMixin
3940

4041
from mig.shared.base import client_id_dir
4142
from mig.shared.functionality.cat import _main as submain, main as realmain
@@ -60,7 +61,7 @@ def _only_output_objects(output_objects, with_object_type=None):
6061
return [o for o in output_objects if o['object_type'] == with_object_type]
6162

6263

63-
class MigSharedFunctionalityCat(MigTestCase):
64+
class MigSharedFunctionalityCat(MigTestCase, FixtureAssertMixin):
6465
"""Wrap unit tests for the corresponding module"""
6566

6667
TEST_CLIENT_ID = '/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/emailAddress=test@example.com'
@@ -78,13 +79,12 @@ def before_each(self):
7879

7980
conf_user_db_home = ensure_dirs_exist(self.configuration.user_db_home)
8081
temppath(conf_user_db_home, self)
81-
db_fixture, db_fixture_file = fixturefile('MiG-users.db--example',
82-
fixture_format='binary',
83-
include_path=True)
84-
test_db_file = temppath(fixturefile_normname('MiG-users.db--example',
85-
prefix=conf_user_db_home),
86-
self)
87-
shutil.copyfile(db_fixture_file, test_db_file)
82+
prepared_fixture = self.prepareFixtureAssert(
83+
'MiG-users.db--example',
84+
fixture_format='binary',
85+
)
86+
87+
test_db_file = prepared_fixture.copy_as_temp(prefix=conf_user_db_home)
8888

8989
# create the test user home directory
9090
self.test_user_dir = ensure_dirs_exist(test_user_dir)

tests/test_mig_shared_install.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
import sys
3838

3939
from tests.support import MIG_BASE, TEST_OUTPUT_DIR, MigTestCase, \
40-
testmain, temppath, cleanpath, fixturepath, is_path_within
40+
testmain, temppath, cleanpath, is_path_within
41+
from tests.support.fixturesupp import fixturepath
4142

4243
from mig.shared.defaults import keyword_auto
4344
from mig.shared.install import determine_timezone, generate_confs

tests/test_mig_shared_localfile.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
sys.path.append(os.path.realpath(
3737
os.path.join(os.path.dirname(__file__), "..")))
3838

39-
from tests.support import MigTestCase, fixturepath, temppath, testmain
39+
from tests.support import MigTestCase, temppath, testmain
40+
from tests.support.fixturesupp import fixturepath
4041
from mig.shared.serverfile import LOCK_EX
4142
from mig.shared.localfile import LocalFile
4243

0 commit comments

Comments
 (0)