diff --git a/tests/basic.toml b/tests/basic.toml index 70eb39e..f79eb09 100644 --- a/tests/basic.toml +++ b/tests/basic.toml @@ -1,2 +1,99 @@ [noargs] - exitcode = 1 \ No newline at end of file + exitcode = 1 + +[libs] + stdout = "-L/test/lib -lfoo\n" + args = ["--libs", "foo"] + + [libs.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lfoo\n" + args = ["--cflags", "--libs", "foo"] + + [lib_cflags.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lfoo\n" + args = ["--cflags", "--libs", "foo > 1.2"] + + [lib_cflags_version.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version_multiple] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" + args = ["--cflags", "--libs", "foo > 1.2 bar >= 1.3"] + + [lib_cflags_version_multiple.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version_multiple_comma] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" + args = ["--cflags", "--libs", "foo > 1.2,bar >= 1.3"] + + [lib_cflags_version_multiple_comma.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version_alt] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lfoo\n" + args = ["--cflags", "--libs", "foo", ">", "1.2"] + + [lib_cflags_version_alt.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version_different] + stdout = "-fPIC -I/test/include/foo -L/test/lib -lfoo\n" + args = ["--cflags", "--libs", "foo", "!=", "1.3"] + + [lib_cflags_version_different.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[lib_cflags_version_different_bad] + exitcode = 1 + stderr = "Package dependency requirement 'foo != 1.2.3' could not be satisfied.\nPackage 'foo' has version '1.2.3', required version is '!= 1.2.3'\n" + args = ["--cflags", "--libs", "foo", "!=", "1.2.3"] + + [lib_cflags_version_different_bad.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[exists_nonexistent] + exitcode = 1 + args = ["--exists", "nonexistant"] + + [exists_nonexistent.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[nonexistent] + exitcode = 1 + args = ["nonexistant"] + + [lexists_nonexistent.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[exists_version] + args = ["--exists", "foo > 1.2"] + + [exists_version.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[exists_version_alt] + args = ["--exists", "foo", ">", "1.2"] + + [exists_version_alt.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[exists_version_bad] + exitcode = 1 + args = ["--exists", "foo > 1.2.3"] + + [exists_version_bad.env] + PKG_CONFIG_PATH="{test_root}/lib1" + +[uninstalled_bad] + exitcode = 1 + args = ["--uninstalled", "foo"] + + [uninstalled_bad.env] + PKG_CONFIG_PATH="{test_root}/lib1" \ No newline at end of file diff --git a/tests/runner.py b/tests/runner.py index 9a18999..c6a49f6 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -13,9 +13,10 @@ """Simple runner for test cases.""" from __future__ import annotations -import asyncio import argparse +import asyncio import dataclasses +import os import sys import typing @@ -28,36 +29,60 @@ if typing.TYPE_CHECKING: class TestDefinition(typing.TypedDict, total=False): exitcode: int + stdout: str + stderr: str + args: typing.List[str] + env: typing.Dict[str, str] class Arguments(typing.Protocol): - suite: str + suites: typing.List[str] pkgconf: str verbose: bool +TEST_ROOT = os.path.dirname(os.path.abspath(__file__)) + + @dataclasses.dataclass class Result: name: str - success: bool = True + success: bool = False reason: typing.Optional[str] = None +def interpolate(input: str) -> str: + return input.format( + test_root=TEST_ROOT, + ) + + async def run_test(pkgconf: str, name: str, test: TestDefinition, verbose: bool) -> Result: - args: typing.List[str] = [] + + env: typing.Dict[str, str] = {} + if (renv := test.get('env', None)) is not None: + env = {k: interpolate(v) for k, v in renv.items()} proc = await asyncio.create_subprocess_exec( - pkgconf, *args, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE) + pkgconf, *test.get('args', []), + stdout=asyncio.subprocess.PIPE if 'stdout' in test is not None else asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.PIPE if 'stderr' in test is not None else asyncio.subprocess.DEVNULL, + env=env) - out, err = await proc.communicate() + rout, rerr = await proc.communicate() + out = rout.decode() if rout is not None else '' + err = rerr.decode() if rerr is not None else '' result = Result(name) - if (ret := test.get('exitcode')) is not None and proc.returncode != ret: - result.success = False + if (ret := test.get('exitcode', 0)) and proc.returncode != ret: result.reason = f"Return code was {proc.returncode}, but expected {ret}" + elif (exp := test.get('stdout')) is not None and out != exp: + result.reason = f"Stdout was {out}, but expected {exp}" + elif (exp := test.get('stderr')) is not None and err != exp: + result.reason = f"Stdout was {err}, but expected {err}" + else: + result.success = True if verbose: if result.success: @@ -69,20 +94,23 @@ async def run_test(pkgconf: str, name: str, test: TestDefinition, verbose: bool) async def run(args: Arguments) -> None: - with open(args.suite, 'rb') as f: - suite: typing.Dict[str, TestDefinition] = tomllib.load(f) + tests: typing.List[typing.Coroutine[typing.Any, typing.Any, Result]] = [] + for suite in args.suites: + with open(suite, 'rb') as f: + suite: typing.Dict[str, TestDefinition] = tomllib.load(f) - results = await asyncio.gather( - *[run_test(args.pkgconf, n, s, args.verbose) for n, s in suite.items()] - ) + tests.extend( + [run_test(args.pkgconf, n, s, args.verbose) for n, s in suite.items()]) + + results = await asyncio.gather(*tests) return all(r.success for r in results) def main() -> None: parser = argparse.ArgumentParser() - parser.add_argument('suite', help="A toml test suite definition") parser.add_argument('pkgconf', help="Path to built pkgconf executable") + parser.add_argument('suites', nargs="+", help="One or more toml test suite definition") parser.add_argument('-v', '--verbose', action='store_true', help="Print more information while running") args: Arguments = parser.parse_args()