Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Convenience classes and functions for unit testing""" 

2import unittest 

3import sys 

4import os 

5import importlib 

6 

7 

8def generate_example_tests(path, testclasses, solvers=None, newtest_fn=None): 

9 """ 

10 Mutate TestCase class so it behaves as described in TestExamples docstring 

11 

12 Arguments 

13 --------- 

14 path : str 

15 directory containing example modules to test 

16 testclass : class 

17 class that inherits from `unittest.TestCase` 

18 newtest_fn : function 

19 function that returns new tests. defaults to import_test_and_log_output 

20 solvers : iterable 

21 solvers to run for; or only for default if solvers is None 

22 """ 

23 import_dict = {} 

24 if newtest_fn is None: 

25 newtest_fn = new_test 

26 tests = [] 

27 for testclass in testclasses: 

28 if os.path.isdir(path): 

29 sys.path.insert(0, path) 

30 for fn in dir(testclass): 

31 if fn[:5] == "test_": 

32 name = fn[5:] 

33 old_test = getattr(testclass, fn) 

34 setattr(testclass, name, old_test) # move to a non-test fn 

35 delattr(testclass, fn) # delete the old old_test 

36 for solver in solvers: 

37 new_name = "test_%s_%s" % (name, solver) 

38 new_fn = newtest_fn(name, solver, import_dict, path) 

39 setattr(testclass, new_name, new_fn) 

40 tests.append(testclass) 

41 return tests 

42 

43 

44def new_test(name, solver, import_dict, path, testfn=None): 

45 """logged_example_testcase with a NewDefaultSolver""" 

46 if testfn is None: 

47 testfn = logged_example_testcase 

48 

49 def test(self): 

50 # pylint: disable=missing-docstring 

51 # No docstring because it'd be uselessly the same for each example 

52 

53 import gpkit 

54 with NewDefaultSolver(solver): 

55 testfn(name, import_dict, path)(self) 

56 

57 # clear modelnums to ensure deterministic script-like output! 

58 gpkit.globals.NamedVariables.reset_modelnumbers() 

59 

60 # check all global state is falsy 

61 for globname, global_thing in [ 

62 ("model numbers", gpkit.globals.NamedVariables.modelnums), 

63 ("lineage", gpkit.NamedVariables.lineage), 

64 ("signomials enabled", gpkit.SignomialsEnabled), 

65 ("vectorization", gpkit.Vectorize.vectorization), 

66 ("namedvars", gpkit.NamedVariables.namedvars)]: 

67 if global_thing: # pragma: no cover 

68 raise ValueError("global attribute %s should have been" 

69 " falsy after the test, but was instead %s" 

70 % (globname, global_thing)) 

71 return test 

72 

73 

74def logged_example_testcase(name, imported, path): 

75 """Returns a method for attaching to a unittest.TestCase that imports 

76 or reloads module 'name' and stores in imported[name]. 

77 Runs top-level code, which is typically a docs example, in the process. 

78 

79 Returns a method. 

80 """ 

81 def test(self): 

82 # pylint: disable=missing-docstring 

83 # No docstring because it'd be uselessly the same for each example 

84 filepath = ("".join([path, os.sep, "%s_output.txt" % name]) 

85 if name not in imported else None) 

86 with StdoutCaptured(logfilepath=filepath): 

87 imported[name] = importlib.import_module(name) 

88 getattr(self, name)(imported[name]) 

89 return test 

90 

91 

92def run_tests(tests, xmloutput=None, verbosity=2): 

93 """Default way to run tests, to be used in __main__. 

94 

95 Arguments 

96 --------- 

97 tests: iterable of unittest.TestCase 

98 xmloutput: string or None 

99 if not None, generate xml output for continuous integration, 

100 with name given by the input string 

101 verbosity: int 

102 verbosity level for unittest.TextTestRunner 

103 """ 

104 suite = unittest.TestSuite() 

105 loader = unittest.TestLoader() 

106 for t in tests: 

107 suite.addTests(loader.loadTestsFromTestCase(t)) 

108 if xmloutput: 

109 import xmlrunner # pylint: disable=import-error 

110 xmlrunner.XMLTestRunner(output=xmloutput).run(suite) 

111 else: # pragma: no cover 

112 unittest.TextTestRunner(verbosity=verbosity).run(suite) 

113 

114 

115class NullFile: 

116 "A fake file interface that does nothing" 

117 def write(self, string): 

118 "Do not write, do not pass go." 

119 

120 def close(self): 

121 "Having not written, cease." 

122 

123 

124class NewDefaultSolver: 

125 "Creates an environment with a different default solver" 

126 def __init__(self, solver): 

127 self.solver = solver 

128 self.prev_default_solver = None 

129 

130 def __enter__(self): 

131 "Change default solver." 

132 import gpkit 

133 self.prev_default_solver = gpkit.settings["default_solver"] 

134 gpkit.settings["default_solver"] = self.solver 

135 

136 def __exit__(self, *args): 

137 "Reset default solver." 

138 import gpkit 

139 gpkit.settings["default_solver"] = self.prev_default_solver 

140 

141 

142class StdoutCaptured: 

143 "Puts everything that would have printed to stdout in a log file instead" 

144 def __init__(self, logfilepath=None): 

145 self.logfilepath = logfilepath 

146 self.original_stdout = None 

147 self.original_unit_printing = None 

148 

149 def __enter__(self): 

150 "Capture stdout" 

151 self.original_stdout = sys.stdout 

152 sys.stdout = (open(self.logfilepath, mode="w") 

153 if self.logfilepath else NullFile()) 

154 

155 def __exit__(self, *args): 

156 "Return stdout" 

157 sys.stdout.close() 

158 sys.stdout = self.original_stdout