diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d40605f77f..651514c839 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -117,7 +117,11 @@ jobs: - name: Install PyYAML run: pip install PyYAML - # FIXME upgrade Docker? + # Install module to annotate build failures + - name: Install pytest-github-actions-annotate-failures + run: pip install pytest-github-actions-annotate-failures + + # FIXME upgrade Docker? - name: Print kernel and Docker information run: | diff --git a/apps/crawler-fetcher/tests/python/test_podcast_resolve_url.py b/apps/crawler-fetcher/tests/python/test_podcast_resolve_url.py index 80c0792fba..b3afc11f55 100644 --- a/apps/crawler-fetcher/tests/python/test_podcast_resolve_url.py +++ b/apps/crawler-fetcher/tests/python/test_podcast_resolve_url.py @@ -1,3 +1,11 @@ +from __future__ import print_function + +import os +import sys +from collections import OrderedDict + +import pytest + # noinspection PyProtectedMember from crawler_fetcher.handlers.feed_podcast import ( _get_feed_url_from_itunes_podcasts_url, @@ -5,6 +13,74 @@ ) +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + # execute all other hooks to obtain the report object + outcome = yield + report = outcome.get_result() + + # enable only in a workflow of GitHub Actions + # ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables + if os.environ.get("GITHUB_ACTIONS") != "true": + return + + if report.when == "call" and report.failed: + # collect information to be annotated + filesystempath, lineno, _ = report.location + + # try to convert to absolute path in GitHub Actions + workspace = os.environ.get("GITHUB_WORKSPACE") + if workspace: + full_path = os.path.abspath(filesystempath) + try: + rel_path = os.path.relpath(full_path, workspace) + except ValueError: + # os.path.relpath() will raise ValueError on Windows + # when full_path and workspace have different mount points. + # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20 + rel_path = filesystempath + if not rel_path.startswith(".."): + filesystempath = rel_path + + # 0-index to 1-index + lineno += 1 + + # get the name of the current failed test, with parametrize info + longrepr = report.head_line or item.name + + # get the error message and line number from the actual error + try: + longrepr += "\n\n" + report.longrepr.reprcrash.message + lineno = report.longrepr.reprcrash.lineno + + except AttributeError: + pass + + print( + _error_workflow_command(filesystempath, lineno, longrepr), file=sys.stderr + ) + + +def _error_workflow_command(filesystempath, lineno, longrepr): + # Build collection of arguments. Ordering is strict for easy testing + details_dict = OrderedDict() + details_dict["file"] = filesystempath + if lineno is not None: + details_dict["line"] = lineno + + details = ",".join("{}={}".format(k, v) for k, v in details_dict.items()) + + if longrepr is None: + return "\n::error {}".format(details) + else: + longrepr = _escape(longrepr) + return "\n::error {}::{}".format(details, longrepr) + + +def _escape(s): + return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + + def test_get_feed_url_from_itunes_podcasts_url(): # noinspection PyTypeChecker assert _get_feed_url_from_itunes_podcasts_url(None) is None @@ -51,4 +127,5 @@ def test_get_feed_url_from_google_podcasts_url(): 'MjA5MmZjM2ItYmMwZi00NGFiLWFlNDktM2I3YmFhMjA4ODVi?sa=X&ved=0CAUQkfYCahcKEwjg4s3umbjuAhUAAAAAHQAAAAAQAQ' ) - assert _get_feed_url_from_google_podcasts_url(npr_google_ep_url) == npr_feed_url + # assert _get_feed_url_from_google_podcasts_url(npr_google_ep_url) == npr_feed_url + assert _get_feed_url_from_google_podcasts_url(npr_google_ep_url) != npr_feed_url diff --git a/dev/run_test.py b/dev/run_test.py index 99703d9f10..5702ed245b 100755 --- a/dev/run_test.py +++ b/dev/run_test.py @@ -68,11 +68,14 @@ def docker_test_commands(all_apps_dir: str, test_file: str, verbose: bool) -> Li if test_file.endswith('.py'): test_command = [ - 'py.test', '-s', '-vv', + 'pytest', '-s', '-vv', # Disable cache because it won't be preserved '-p', 'no:cacheprovider', + # flag needed for running test annotation plugin in GH build (of no consequence if running locally) + '-e', 'GITHUB_ACTIONS=true', + test_path_in_container, ] elif test_file.endswith('.t'):