Debug Tools
testing_utilities.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 #
5 # Licensing
6 #
7 # Unit Tests Utilities
8 # Copyright (C) 2018 Evandro Coan <https://github.com/evandrocoan>
9 #
10 # This program is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by the
12 # Free Software Foundation; either version 3 of the License, or ( at
13 # your option ) any later version.
14 #
15 # This program is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # 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, see <http://www.gnu.org/licenses/>.
22 #
23 
24 import os
25 import re
26 
27 import difflib
28 import unittest
29 import traceback
30 
31 from debug_tools import getLogger
32 log = getLogger( 127, __name__ )
33 
34 from .utilities import wrap_text
35 from .utilities import diffmatchpatch
36 from .utilities import diff_match_patch
37 from .lockable_type import LockableType
38 
39 
40 class AssertionErrorData(object):
41 
42  def __init__(self, stacktrace, message):
43  super(AssertionErrorData, self).__init__()
44  self.stacktrace = stacktrace
45  self.message = message
46 
47 
48 class MultipleAssertionFailures(unittest.TestCase):
49 
50  def __init__(self, *args, **kwargs):
51  self.verificationErrors = []
52 
53  # https://thingspython.wordpress.com/2010/09/27/another-super-wrinkle-raising-typeerror/
54  self.cached_super = super(MultipleAssertionFailures, self)
55  self.cached_super.__init__( *args, **kwargs )
56 
57  def tearDown(self):
58  self.cached_super.tearDown()
59 
60  if self.verificationErrors:
61  index = 0
62  errors = []
63 
64  for error in self.verificationErrors:
65  index += 1
66  errors.append( "%s\nAssertionError %s: %s" % ( error.stacktrace, index, error.message ) )
67 
68  self.fail( '\n\n' + "\n".join( errors ) )
69  self.verificationErrors.clear()
70 
71  def assertEqual(self, goal, results, msg=None):
72 
73  try:
74  self.cached_super.assertEqual( goal, results, msg )
75 
76  except unittest.TestCase.failureException as error:
77 
78  try:
79  goodtraces = self._goodStackTraces()
80  self.verificationErrors.append( AssertionErrorData( "\n".join( goodtraces[:-2] ), error ) )
81 
82  except Exception as exception:
83  badtraces = traceback.format_list( traceback.extract_stack() )
84  self.verificationErrors.append( AssertionErrorData( "".join( badtraces[:-2] ), str(error) + '\n' + str(exception) ) )
85 
86  def _goodStackTraces(self):
87  """
88  Get only the relevant part of stacktrace.
89  """
90  stop = False
91  found = False
92  goodtraces = []
93 
94  # stacktrace = traceback.format_exc()
95  # stacktrace = traceback.format_stack()
96  stacktrace = traceback.extract_stack()
97 
98  # https://stackoverflow.com/questions/54499367/how-to-correctly-override-testcase-assertequal
99  for stack in stacktrace:
100  filename = stack.filename
101 
102  if found and not stop and not filename.find( 'lib' ) < filename.find( 'unittest' ):
103  stop = True
104 
105  if not found and filename.find( 'lib' ) < filename.find( 'unittest' ):
106  found = True
107 
108  if stop and found:
109  stackline = ' File "%s", line %s, in %s\n %s' % ( stack.filename, stack.lineno, stack.name, stack.line )
110  goodtraces.append( stackline )
111 
112  return goodtraces
113 
114 
115 class TestingUtilities(unittest.TestCase):
116  """
117  Holds common features across all Unit Tests.
118  """
119 
120  maxDiff = None
121 
122 
123  diffMode = 1
124 
125  def __init__(self, *args, **kwargs):
126  diffMode = kwargs.pop('diffMode', -1)
127  if diffMode > -1: self.diffMode = diffMode
128 
129  super(TestingUtilities, self).__init__(*args, **kwargs)
130 
131  def setUp(self):
132  if diff_match_patch: self.addTypeEqualityFunc(str, self.assertTextEqual)
133  super(TestingUtilities, self).setUp()
134 
135  def diffMatchPatchAssertEqual(self, expected, actual, msg=None):
136  """
137  How to wrap correctly the unit testing diff?
138  https://stackoverflow.com/questions/52682351/how-to-wrap-correctly-the-unit-testing-diff
139  """
140  # print( '\n\nexpected\n%s' % expected )
141  # print( '\n\nactual\n%s' % actual )
142 
143  # print( goal.encode( 'ascii' ) )
144  # print( results.encode( 'ascii' ) )
145 
146  # self.unidiff_output( goal, results )
147  if expected != actual:
148  diff_match = diffmatchpatch()
149 
150  if self.diffMode == 0:
151  diffs = diff_match.diff_main(expected, actual)
152 
153  else:
154  diff_struct = diff_match.diff_linesToWords(expected, actual,
155  re.compile(r'\b| ') if self.diffMode == 1 else re.compile(r'\n|\r\n') )
156 
157  lineText1 = diff_struct[0] # .chars1;
158  lineText2 = diff_struct[1] # .chars2;
159  lineArray = diff_struct[2] # .lineArray;
160 
161  diffs = diff_match.diff_main(lineText1, lineText2, False);
162  diff_match.diff_charsToLines(diffs, lineArray);
163  diff_match.diff_cleanupSemantic(diffs)
164 
165  if msg:
166  msg += '\n'
167 
168  else:
169  msg = "The strings does not match...\n"
170 
171  self.fail( msg + diff_match.diff_prettyText(diffs) )
172 
173  def tearDown(self):
174  """
175  Called right after each Unit Test finish its execution, to clean up settings values.
176  """
177  LockableType.USE_STRING = True
178  super(TestingUtilities, self).tearDown()
179 
180  def assertTextEqual(self, goal, results, msg=None, trim_tabs=' ', trim_spaces=' ', trim_plus='+', trim_lines=None, indent=""):
181  """
182  Remove both input texts indentation and trailing white spaces, then assertEquals() both
183  of the inputs.
184  """
185  goal = wrap_text( goal, trim_tabs=trim_tabs, trim_spaces=trim_spaces,
186  trim_plus=trim_plus, indent=indent, trim_lines=trim_lines )
187 
188  results = wrap_text( results, trim_tabs=trim_tabs, trim_spaces=trim_spaces,
189  trim_plus=trim_plus, indent=indent, trim_lines=trim_lines )
190 
191  if diff_match_patch:
192  self.diffMatchPatchAssertEqual( goal, results, msg=msg )
193 
194  else:
195  super( TestingUtilities, self ).assertEqual( goal, results, msg )
196 
197  def unidiff_output(self, expected, actual):
198  """
199  Helper function. Returns a string containing the unified diff of two multiline strings.
200 
201  https://stackoverflow.com/questions/845276/how-to-print-the-comparison-of-two-multiline-strings-in-unified-diff-format
202  https://stackoverflow.com/questions/15864641/python-difflib-comparing-files
203  https://stackoverflow.com/questions/32359402/comparison-of-multi-line-strings-in-python-unit-test
204  """
205  expected = expected.splitlines( 1 )
206  actual = actual.splitlines( 1 )
207 
208  # diff = difflib.ndiff( expected, actual )
209  if expected != actual:
210  diff = difflib.context_diff( expected, actual, fromfile='expected input', tofile='actual output', lineterm='\n' )
211  self.fail( '\n' + ''.join( diff ) )
212 
def diffMatchPatchAssertEqual(self, expected, actual, msg=None)
def assertTextEqual(self, goal, results, msg=None, trim_tabs=' ', trim_spaces=' ', trim_plus='+', trim_lines=None, indent="")
int diffMode
Whether characters diff=0, words diff=1 or lines diff=2 will be used.