Skip to content

Commit 3c15f06

Browse files
committed
Split the support library into separate files within a module directory.
1 parent 7711890 commit 3c15f06

File tree

4 files changed

+211
-113
lines changed

4 files changed

+211
-113
lines changed

tests/support.py renamed to tests/support/__init__.py

Lines changed: 10 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#
44
# --- BEGIN_HEADER ---
55
#
6-
# support - helper functions for unit testing
76
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
87
#
98
# This file is part of MiG.
@@ -27,22 +26,18 @@
2726

2827
"""Supporting functions for the unit test framework"""
2928

30-
from collections import defaultdict
3129
import difflib
3230
import errno
3331
import io
3432
import logging
3533
import os
36-
import re
3734
import shutil
3835
import stat
3936
import sys
4037
from unittest import TestCase, main as testmain
4138

42-
TEST_BASE = os.path.dirname(__file__)
43-
TEST_FIXTURE_DIR = os.path.join(TEST_BASE, "fixture")
44-
TEST_OUTPUT_DIR = os.path.join(TEST_BASE, "output")
45-
MIG_BASE = os.path.realpath(os.path.join(TEST_BASE, ".."))
39+
from tests.support.suppconst import MIG_BASE, \
40+
TEST_BASE, TEST_FIXTURE_DIR, TEST_OUTPUT_DIR
4641
PY2 = sys.version_info[0] == 2
4742

4843
# force defaults to a local environment
@@ -63,6 +58,12 @@
6358
shutil.rmtree(TEST_OUTPUT_DIR)
6459
os.mkdir(TEST_OUTPUT_DIR)
6560

61+
# Exports to expose at the top level from the support library.
62+
63+
from tests.support.configsupp import FakeConfiguration
64+
from tests.support.loggersupp import FakeLogger
65+
66+
6667
# Basic global logging configuration for testing
6768

6869

@@ -80,111 +81,6 @@ def write(self, message):
8081
logging.captureWarnings(True)
8182

8283

83-
class FakeLogger:
84-
"""An output capturing logger suitable for being passed to the
85-
majority of MiG code by presenting an API compatible interface
86-
with the common logger module.
87-
88-
An instance of this class is made available to test cases which
89-
can pass it down into function calls and subsequenently make
90-
assertions against any output strings hat were recorded during
91-
execution while also avoiding noise hitting the console.
92-
"""
93-
94-
RE_UNCLOSEDFILE = re.compile(
95-
'unclosed file <.*? name=\'(?P<location>.*?)\'( .*?)?>')
96-
97-
def __init__(self):
98-
self.channels_dict = defaultdict(list)
99-
self.forgive_by_channel = defaultdict(lambda: False)
100-
self.unclosed_by_file = defaultdict(list)
101-
102-
def _append_as(self, channel, line):
103-
self.channels_dict[channel].append(line)
104-
105-
def check_empty_and_reset(self):
106-
channels_dict = self.channels_dict
107-
forgive_by_channel = self.forgive_by_channel
108-
unclosed_by_file = self.unclosed_by_file
109-
110-
# reset the record of any logged messages
111-
self.channels_dict = defaultdict(list)
112-
self.forgive_by_channel = defaultdict(lambda: False)
113-
self.unclosed_by_file = defaultdict(list)
114-
115-
# complain loudly (and in detail) in the case of unclosed files
116-
if len(unclosed_by_file) > 0:
117-
messages = '\n'.join({' --> %s: line=%s, file=%s' % (fname, lineno, outname)
118-
for fname, (lineno, outname) in unclosed_by_file.items()})
119-
raise RuntimeError('unclosed files encountered:\n%s' % (messages,))
120-
121-
if channels_dict['error'] and not forgive_by_channel['error']:
122-
raise RuntimeError('errors reported to logger:\n%s' % '\n'.join(channels_dict['error']))
123-
124-
125-
def forgive_errors(self):
126-
self.forgive_by_channel['error'] = True
127-
128-
# logger interface
129-
130-
def debug(self, line):
131-
self._append_as('debug', line)
132-
133-
def error(self, line):
134-
self._append_as('error', line)
135-
136-
def info(self, line):
137-
self._append_as('info', line)
138-
139-
def warning(self, line):
140-
self._append_as('warning', line)
141-
142-
def write(self, message):
143-
channel, namespace, specifics = message.split(':', 2)
144-
145-
# ignore everything except warnings sent by the python runtime
146-
if not (channel == 'WARNING' and namespace == 'py.warnings'):
147-
return
148-
149-
filename_and_datatuple = FakeLogger.identify_unclosed_file(specifics)
150-
if filename_and_datatuple is not None:
151-
self.unclosed_by_file.update((filename_and_datatuple,))
152-
153-
@staticmethod
154-
def identify_unclosed_file(specifics):
155-
filename, lineno, exc_name, message = specifics.split(':', 3)
156-
157-
exc_name = exc_name.lstrip()
158-
if exc_name != 'ResourceWarning':
159-
return
160-
161-
matched = FakeLogger.RE_UNCLOSEDFILE.match(message.lstrip())
162-
if matched is None:
163-
return
164-
165-
relative_testfile = os.path.relpath(filename, start=MIG_BASE)
166-
relative_outputfile = os.path.relpath(
167-
matched.groups('location')[0], start=TEST_BASE)
168-
return (relative_testfile, (lineno, relative_outputfile))
169-
170-
171-
class FakeConfiguration:
172-
"""A simple helper to pretend we have a real Configuration object with any
173-
required attributes explicitly passed.
174-
Automatically attaches a FakeLogger instance if no logger is provided in
175-
kwargs.
176-
"""
177-
178-
def __init__(self, **kwargs):
179-
"""Initialise instance attributes to be any named args provided and a
180-
FakeLogger instance attached if not provided.
181-
"""
182-
self.__dict__.update(kwargs)
183-
if not 'logger' in self.__dict__:
184-
dummy_logger = FakeLogger()
185-
self.__dict__.update({'logger': dummy_logger})
186-
187-
18884
class MigTestCase(TestCase):
18985
"""Embellished base class for MiG test cases. Provides additional commonly
19086
used assertions as well as some basics for the standardised and idiomatic
@@ -303,7 +199,8 @@ def cleanpath(relative_path, test_case, ensure_dir=False):
303199
try:
304200
os.mkdir(tmp_path)
305201
except FileExistsError:
306-
raise AssertionError("ABORT: use of unclean output path: %s" % relative_path)
202+
raise AssertionError(
203+
"ABORT: use of unclean output path: %s" % relative_path)
307204
test_case._cleanup_paths.add(tmp_path)
308205
return tmp_path
309206

tests/support/configsupp.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# --- BEGIN_HEADER ---
5+
#
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23+
#
24+
# -- END_HEADER ---
25+
#
26+
27+
"""Configuration related details within the test support library."""
28+
29+
from tests.support.loggersupp import FakeLogger
30+
31+
32+
class FakeConfiguration:
33+
"""A simple helper to pretend we have a real Configuration object with any
34+
required attributes explicitly passed.
35+
Automatically attaches a FakeLogger instance if no logger is provided in
36+
kwargs.
37+
"""
38+
39+
def __init__(self, **kwargs):
40+
"""Initialise instance attributes to be any named args provided and a
41+
FakeLogger instance attached if not provided.
42+
"""
43+
self.__dict__.update(kwargs)
44+
if not 'logger' in self.__dict__:
45+
dummy_logger = FakeLogger()
46+
self.__dict__.update({'logger': dummy_logger})

tests/support/loggersupp.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# --- BEGIN_HEADER ---
5+
#
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23+
#
24+
# -- END_HEADER ---
25+
#
26+
27+
"""Logger related details within the test support library."""
28+
29+
from collections import defaultdict
30+
import os
31+
import re
32+
33+
from tests.support.suppconst import MIG_BASE, TEST_BASE
34+
35+
36+
class FakeLogger:
37+
"""An output capturing logger suitable for being passed to the
38+
majority of MiG code by presenting an API compatible interface
39+
with the common logger module.
40+
41+
An instance of this class is made available to test cases which
42+
can pass it down into function calls and subsequenently make
43+
assertions against any output strings hat were recorded during
44+
execution while also avoiding noise hitting the console.
45+
"""
46+
47+
RE_UNCLOSEDFILE = re.compile(
48+
'unclosed file <.*? name=\'(?P<location>.*?)\'( .*?)?>')
49+
50+
def __init__(self):
51+
self.channels_dict = defaultdict(list)
52+
self.forgive_by_channel = defaultdict(lambda: False)
53+
self.unclosed_by_file = defaultdict(list)
54+
55+
def _append_as(self, channel, line):
56+
self.channels_dict[channel].append(line)
57+
58+
def check_empty_and_reset(self):
59+
channels_dict = self.channels_dict
60+
forgive_by_channel = self.forgive_by_channel
61+
unclosed_by_file = self.unclosed_by_file
62+
63+
# reset the record of any logged messages
64+
self.channels_dict = defaultdict(list)
65+
self.forgive_by_channel = defaultdict(lambda: False)
66+
self.unclosed_by_file = defaultdict(list)
67+
68+
# complain loudly (and in detail) in the case of unclosed files
69+
if len(unclosed_by_file) > 0:
70+
messages = '\n'.join({' --> %s: line=%s, file=%s' % (fname, lineno, outname)
71+
for fname, (lineno, outname) in unclosed_by_file.items()})
72+
raise RuntimeError('unclosed files encountered:\n%s' % (messages,))
73+
74+
if channels_dict['error'] and not forgive_by_channel['error']:
75+
raise RuntimeError('errors reported to logger:\n%s' % '\n'.join(channels_dict['error']))
76+
77+
78+
def forgive_errors(self):
79+
self.forgive_by_channel['error'] = True
80+
81+
# logger interface
82+
83+
def debug(self, line):
84+
self._append_as('debug', line)
85+
86+
def error(self, line):
87+
self._append_as('error', line)
88+
89+
def info(self, line):
90+
self._append_as('info', line)
91+
92+
def warning(self, line):
93+
self._append_as('warning', line)
94+
95+
def write(self, message):
96+
channel, namespace, specifics = message.split(':', 2)
97+
98+
# ignore everything except warnings sent by the python runtime
99+
if not (channel == 'WARNING' and namespace == 'py.warnings'):
100+
return
101+
102+
filename_and_datatuple = FakeLogger.identify_unclosed_file(specifics)
103+
if filename_and_datatuple is not None:
104+
self.unclosed_by_file.update((filename_and_datatuple,))
105+
106+
@staticmethod
107+
def identify_unclosed_file(specifics):
108+
filename, lineno, exc_name, message = specifics.split(':', 3)
109+
110+
exc_name = exc_name.lstrip()
111+
if exc_name != 'ResourceWarning':
112+
return
113+
114+
matched = FakeLogger.RE_UNCLOSEDFILE.match(message.lstrip())
115+
if matched is None:
116+
return
117+
118+
relative_testfile = os.path.relpath(filename, start=MIG_BASE)
119+
relative_outputfile = os.path.relpath(
120+
matched.groups('location')[0], start=TEST_BASE)
121+
return (relative_testfile, (lineno, relative_outputfile))

tests/support/suppconst.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# --- BEGIN_HEADER ---
5+
#
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23+
#
24+
# -- END_HEADER ---
25+
#
26+
27+
import os
28+
29+
30+
_SUPPORT_DIR = os.path.dirname(os.path.abspath(__file__)) # use of abspath for __file__ on Py2
31+
TEST_BASE = os.path.normpath(os.path.join(_SUPPORT_DIR, ".."))
32+
TEST_FIXTURE_DIR = os.path.join(TEST_BASE, "fixture")
33+
TEST_OUTPUT_DIR = os.path.join(TEST_BASE, "output")
34+
MIG_BASE = os.path.realpath(os.path.join(TEST_BASE, ".."))

0 commit comments

Comments
 (0)