diff --git a/tests/ctypesgentest.py b/tests/ctypesgentest.py index 85d4e6f2..e8429c6d 100644 --- a/tests/ctypesgentest.py +++ b/tests/ctypesgentest.py @@ -1,12 +1,11 @@ import os import sys -import glob import json +import atexit import types import subprocess import shutil from itertools import product -import tempfile from pathlib import Path import ctypesgen.__main__ @@ -15,6 +14,22 @@ TEST_DIR = Path(__file__).resolve().parent COMMON_DIR = TEST_DIR/"common" +TMP_DIR = TEST_DIR/"tmp" +COUNTER = 0 +CLEANUP_OK = bool(int(os.environ.get("CLEANUP_OK", "1"))) + + +def init_tmpdir(): + if TMP_DIR.exists(): + shutil.rmtree(TMP_DIR) + TMP_DIR.mkdir() + +def cleanup_tmpdir(): + if CLEANUP_OK: + shutil.rmtree(TMP_DIR) + +init_tmpdir() +atexit.register(cleanup_tmpdir) def ctypesgen_main(args): @@ -32,14 +47,22 @@ def module_from_code(name, python_code): def generate(header_str, args=[], lang="py"): - tmp_in = TEST_DIR/"tmp_in.h" - tmp_in.write_text(header_str) - tmp_out = TEST_DIR/"tmp_out.py" - ctypesgen_main(["-i", tmp_in, "-o", tmp_out, "--output-language", lang, *args]) - content = tmp_out.read_text() + # use custom tempfiles scoping so we may retain data for inspection + # also note that python stdlib tempfiles don't play well with windows + + global COUNTER + COUNTER += 1 - tmp_in.unlink() - tmp_out.unlink() + tmp_in = TMP_DIR/f"in_header_{COUNTER:02d}.h" + tmp_in.write_text(header_str) + try: + tmp_out = TMP_DIR/f"out_bindings_{COUNTER:02d}.py" + ctypesgen_main(["-i", tmp_in, "-o", tmp_out, "--output-language", lang, *args]) + content = tmp_out.read_text() + finally: + if CLEANUP_OK: + tmp_in.unlink() + tmp_out.unlink() if lang.startswith("py"): return module_from_code("tmp_module", content) @@ -49,12 +72,6 @@ def generate(header_str, args=[], lang="py"): assert False -def cleanup(filepattern="temp.*"): - fnames = glob.glob(filepattern) - for fname in fnames: - os.unlink(fname) - - def set_logging_level(log_level): messages.log.setLevel(log_level) @@ -168,11 +185,9 @@ def _create_common_files(): void bar(struct mystruct *m) { } """ - try: - COMMON_DIR.mkdir() - except FileExistsError: + if COMMON_DIR.exists(): shutil.rmtree(COMMON_DIR) - COMMON_DIR.mkdir() + COMMON_DIR.mkdir() for (name, source) in names.items(): with (COMMON_DIR/name).open("w") as f: @@ -202,4 +217,5 @@ def cleanup_common(): # Attention: currently not working on MS Windows. # cleanup_common() tries to delete "common.dll" while it is still loaded # by ctypes. See unittest for further details. - shutil.rmtree(COMMON_DIR) + if CLEANUP_OK: + shutil.rmtree(COMMON_DIR) diff --git a/tests/testsuite.py b/tests/testsuite.py index e148f8fc..27948125 100755 --- a/tests/testsuite.py +++ b/tests/testsuite.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Simple test suite using unittest. -By clach04 (Chris Clark). +Originally written by clach04 (Chris Clark). Calling: @@ -21,6 +21,9 @@ Aims to test for regressions. Where possible use stdlib to avoid the need to compile C code. + +Note, you may set CLEANUP_OK=0 to retain generated data. +This can be useful for inspection. """ import sys @@ -29,19 +32,18 @@ import json as JSON import math import unittest -import logging from subprocess import Popen, PIPE -from tempfile import NamedTemporaryFile from tests.ctypesgentest import ( - cleanup, cleanup_common, ctypesgen_version, generate, generate_common, JsonHelper, - set_logging_level, TEST_DIR, + TMP_DIR, + COUNTER, + CLEANUP_OK, ) @@ -115,7 +117,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module - cleanup() def test_getenv_returns_string(self): """ Test string return """ @@ -166,10 +167,12 @@ def test_getenv_returns_null(self): # means the dll is loaded to memory. # On Linux/macOS this is no problem, as .so libaries can be overwritten/deleted # on file system while still loaded to memory. -@unittest.skipIf( - sys.platform == "win32", - "Currently not working on Windows. See code comment for details." -) + +# NOTE skip commented out for testing +# @unittest.skipIf( +# sys.platform == "win32", +# "Currently not working on Windows. See code comment for details." +# ) class CommonHeaderTest(unittest.TestCase): @classmethod def setUpClass(cls): @@ -177,6 +180,10 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # try to fix the issue described above + if sys.platform.startswith("win32"): + from .common._ctg_loader import _libs as loader_libs + ctypes.windll.kernel32.FreeLibrary(loader_libs["c"]._handle) cleanup_common() # NOTE `common` is a meta-module hosted by the test class, and {a,b}{shared,unshared} are the actual python files in question @@ -227,7 +234,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module - cleanup() def test_stdbool_type(self): """Test if bool is parsed correctly""" @@ -261,7 +267,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module - cleanup() def test_int_types(self): """Test if different integer types are parsed correctly""" @@ -316,7 +321,6 @@ def _json(self, name): @classmethod def tearDownClass(cls): del cls.module, cls.json - cleanup() def test_macro_constant_int(self): """Tests from simple_macros.py""" @@ -638,7 +642,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del StructuresTest.module - cleanup() def test_struct_json(self): json_ans = [ @@ -2053,7 +2056,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module - cleanup() def test_sin(self): """Based on math_functions.py""" @@ -2103,7 +2105,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module, cls.json - cleanup() def test_enum(self): self.assertEqual(EnumTest.module.TEST_1, 0) @@ -2215,7 +2216,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.json - cleanup() def test_function_prototypes_json(self): json_ans = [ @@ -2368,7 +2368,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del cls.module - cleanup() def test_longdouble_type(self): """Test if long double is parsed correctly""" @@ -2442,7 +2441,6 @@ def test_unchecked_prototype(self): @classmethod def tearDownClass(cls): del cls.module - cleanup() class ConstantsTest(unittest.TestCase): @@ -2482,7 +2480,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del ConstantsTest.module - cleanup() def test_integer_constants(self): """Test if integer constants are parsed correctly""" @@ -2534,7 +2531,6 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): del NULLTest.module - cleanup() def test_null_type(self): """Test if NULL is parsed correctly""" @@ -2572,7 +2568,6 @@ def setUpClass(cls): def tearDownClass(cls): del cls.module os.remove(cls.mac_roman_file) - cleanup() def test_macroman_encoding_source(self): module = MacromanEncodeTest.module @@ -2590,23 +2585,17 @@ def setUpClass(cls): def test_type_error_catch(self): with self.assertRaises(ctypes.ArgumentError): - self.module.printf(123) + # in case this slipped through as binary data, you would see chr(33) = '!' at the end + self.module.printf(33) def test_call(self): - tmp = TEST_DIR/"tmp_testdata.txt" + tmp = TMP_DIR/f"out_data_{COUNTER:02d}.txt" tmp.touch() - c_file = self.module.fopen(str(tmp).encode(), b"w") - self.module.fprintf(c_file, b"Test variadic function: %s %d", b"Hello", 123) - self.module.fclose(c_file) - assert tmp.read_bytes() == b"Test variadic function: Hello 123" - tmp.unlink() - - -def main(): - set_logging_level(logging.CRITICAL) # do not log anything - unittest.main() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) + try: + c_file = self.module.fopen(str(tmp).encode(), b"w") + self.module.fprintf(c_file, b"Test variadic function: %s %d", b"Hello", 123) + self.module.fclose(c_file) + assert tmp.read_bytes() == b"Test variadic function: Hello 123" + finally: + if CLEANUP_OK: + tmp.unlink()