summaryrefslogtreecommitdiffstats
path: root/scripts/devscripts/test
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/devscripts/test')
-rw-r--r--scripts/devscripts/test/__init__.py63
-rw-r--r--scripts/devscripts/test/pylint.conf59
-rw-r--r--scripts/devscripts/test/test_flake8.py53
-rw-r--r--scripts/devscripts/test/test_help.py76
-rw-r--r--scripts/devscripts/test/test_logger.py57
-rw-r--r--scripts/devscripts/test/test_pylint.py68
6 files changed, 376 insertions, 0 deletions
diff --git a/scripts/devscripts/test/__init__.py b/scripts/devscripts/test/__init__.py
new file mode 100644
index 0000000..335f1d4
--- /dev/null
+++ b/scripts/devscripts/test/__init__.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2017, Benjamin Drung <benjamin.drung@profitbricks.com>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import inspect
+import os
+import sys
+import unittest
+
+SCRIPTS = [
+ "debdiff-apply",
+ "reproducible-check",
+ "sadt",
+ "suspicious-source",
+ "wrap-and-sort",
+]
+
+
+def get_source_files():
+ """Return a list of sources files/directories (to check with flake8/pylint)"""
+ modules = ["devscripts"]
+ py_files = ["setup.py"]
+
+ files = []
+ for code_file in SCRIPTS + modules + py_files:
+ is_script = code_file in SCRIPTS
+ if not os.path.exists(code_file): # pragma: no cover
+ # The alternative path is needed for Debian's pybuild
+ alternative = os.path.join(os.environ.get("OLDPWD", ""), code_file)
+ if os.path.exists(alternative):
+ code_file = alternative
+ if is_script:
+ with open(code_file, "rb") as script_file:
+ shebang = script_file.readline().decode("utf-8")
+ if ((sys.version_info[0] == 3 and "python3" in shebang) or
+ ("python" in shebang and "python3" not in shebang)):
+ files.append(code_file)
+ else:
+ files.append(code_file)
+ return files
+
+
+def unittest_verbosity():
+ """Return the verbosity setting of the currently running unittest
+ program, or None if none is running.
+ """
+ frame = inspect.currentframe()
+ while frame:
+ self = frame.f_locals.get("self")
+ if isinstance(self, unittest.TestProgram):
+ return self.verbosity
+ frame = frame.f_back
+ return None # pragma: no cover
diff --git a/scripts/devscripts/test/pylint.conf b/scripts/devscripts/test/pylint.conf
new file mode 100644
index 0000000..b0a80f1
--- /dev/null
+++ b/scripts/devscripts/test/pylint.conf
@@ -0,0 +1,59 @@
+[MASTER]
+
+# Pickle collected data for later comparisons.
+persistent=no
+
+
+[MESSAGES CONTROL]
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then re-enable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=fixme,invalid-name,locally-disabled,missing-docstring
+
+
+[REPORTS]
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+
+[TYPECHECK]
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=apt_pkg,magic
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=99
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[BASIC]
+
+# Allow variables called e, f, lp
+good-names=i,j,k,ex,Run,_,e,f,lp,fp
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=5
+
+
+[DESIGN]
+
+# Maximum number of arguments per function
+max-args=10
diff --git a/scripts/devscripts/test/test_flake8.py b/scripts/devscripts/test/test_flake8.py
new file mode 100644
index 0000000..8559f4d
--- /dev/null
+++ b/scripts/devscripts/test/test_flake8.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2017-2018, Benjamin Drung <bdrung@debian.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""test_flake8.py - Run flake8 check"""
+
+import subprocess
+import sys
+import unittest
+
+from . import get_source_files, unittest_verbosity
+
+
+class Flake8TestCase(unittest.TestCase):
+ """
+ This unittest class provides a test that runs the flake8 code
+ checker (which combines pycodestyle and pyflakes) on the Python
+ source code. The list of source files is provided by the
+ get_source_files() function.
+ """
+
+ def test_flake8(self):
+ """Test: Run flake8 on Python source code"""
+ cmd = [sys.executable, "-m", "flake8", "--max-line-length=99"] + get_source_files()
+ if unittest_verbosity() >= 2:
+ sys.stderr.write(
+ "Running following command:\n{}\n".format(" ".join(cmd)))
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, close_fds=True)
+
+ out, err = process.communicate()
+ if process.returncode != 0: # pragma: no cover
+ msgs = []
+ if err:
+ msgs.append("flake8 exited with code {} and has unexpected output on stderr:\n{}"
+ .format(process.returncode, err.decode().rstrip()))
+ if out:
+ msgs.append("flake8 found issues:\n{}".format(
+ out.decode().rstrip()))
+ if not msgs:
+ msgs.append("flake8 exited with code {} and has no output on stdout or stderr."
+ .format(process.returncode))
+ self.fail("\n".join(msgs))
diff --git a/scripts/devscripts/test/test_help.py b/scripts/devscripts/test/test_help.py
new file mode 100644
index 0000000..42a7d10
--- /dev/null
+++ b/scripts/devscripts/test/test_help.py
@@ -0,0 +1,76 @@
+# test_help.py - Ensure scripts can run --help.
+#
+# Copyright (C) 2010, Stefano Rivera <stefanor@ubuntu.com>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+import fcntl
+import os
+import select
+import signal
+import subprocess
+import time
+import unittest
+
+from . import SCRIPTS
+
+TIMEOUT = 5
+
+
+def load_tests(loader, tests, pattern): # pylint: disable=unused-argument
+ "Give HelpTestCase a chance to populate before loading its test cases"
+ suite = unittest.TestSuite()
+ HelpTestCase.populate()
+ suite.addTests(loader.loadTestsFromTestCase(HelpTestCase))
+ return suite
+
+
+class HelpTestCase(unittest.TestCase):
+ @classmethod
+ def populate(cls):
+ for script in SCRIPTS:
+ setattr(cls, 'test_' + script, cls.make_help_tester(script))
+
+ @classmethod
+ def make_help_tester(cls, script):
+ def tester(self):
+ with open('/dev/null', 'r') as null:
+ process = subprocess.Popen(['./' + script, '--help'],
+ close_fds=True, stdin=null,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ started = time.time()
+ out = []
+
+ fds = [process.stdout.fileno(), process.stderr.fileno()]
+ for fd in fds:
+ fcntl.fcntl(fd, fcntl.F_SETFL,
+ fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+ while time.time() - started < TIMEOUT:
+ for fd in select.select(fds, [], fds, TIMEOUT)[0]:
+ out.append(os.read(fd, 1024))
+ if process.poll() is not None:
+ break
+
+ if process.poll() is None:
+ os.kill(process.pid, signal.SIGTERM)
+ time.sleep(1)
+ if process.poll() is None:
+ os.kill(process.pid, signal.SIGKILL)
+
+ self.assertEqual(process.poll(), 0,
+ "%s failed to return usage within %i seconds.\n"
+ "Output:\n%s"
+ % (script, TIMEOUT, ''.encode('ascii').join(out)))
+ return tester
diff --git a/scripts/devscripts/test/test_logger.py b/scripts/devscripts/test/test_logger.py
new file mode 100644
index 0000000..f03010f
--- /dev/null
+++ b/scripts/devscripts/test/test_logger.py
@@ -0,0 +1,57 @@
+# test_logger.py - Test devscripts.logger.Logger.
+#
+# Copyright (C) 2012, Stefano Rivera <stefanor@debian.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+import io
+import sys
+
+from devscripts.logger import Logger
+from devscripts.test import unittest
+
+
+class LoggerTestCase(unittest.TestCase):
+ def setUp(self):
+ Logger.stdout = io.StringIO()
+ Logger.stderr = io.StringIO()
+ self._script_name = Logger.script_name
+ Logger.script_name = 'test'
+ self._verbose = Logger.verbose
+
+ def tearDown(self):
+ Logger.stdout = sys.stdout
+ Logger.stderr = sys.stderr
+ Logger.script_name = self._script_name
+ Logger.verbose = self._verbose
+
+ def testCommand(self):
+ # pylint: disable=no-member
+ Logger.command(('ls', 'a b'))
+ self.assertEqual(Logger.stdout.getvalue(), '')
+ Logger.set_verbosity(True)
+ Logger.command(('ls', 'a b'))
+ self.assertEqual(Logger.stdout.getvalue(), 'test: I: ls "a b"\n')
+ self.assertEqual(Logger.stderr.getvalue(), '')
+
+ def testNoArgs(self):
+ # pylint: disable=no-member
+ Logger.normal('hello %s')
+ self.assertEqual(Logger.stdout.getvalue(), 'test: hello %s\n')
+ self.assertEqual(Logger.stderr.getvalue(), '')
+
+ def testArgs(self):
+ # pylint: disable=no-member
+ Logger.normal('hello %s', 'world')
+ self.assertEqual(Logger.stdout.getvalue(), 'test: hello world\n')
+ self.assertEqual(Logger.stderr.getvalue(), '')
diff --git a/scripts/devscripts/test/test_pylint.py b/scripts/devscripts/test/test_pylint.py
new file mode 100644
index 0000000..ac08fda
--- /dev/null
+++ b/scripts/devscripts/test/test_pylint.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2010, Stefano Rivera <stefanor@debian.org>
+# Copyright (C) 2017-2018, Benjamin Drung <bdrung@debian.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""test_pylint.py - Run pylint"""
+
+import os
+import re
+import subprocess
+import sys
+import unittest
+
+from . import get_source_files, unittest_verbosity
+
+CONFIG = os.path.join(os.path.dirname(__file__), "pylint.conf")
+
+
+class PylintTestCase(unittest.TestCase):
+ """
+ This unittest class provides a test that runs the pylint code check
+ on the Python source code. The list of source files is provided by
+ the get_source_files() function and pylint is purely configured via
+ a config file.
+ """
+
+ def test_pylint(self):
+ """Test: Run pylint on Python source code"""
+
+ cmd = [sys.executable, "-m", "pylint", "--rcfile=" + CONFIG, "--"] + get_source_files()
+ if unittest_verbosity() >= 2:
+ sys.stderr.write("Running following command:\n{}\n".format(" ".join(cmd)))
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ close_fds=True)
+ out, err = process.communicate()
+
+ if process.returncode != 0: # pragma: no cover
+ # Strip trailing summary (introduced in pylint 1.7). This summary might look like:
+ #
+ # ------------------------------------
+ # Your code has been rated at 10.00/10
+ #
+ out = re.sub("^(-+|Your code has been rated at .*)$", "", out.decode(),
+ flags=re.MULTILINE).rstrip()
+
+ # Strip logging of used config file (introduced in pylint 1.8)
+ err = re.sub("^Using config file .*\n", "", err.decode()).rstrip()
+
+ msgs = []
+ if err:
+ msgs.append("pylint exited with code {} and has unexpected output on stderr:\n{}"
+ .format(process.returncode, err))
+ if out:
+ msgs.append("pylint found issues:\n{}".format(out))
+ if not msgs:
+ msgs.append("pylint exited with code {} and has no output on stdout or stderr."
+ .format(process.returncode))
+ self.fail("\n".join(msgs))