From 1d119d98b9ce0768927d1d71abe46cf05bd12fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jard=C3=B3n?= Date: Sun, 18 Sep 2022 21:21:24 +0100 Subject: [PATCH 01/12] .github: Run tests in fedora 37 as well And remove soon to be deprecated fedora 35 --- .github/common.env | 2 +- .github/compose/ci.docker-compose.yml | 10 +++++----- .github/run-ci.sh | 2 +- .github/workflows/ci.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/common.env b/.github/common.env index e8f0776f4..9193d164e 100644 --- a/.github/common.env +++ b/.github/common.env @@ -1,5 +1,5 @@ # Shared common variables -CI_IMAGE_VERSION=master-533491591 +CI_IMAGE_VERSION=master-784208155 CI_TOXENV_MAIN=py36-nocover,py37-nocover,py38-nocover,py39-nocover,py310-nocover CI_TOXENV_ALL="${CI_TOXENV_MAIN}" diff --git a/.github/compose/ci.docker-compose.yml b/.github/compose/ci.docker-compose.yml index 05a704b67..0f693e6eb 100644 --- a/.github/compose/ci.docker-compose.yml +++ b/.github/compose/ci.docker-compose.yml @@ -1,7 +1,7 @@ version: '3.4' x-tests-template: &tests-template - image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:35-${CI_IMAGE_VERSION:-latest} + image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:36-${CI_IMAGE_VERSION:-latest} command: tox -vvvvv -- --color=yes --integration environment: TOXENV: ${CI_TOXENV_ALL} @@ -22,14 +22,14 @@ x-tests-template: &tests-template services: - fedora-35: - <<: *tests-template - image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:35-${CI_IMAGE_VERSION:-latest} - fedora-36: <<: *tests-template image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:36-${CI_IMAGE_VERSION:-latest} + fedora-37: + <<: *tests-template + image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-fedora:37-${CI_IMAGE_VERSION:-latest} + debian-10: <<: *tests-template image: registry.gitlab.com/buildstream/buildstream-docker-images/testsuite-debian:10-${CI_IMAGE_VERSION:-latest} diff --git a/.github/run-ci.sh b/.github/run-ci.sh index e923b9d2a..b19f8ad32 100755 --- a/.github/run-ci.sh +++ b/.github/run-ci.sh @@ -67,8 +67,8 @@ set -e if [ -z "${test_names}" ]; then runTest "lint" runTest "debian-10" - runTest "fedora-35" runTest "fedora-36" + runTest "fedora-37" else for test_name in "${test_names}"; do runTest "${test_name}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79ea923b6..c6118d30f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,8 +26,8 @@ jobs: # "../compose/ci.docker-compose.yml" test-name: - debian-10 - - fedora-35 - fedora-36 + - fedora-37 - lint steps: From 70e9d7e8c3cab9b1c6ce984fb9c84944e31059a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jard=C3=B3n?= Date: Sun, 18 Sep 2022 23:04:06 +0100 Subject: [PATCH 02/12] Enable python 3.11 test This is the python version in fedora 37 --- .github/common.env | 2 +- buildstream/plugins/sources/pip.py | 1 + tox.ini | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/common.env b/.github/common.env index 9193d164e..9db39fb16 100644 --- a/.github/common.env +++ b/.github/common.env @@ -1,5 +1,5 @@ # Shared common variables CI_IMAGE_VERSION=master-784208155 -CI_TOXENV_MAIN=py36-nocover,py37-nocover,py38-nocover,py39-nocover,py310-nocover +CI_TOXENV_MAIN=py36-nocover,py37-nocover,py38-nocover,py39-nocover,py310-nocover,py311-nocover CI_TOXENV_ALL="${CI_TOXENV_MAIN}" diff --git a/buildstream/plugins/sources/pip.py b/buildstream/plugins/sources/pip.py index 6ea8476e0..381f24a72 100644 --- a/buildstream/plugins/sources/pip.py +++ b/buildstream/plugins/sources/pip.py @@ -93,6 +93,7 @@ 'python3.8', 'python3.9', 'python3.10', + 'python3.11', ] # List of allowed extensions taken from diff --git a/tox.ini b/tox.ini index a5d14a209..f388ce4a5 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # Tox global configuration # [tox] -envlist = py36-nocover,py37-nocover,py38-nocover,py39-nocover,py310-nocover +envlist = py36-nocover,py37-nocover,py38-nocover,py39-nocover,py310-nocover,py311-nocover skip_missing_interpreters = true # @@ -13,16 +13,16 @@ skip_missing_interpreters = true [testenv] commands = # Running with coverage reporting enabled - py{36,37,38,39,310}-!nocover: pytest --basetemp {envtmpdir} --cov=buildstream --cov-config .coveragerc {posargs} - py{36,37,38,39,310}-!nocover: mkdir -p .coverage-reports - py{36,37,38,39,310}-!nocover: mv {envtmpdir}/.coverage {toxinidir}/.coverage-reports/.coverage.{env:COVERAGE_PREFIX:}{envname} + py{36,37,38,39,310,311}-!nocover: pytest --basetemp {envtmpdir} --cov=buildstream --cov-config .coveragerc {posargs} + py{36,37,38,39,310,311}-!nocover: mkdir -p .coverage-reports + py{36,37,38,39,310,311}-!nocover: mv {envtmpdir}/.coverage {toxinidir}/.coverage-reports/.coverage.{env:COVERAGE_PREFIX:}{envname} # Running with coverage reporting disabled - py{36,37,38,39,310}-nocover: pytest --basetemp {envtmpdir} {posargs} + py{36,37,38,39,310,311}-nocover: pytest --basetemp {envtmpdir} {posargs} deps = - py{36,37,38,39,310}: -rrequirements/requirements.txt - py{36,37,38,39,310}: -rrequirements/dev-requirements.txt - py{36,37,38,39,310}: -rrequirements/plugin-requirements.txt + py{36,37,38,39,310,311}: -rrequirements/requirements.txt + py{36,37,38,39,310,311}: -rrequirements/dev-requirements.txt + py{36,37,38,39,310,311}: -rrequirements/plugin-requirements.txt # Only require coverage and pytest-cov when using it !nocover: -rrequirements/cov-requirements.txt @@ -35,9 +35,9 @@ passenv = # These keys are not inherited by any other sections # setenv = - py{36,37,38,39,310}: COVERAGE_FILE = {envtmpdir}/.coverage + py{36,37,38,39,310,311}: COVERAGE_FILE = {envtmpdir}/.coverage whitelist_externals = - py{36,37,38,39,310}: + py{36,37,38,39,310,311}: mv mkdir From 4695461558218bd0f0340b1059004171c378f2a3 Mon Sep 17 00:00:00 2001 From: Seppo Yli-Olli Date: Fri, 4 Nov 2022 00:04:52 +0200 Subject: [PATCH 03/12] Update pinned ruamel.yaml.clib to 0.2.7 The older version doesn't support Python 3.11 --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 9044ca1ba..39de8fb81 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -9,5 +9,5 @@ setuptools==44.1.1 ujson==5.4.0 ## The following requirements were added by pip freeze: MarkupSafe==2.1.1 -ruamel.yaml.clib==0.2.6 +ruamel.yaml.clib==0.2.7 six==1.16.0 From 4dbbd0a76b1157180bce6cdd40a05552f5ce4f7e Mon Sep 17 00:00:00 2001 From: Seppo Yli-Olli Date: Sat, 12 Nov 2022 16:20:06 +0200 Subject: [PATCH 04/12] Bump tested grpcio to 1.51.1 This is first upstream release that advertises Python 3.11 support --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 39de8fb81..c9a8f0a1b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ click==8.1.3 -grpcio==1.48.0 +grpcio==1.51.1 Jinja2==3.1.2 pluginbase==1.0.1 protobuf==4.21.4 From ebf8b8374b8818b774ecaf4f57ee60cb825b748a Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 14:32:25 +0900 Subject: [PATCH 05/12] tests/sources/tar.py: xfail test_homeless_environment Cannot set environment variables to None when running tests in subprocesses. --- tests/sources/tar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sources/tar.py b/tests/sources/tar.py index 819502ff7..81cad8b90 100644 --- a/tests/sources/tar.py +++ b/tests/sources/tar.py @@ -348,6 +348,7 @@ def test_netrc_already_specified_user(cli, datafiles, server_type, tmpdir): # Test that BuildStream doesnt crash if HOME is unset while # the netrc module is trying to find it's ~/.netrc file. +@pytest.mark.xfail(reason="Cannot set environment variable to None when running tests in subprocesses") @pytest.mark.datafiles(os.path.join(DATA_DIR, 'fetch')) def test_homeless_environment(cli, tmpdir, datafiles): project = os.path.join(datafiles.dirname, datafiles.basename) From d3dffd31262f3809cf6272ca58837a38747e2596 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 14:33:26 +0900 Subject: [PATCH 06/12] tests/artifactcache/expiry.py: xfail a test We cannot use unittest.mock() when running tests in subprocesses. --- tests/artifactcache/expiry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/artifactcache/expiry.py b/tests/artifactcache/expiry.py index d8c0dd534..4faa0cad4 100644 --- a/tests/artifactcache/expiry.py +++ b/tests/artifactcache/expiry.py @@ -289,6 +289,7 @@ def test_never_delete_required_track(cli, datafiles, tmpdir): # has 10K total disk space, and 6K of it is already in use (not # including any space used by the artifact cache). # +@pytest.mark.xfail(reason="unittest.mock() not supported when running tests in subprocesses") @pytest.mark.parametrize("quota,err_domain,err_reason", [ # Valid configurations ("1", 'success', None), From 32d9318d57c493be61a7f28ab478c1872b97e59a Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 14:34:24 +0900 Subject: [PATCH 07/12] tests/format/optionarch.py: xfail tests We cannot monkeypatch os.uname() in a subprocess. --- tests/format/optionarch.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/format/optionarch.py b/tests/format/optionarch.py index 4c126aa87..cfe347ed5 100644 --- a/tests/format/optionarch.py +++ b/tests/format/optionarch.py @@ -12,6 +12,12 @@ # Context manager to override the reported value of `os.uname()` @contextmanager def override_uname_arch(name): + + # + # Disabling this test since we now run bst in a subprocess during tests. + # + pytest.xfail("Overriding os.uname() in bst subprocess is unsupported") + orig_uname = os.uname orig_tuple = tuple(os.uname()) override_result = (orig_tuple[0], orig_tuple[1], From e838c722d980937424d68d0ddd8441f29a3a75ea Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 15:34:28 +0900 Subject: [PATCH 08/12] tests/loader/junctions.py: Use Result.assert_main_error() Instead of manually asserting Result members, this is required because some of the Result attributes are being removed. --- tests/loader/junctions.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/tests/loader/junctions.py b/tests/loader/junctions.py index aee164396..2d87146ae 100644 --- a/tests/loader/junctions.py +++ b/tests/loader/junctions.py @@ -135,10 +135,7 @@ def test_nested_conflict(cli, datafiles): copy_subprojects(project, datafiles, ['foo', 'bar']) result = cli.run(project=project, args=['build', 'target.bst']) - assert result.exit_code != 0 - assert result.exception - assert isinstance(result.exception, LoadError) - assert result.exception.reason == LoadErrorReason.CONFLICTING_JUNCTION + result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.CONFLICTING_JUNCTION) @pytest.mark.datafiles(DATA_DIR) @@ -146,10 +143,7 @@ def test_invalid_missing(cli, datafiles): project = os.path.join(str(datafiles), 'invalid') result = cli.run(project=project, args=['build', 'missing.bst']) - assert result.exit_code != 0 - assert result.exception - assert isinstance(result.exception, LoadError) - assert result.exception.reason == LoadErrorReason.MISSING_FILE + result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.MISSING_FILE) @pytest.mark.datafiles(DATA_DIR) @@ -158,10 +152,7 @@ def test_invalid_with_deps(cli, datafiles): copy_subprojects(project, datafiles, ['base']) result = cli.run(project=project, args=['build', 'junction-with-deps.bst']) - assert result.exit_code != 0 - assert result.exception - assert isinstance(result.exception, ElementError) - assert result.exception.reason == 'element-forbidden-depends' + result.assert_main_error(ErrorDomain.ELEMENT, 'element-forbidden-depends') @pytest.mark.datafiles(DATA_DIR) @@ -170,10 +161,7 @@ def test_invalid_junction_dep(cli, datafiles): copy_subprojects(project, datafiles, ['base']) result = cli.run(project=project, args=['build', 'junction-dep.bst']) - assert result.exit_code != 0 - assert result.exception - assert isinstance(result.exception, LoadError) - assert result.exception.reason == LoadErrorReason.INVALID_DATA + result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA) @pytest.mark.datafiles(DATA_DIR) @@ -248,10 +236,7 @@ def test_git_show(cli, tmpdir, datafiles): # Verify that bst show does not implicitly fetch subproject result = cli.run(project=project, args=['show', 'target.bst']) - assert result.exit_code != 0 - assert result.exception - assert isinstance(result.exception, LoadError) - assert result.exception.reason == LoadErrorReason.SUBPROJECT_FETCH_NEEDED + result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.SUBPROJECT_FETCH_NEEDED) # Explicitly fetch subproject result = cli.run(project=project, args=['fetch', 'base.bst']) From ee74a2c9d6ecdbd080a77a0c5c6fe3476019093e Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 15:35:34 +0900 Subject: [PATCH 09/12] tests/frontend/show.py: xfail a test which we cannot properly assert We can no longer assert that the client has exited without throwing an exception. --- tests/frontend/show.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/frontend/show.py b/tests/frontend/show.py index 7df5b7679..04e3fd863 100644 --- a/tests/frontend/show.py +++ b/tests/frontend/show.py @@ -269,6 +269,7 @@ def test_fetched_junction(cli, tmpdir, datafiles, element_name, workspaced): ############################################################### # Testing recursion depth # ############################################################### +@pytest.mark.xfail(reason="recursion errors not currently detectable") @pytest.mark.parametrize("dependency_depth", [100, 500, 1200]) def test_exceed_max_recursion_depth(cli, tmpdir, dependency_depth): project_name = "recursion-test" @@ -314,8 +315,14 @@ def setup_test(): if dependency_depth <= recursion_limit: result.assert_success() else: - # Assert exception is thown and handled - assert not result.unhandled_exception + # XXX Assert exception is thown and handled + # + # We need to assert that the client has not thrown a stack trace for + # a recursion error, this should be done by creating a BstError instead + # of just handling it in app.py and doing sys.exit(), because we no longer + # have any way of detecting whether the client has thrown an exception + # otherwise + # assert result.exit_code == -1 shutil.rmtree(project_path) From b20a518e13b3081018a803c55c2b2ac6931ceae7 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 18:40:29 +0900 Subject: [PATCH 10/12] tests/integration/symlinks.py: Fix test expectation When running tests in subprocesses the exit code is unsigned, so check failures with != 0 instead of explicitly checking for -1. --- tests/integration/symlinks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/symlinks.py b/tests/integration/symlinks.py index 18bd724c1..c63e93ca3 100644 --- a/tests/integration/symlinks.py +++ b/tests/integration/symlinks.py @@ -70,5 +70,5 @@ def test_detect_symlink_overlaps_pointing_outside_sandbox(cli, tmpdir, datafiles # point outside the sandbox which BuildStream needs to detect before it # tries to actually write there. result = cli.run(project=project, args=['checkout', element_name, checkout]) - assert result.exit_code == -1 + assert result.exit_code != 0 assert "Destination path resolves to a path outside of the staging area" in result.stderr From ef00c1f27ed79bb6c7e887bb1fb627878a41ab53 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 14:36:11 +0900 Subject: [PATCH 11/12] Run tests in subprocesses This is based off of work started by Seppo Yli-Olli We now run tests in subprocesses instead of using multiprocessing, this patch adds serialization of machine readable error codes so that tests can work. We no longer provide the raw exceptions in the test Result object returned by Cli.run(), which limits the tests in some ways. --- buildstream/_exceptions.py | 19 ---- buildstream/_frontend/app.py | 18 +++- tests/format/assertion.py | 2 +- tests/frontend/fetch.py | 3 +- tests/testutils/runcli.py | 172 ++++++++++++----------------------- 5 files changed, 77 insertions(+), 137 deletions(-) diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py index 44d890ead..4cdbc0bf2 100644 --- a/buildstream/_exceptions.py +++ b/buildstream/_exceptions.py @@ -24,25 +24,10 @@ # pylint: disable=global-statement # The last raised exception, this is used in test cases only -_last_exception = None _last_task_error_domain = None _last_task_error_reason = None -# get_last_exception() -# -# Fetches the last exception from the main process -# -# Used by regression tests -# -def get_last_exception(): - global _last_exception - - le = _last_exception - _last_exception = None - return le - - # get_last_task_error() # # Fetches the last exception from a task @@ -102,7 +87,6 @@ class ErrorDomain(Enum): class BstError(Exception): def __init__(self, message, *, detail=None, domain=None, reason=None, temporary=False): - global _last_exception super().__init__(message) @@ -126,9 +110,6 @@ def __init__(self, message, *, detail=None, domain=None, reason=None, temporary= self.domain = domain self.reason = reason - # Hold on to the last raised exception for testing purposes - _last_exception = self - # PluginError # diff --git a/buildstream/_frontend/app.py b/buildstream/_frontend/app.py index e9c646fb4..05fb296cb 100644 --- a/buildstream/_frontend/app.py +++ b/buildstream/_frontend/app.py @@ -22,9 +22,11 @@ import resource import traceback import datetime +from enum import Enum from textwrap import TextWrapper from contextlib import contextmanager +import ujson import click from click import UsageError @@ -35,7 +37,7 @@ from .._context import Context from .._platform import Platform from .._project import Project -from .._exceptions import BstError, StreamError, LoadError, LoadErrorReason, AppError +from .._exceptions import BstError, StreamError, LoadError, LoadErrorReason, AppError, get_last_task_error from .._message import Message, MessageType, unconditional_messages from .._stream import Stream from .._versions import BST_FORMAT_VERSION @@ -682,6 +684,20 @@ def _error_exit(self, error, prefix=None): detail = '\n' + indent + indent.join(error.detail.splitlines(True)) click.echo("{}".format(detail), err=True) + # Record machine readable errors in a tempfile for the test harness to read back + if 'BST_TEST_ERROR_CODES' in os.environ: + task_error_domain, task_error_reason = get_last_task_error () + error_codes = ujson.dumps ({ + 'main_error_domain': error.domain.value if error.domain else None, + 'main_error_reason': error.reason.value if isinstance (error.reason, Enum) else error.reason, + 'task_error_domain': task_error_domain.value if task_error_domain else None, + 'task_error_reason': ( + task_error_reason.value if isinstance (task_error_reason, Enum) else task_error_reason + ) + }) + with open (os.environ['BST_TEST_ERROR_CODES'], "w", encoding="utf-8") as f: + f.write (error_codes) + sys.exit(-1) # diff --git a/tests/format/assertion.py b/tests/format/assertion.py index 42f69b759..d82fa3226 100644 --- a/tests/format/assertion.py +++ b/tests/format/assertion.py @@ -32,4 +32,4 @@ def test_assertion_cli(cli, datafiles, target, opt_pony, opt_horsy, assertion): # Assert that the assertion text provided by the user # is found in the exception text - assert assertion in str(result.exception) + assert assertion in str(result.stderr) diff --git a/tests/frontend/fetch.py b/tests/frontend/fetch.py index e896f4a67..5351ee8fe 100644 --- a/tests/frontend/fetch.py +++ b/tests/frontend/fetch.py @@ -71,8 +71,7 @@ def test_fetch_consistency_bug(cli, tmpdir, datafiles): # more gracefully as a BUG message. # result = cli.run(project=project, args=['fetch', 'bug.bst']) - assert result.exc is not None - assert str(result.exc) == "Something went terribly wrong" + assert "Something went terribly wrong" in result.stderr @pytest.mark.datafiles(DATA_DIR) diff --git a/tests/testutils/runcli.py b/tests/testutils/runcli.py index 29a2ec1e9..77912bbce 100644 --- a/tests/testutils/runcli.py +++ b/tests/testutils/runcli.py @@ -7,6 +7,8 @@ import traceback import subprocess from contextlib import contextmanager, ExitStack +from enum import Enum +import ujson import pytest # XXX Using pytest private internals here @@ -22,50 +24,21 @@ from buildstream._frontend import cli as bst_cli from buildstream import _yaml -# Special private exception accessor, for test case purposes -from buildstream._exceptions import BstError, get_last_exception, get_last_task_error - # Wrapper for the click.testing result class Result(): def __init__(self, exit_code=None, - exception=None, - exc_info=None, output=None, stderr=None): self.exit_code = exit_code - self.exc = exception - self.exc_info = exc_info self.output = output self.stderr = stderr - self.unhandled_exception = False - - # The last exception/error state is stored at exception - # creation time in BstError(), but this breaks down with - # recoverable errors where code blocks ignore some errors - # and fallback to alternative branches. - # - # For this reason, we just ignore the exception and errors - # in the case that the exit code reported is 0 (success). - # - if self.exit_code != 0: - - # Check if buildstream failed to handle an - # exception, topevel CLI exit should always - # be a SystemExit exception. - # - if not isinstance(exception, SystemExit): - self.unhandled_exception = True - - self.exception = get_last_exception() - self.task_error_domain, \ - self.task_error_reason = get_last_task_error() - else: - self.exception = None - self.task_error_domain = None - self.task_error_reason = None + self.main_error_domain = None + self.main_error_reason = None + self.task_error_domain = None + self.task_error_reason = None # assert_success() # @@ -79,9 +52,6 @@ def __init__(self, # def assert_success(self, fail_message=''): assert self.exit_code == 0, fail_message - assert self.exc is None, fail_message - assert self.exception is None, fail_message - assert self.unhandled_exception is False # assert_main_error() # @@ -106,23 +76,20 @@ def assert_main_error(self, print( """ Exit code: {} - Exception: {} Domain: {} Reason: {} """.format( self.exit_code, - self.exception, - self.exception.domain, - self.exception.reason + self.main_error_domain, + self.main_error_reason )) - assert self.exit_code == -1, fail_message - assert self.exc is not None, fail_message - assert self.exception is not None, fail_message - assert isinstance(self.exception, BstError), fail_message - assert self.unhandled_exception is False + assert self.exit_code != 0, fail_message + + test_domain = error_domain.value if isinstance (error_domain, Enum) else error_domain + test_reason = error_reason.value if isinstance (error_reason, Enum) else error_reason - assert self.exception.domain == error_domain, fail_message - assert self.exception.reason == error_reason, fail_message + assert self.main_error_domain == test_domain, fail_message + assert self.main_error_reason == test_reason, fail_message # assert_task_error() # @@ -143,14 +110,12 @@ def assert_task_error(self, error_reason, fail_message=''): - assert self.exit_code == -1, fail_message - assert self.exc is not None, fail_message - assert self.exception is not None, fail_message - assert isinstance(self.exception, BstError), fail_message - assert self.unhandled_exception is False + test_domain = error_domain.value if isinstance (error_domain, Enum) else error_domain + test_reason = error_reason.value if isinstance (error_reason, Enum) else error_reason - assert self.task_error_domain == error_domain, fail_message - assert self.task_error_reason == error_reason, fail_message + assert self.exit_code != 0, fail_message + assert self.task_error_domain == test_domain, fail_message + assert self.task_error_reason == test_reason, fail_message # get_tracked_elements() # @@ -241,9 +206,21 @@ def run(self, configure=True, project=None, silent=False, env=None, if options is None: options = [] + if env is None: + env = os.environ.copy() + else: + orig_env = os.environ.copy() + orig_env.update (env) + env = orig_env + options = self.default_options + options with ExitStack() as stack: + + # Prepare a tempfile for buildstream to record machine readable error codes + error_codes = stack.enter_context (tempfile.NamedTemporaryFile()) + env['BST_TEST_ERROR_CODES'] = error_codes.name + bst_args = ['--no-colors'] if silent: @@ -263,21 +240,34 @@ def run(self, configure=True, project=None, silent=False, env=None, bst_args += args - if cwd is not None: - stack.enter_context(chdir(cwd)) - - if env is not None: - stack.enter_context(environment(env)) + cmd = ["bst"] + bst_args + process = subprocess.Popen( + cmd, + env=env, + cwd=cwd, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + out, err = process.communicate() + result = Result( + exit_code=process.poll(), + output=out.decode('utf-8'), + stderr=err.decode('utf-8') + ) - # Ensure we have a working stdout - required to work - # around a bug that appears to cause AIX to close - # sys.__stdout__ after setup.py - try: - sys.__stdout__.fileno() - except ValueError: - sys.__stdout__ = open('/dev/stdout', 'w') - - result = self.invoke(bst_cli, bst_args) + # + # Collect machine readable error codes from the tmpfile + # + result_error_codes_string = error_codes.read() + result_error_codes = {} + if result_error_codes_string: + result_error_codes = ujson.loads (result_error_codes_string) + if result_error_codes: + result.main_error_domain = result_error_codes['main_error_domain'] + result.main_error_reason = result_error_codes['main_error_reason'] + result.task_error_domain = result_error_codes['task_error_domain'] + result.task_error_reason = result_error_codes['task_error_reason'] # Some informative stdout we can observe when anything fails if self.verbose: @@ -289,54 +279,8 @@ def run(self, configure=True, project=None, silent=False, env=None, if result.stderr: print("Program stderr was:\n{}".format(result.stderr)) - if result.exc_info and result.exc_info[0] != SystemExit: - traceback.print_exception(*result.exc_info) - return result - def invoke(self, cli, args=None, color=False, **extra): - exc_info = None - exception = None - exit_code = 0 - - # Temporarily redirect sys.stdin to /dev/null to ensure that - # Popen doesn't attempt to read pytest's dummy stdin. - old_stdin = sys.stdin - with open(os.devnull) as devnull: - sys.stdin = devnull - capture = MultiCapture(out=FDCapture(1), err=FDCapture(2), in_=None) - capture.start_capturing() - - try: - cli.main(args=args or (), prog_name=cli.name, **extra) - except SystemExit as e: - if e.code != 0: - exception = e - - exc_info = sys.exc_info() - - exit_code = e.code - if not isinstance(exit_code, int): - sys.stdout.write('Program exit code was not an integer: ') - sys.stdout.write(str(exit_code)) - sys.stdout.write('\n') - exit_code = 1 - except Exception as e: - exception = e - exit_code = -1 - exc_info = sys.exc_info() - finally: - sys.stdout.flush() - - sys.stdin = old_stdin - out, err = capture.readouterr() - capture.stop_capturing() - - return Result(exit_code=exit_code, - exception=exception, - exc_info=exc_info, - output=out, - stderr=err) # Fetch an element state by name by # invoking bst show on the project with the CLI From b3cce90d827ce8080eb568df0c05975c901de1a3 Mon Sep 17 00:00:00 2001 From: Tristan van Berkom Date: Tue, 21 Feb 2023 14:52:01 +0900 Subject: [PATCH 12/12] setup.py: Advertize support for Python 3.10 and 3.11 --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index e174ee337..77d226f20 100755 --- a/setup.py +++ b/setup.py @@ -263,6 +263,8 @@ def get_cmdclass(): 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Topic :: Software Development :: Build Tools' ], description='A framework for modelling build pipelines in YAML',