Merge branch 'unify-tests-launcher-scripts' into 'main'

tests: Cleanup python tests and add tests for gi-compile-repository and gi-inspect-typelib

See merge request GNOME/glib!4476
This commit is contained in:
Philip Withnall 2025-02-12 17:00:15 +00:00
commit 3c03076ac5
21 changed files with 628 additions and 800 deletions

View File

@ -372,9 +372,15 @@ installed-tests:
extends: extends:
- .build-linux - .build-linux
- .only-schedules-or-manual - .only-schedules-or-manual
- .with-git
- .build-gobject-introspection
image: "${FEDORA_IMAGE}" image: "${FEDORA_IMAGE}"
stage: build stage: build
needs: [] needs: []
before_script:
- !reference [".build-linux", "before_script"]
- !reference [".with-git", "before_script"]
- !reference [".build-gobject-introspection", "before_script"]
script: script:
# dtrace is disabled because it breaks the static-link.py test # dtrace is disabled because it breaks the static-link.py test
- meson setup ${MESON_COMMON_OPTIONS} - meson setup ${MESON_COMMON_OPTIONS}
@ -383,6 +389,7 @@ installed-tests:
-Dinstalled_tests=true -Dinstalled_tests=true
-Ddefault_library=both -Ddefault_library=both
-Ddtrace=disabled -Ddtrace=disabled
-Dintrospection=enabled
_build _build
- meson compile -C _build - meson compile -C _build
- sudo meson install -C _build - sudo meson install -C _build

View File

@ -22,7 +22,6 @@
"""Integration tests for gdbus-codegen utility.""" """Integration tests for gdbus-codegen utility."""
import collections
import os import os
import shutil import shutil
import subprocess import subprocess
@ -33,15 +32,13 @@ import unittest
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import taptestrunner import taptestrunner
import testprogramrunner
# Disable line length warnings as wrapping the C code templates would be hard # Disable line length warnings as wrapping the C code templates would be hard
# flake8: noqa: E501 # flake8: noqa: E501
Result = collections.namedtuple("Result", ("info", "out", "err", "subs")) class TestCodegen(testprogramrunner.TestProgramRunner):
class TestCodegen(unittest.TestCase):
"""Integration test for running gdbus-codegen. """Integration test for running gdbus-codegen.
This can be run when installed or uninstalled. When uninstalled, it This can be run when installed or uninstalled. When uninstalled, it
@ -54,8 +51,8 @@ class TestCodegen(unittest.TestCase):
just test command line behaviour in this integration test. just test command line behaviour in this integration test.
""" """
# Track the cwd, we want to back out to that to clean up our tempdir PROGRAM_NAME = "gdbus-codegen"
cwd = "" PROGRAM_TYPE = testprogramrunner.ProgramType.INTERPRETED
ARGUMENTS_TYPES = { ARGUMENTS_TYPES = {
"b": {"value_type": "boolean"}, "b": {"value_type": "boolean"},
@ -78,59 +75,12 @@ class TestCodegen(unittest.TestCase):
"asv": {"value_type": "variant", "variant_type": "a{sv}"}, "asv": {"value_type": "variant", "variant_type": "a{sv}"},
} }
def setUp(self):
self.timeout_seconds = 6 # seconds per test
self.tmpdir = tempfile.TemporaryDirectory()
self.cwd = os.getcwd()
os.chdir(self.tmpdir.name)
print("tmpdir:", self.tmpdir.name)
if "G_TEST_BUILDDIR" in os.environ:
self.__codegen = os.path.join(
os.environ["G_TEST_BUILDDIR"],
"..",
"gdbus-2.0",
"codegen",
"gdbus-codegen",
)
else:
self.__codegen = shutil.which("gdbus-codegen")
print("codegen:", self.__codegen)
def tearDown(self):
os.chdir(self.cwd)
self.tmpdir.cleanup()
def runCodegen(self, *args): def runCodegen(self, *args):
argv = [self.__codegen] return self.runTestProgram(args)
# shebang lines are not supported on native
# Windows consoles
if os.name == "nt":
argv.insert(0, sys.executable)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
info.check_returncode()
out = info.stdout.strip()
err = info.stderr.strip()
def _getSubs(self):
# Known substitutions for standard boilerplate # Known substitutions for standard boilerplate
subs = { return {
"standard_top_comment": "/*\n" "standard_top_comment": "/*\n"
" * This file is generated by gdbus-codegen, do not modify it.\n" " * This file is generated by gdbus-codegen, do not modify it.\n"
" *\n" " *\n"
@ -326,11 +276,6 @@ class TestCodegen(unittest.TestCase):
"}", "}",
} }
result = Result(info, out, err, subs)
print("Output:", result.out)
return result
def runCodegenWithInterface(self, interface_contents, *args): def runCodegenWithInterface(self, interface_contents, *args):
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
dir=self.tmpdir.name, suffix=".xml", delete=False dir=self.tmpdir.name, suffix=".xml", delete=False

View File

@ -23,21 +23,16 @@
"""Integration tests for the gio utility.""" """Integration tests for the gio utility."""
import collections
import os
import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
Result = collections.namedtuple("Result", ("info", "out", "err")) class TestGioTool(testprogramrunner.TestProgramRunner):
class TestGioTool(unittest.TestCase):
"""Integration test for running the gio tool. """Integration test for running the gio tool.
This can be run when installed or uninstalled. When uninstalled, it This can be run when installed or uninstalled. When uninstalled, it
@ -48,61 +43,11 @@ class TestGioTool(unittest.TestCase):
effects on the file system. effects on the file system.
""" """
# Track the cwd, we want to back out to that to clean up our tempdir PROGRAM_NAME = "gio"
cwd = "" PROGRAM_TYPE = testprogramrunner.ProgramType.NATIVE
def setUp(self):
self.timeout_seconds = 6 # seconds per test
self.tmpdir = tempfile.TemporaryDirectory()
self.cwd = os.getcwd()
os.chdir(self.tmpdir.name)
print("tmpdir:", self.tmpdir.name)
ext = ""
if os.name == "nt":
ext = ".exe"
if "G_TEST_BUILDDIR" in os.environ:
self.__gio = os.path.join(
os.environ["G_TEST_BUILDDIR"],
"..",
"gio" + ext,
)
else:
self.__gio = shutil.which("gio" + ext)
print("gio:", self.__gio)
def tearDown(self):
os.chdir(self.cwd)
self.tmpdir.cleanup()
def runGio(self, *args): def runGio(self, *args):
argv = [self.__gio] return self.runTestProgram(args, timeout_seconds=6)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
info.check_returncode()
out = info.stdout.strip()
err = info.stderr.strip()
result = Result(info, out, err)
print("Output:", result.out)
return result
def test_help(self): def test_help(self):
"""Test the --help argument and help subcommand.""" """Test the --help argument and help subcommand."""

View File

@ -186,12 +186,14 @@ test_extra_programs = {
python_tests = { python_tests = {
# FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/2764 # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/2764
'codegen.py' : { 'codegen.py' : {
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(gdbus_codegen.full_path())},
'can_fail' : host_system == 'freebsd', 'can_fail' : host_system == 'freebsd',
'suite': ['gdbus-codegen', 'slow'], 'suite': ['gdbus-codegen', 'slow'],
'timeout': 90, 'timeout': 90,
}, },
'gio-tool.py' : { 'gio-tool.py' : {
'depends' : gio_tool, 'depends' : gio_tool,
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(gio_tool.full_path())},
'can_fail' : host_system == 'windows', 'can_fail' : host_system == 'windows',
}, },
} }
@ -1167,6 +1169,9 @@ foreach test_name, extra_args : gio_tests
) )
endforeach endforeach
python_test_env = test_env
python_test_env.prepend('PYTHONPATH', python_test_libraries_path)
foreach test_name, extra_args : python_tests foreach test_name, extra_args : python_tests
depends = [extra_args.get('depends', [])] depends = [extra_args.get('depends', [])]
suite = ['gio', 'no-valgrind'] + extra_args.get('suite', []) suite = ['gio', 'no-valgrind'] + extra_args.get('suite', [])
@ -1180,13 +1185,18 @@ foreach test_name, extra_args : python_tests
depends += test_extra_programs_targets[program] depends += test_extra_programs_targets[program]
endforeach endforeach
local_test_env = python_test_env
foreach var, value : extra_args.get('env', {})
local_test_env.append(var, value)
endforeach
test( test(
test_name, test_name,
python, python,
protocol : extra_args.get('protocol', test_protocol), protocol : extra_args.get('protocol', test_protocol),
depends: depends, depends: depends,
args: ['-B', files(test_name)], args: ['-B', files(test_name)],
env: test_env, env: local_test_env,
timeout: timeout, timeout: timeout,
suite: suite, suite: suite,
) )
@ -1213,15 +1223,6 @@ foreach test_name, extra_args : python_tests
endif endif
endforeach endforeach
# TAP test runner for Python tests
if installed_tests_enabled
install_data(
files('taptestrunner.py'),
install_dir: installed_tests_execdir,
install_tag: 'tests',
)
endif
if have_bash and have_pkg_config if have_bash and have_pkg_config
prefix = get_option('prefix') prefix = get_option('prefix')
if prefix.endswith(':/') if prefix.endswith(':/')

View File

@ -1,186 +0,0 @@
#!/usr/bin/env python
# coding=utf-8
# Copyright (c) 2015 Remko Tronçon (https://el-tramo.be)
# Copied from https://github.com/remko/pycotap/
#
# Released under the MIT license
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import unittest
import sys
import base64
from io import StringIO
# Log modes
class LogMode(object):
LogToError, LogToDiagnostics, LogToYAML, LogToAttachment = range(4)
class TAPTestResult(unittest.TestResult):
def __init__(self, output_stream, error_stream, message_log, test_output_log):
super(TAPTestResult, self).__init__(self, output_stream)
self.output_stream = output_stream
self.error_stream = error_stream
self.orig_stdout = None
self.orig_stderr = None
self.message = None
self.test_output = None
self.message_log = message_log
self.test_output_log = test_output_log
self.output_stream.write("TAP version 13\n")
self._set_streams()
def printErrors(self):
self.print_raw("1..%d\n" % self.testsRun)
self._reset_streams()
def _set_streams(self):
self.orig_stdout = sys.stdout
self.orig_stderr = sys.stderr
if self.message_log == LogMode.LogToError:
self.message = self.error_stream
else:
self.message = StringIO()
if self.test_output_log == LogMode.LogToError:
self.test_output = self.error_stream
else:
self.test_output = StringIO()
if self.message_log == self.test_output_log:
self.test_output = self.message
sys.stdout = sys.stderr = self.test_output
def _reset_streams(self):
sys.stdout = self.orig_stdout
sys.stderr = self.orig_stderr
def print_raw(self, text):
self.output_stream.write(text)
self.output_stream.flush()
def print_result(self, result, test, directive=None):
self.output_stream.write("%s %d %s" % (result, self.testsRun, test.id()))
if directive:
self.output_stream.write(" # " + directive)
self.output_stream.write("\n")
self.output_stream.flush()
def ok(self, test, directive=None):
self.print_result("ok", test, directive)
def not_ok(self, test):
self.print_result("not ok", test)
def startTest(self, test):
super(TAPTestResult, self).startTest(test)
def stopTest(self, test):
super(TAPTestResult, self).stopTest(test)
if self.message_log == self.test_output_log:
logs = [(self.message_log, self.message, "output")]
else:
logs = [
(self.test_output_log, self.test_output, "test_output"),
(self.message_log, self.message, "message"),
]
for log_mode, log, log_name in logs:
if log_mode != LogMode.LogToError:
output = log.getvalue()
if len(output):
if log_mode == LogMode.LogToYAML:
self.print_raw(" ---\n")
self.print_raw(" " + log_name + ": |\n")
self.print_raw(
" " + output.rstrip().replace("\n", "\n ") + "\n"
)
self.print_raw(" ...\n")
elif log_mode == LogMode.LogToAttachment:
self.print_raw(" ---\n")
self.print_raw(" " + log_name + ":\n")
self.print_raw(" File-Name: " + log_name + ".txt\n")
self.print_raw(" File-Type: text/plain\n")
self.print_raw(
" File-Content: " + base64.b64encode(output) + "\n"
)
self.print_raw(" ...\n")
else:
self.print_raw(
"# " + output.rstrip().replace("\n", "\n# ") + "\n"
)
# Truncate doesn't change the current stream position.
# Seek to the beginning to avoid extensions on subsequent writes.
log.seek(0)
log.truncate(0)
def addSuccess(self, test):
super(TAPTestResult, self).addSuccess(test)
self.ok(test)
def addError(self, test, err):
super(TAPTestResult, self).addError(test, err)
self.message.write(self.errors[-1][1] + "\n")
self.not_ok(test)
def addFailure(self, test, err):
super(TAPTestResult, self).addFailure(test, err)
self.message.write(self.failures[-1][1] + "\n")
self.not_ok(test)
def addSkip(self, test, reason):
super(TAPTestResult, self).addSkip(test, reason)
self.ok(test, "SKIP " + reason)
def addExpectedFailure(self, test, err):
super(TAPTestResult, self).addExpectedFailure(test, err)
self.ok(test)
def addUnexpectedSuccess(self, test):
super(TAPTestResult, self).addUnexpectedSuccess(test)
self.message.write("Unexpected success" + "\n")
self.not_ok(test)
class TAPTestRunner(object):
def __init__(
self,
message_log=LogMode.LogToYAML,
test_output_log=LogMode.LogToDiagnostics,
output_stream=sys.stdout,
error_stream=sys.stderr,
):
self.output_stream = output_stream
self.error_stream = error_stream
self.message_log = message_log
self.test_output_log = test_output_log
def run(self, test):
result = TAPTestResult(
self.output_stream,
self.error_stream,
self.message_log,
self.test_output_log,
)
test(result)
result.printErrors()
return result

View File

@ -248,9 +248,9 @@ if enable_gir
subdir('introspection') subdir('introspection')
endif endif
subdir('decompiler')
subdir('inspector')
if build_tests if build_tests
subdir('tests') subdir('tests')
endif endif
subdir('decompiler')
subdir('inspector')

View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright © 2025 Marco Trevisan <mail@3v1n0.net>
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
""" Integration tests for gi-compile-repository. """
import os
import unittest
import taptestrunner
import testprogramrunner
class TestGICompileRepositoryBase(testprogramrunner.TestProgramRunner):
"""Integration test base class for checking gi-compile-repository behavior"""
PROGRAM_NAME = "gi-compile-repository"
PROGRAM_TYPE = testprogramrunner.ProgramType.NATIVE
@classmethod
def setUpClass(cls):
super().setUpClass()
if "G_TEST_BUILDDIR" in os.environ:
cls._gir_path = os.path.join(
os.environ["G_TEST_BUILDDIR"], "..", "introspection"
)
else:
cls._gir_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"..",
"..",
"..",
"share",
"gir-1.0",
)
print(f"gir path set to {cls._gir_path}")
class TestGICompileRepository(TestGICompileRepositoryBase):
"""Integration test for checking gi-compile-repository behavior"""
def test_open_failure(self):
gir_path = "this-is/not/a-file.gir"
result = self.runTestProgram(
[gir_path, "--output", os.path.join(self.tmpdir.name, "invalid.typelib")],
should_fail=True,
)
self.assertEqual(result.info.returncode, 1)
self.assertIn(f"Error parsing file {gir_path}", result.err)
class TestGICompileRepositoryForGLib(TestGICompileRepositoryBase):
GIR_NAME = "GLib-2.0"
def runTestProgram(self, *args, **kwargs):
gir_file = os.path.join(self._gir_path, f"{self.GIR_NAME}.gir")
self.assertTrue(os.path.exists(gir_file))
argv = [gir_file]
argv.extend(*args)
if self.GIR_NAME != "GLib-2.0":
argv.extend(["--includedir", self._gir_path])
return super().runTestProgram(argv, **kwargs)
def test_write_failure(self):
typelib_path = "this-is/not/a-good-output/invalid.typelib"
result = self.runTestProgram(
["--output", typelib_path],
should_fail=True,
)
self.assertEqual(result.info.returncode, 1)
self.assertIn(f"Failed to open {typelib_path}.tmp", result.err)
def test_compile(self):
typelib_name = os.path.splitext(self.GIR_NAME)[0]
typelib_path = os.path.join(self.tmpdir.name, f"{typelib_name}.typelib")
argv = ["--output", typelib_path]
result = self.runTestProgram(argv)
self.assertFalse(result.out)
self.assertFalse(result.err)
self.assertTrue(os.path.exists(typelib_path))
class TestGICompileRepositoryForGObject(TestGICompileRepositoryForGLib):
GIR_NAME = "GObject-2.0"
class TestGICompileRepositoryForGio(TestGICompileRepositoryForGLib):
GIR_NAME = "Gio-2.0"
if __name__ == "__main__":
unittest.main(testRunner=taptestrunner.TAPTestRunner())

View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright © 2025 Marco Trevisan <mail@3v1n0.net>
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
""" Integration tests for gi-inspect-typelib. """
import os
import sys
import unittest
import taptestrunner
import testprogramrunner
class TestGIInspectTypelibBase(testprogramrunner.TestProgramRunner):
"""Integration test base class for checking gi-inspect-typelib behavior"""
PROGRAM_NAME = "gi-inspect-typelib"
PROGRAM_TYPE = testprogramrunner.ProgramType.NATIVE
def runTestProgram(self, *args, **kwargs):
return super().runTestProgram(args, **kwargs)
class TestGIInspectTypelibCommandLine(TestGIInspectTypelibBase):
def test_help(self):
"""Test the --help argument."""
result = self.runTestProgram("--help")
self.assertIn("Usage:", result.out)
self.assertIn(
f"{self._program_name} [OPTION…] NAMESPACE - Inspect GI typelib", result.out
)
self.assertIn("--typelib-version=VERSION", result.out)
self.assertIn("--print-shlibs", result.out)
self.assertIn("--print-typelibs", result.out)
def test_no_args(self):
"""Test running with no arguments at all."""
result = self.runTestProgram(should_fail=True)
self.assertEqual("Please specify exactly one namespace", result.err)
def test_invalid_typelib(self):
res = self.runTestProgram(
"--print-typelibs", "--print-shlibs", "AnInvalidNameSpace", should_fail=True
)
self.assertFalse(res.out)
self.assertIn(
"Typelib file for namespace 'AnInvalidNameSpace' (any version) not found",
res.err,
)
class TestGIInspectTypelibForGLibTypelib(TestGIInspectTypelibBase):
"""Test introspection of typelib for GLib typelib"""
TYPELIB_NAMESPACE = "GLib"
TYPELIB_VERSION = "2.0"
LIB_SONAME = "0"
@classmethod
def setUpClass(cls):
super().setUpClass()
if "G_TEST_BUILDDIR" in os.environ:
os.environ["GI_TYPELIB_PATH"] = os.path.join(
os.environ["G_TEST_BUILDDIR"], "..", "introspection"
)
def runTestProgram(self, *args, **kwargs):
argv = list(args)
argv.append(self.TYPELIB_NAMESPACE)
if self.TYPELIB_VERSION:
argv.append(f"--typelib-version={self.TYPELIB_VERSION}")
return super().runTestProgram(*argv, **kwargs)
def get_shlib_ext(self):
if os.name == "nt":
return f"-{self.LIB_SONAME}.dll"
elif sys.platform == "darwin":
return f".{self.LIB_SONAME}.dylib"
return f".so.{self.LIB_SONAME}"
def check_shlib(self, out):
self.assertIn(
f"lib{self.TYPELIB_NAMESPACE.lower()}-{self.TYPELIB_VERSION}"
+ f"{self.get_shlib_ext()}",
out,
)
def check_typelib_deps(self, out):
self.assertNotIn("GLib-2.0", out)
self.assertNotIn("GModule-2.0", out)
self.assertNotIn("GObject-2.0", out)
self.assertNotIn("Gio-2.0", out)
def test_print_typelibs(self):
res = self.runTestProgram("--print-typelibs")
self.assertFalse(res.err)
if self.TYPELIB_NAMESPACE == "GLib":
self.assertFalse(res.out)
self.check_typelib_deps(res.out)
def test_print_shlibs(self):
res = self.runTestProgram("--print-shlibs")
self.assertFalse(res.err)
self.check_shlib(res.out)
self.assertNotIn("GLib-2.0", res.out)
def test_print_typelibs_and_shlibs(self):
res = self.runTestProgram("--print-typelibs", "--print-shlibs")
self.assertFalse(res.err)
self.check_shlib(res.out)
class TestGIInspectTypelibForGObjectTypelib(TestGIInspectTypelibForGLibTypelib):
"""Test introspection of typelib for GObject typelib"""
TYPELIB_NAMESPACE = "GObject"
def check_typelib_deps(self, out):
self.assertIn("GLib-2.0", out)
self.assertNotIn("GModule-2.0", out)
self.assertNotIn("GObject-2.0", out)
self.assertNotIn("Gio-2.0", out)
class TestGIInspectTypelibForGioTypelib(TestGIInspectTypelibForGLibTypelib):
"""Test introspection of typelib for Gio typelib"""
TYPELIB_NAMESPACE = "Gio"
def check_typelib_deps(self, out):
self.assertIn("GLib-2.0", out)
self.assertIn("GObject-2.0", out)
self.assertIn("GModule-2.0", out)
self.assertNotIn("Gio-2.0", out)
if __name__ == "__main__":
unittest.main(testRunner=taptestrunner.TAPTestRunner())

View File

@ -161,3 +161,83 @@ foreach test_name, extra_args : girepository_tests
should_fail: extra_args.get('should_fail', false), should_fail: extra_args.get('should_fail', false),
) )
endforeach endforeach
python_tests = {}
if enable_gir
python_tests += {
'gi-compile-repository.py': {
'depends': [gicompilerepository, glib_gir[0], gobject_gir[0], gio_gir[0]],
'env': {
'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(gicompilerepository.full_path()),
},
'suite': ['compiler'],
},
'gi-inspect-typelib.py': {
'depends': [giinspecttypelib, glib_gir[1], gobject_gir[1], gio_gir[1]],
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(giinspecttypelib.full_path())},
'suite': ['inspector'],
},
}
endif
python_test_env = test_env
python_test_env.prepend('PYTHONPATH', python_test_libraries_path)
foreach test_name, extra_args : python_tests
depends = [extra_args.get('depends', [])]
suite = ['girepository', 'no-valgrind'] + extra_args.get('suite', [])
if extra_args.get('can_fail', false)
suite += 'failing'
endif
local_test_env = python_test_env
foreach var, value : extra_args.get('env', {})
local_test_env.append(var, value)
endforeach
test(
test_name,
python,
protocol : extra_args.get('protocol', test_protocol),
depends: depends,
args: ['-B', files(test_name)],
env: local_test_env,
suite: suite,
)
if installed_tests_enabled
installed_tests_env = extra_args.get('installed_tests_env', {})
install_data(
files(test_name),
install_dir: installed_tests_execdir,
install_tag: 'tests',
install_mode: 'rwxr-xr-x',
)
test_conf = configuration_data()
test_conf.set('installed_tests_dir', installed_tests_execdir)
test_conf.set('program', test_name)
test_env_override = ''
installed_tests_env = extra_args.get('installed_tests_env', {})
if installed_tests_env != {}
envs = []
foreach var, value : installed_tests_env
envs += '@0@="@1@"'.format(var, value)
endforeach
test_env_override = '@0@ @1@ '.format(env_program.full_path(), ' '.join(envs))
endif
test_conf.set('env', test_env_override)
configure_file(
input: installed_tests_template_tap,
output: test_name + '.test',
install_dir: installed_tests_metadir,
install_tag: 'tests',
configuration: test_conf,
)
endif
endforeach

View File

@ -20,16 +20,13 @@
""" Integration tests for g_assert() functions. """ """ Integration tests for g_assert() functions. """
import collections
import os import os
import shutil import shutil
import subprocess
import tempfile import tempfile
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
Result = collections.namedtuple("Result", ("info", "out", "err"))
GDB_SCRIPT = """ GDB_SCRIPT = """
# Work around https://sourceware.org/bugzilla/show_bug.cgi?id=22501 # Work around https://sourceware.org/bugzilla/show_bug.cgi?id=22501
@ -42,7 +39,7 @@ quit
""" """
class TestAssertMessage(unittest.TestCase): class TestAssertMessage(testprogramrunner.TestProgramRunner):
"""Integration test for throwing message on g_assert(). """Integration test for throwing message on g_assert().
This can be run when installed or uninstalled. When uninstalled, This can be run when installed or uninstalled. When uninstalled,
@ -54,80 +51,21 @@ class TestAssertMessage(unittest.TestCase):
and automated tools can more easily debug assertion failures. and automated tools can more easily debug assertion failures.
""" """
def setUp(self): PROGRAM_NAME = "assert-msg-test"
self.__gdb = shutil.which("gdb") PROGRAM_TYPE = testprogramrunner.ProgramType.NATIVE
self.timeout_seconds = 10 # seconds per test
ext = "" def setUp(self):
if os.name == "nt": super().setUp()
ext = ".exe" self.__gdb = shutil.which("gdb")
if "G_TEST_BUILDDIR" in os.environ:
self.__assert_msg_test = os.path.join(
os.environ["G_TEST_BUILDDIR"], "assert-msg-test" + ext
)
else:
self.__assert_msg_test = os.path.join(
os.path.dirname(__file__), "assert-msg-test" + ext
)
print("assert-msg-test:", self.__assert_msg_test)
def runAssertMessage(self, *args): def runAssertMessage(self, *args):
argv = [self.__assert_msg_test] return self.runTestProgram(args, should_fail=True)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
out = info.stdout.strip()
err = info.stderr.strip()
result = Result(info, out, err)
print("Output:", result.out)
print("Error:", result.err)
return result
def runGdbAssertMessage(self, *args): def runGdbAssertMessage(self, *args):
if self.__gdb is None: if self.__gdb is None:
return Result(None, "", "") return testprogramrunner.Result()
argv = ["gdb", "-n", "--batch"] return self.runTestProgram(args, wrapper_args=["gdb", "-n", "--batch"])
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
out = info.stdout.strip()
err = info.stderr.strip()
result = Result(info, out, err)
print("Output:", result.out)
print("Error:", result.err)
print(result.info)
return result
def test_gassert(self): def test_gassert(self):
"""Test running g_assert() and fail the program.""" """Test running g_assert() and fail the program."""
@ -154,9 +92,7 @@ class TestAssertMessage(unittest.TestCase):
try: try:
tmp.write(GDB_SCRIPT) tmp.write(GDB_SCRIPT)
tmp.close() tmp.close()
result = self.runGdbAssertMessage( result = self.runGdbAssertMessage("-x", tmp.name)
"-x", tmp.name, self.__assert_msg_test
)
finally: finally:
os.unlink(tmp.name) os.unlink(tmp.name)

View File

@ -512,6 +512,9 @@ if 'messages-low-memory' in test_extra_programs
} }
endif endif
python_test_env = test_env
python_test_env.prepend('PYTHONPATH', python_test_libraries_path)
foreach test_name, extra_args : python_tests foreach test_name, extra_args : python_tests
depends = [extra_args.get('depends', [])] depends = [extra_args.get('depends', [])]
suite = ['glib', 'core', 'no-valgrind'] suite = ['glib', 'core', 'no-valgrind']
@ -520,7 +523,7 @@ foreach test_name, extra_args : python_tests
suite += 'failing' suite += 'failing'
endif endif
local_test_env = test_env local_test_env = python_test_env
foreach var, value : extra_args.get('env', {}) foreach var, value : extra_args.get('env', {})
local_test_env.append(var, value) local_test_env.append(var, value)
endforeach endforeach

View File

@ -21,17 +21,14 @@
""" Integration tests for g_message functions on low-memory. """ """ Integration tests for g_message functions on low-memory. """
import collections
import os import os
import subprocess
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
Result = collections.namedtuple("Result", ("info", "out", "err"))
class TestMessagesLowMemory(unittest.TestCase): class TestMessagesLowMemory(testprogramrunner.TestProgramRunner):
"""Integration test for checking g_message()s behavior on low memory. """Integration test for checking g_message()s behavior on low memory.
This can be run when installed or uninstalled. When uninstalled, This can be run when installed or uninstalled. When uninstalled,
@ -42,51 +39,12 @@ class TestMessagesLowMemory(unittest.TestCase):
error message. error message.
""" """
test_binary = "messages-low-memory" PROGRAM_NAME = "messages-low-memory"
PROGRAM_TYPE = testprogramrunner.ProgramType.NATIVE
def setUp(self):
ext = ""
if os.name == "nt":
ext = ".exe"
if "G_TEST_BUILDDIR" in os.environ:
self._test_binary = os.path.join(
os.environ["G_TEST_BUILDDIR"], self.test_binary + ext
)
else:
self._test_binary = os.path.join(
os.path.dirname(__file__), self.test_binary + ext
)
print("messages-low-memory:", self._test_binary)
def runTestBinary(self, *args):
print("Running:", *args)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
*args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
out = info.stdout.strip()
err = info.stderr.strip()
result = Result(info, out, err)
print("Return code:", result.info.returncode)
print("Output:", result.out)
print("Error:", result.err)
return result
def test_message_memory_allocation_failure(self): def test_message_memory_allocation_failure(self):
"""Test running g_message() when memory is exhausted.""" """Test running g_message() when memory is exhausted."""
result = self.runTestBinary(self._test_binary) result = self.runTestProgram([], should_fail=True)
if result.info.returncode == 77: if result.info.returncode == 77:
self.skipTest("Not supported") self.skipTest("Not supported")

View File

@ -22,7 +22,6 @@
"""Integration tests for glib-genmarshal utility.""" """Integration tests for glib-genmarshal utility."""
import collections
import os import os
import shutil import shutil
import subprocess import subprocess
@ -32,16 +31,14 @@ from textwrap import dedent
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
# Disable line length warnings as wrapping the C code templates would be hard # Disable line length warnings as wrapping the C code templates would be hard
# flake8: noqa: E501 # flake8: noqa: E501
Result = collections.namedtuple("Result", ("info", "out", "err", "subs")) class TestGenmarshal(testprogramrunner.TestProgramRunner):
class TestGenmarshal(unittest.TestCase):
"""Integration test for running glib-genmarshal. """Integration test for running glib-genmarshal.
This can be run when installed or uninstalled. When uninstalled, it This can be run when installed or uninstalled. When uninstalled, it
@ -54,58 +51,15 @@ class TestGenmarshal(unittest.TestCase):
convert this test to just check command line behaviour. convert this test to just check command line behaviour.
""" """
# Track the cwd, we want to back out to that to clean up our tempdir PROGRAM_NAME = "glib-genmarshal"
cwd = "" PROGRAM_TYPE = testprogramrunner.ProgramType.INTERPRETED
def setUp(self):
self.timeout_seconds = 10 # seconds per test
self.tmpdir = tempfile.TemporaryDirectory()
self.cwd = os.getcwd()
os.chdir(self.tmpdir.name)
print("tmpdir:", self.tmpdir.name)
if "G_TEST_BUILDDIR" in os.environ:
self.__genmarshal = os.path.join(
os.environ["G_TEST_BUILDDIR"], "..", "glib-genmarshal"
)
else:
self.__genmarshal = shutil.which("glib-genmarshal")
print("genmarshal:", self.__genmarshal)
def tearDown(self):
os.chdir(self.cwd)
self.tmpdir.cleanup()
def runGenmarshal(self, *args): def runGenmarshal(self, *args):
argv = [self.__genmarshal] return self.runTestProgram(args)
# shebang lines are not supported on native
# Windows consoles
if os.name == "nt":
argv.insert(0, sys.executable)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
info.check_returncode()
out = info.stdout.strip()
err = info.stderr.strip()
def _getSubs(self):
# Known substitutions for standard boilerplate # Known substitutions for standard boilerplate
subs = { return {
"standard_top_comment": "This file is generated by glib-genmarshal, do not modify " "standard_top_comment": "This file is generated by glib-genmarshal, do not modify "
"it. This code is licensed under the same license as the " "it. This code is licensed under the same license as the "
"containing project. Note that it links to GLib, so must " "containing project. Note that it links to GLib, so must "

View File

@ -20,19 +20,13 @@
"""Integration tests for gobject-query utility.""" """Integration tests for gobject-query utility."""
import collections
import os
import shutil
import subprocess
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
Result = collections.namedtuple("Result", ("info", "out", "err")) class TestGobjectQuery(testprogramrunner.TestProgramRunner):
class TestGobjectQuery(unittest.TestCase):
"""Integration test for running gobject-query. """Integration test for running gobject-query.
This can be run when installed or uninstalled. When uninstalled, it This can be run when installed or uninstalled. When uninstalled, it
@ -42,44 +36,10 @@ class TestGobjectQuery(unittest.TestCase):
handling of command line arguments, and its exit statuses. handling of command line arguments, and its exit statuses.
""" """
def setUp(self): PROGRAM_NAME = "gobject-query"
self.timeout_seconds = 10 # seconds per test
if "G_TEST_BUILDDIR" in os.environ:
self.__gobject_query = os.path.join(
os.environ["G_TEST_BUILDDIR"], "..", "gobject-query"
)
else:
self.__gobject_query = shutil.which("gobject-query")
print("gobject-query:", self.__gobject_query)
def runGobjectQuery(self, *args): def runGobjectQuery(self, *args):
argv = [self.__gobject_query] return self.runTestProgram(args)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
text=True,
encoding="utf-8",
)
info.check_returncode()
out = info.stdout.strip()
err = info.stderr.strip()
result = Result(info, out, err)
print("Output:", result.out)
return result
def test_help(self): def test_help(self):
"""Test the --help argument.""" """Test the --help argument."""

View File

@ -161,12 +161,17 @@ if cc.get_id() != 'msvc'
endif endif
python_tests = { python_tests = {
'genmarshal.py' : {}, 'genmarshal.py' : {
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(glib_genmarshal.full_path())},
},
'gobject-query.py' : { 'gobject-query.py' : {
'depends' : gobject_query, 'depends' : gobject_query,
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(gobject_query.full_path())},
'can_fail' : host_system == 'windows', 'can_fail' : host_system == 'windows',
}, },
'mkenums.py' : {}, 'mkenums.py' : {
'env': {'_G_TEST_PROGRAM_RUNNER_PATH': fs.parent(glib_mkenums.full_path())},
},
} }
test_env = environment() test_env = environment()
@ -227,6 +232,9 @@ foreach test_name, extra_args : gobject_tests
) )
endforeach endforeach
python_test_env = test_env
python_test_env.prepend('PYTHONPATH', python_test_libraries_path)
foreach test_name, extra_args : python_tests foreach test_name, extra_args : python_tests
depends = [extra_args.get('depends', [])] depends = [extra_args.get('depends', [])]
suite = ['gobject', 'no-valgrind'] suite = ['gobject', 'no-valgrind']
@ -235,13 +243,18 @@ foreach test_name, extra_args : python_tests
suite += 'failing' suite += 'failing'
endif endif
local_test_env = python_test_env
foreach var, value : extra_args.get('env', {})
local_test_env.set(var, value)
endforeach
test( test(
test_name, test_name,
python, python,
protocol : extra_args.get('protocol', test_protocol), protocol : extra_args.get('protocol', test_protocol),
depends: depends, depends: depends,
args: ['-B', files(test_name)], args: ['-B', files(test_name)],
env: test_env, env: local_test_env,
suite: suite, suite: suite,
) )
@ -266,12 +279,3 @@ foreach test_name, extra_args : python_tests
) )
endif endif
endforeach endforeach
# TAP test runner for Python tests
if installed_tests_enabled
install_data(
files('taptestrunner.py'),
install_dir: installed_tests_execdir,
install_tag: 'tests',
)
endif

View File

@ -22,22 +22,16 @@
"""Integration tests for glib-mkenums utility.""" """Integration tests for glib-mkenums utility."""
import collections
import os import os
import shutil
import subprocess
import sys
import tempfile import tempfile
import textwrap import textwrap
import unittest import unittest
import taptestrunner import taptestrunner
import testprogramrunner
Result = collections.namedtuple("Result", ("info", "out", "err", "subs")) class TestMkenums(testprogramrunner.TestProgramRunner):
class TestMkenums(unittest.TestCase):
"""Integration test for running glib-mkenums. """Integration test for running glib-mkenums.
This can be run when installed or uninstalled. When uninstalled, it This can be run when installed or uninstalled. When uninstalled, it
@ -50,27 +44,14 @@ class TestMkenums(unittest.TestCase):
convert this test to just check command line behaviour. convert this test to just check command line behaviour.
""" """
# Track the cwd, we want to back out to that to clean up our tempdir PROGRAM_NAME = "glib-mkenums"
cwd = "" PROGRAM_TYPE = testprogramrunner.ProgramType.INTERPRETED
rspfile = False rspfile = False
def setUp(self): def setUp(self):
self.timeout_seconds = 10 # seconds per test super().setUp()
self.tmpdir = tempfile.TemporaryDirectory() print("rspfile: {}".format(self.rspfile))
self.cwd = os.getcwd()
os.chdir(self.tmpdir.name)
print("tmpdir:", self.tmpdir.name)
if "G_TEST_BUILDDIR" in os.environ:
self.__mkenums = os.path.join(
os.environ["G_TEST_BUILDDIR"], "..", "glib-mkenums"
)
else:
self.__mkenums = shutil.which("glib-mkenums")
print("rspfile: {}, mkenums:".format(self.rspfile), self.__mkenums)
def tearDown(self):
os.chdir(self.cwd)
self.tmpdir.cleanup()
def _write_rspfile(self, argv): def _write_rspfile(self, argv):
import shlex import shlex
@ -85,39 +66,17 @@ class TestMkenums(unittest.TestCase):
return f.name return f.name
def runMkenums(self, *args): def runMkenums(self, *args):
argv = list(args)
if self.rspfile: if self.rspfile:
rspfile = self._write_rspfile(args) rspfile = self._write_rspfile(args)
args = ["@" + rspfile] argv = ["@" + rspfile]
argv = [self.__mkenums]
# shebang lines are not supported on native return self.runTestProgram(argv)
# Windows consoles
if os.name == "nt":
argv.insert(0, sys.executable)
argv.extend(args)
print("Running:", argv)
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
print("Environment:", env)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=self.timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
)
info.check_returncode()
out = info.stdout.strip()
err = info.stderr.strip()
def _getSubs(self):
# Known substitutions for standard boilerplate # Known substitutions for standard boilerplate
subs = { return {
"standard_top_comment": "This file is generated by glib-mkenums, do not modify " "standard_top_comment": "This file is generated by glib-mkenums, do not modify "
"it. This code is licensed under the same license as the " "it. This code is licensed under the same license as the "
"containing project. Note that it links to GLib, so must " "containing project. Note that it links to GLib, so must "
@ -125,11 +84,6 @@ class TestMkenums(unittest.TestCase):
"standard_bottom_comment": "Generated data ends here", "standard_bottom_comment": "Generated data ends here",
} }
result = Result(info, out, err, subs)
print("Output:", result.out)
return result
def runMkenumsWithTemplate(self, template_contents, *args): def runMkenumsWithTemplate(self, template_contents, *args):
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
dir=self.tmpdir.name, suffix=".template", delete=False dir=self.tmpdir.name, suffix=".template", delete=False

View File

@ -1,188 +0,0 @@
#!/usr/bin/env python
# coding=utf-8
# Copyright (c) 2015 Remko Tronçon (https://el-tramo.be)
# Copied from https://github.com/remko/pycotap/
#
# SPDX-License-Identifier: MIT
#
# Released under the MIT license
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import unittest
import sys
import base64
from io import StringIO
# Log modes
class LogMode(object):
LogToError, LogToDiagnostics, LogToYAML, LogToAttachment = range(4)
class TAPTestResult(unittest.TestResult):
def __init__(self, output_stream, error_stream, message_log, test_output_log):
super(TAPTestResult, self).__init__(self, output_stream)
self.output_stream = output_stream
self.error_stream = error_stream
self.orig_stdout = None
self.orig_stderr = None
self.message = None
self.test_output = None
self.message_log = message_log
self.test_output_log = test_output_log
self.output_stream.write("TAP version 13\n")
self._set_streams()
def printErrors(self):
self.print_raw("1..%d\n" % self.testsRun)
self._reset_streams()
def _set_streams(self):
self.orig_stdout = sys.stdout
self.orig_stderr = sys.stderr
if self.message_log == LogMode.LogToError:
self.message = self.error_stream
else:
self.message = StringIO()
if self.test_output_log == LogMode.LogToError:
self.test_output = self.error_stream
else:
self.test_output = StringIO()
if self.message_log == self.test_output_log:
self.test_output = self.message
sys.stdout = sys.stderr = self.test_output
def _reset_streams(self):
sys.stdout = self.orig_stdout
sys.stderr = self.orig_stderr
def print_raw(self, text):
self.output_stream.write(text)
self.output_stream.flush()
def print_result(self, result, test, directive=None):
self.output_stream.write("%s %d %s" % (result, self.testsRun, test.id()))
if directive:
self.output_stream.write(" # " + directive)
self.output_stream.write("\n")
self.output_stream.flush()
def ok(self, test, directive=None):
self.print_result("ok", test, directive)
def not_ok(self, test):
self.print_result("not ok", test)
def startTest(self, test):
super(TAPTestResult, self).startTest(test)
def stopTest(self, test):
super(TAPTestResult, self).stopTest(test)
if self.message_log == self.test_output_log:
logs = [(self.message_log, self.message, "output")]
else:
logs = [
(self.test_output_log, self.test_output, "test_output"),
(self.message_log, self.message, "message"),
]
for log_mode, log, log_name in logs:
if log_mode != LogMode.LogToError:
output = log.getvalue()
if len(output):
if log_mode == LogMode.LogToYAML:
self.print_raw(" ---\n")
self.print_raw(" " + log_name + ": |\n")
self.print_raw(
" " + output.rstrip().replace("\n", "\n ") + "\n"
)
self.print_raw(" ...\n")
elif log_mode == LogMode.LogToAttachment:
self.print_raw(" ---\n")
self.print_raw(" " + log_name + ":\n")
self.print_raw(" File-Name: " + log_name + ".txt\n")
self.print_raw(" File-Type: text/plain\n")
self.print_raw(
" File-Content: " + base64.b64encode(output) + "\n"
)
self.print_raw(" ...\n")
else:
self.print_raw(
"# " + output.rstrip().replace("\n", "\n# ") + "\n"
)
# Truncate doesn't change the current stream position.
# Seek to the beginning to avoid extensions on subsequent writes.
log.seek(0)
log.truncate(0)
def addSuccess(self, test):
super(TAPTestResult, self).addSuccess(test)
self.ok(test)
def addError(self, test, err):
super(TAPTestResult, self).addError(test, err)
self.message.write(self.errors[-1][1] + "\n")
self.not_ok(test)
def addFailure(self, test, err):
super(TAPTestResult, self).addFailure(test, err)
self.message.write(self.failures[-1][1] + "\n")
self.not_ok(test)
def addSkip(self, test, reason):
super(TAPTestResult, self).addSkip(test, reason)
self.ok(test, "SKIP " + reason)
def addExpectedFailure(self, test, err):
super(TAPTestResult, self).addExpectedFailure(test, err)
self.ok(test)
def addUnexpectedSuccess(self, test):
super(TAPTestResult, self).addUnexpectedSuccess(test)
self.message.write("Unexpected success" + "\n")
self.not_ok(test)
class TAPTestRunner(object):
def __init__(
self,
message_log=LogMode.LogToYAML,
test_output_log=LogMode.LogToDiagnostics,
output_stream=sys.stdout,
error_stream=sys.stderr,
):
self.output_stream = output_stream
self.error_stream = error_stream
self.message_log = message_log
self.test_output_log = test_output_log
def run(self, test):
result = TAPTestResult(
self.output_stream,
self.error_stream,
self.message_log,
self.test_output_log,
)
test(result)
result.printErrors()
return result

View File

@ -2495,6 +2495,8 @@ python_shebang = '/usr/bin/env python3'
python_version = python.language_version() python_version = python.language_version()
python_version_req = '>=3.7' python_version_req = '>=3.7'
python_test_libraries_path = meson.project_source_root() / 'tests' / 'lib'
assert(fs.exists(python_test_libraries_path))
if not python_version.version_compare(python_version_req) if not python_version.version_compare(python_version_req)
error('Requires Python @0@, @1@ found.'.format(python_version_req, python_version)) error('Requires Python @0@, @1@ found.'.format(python_version_req, python_version))
endif endif

View File

@ -0,0 +1,159 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright © 2018 Endless Mobile, Inc.
# Copyright © 2025 Canonical Ltd.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
"""Integration tests for GLib utilities."""
import os
import shutil
import subprocess
import tempfile
import unittest
import sys
from dataclasses import dataclass
from enum import Enum
class ProgramType(Enum):
"""Enum to define the kind of tool to use"""
NATIVE = 1
INTERPRETED = 2
@dataclass
class Result:
"""Class for keeping track of the executable result."""
info: subprocess.CompletedProcess
out: str
err: str
subs: dict
class TestProgramRunner(unittest.TestCase):
"""Integration test for running glib-based tools.
This can be run when installed or uninstalled. When uninstalled, it
requires G_TEST_BUILDDIR or _G_TEST_PROGRAM_RUNNER_PATH to be set.
"""
PROGRAM_NAME: str = None
PROGRAM_TYPE: ProgramType = ProgramType.NATIVE
INTERPRETER: str = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.assertTrue(cls.PROGRAM_NAME, "class PROGRAM_NAME must be set")
ext = ""
if cls.PROGRAM_TYPE == ProgramType.NATIVE and os.name == "nt":
ext = ".exe"
cls._program_name = f"{cls.PROGRAM_NAME}{ext}"
if "_G_TEST_PROGRAM_RUNNER_PATH" in os.environ:
cls.__program = os.path.join(
os.environ["_G_TEST_PROGRAM_RUNNER_PATH"], cls._program_name
)
elif "G_TEST_BUILDDIR" in os.environ:
cls.__program = os.path.join(
os.environ["G_TEST_BUILDDIR"], cls._program_name
)
else:
cls.__program = os.path.join(os.path.dirname(__file__), cls._program_name)
if not os.path.exists(cls.__program):
cls.__program = shutil.which(cls._program_name)
def setUp(self):
print(f"{self.PROGRAM_NAME}: {self.__program}")
self.assertTrue(os.path.exists(self.__program))
self.tmpdir = tempfile.TemporaryDirectory()
self.addCleanup(self.tmpdir.cleanup)
old_cwd = os.getcwd()
self.addCleanup(os.chdir, old_cwd)
os.chdir(self.tmpdir.name)
print("tmpdir:", self.tmpdir.name)
def runTestProgram(
self,
*args,
should_fail=False,
timeout_seconds=10,
wrapper_args=[],
environment={},
) -> Result:
argv = [self.__program]
argv.extend(*args)
# shebang lines are not supported on native
# Windows consoles
if self.PROGRAM_TYPE == ProgramType.INTERPRETED and os.name == "nt":
argv.insert(0, self.INTERPRETER if self.INTERPRETER else sys.executable)
argv = wrapper_args + argv
env = os.environ.copy()
env["LC_ALL"] = "C.UTF-8"
env["G_DEBUG"] = "fatal-warnings"
env.update(environment)
print("Running:", argv)
# We want to ensure consistent line endings...
info = subprocess.run(
argv,
timeout=timeout_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
universal_newlines=True,
text=True,
encoding="utf-8",
check=False,
)
result = Result(
info=info,
out=info.stdout.strip(),
err=info.stderr.strip(),
subs=self._getSubs(),
)
print("Return code:", result.info.returncode)
print("Output:\n", result.out)
print("Error:\n", result.err)
if should_fail:
with self.assertRaises(subprocess.CalledProcessError):
info.check_returncode()
else:
info.check_returncode()
return result
def _getSubs(self) -> dict:
return {}

View File

@ -33,3 +33,19 @@ test(
suite : ['lint', 'no-valgrind'], suite : ['lint', 'no-valgrind'],
protocol : 'tap', protocol : 'tap',
) )
# TAP test runner for Python tests
if installed_tests_enabled
lib_path = fs.relative_to(
meson.project_source_root() / python_test_libraries_path,
meson.current_source_dir())
install_data(
files(
lib_path / 'taptestrunner.py',
lib_path / 'testprogramrunner.py',
),
install_dir: installed_tests_execdir,
install_tag: 'tests',
)
endif