From 581e4e03ef078175a2a61fc195e93d044da35ec7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 28 May 2025 18:00:53 +0200 Subject: [PATCH] chore(linux): add TC test runner This adds a modified version of JetBrains' TeamCity test runner that makes use of the `parent` property in the service messages and thus allows to properly indent and fold the block that contains the test output. Test-bot: skip --- linux/keyman-config/run-tests.sh | 12 +++- linux/tools/teamcity_testrunner/README.md | 12 ++++ linux/tools/teamcity_testrunner/unittestpy.py | 61 +++++++++++++++++++ resources/builder.inc.sh | 24 +++++--- resources/teamcity/includes/tc-actions.inc.sh | 5 +- 5 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 linux/tools/teamcity_testrunner/README.md create mode 100644 linux/tools/teamcity_testrunner/unittestpy.py diff --git a/linux/keyman-config/run-tests.sh b/linux/keyman-config/run-tests.sh index 3fa49f081d..baa90a41f8 100755 --- a/linux/keyman-config/run-tests.sh +++ b/linux/keyman-config/run-tests.sh @@ -1,5 +1,5 @@ #!/bin/bash -PYTHONPATH=.:${PYTHONPATH} +export PYTHONPATH=.:${PYTHONPATH} XDG_CONFIG_HOME=$(mktemp --directory) export XDG_CONFIG_HOME @@ -13,6 +13,8 @@ if [[ "$1" == "--coverage" ]]; then fi if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then + PYTHONPATH=$(dirname "$0")/../tools:${PYTHONPATH} + if ! pip3 list --format=columns | grep -q teamcity-messages; then if [[ -n "${TEAMCITY_PLATFORM}" ]] || [[ -n "${DOCKER_RUNNING}" ]]; then # Ubuntu 24.04+ prevents mixing pip and system packages and wants us @@ -29,7 +31,9 @@ if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then # shellcheck disable=SC2086 pip3 install --user ${PIP_ARGS:-} teamcity-messages fi - test_module=teamcity.unittestpy + test_module=teamcity_testrunner.unittestpy + echo "##teamcity[testStarted name='|[keyman-config|] Running unit tests']" + echo "##teamcity[flowStarted flowId='unit_tests']" else test_module=unittest extra_opts=-v @@ -38,4 +42,8 @@ fi # shellcheck disable=SC2086 python3 ${coverage:-} -m "${test_module:-}" discover ${extra_opts:-} -s tests/ -p "*_tests.py" +if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then + echo "##teamcity[flowFinished flowId='unit_tests']" + echo "##teamcity[testFinished name='|[keyman-config|] Finished running unit tests']" +fi rm -rf "${XDG_CONFIG_HOME}" diff --git a/linux/tools/teamcity_testrunner/README.md b/linux/tools/teamcity_testrunner/README.md new file mode 100644 index 0000000000..0872c0e7fa --- /dev/null +++ b/linux/tools/teamcity_testrunner/README.md @@ -0,0 +1,12 @@ +# Teamcity Test Runner + +This implements an improved version of JetBrains' unittest runner for +Teamcity. It makes the output of the tests indented and collapsible under +the test block by specifying a parent flow. + +Requires the `teamcity-messages` package. + +## References + +- +- diff --git a/linux/tools/teamcity_testrunner/unittestpy.py b/linux/tools/teamcity_testrunner/unittestpy.py new file mode 100644 index 0000000000..543277cce1 --- /dev/null +++ b/linux/tools/teamcity_testrunner/unittestpy.py @@ -0,0 +1,61 @@ +import sys +from teamcity.messages import TeamcityServiceMessages +from teamcity.unittestpy import TeamcityTestResult, TeamcityTestRunner +from unittest import main + + +class KeymanTeamcityServiceMessages(TeamcityServiceMessages): + def __init__(self, parent_flow='unit_tests'): + self.parent_flow = parent_flow + super().__init__() + + def testStarted(self, testName, captureStandardOutput=None, flowId=None, metainfo=None): + self.message('testStarted', name=testName, metainfo=metainfo) + self.message('flowStarted', flowId=flowId, parent=self.parent_flow) + + def testFinished(self, testName, testDuration=None, flowId=None): + self.message('flowFinished', flowId=flowId, parent=self.parent_flow) + if testDuration is not None: + duration_ms = testDuration.days * 86400000 + \ + testDuration.seconds * 1000 + \ + int(testDuration.microseconds / 1000) + self.message('testFinished', name=testName, duration=str(duration_ms)) + else: + self.message('testFinished', name=testName) + + def testIgnored(self, testName, message='', flowId=None): + self.message('flowFinished', flowId=flowId, parent=self.parent_flow) + self.message('testIgnored', name=testName, message=message) + + def testFailed(self, testName, message='', details='', flowId=None, comparison_failure=None): + self.message('flowFinished', flowId=flowId, parent=self.parent_flow) + if not comparison_failure: + self.message('testFailed', name=testName, message=message, details=details) + else: + diff_message = u"\n{0} != {1}\n".format(comparison_failure.actual, comparison_failure.expected) + self.message( + 'testFailed', + name=testName, + message=str(message) + diff_message, + details=details, + type="comparisonFailure", + actual=comparison_failure.actual, + expected=comparison_failure.expected, + ) + + +class KeymanTestResult(TeamcityTestResult): + def __init__(self, stream=sys.stdout, descriptions=None, verbosity=None): + super().__init__(stream, descriptions, verbosity) + self.messages = KeymanTeamcityServiceMessages() + + +class KeymanTestRunner(TeamcityTestRunner): + resultclass = KeymanTestResult + + def run(self, test): + return super().run(test) + + +if __name__ == '__main__': + main(module=None, testRunner=KeymanTestRunner()) diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 24683543b8..25c9b5994c 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -181,28 +181,30 @@ function builder_heading() { builder_echo() { - local color=white message= mark= block= action= do_output=true + local color=white message= mark= block= action= do_output=true test= local echo_target=echo if [[ $# -gt 1 ]]; then if [[ $1 =~ ^(white|grey|green|success|blue|heading|yellow|warning|red|error|purple|brightwhite|teal|debug|setmark)$ ]]; then color="$1" shift - elif [[ $1 == "start" ]]; then + elif [[ $1 == "start" ]] || [[ $1 == "startTest" ]]; then # builder_echo start block message - action="$1" + test="$1" block="$2" shift 2 + action="start" color="heading" if ! builder_is_running_on_teamcity && builder_is_child_build; then do_output=${_builder_debug_internal:-false} fi - elif [[ $1 == "end" ]]; then + elif [[ $1 == "end" ]] || [[ $1 == "endTest" ]]; then # builder_echo end block status message - action="$1" + test="$1" block="$2" color="$3" shift 3 + action="end" if [[ "${color}" != "error" ]] && ! builder_is_running_on_teamcity && builder_is_child_build; then do_output=${_builder_debug_internal:-false} fi @@ -211,7 +213,11 @@ builder_echo() { message="$*" if [[ "${action}" == "start" ]] && builder_is_running_on_teamcity; then - $echo_target -e "##teamcity[blockOpened name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + if [[ "${test}" == "startTest" ]]; then + $echo_target -e "##teamcity[testSuiteStarted name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + else + $echo_target -e "##teamcity[blockOpened name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + fi fi if ${do_output}; then @@ -241,7 +247,11 @@ builder_echo() { fi if [[ "${action}" == "end" ]] && builder_is_running_on_teamcity; then - $echo_target -e "##teamcity[blockClosed name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + if [[ "${test}" == "endTest" ]]; then + $echo_target -e "##teamcity[testSuiteFinished name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + else + $echo_target -e "##teamcity[blockClosed name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']" + fi fi } diff --git a/resources/teamcity/includes/tc-actions.inc.sh b/resources/teamcity/includes/tc-actions.inc.sh index 4b817a6f21..df008894c2 100644 --- a/resources/teamcity/includes/tc-actions.inc.sh +++ b/resources/teamcity/includes/tc-actions.inc.sh @@ -47,7 +47,8 @@ linux_build_action() { # Run unit tests for Keyman for Linux. linux_unit_tests_action() { - builder_echo start unit_tests "Running unit tests" + builder_echo startTest unit_tests "Running unit tests" + rm -f /tmp/ibus-engine-keyman.log rm -f /tmp/ibus-daemon.log # symlink might point to wrong location, so delete it - will be re-created during tests @@ -56,6 +57,6 @@ linux_unit_tests_action() { export NO_AT_BRIDGE=1 # shellcheck disable=SC2068 "${KEYMAN_ROOT}/linux/build.sh" test $@ - builder_echo end unit_tests success "Finished running unit tests" + builder_echo endTest unit_tests success "Finished running unit tests" }