3
3
#
4
4
# --- BEGIN_HEADER ---
5
5
#
6
- # support - helper functions for unit testing
7
6
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
8
7
#
9
8
# This file is part of MiG.
27
26
28
27
"""Supporting functions for the unit test framework"""
29
28
30
- from collections import defaultdict
31
29
import difflib
32
30
import errno
33
31
import io
34
32
import logging
35
33
import os
36
- import re
37
34
import shutil
38
35
import stat
39
36
import sys
40
37
from unittest import TestCase , main as testmain
41
38
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
46
41
PY2 = sys .version_info [0 ] == 2
47
42
48
43
# force defaults to a local environment
63
58
shutil .rmtree (TEST_OUTPUT_DIR )
64
59
os .mkdir (TEST_OUTPUT_DIR )
65
60
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
+
66
67
# Basic global logging configuration for testing
67
68
68
69
@@ -80,111 +81,6 @@ def write(self, message):
80
81
logging .captureWarnings (True )
81
82
82
83
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
-
188
84
class MigTestCase (TestCase ):
189
85
"""Embellished base class for MiG test cases. Provides additional commonly
190
86
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):
303
199
try :
304
200
os .mkdir (tmp_path )
305
201
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 )
307
204
test_case ._cleanup_paths .add (tmp_path )
308
205
return tmp_path
309
206
0 commit comments