about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorIvan “CLOVIS” Canet <ivan.canet@gmail.com>2022-03-22 14:15:23 +0100
committerIvan “CLOVIS” Canet <ivan.canet@gmail.com>2022-03-24 12:53:41 +0100
commit29c63d29d6ba5b80cd680e690aa36fb6281e0467 (patch)
tree72449c96844302abad75a6fa227bfc166f018dd1
parentb9ecc43cf5ae1583cb9a1e053bac5be2e6c68aa0 (diff)
downloadfocaccia-miasm-29c63d29d6ba5b80cd680e690aa36fb6281e0467.tar.gz
focaccia-miasm-29c63d29d6ba5b80cd680e690aa36fb6281e0467.zip
Compatibility of our tests with unittest
This commit introduces a compatibility layer to run the Miasm tests using Python's unittest. Due to unittest not knowing how to execute tests in parallel, this is much slower than the current alternative.

Supporting unittest (which is a Python standard) as an addition to our own homegrown runner, even if slower, is useful for integration with other tools thanks to the shared format (eg. see full standard output logs for each test in PyCharm, generate XUnit test reports in CI...).
-rw-r--r--README.md7
-rw-r--r--optional_requirements.txt1
-rwxr-xr-xtest/test_all.py67
3 files changed, 68 insertions, 7 deletions
diff --git a/README.md b/README.md
index 07e1d8e6..50b8de59 100644
--- a/README.md
+++ b/README.md
@@ -583,7 +583,14 @@ Miasm comes with a set of regression tests. To run all of them:
 
 ```pycon
 cd miasm_directory/test
+
+# Run tests using our own test runner
 python test_all.py
+
+# Run tests using standard frameworks (slower, require 'parameterized')
+python -m unittest test_all.py        # sequential, requires 'unittest'
+python -m pytest test_all.py          # sequential, requires 'pytest'
+python -m pytest -n auto test_all.py  # parallel, requires 'pytest' and 'pytest-xdist'
 ```
 
 Some options can be specified:
diff --git a/optional_requirements.txt b/optional_requirements.txt
index 39d92a93..e8e1782c 100644
--- a/optional_requirements.txt
+++ b/optional_requirements.txt
@@ -1,3 +1,4 @@
 pycparser
 z3-solver==4.8.7.0
 llvmlite==0.31.0
+parameterized~=0.8.1
diff --git a/test/test_all.py b/test/test_all.py
index 1ec49324..2d078bf1 100755
--- a/test/test_all.py
+++ b/test/test_all.py
@@ -1,20 +1,23 @@
 #! /usr/bin/env python2
 
 from __future__ import print_function
-from builtins import map
-from builtins import range
+
 import argparse
-from distutils.spawn import find_executable
 import os
 import platform
-import time
+import subprocess
+import sys
 import tempfile
-import platform
+import time
+import unittest
+from builtins import map
+from builtins import range
+
+from parameterized import parameterized
 
+from utils import cosmetics, multithread
 from utils.test import Test
 from utils.testset import TestSet
-from utils import cosmetics, multithread
-from multiprocessing import Queue
 
 is_win = platform.system() == "Windows"
 is_64bit = platform.architecture()[0] == "64bit"
@@ -847,6 +850,56 @@ testset += RegressionTest(["launch.py"], base_dir="arch/mep/asm")
 testset += RegressionTest(["launch.py"], base_dir="arch/mep/ir")
 testset += RegressionTest(["launch.py"], base_dir="arch/mep/jit")
 
+
+# region Unittest compatibility
+
+class TestSequence(unittest.TestCase):
+    # Compatibility layer for Python's unittest module
+    # Instead of calling the '__main__' defined below, we parameterize a single test with all the tests selected in
+    # testset, and run them as we would have.
+
+    tests = testset.tests
+    tests_without_shellcodes = (t for t in tests if "shellcode.py" not in t.command_line[0])
+
+    @staticmethod
+    def run_process(t):
+        """
+        @type t: Test
+        """
+        print("Base dir:", t.base_dir)
+        print("Command: ", t.command_line)
+        print("Depends: ", [t.command_line for t in t.depends])
+        print("Tags:    ", t.tags)
+        print("Products:", t.products)
+        executable = t.executable if t.executable else sys.executable
+        print("Exec:    ", executable, "(explicit)" if t.executable else "(default)")
+
+        for t in t.depends:
+            assert "shellcode.py" in t.command_line[0], "At the moment, only dependencies on 'shellcode.py' are handled"
+
+        subprocess.check_call(
+            [executable] + t.command_line,
+            cwd=testset.base_dir + t.base_dir,
+        )
+
+        print("Done")
+
+    @classmethod
+    def setUpClass(cls):
+        for t in testset.tests:
+            if "shellcode.py" in t.command_line[0]:
+                print("\n*** Shellcode generation ***")
+                cls.run_process(t)
+
+    @parameterized.expand(("_".join(test.command_line), test) for test in tests_without_shellcodes)
+    def test(self, name, t):
+        print("***", name, "***")
+        TestSequence.run_process(t)
+
+
+# endregion
+
+
 if __name__ == "__main__":
     # Argument parsing
     parser = argparse.ArgumentParser(description="Miasm2 testing tool")