From 829b630d642e20a72ae9a54946015fbc8e276d6b Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sat, 17 Sep 2016 23:40:53 +0200 Subject: [PATCH] Add unittests which emulate calling the scour module from command line --- testscour.py | 162 ++++++++++++++++++++++++++++++++++++++++-- unittests/minimal.svg | 2 + 2 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 unittests/minimal.svg diff --git a/testscour.py b/testscour.py index 9878b01..da67562 100755 --- a/testscour.py +++ b/testscour.py @@ -20,15 +20,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import +from __future__ import print_function # use print() as a function in Python 2 (see PEP 3105) +from __future__ import absolute_import # use absolute imports by default in Python 2 (see PEP 328) +import os +import sys import unittest import six from six.moves import map, range -from scour.scour import makeWellFormed, parse_args, scourString, scourXmlFile +from scour.scour import makeWellFormed, parse_args, scourString, scourXmlFile, run from scour.svg_regex import svg_parser +from scour import __version__ SVGNS = 'http://www.w3.org/2000/svg' @@ -2112,7 +2116,7 @@ class StripXmlSpaceAttribute(unittest.TestCase): "'xml:space' attribute not removed from root SVG element" "when '--strip-xml-space' was specified") self.assertNotEqual(doc.getElementById('text1').getAttribute('xml:space'), '', - "'xml:space' attribute removed from a child element" + "'xml:space' attribute removed from a child element " "when '--strip-xml-space' was specified (should only operate on root SVG element)") @@ -2124,10 +2128,160 @@ class DoNotStripXmlSpaceAttribute(unittest.TestCase): "'xml:space' attribute removed from root SVG element" "when '--strip-xml-space' was NOT specified") self.assertNotEqual(doc.getElementById('text1').getAttribute('xml:space'), '', - "'xml:space' attribute removed from a child element" + "'xml:space' attribute removed from a child element " "when '--strip-xml-space' was NOT specified (should never be removed!)") +class CommandLineUsage(unittest.TestCase): + + USAGE_STRING = "Usage: scour [INPUT.SVG [OUTPUT.SVG]] [OPTIONS]" + MINIMAL_SVG = '\n' \ + '\n' + TEMP_SVG_FILE = 'testscour_temp.svg' + + # wrapper function for scour.run() to emulate command line usage + # + # returns an object with the following attributes: + # status: the exit status + # stdout: a string representing the combined output to 'stdout' + # stderr: a string representing the combined output to 'stderr' + def _run_scour(self): + class Result(object): + pass + + result = Result() + try: + run() + result.status = 0 + except SystemExit as exception: # catch any calls to sys.exit() + result.status = exception.code + result.stdout = self.temp_stdout.getvalue() + result.stderr = self.temp_stderr.getvalue() + + return result + + def setUp(self): + # store current values of 'argv', 'stdin', 'stdout' and 'stderr' + self.argv = sys.argv + self.stdin = sys.stdin + self.stdout = sys.stdout + self.stderr = sys.stderr + + # start with a fresh 'argv' + sys.argv = ['scour'] # TODO: Do we need a (more) valid 'argv[0]' for anything? + + # create 'stdin', 'stdout' and 'stderr' with behavior close to the original + # TODO: can we create file objects that behave *exactly* like the original? + # this is a mess since we have to ensure compatibility across Python 2 and 3 and it seems impossible + # to replicate all the details of 'stdin', 'stdout' and 'stderr' + class InOutBuffer(six.StringIO, object): + def write(self, string): + try: + return super(InOutBuffer, self).write(string) + except TypeError: + return super(InOutBuffer, self).write(string.decode()) + + sys.stdin = self.temp_stdin = InOutBuffer() + sys.stdout = self.temp_stdout = InOutBuffer() + sys.stderr = self.temp_stderr = InOutBuffer() + + self.temp_stdin.name = '' # Scour wants to print the name of the input file... + + def tearDown(self): + # restore previous values of 'argv', 'stdin', 'stdout' and 'stderr' + sys.argv = self.argv + sys.stdin = self.stdin + sys.stdout = self.stdout + sys.stderr = self.stderr + + # clean up + self.temp_stdin.close() + self.temp_stdout.close() + self.temp_stderr.close() + + def test_no_arguments(self): + # we have to pretend that our input stream is a TTY, otherwise Scour waits for input from stdin + self.temp_stdin.isatty = lambda: True + + result = self._run_scour() + + self.assertEqual(result.status, 2, "Execution of 'scour' without any arguments should exit with status '2'") + self.assertTrue(self.USAGE_STRING in result.stderr, + "Usage information not displayed when calling 'scour' without any arguments") + + def test_version(self): + sys.argv.append('--version') + + result = self._run_scour() + + self.assertEqual(result.status, 0, "Execution of 'scour --version' erorred'") + self.assertEqual(__version__ + "\n", result.stdout, "Unexpected output of 'scour --version'") + + def test_help(self): + sys.argv.append('--help') + + result = self._run_scour() + + self.assertEqual(result.status, 0, "Execution of 'scour --help' erorred'") + self.assertTrue(self.USAGE_STRING in result.stdout and 'Options:' in result.stdout, + "Unexpected output of 'scour --help'") + + def test_stdin_stdout(self): + sys.stdin.write(self.MINIMAL_SVG) + sys.stdin.seek(0) + + result = self._run_scour() + + self.assertEqual(result.status, 0, "Usage of Scour via 'stdin' / 'stdout' erorred'") + self.assertEqual(result.stdout, self.MINIMAL_SVG, "Unexpected SVG output via 'stdout'") + + def test_filein_fileout_named(self): + sys.argv.extend(['-i', 'unittests/minimal.svg', '-o', self.TEMP_SVG_FILE]) + + result = self._run_scour() + + self.assertEqual(result.status, 0, "Usage of Scour with filenames specified as named parameters errored'") + with open(self.TEMP_SVG_FILE) as file: + file_content = file.read() + self.assertEqual(file_content, self.MINIMAL_SVG, "Unexpected SVG output in generated file") + os.remove(self.TEMP_SVG_FILE) + + def test_filein_fileout_positional(self): + sys.argv.extend(['unittests/minimal.svg', self.TEMP_SVG_FILE]) + + result = self._run_scour() + + self.assertEqual(result.status, 0, "Usage of Scour with filenames specified as positional parameters errored'") + with open(self.TEMP_SVG_FILE) as file: + file_content = file.read() + self.assertEqual(file_content, self.MINIMAL_SVG, "Unexpected SVG output in generated file") + os.remove(self.TEMP_SVG_FILE) + + def test_quiet(self): + sys.argv.append('-q') + sys.argv.extend(['-i', 'unittests/minimal.svg', '-o', self.TEMP_SVG_FILE]) + + result = self._run_scour() + os.remove(self.TEMP_SVG_FILE) + + self.assertEqual(result.status, 0, "Execution of 'scour -q ...' erorred'") + self.assertEqual(result.stdout, '', "Output writtent to 'stdout' when '--quiet' options was used") + self.assertEqual(result.stderr, '', "Output writtent to 'stderr' when '--quiet' options was used") + + def test_verbose(self): + sys.argv.append('-v') + sys.argv.extend(['-i', 'unittests/minimal.svg', '-o', self.TEMP_SVG_FILE]) + + result = self._run_scour() + os.remove(self.TEMP_SVG_FILE) + + self.assertEqual(result.status, 0, "Execution of 'scour -v ...' erorred'") + self.assertEqual(result.stdout.count('Number'), 14, + "Statistics output not as expected when '--verbose' option was used") + self.assertEqual(result.stdout.count(': 0'), 14, + "Statistics output not as expected when '--verbose' option was used") + + # TODO: write tests for --enable-viewboxing # TODO; write a test for embedding rasters # TODO: write a test for --disable-embed-rasters diff --git a/unittests/minimal.svg b/unittests/minimal.svg new file mode 100644 index 0000000..b9d264c --- /dev/null +++ b/unittests/minimal.svg @@ -0,0 +1,2 @@ + +