Factor out common test primitives. NFCI.

Create an LLVMTestCase class that will contain common helpers for
writing tests for llvm.py subcommands.

Change-Id: Ie1fe9dad9c9aeb9eb673726c80077d26739c0697
diff --git a/tests/cli/llvmtestcase.py b/tests/cli/llvmtestcase.py
new file mode 100644
index 0000000..b2fc765
--- /dev/null
+++ b/tests/cli/llvmtestcase.py
@@ -0,0 +1,106 @@
+"""Common TestCase used for testing llvm.py subcommands."""
+
+import shutil
+import os
+import subprocess
+import unittest
+
+from tempfile import mkdtemp
+from uuid import uuid4
+
+from linaropy.cd import cd
+
+
+# TODO: move this somewhere more public (maybe linaropy?)
+def debug(test):
+    """
+    Decorator that dumps the output of any subprocess.CalledProcessError
+    exception. Use this to decorate a test function when you can't tell what the
+    problem is.
+    """
+    def wrapper(*args, **kwargs):
+        # Catch any exceptions so we can dump all the output
+        try:
+            test(*args, **kwargs)
+        except subprocess.CalledProcessError as exc:
+            print("Error in {}:".format(test.__name__))
+            print("Command {} exited with error code {}:\n{}".format(
+                exc.cmd, exc.returncode, exc.output))
+    return wrapper
+
+
+class LLVMTestCase(unittest.TestCase):
+    python = "python3"
+    script = os.path.join("scripts", "llvm.py")
+
+    @classmethod
+    def create_dummy_commit(cls):
+        filename = "filethatshouldntexist"
+        cls.run_quietly(["touch", filename])
+        cls.run_quietly(["git", "add", filename])
+        cls.run_quietly(["git", "commit", "-m", "Dummy commit"])
+
+    @classmethod
+    def create_dummy_repo(cls, repopath):
+        if not os.path.isdir(repopath):
+            os.makedirs(repopath)
+
+        with cd(repopath):
+            cls.run_quietly(["git", "init"])
+            cls.create_dummy_commit()
+
+    @classmethod
+    def add_worktree(cls, repopath, worktreepath, branch):
+        with cd(repopath):
+            cls.run_quietly(["git", "worktree", "add", worktreepath,
+                             "-b", branch])
+
+    @classmethod
+    def get_subproj_repo(cls, subproj):
+        return os.path.join(cls.repos, subproj)
+
+    @staticmethod
+    def run_with_output(*args, **kwargs):
+        """Helper for running a command and capturing stdout and stderr"""
+        kwargs["stderr"] = subprocess.STDOUT
+        return str(subprocess.check_output(*args, **kwargs), 'utf-8')
+
+    @staticmethod
+    def run_quietly(*args, **kwargs):
+        """
+        Helper for running a command and ignoring stdout and stderr. Exceptions
+        are still thrown if something goes wrong
+        """
+        kwargs["stdout"] = subprocess.DEVNULL
+        kwargs["stderr"] = subprocess.DEVNULL
+        return subprocess.check_call(*args, **kwargs)
+
+    @classmethod
+    def command_with_defaults(cls, subcommand, *args, **kwargs):
+        """
+        Build a list representing a llvm subcommand with the given
+        args. Unless otherwise specified in kwargs, this uses the values for
+        repos and env that it finds in cls.
+        """
+        command = [cls.python, cls.script]
+
+        repos = cls.repos
+        if "repos" in kwargs:
+            repos = kwargs["repos"]
+        if repos:
+            command.append("--repos")
+            command.append(repos)
+
+        env = cls.env
+        if "env" in kwargs:
+            env = kwargs["env"]
+        if env:
+            command.append("--env")
+            command.append(env)
+
+        command.append(subcommand)
+
+        if len(args):
+            command.extend(args)
+
+        return command