#!/usr/bin/env python3 """Linux host integration tests for the real dladdr/symlinkat helper; needs cc.""" import ctypes import os from pathlib import Path import subprocess import sys import tempfile HEADER = Path(__file__).resolve().parents[1] / 'component-source/component' CASES = ['missing', 'correct', 'file', 'wrong_link', 'dangling', 'same_directory', 'unwritable', 'missing_source'] def child(case, base): base = Path(base) module = base / 'PlcLogic/libCmpAtomModbus.so' lib = ctypes.CDLL(str(module), use_errno=True) lib.warning_count.restype = ctypes.c_int lib.info_count.restype = ctypes.c_int os.chdir(base) link = base / 'libCmpAtomModbus.so' if case == 'correct': link.symlink_to('PlcLogic/libCmpAtomModbus.so') elif case == 'file': link.write_text('do not replace me') elif case in ('wrong_link', 'dangling'): if case == 'wrong_link': (base / 'other').write_text('other file') link.symlink_to('other') elif case == 'same_directory': os.chdir(module.parent) elif case == 'missing_source': module.unlink() elif case == 'unwritable': # sysfs cannot accept arbitrary regular symlinks, even as root. os.chdir('/sys') link = Path('/sys/libCmpAtomModbus.so') assert not os.path.lexists(link) ctypes.set_errno(123) lib.run_check() assert ctypes.get_errno() == 123, 'caller errno changed' warnings = lib.warning_count() infos = lib.info_count() lib.run_check() assert (warnings, infos) == (lib.warning_count(), lib.info_count()), 'retried or logged twice' if case == 'missing': assert link.is_symlink() and link.resolve() == module assert (warnings, infos) == (0, 1) elif case == 'correct': assert os.readlink(link) == 'PlcLogic/libCmpAtomModbus.so' assert (warnings, infos) == (0, 0) elif case == 'same_directory': assert module.is_file() and not module.is_symlink() assert (warnings, infos) == (0, 0) else: assert (warnings, infos) == (1, 0) if case == 'file': assert link.read_text() == 'do not replace me' elif case in ('wrong_link', 'dangling'): assert os.readlink(link) == 'other' else: assert not os.path.lexists(link) print('PASS', case) if len(sys.argv) == 3: child(sys.argv[1], sys.argv[2]) else: with tempfile.TemporaryDirectory(prefix='atmb-link-test-') as temp: root = Path(temp) root.chmod(0o755) harness = root / 'harness.c' harness.write_text('''#define _GNU_SOURCE #define syslog test_syslog #include "atmb_boot_link.h" static int warnings, infos; void test_syslog(int priority, const char *format, ...) { (void)format; if (priority == LOG_WARNING) ++warnings; if (priority == LOG_INFO) ++infos; } void run_check(void) { atmb_ensure_boot_link(); } int warning_count(void) { return warnings; } int info_count(void) { return infos; } ''') output = root / 'test.so' subprocess.run([os.environ.get('CC', 'cc'), '-std=c99', '-Wall', '-Wextra', '-Werror', '-fPIC', '-shared', '-I', str(HEADER), str(harness), '-ldl', '-o', str(output)], check=True) for case in CASES: base = root / case (base / 'PlcLogic').mkdir(parents=True) (base / 'PlcLogic/libCmpAtomModbus.so').write_bytes(output.read_bytes()) subprocess.run([sys.executable, __file__, case, str(base)], check=True) base.chmod(0o755)