Files
style_checker/testsuite/lib/support.py
Joel Brobecker 5142153194 TestCase.run_style_checker: Enhance to allow calling via Python interpreter
This commit allows the few testcases that were calling the style_checker
directly to now do so by calling self.run_style_checker instead.
This centralizes the way the style_checker is called, which is
a useful enhancement in and of itself. The other upside is that
it should make it easier to transition the testsuite to a different
framework, one that is independent of gnatpython or e3, should we
want to (the framework itself is currently self-sufficient; it would
be nice if we could make the testsuite not depend on those either,
since this is a project used by contributors outside of AdaCore).

Change-Id: I3640c504426d23d363db16069551249946400e8d
TN: T605-002
2020-06-23 11:53:55 -07:00

191 lines
7.1 KiB
Python

import gnatpython.ex
from gnatpython.fileutils import diff
import os
import sys
from tempfile import mkdtemp
import unittest
TEST_DIR = os.path.dirname(sys.modules['__main__'].__file__)
TEST_DIR = os.path.abspath(TEST_DIR)
class TestCase(unittest.TestCase):
def setUp(self):
# Create a directory to be used as tmp by this testcase.
# We want that directory to be inside the testsuite's
# global tmp directory, so that anything accidently left
# behind will be automatically caught and cleaned up by
# the mainloop.
#
# The objective is to force the scripts to use this testcase
# tmp directory during testing, allowing us to verify once
# the testcase returns that the git-hooks scripts do not leak
# any temporary files/directories. We do this by force-setting
# the various environment variables that gnatpython's Env
# and the tempfile modules use as the default tmp.
self.testcase_tmp_dir = \
mkdtemp('', '', os.environ['ASC_TESTSUITE_TMP'])
os.environ['TMP'] = self.testcase_tmp_dir
os.environ['TMPDIR'] = self.testcase_tmp_dir
# Provide the testcase with an attribute which contains
# the fullpath to the style_checker program, to make it
# easy to call it.
lib_dir = os.path.dirname(os.path.realpath(__file__))
testsuite_dir = os.path.dirname(lib_dir)
prefix_dir = os.path.dirname(testsuite_dir)
self.style_checker_exe = os.path.join(prefix_dir, 'style_checker')
self.forced_year = None
def tearDown(self):
# One last check: Verify that the scripts did not leak any
# temporary files/directories, by looking at the number of
# files in the testcase tmp dir (we forced all scripts to
# use this tmp directory during the setUp phase).
self.assertFalse(os.listdir(self.testcase_tmp_dir))
def set_year(self, year):
self.forced_year = year
def run_style_checker(self, *args, **kwargs):
"""Run style_checker with the given arguments.
Run this repository's style checker (through the "Run" class
below), and return the corresponding Run object.
Note: The reason why we use **kwargs to support named arguments
is that we'd like those named arguments to have a default
value, and those cannot be listed after the "*args" argument.
And we use the "*args" argument in order to make simpler to
call this method. In The vast majority of the cases, we just
call the style_checker with a couple of arguments, like so:
obj.run_style_checker('repo_name', 'filename')
The alternative is to change the "*args" parameter into
a parameter taking a list, similar to what e3.os.process.Run
does. But then, it makes the call to style_checker a little
more cumbersome:
obj.run_style_checker(['repo_name', 'filename'])
We document the list of arguments we support in the "ARGUMENTS"
section just below, and we also raise an exception when
an unexpected argument is used. So it seems worth slightly
obscuring the one location where the method is defined
in favor of the many locations where this method is called.
ARGUMENTS
args: The arguments passed to the style-checker.
use_sys_executable: If True, then call the style_checker via
the sys.executable Python interpreter. If False (the default),
the style_checker script is called directly.
This can be useful when the PATH has been manipulated
such that the Python interpreter is no longer in the PATH
(for instance).
input: Same as in e3.os.process.Run.__init__.
RETURN VALUE
A Run object.
"""
use_sys_executable = False
run_kwargs = {}
for arg_name, arg_val in kwargs.items():
if arg_name in ('input', ):
run_kwargs[arg_name] = arg_val
elif arg_name == 'use_sys_executable' and arg_val:
use_sys_executable = True
else:
raise ValueError(
'Invalid argument in call to run_style_checker: {}'
.format(arg_name))
cmd = []
if use_sys_executable:
cmd.append(sys.executable)
cmd.append(self.style_checker_exe)
if self.forced_year is not None:
cmd.append('--forced-year=%d' % self.forced_year)
cmd.extend(list(args))
return Run(cmd, **run_kwargs)
def enable_unit_test(self):
"""Setup the environment in a way that allows us to perform unit test.
"""
lib_dir = os.path.dirname(os.path.realpath(__file__))
testsuite_dir = os.path.dirname(lib_dir)
prefix_dir = os.path.dirname(testsuite_dir)
sys.path.insert(0, prefix_dir)
def assertOutputEqual(self, expected, actual):
"""same as assertEqual but for strings and with a diff if not equal.
This a convenience function that allows us to quickly diagnose
what's wrong when the output does not match...
"""
self.assertEqual(expected, actual,
"Diff:\n\n%s" % diff(expected.splitlines(),
actual.splitlines()))
def assertRunOutputEqual(self, r, expected_out):
"""assert that r.cmd_out is equal to expected_out...
... And if the assertion is not met, then produce a useful
output.
"""
self.assertEqual(expected_out, r.cmd_out, r.diff(expected_out))
def assertRunOutputEmpty(self, r):
"""Same as assertRunOutputEqual with an empty expected output.
"""
self.assertRunOutputEqual(r, '')
def runtests():
"""Call unittest.main.
"""
unittest.main()
class Run(gnatpython.ex.Run):
"""A gnatpython.ex.Run subclass providing access to a sanitized output.
"""
@property
def cmd_out(self):
"""Same as self.out, except that the output is sanitized.
RETURN VALUE
A sanitized version of self.out.
"""
# For now, just return the output unchanged. We'll see if we need
# this as implement the full array of tests.
return self.out
@property
def image(self):
"""Return an image of the command and its result and output.
REMARKS
This assumes that this command has run to completion.
"""
return '%% %s -> %s\n%s' % (self.command_line_image(),
self.status,
self.cmd_out)
def diff(self, expected_out):
"""Return self.out followed by a diff self.cmd_out and expected_out.
PARAMETERS
expected_out: A string containing the expected output.
"""
diff_str = diff(expected_out.splitlines(),
self.cmd_out.splitlines())
return '%s\n\nDiff:\n\n%s' % (self.image, diff_str)