gitstats: Replace backslashes with parens to do line continuation
[git-stats.git] / src / git_stats / testing.py
blobabc66fcb7e10bbbe95a07187b47406e2c6c91a46
1 #!/usr/bin/env python
3 import unittest
4 import sys
6 class OutputCatcher:
7 """Redirects sys.stdout to self and stores all that is written to it
8 """
10 def __init__(self):
11 """Initilalizes with an empty buffer
12 """
14 self.buf = []
15 self.original = sys.stdout
17 def write(self, line):
18 """Mandatory write method that stores the written line
19 """
21 self.buf.append(line)
23 def status(self, line):
24 """Prints a status line to stdout
26 This line is written to the true stdout even if stdout is redirected.
28 Args:
29 line: The line to be written to stdout.
30 """
32 self.original.write(line)
34 def error(self, line):
35 """Prints an error line to stdout
37 This line is written to the true stdout even if stdout is redirected.
38 The line is prefix with \033[31m to make it show up in red on a terminal
39 and suffixed with \033[m to restore to regular color.
40 """
42 self.original.write("\033[31m" + line + "\033[m")
44 def good(self, line):
45 """Prints an success line to stdout
47 This line is written to the true stdout even if stdout is redirected.
48 The line is prefix with \033[32m to make it show up in green on a terminal
49 and suffixed with \033[m to restore to regular color.
50 """
52 self.original.write("\033[32m" + line + "\033[m")
54 def release(self):
55 """Restores sys.stdout to the original stdout
57 When created, or when catch is called, the current
58 sys.stdout is stored. Upon calling this method the
59 stored stdout is restored.
60 """
62 sys.stdout = self.original
64 def catch(self):
65 """Stores the current stdout and redirects it to self
66 """
68 self.original = sys.stdout
69 sys.stdout = self
71 def __str__(self):
72 """Returns all the output catched so far as one big string
73 """
75 return "".join(self.buf)
77 class GitResult(unittest.TestResult):
78 """A custom implementation to match Git's test suite's output style
79 """
81 def __init__(self, output, verbose=False):
82 """Initializes all counters to zero
83 """
85 self.output = output
86 self.shouldStop = False
87 self.verbose=False
88 self.test_count = 0
89 self.test_success = 0
90 self.test_failure = 0
91 self.test_error = 0
93 pass
95 def writeError(self, test):
96 """Outputs an error message for the specified test
97 """
99 msg = ("* FAIL %d: %s\n\t %s\n" %
100 (self.test_count, test.shortDescription(), test.__str__()))
101 self.output.error(msg)
103 def addError(self, test, err):
104 self.test_error += 1
105 self.writeError(test)
106 print(str(err[1]))
108 def addFailure(self, test, err):
109 self.test_failure += 1
110 self.writeError(test)
111 print(str(err[1]))
113 def addSuccess(self, test):
114 self.test_success += 1
115 msg = "* ok %d: %s\n" % (self.test_count, test.shortDescription())
116 self.output.status(msg)
118 def startTest(self, test):
119 # Check if the description is set
120 description = test.shortDescription()
121 if not description:
122 raise TypeError("Description should be set")
124 self.test_count += 1
126 print("* expecting success:")
127 print("\t" + test.id())
129 def wasSuccessful(self):
130 return not self.test_failure and not self.test_error
132 def testDone(self):
133 """Prints a status line
136 if self.wasSuccessful():
137 msg = "* passed all %d test(s)\n" % self.test_count
138 self.output.good(msg)
139 else:
140 msg = "* "
141 if self.test_failure:
142 msg += "failed %d test(s)" % self.test_failure
143 if self.test_error:
144 msg += " and "
146 if self.test_error:
147 msg += "errored on %d test(s)" % self.test_error
149 msg += "\n"
151 self.output.error(msg)
153 class GitTestRunner:
154 """A test running class that makes use of GitResult to run the tests
157 def run(self, test, verbose=False):
158 """Runs the specified test
161 # Redired stdout
162 catcher = OutputCatcher()
163 catcher.catch()
165 # But not if verbose
166 if self.verbose:
167 catcher.release()
169 # Set up a result object
170 result = GitResult(catcher, verbose)
172 # Run the test
173 test(result)
175 # Restore stdout
176 catcher.release()
178 # Print the test results
179 result.testDone()
181 return result
183 def printUsage(self):
184 """Prints a simple usage message
187 msg = "Unknown option.\nYou can specify -v/--verbose to be more verbose"
188 print(msg)
190 def runTests(self, args, classes):
191 """Runs the specified test in a test suite after parsing args
193 Args:
194 args: A list of arguments to parse, supported: -v/--verbose
195 tests: The tests to run
198 self.verbose = False
200 # Check for a sane args length
201 if len(args) > 2:
202 printUsage()
203 return 128
205 # Sane length, check if known argument
206 if len(args) == 2:
207 if args[1] == "-v" or args[1] == "--verbose":
208 self.verbose = True
209 else:
210 printUsage()
211 return 128
213 # Create suites from the supplied classes
214 loader = unittest.defaultTestLoader
216 suites = []
218 for testClass in classes:
219 suite = loader.loadTestsFromTestCase(testClass)
220 suites.append(suite)
222 # Create a suite for the tests
223 suite = unittest.TestSuite(suites)
225 # Run the suite
226 res = self.run(suite, verbose=self.verbose)
228 if res.wasSuccessful():
229 return 0
230 else:
231 return 1