diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3fc2ab3b..5ef02391 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -202,7 +202,7 @@ jobs: id: test-results if: always() run: | - docker run --workdir $GITHUB_WORKSPACE --rm -e INPUT_CHECK_NAME -e INPUT_JUNIT_FILES -e INPUT_TRX_FILES -e INPUT_TIME_UNIT -e INPUT_GITHUB_TOKEN -e INPUT_GITHUB_RETRIES -e INPUT_COMMIT -e INPUT_COMMENT_TITLE -e INPUT_FAIL_ON -e INPUT_REPORT_INDIVIDUAL_RUNS -e INPUT_DEDUPLICATE_CLASSES_BY_FILE_NAME -e INPUT_IGNORE_RUNS -e INPUT_HIDE_COMMENTS -e INPUT_COMMENT_ON_PR -e INPUT_COMMENT_MODE -e INPUT_COMPARE_TO_EARLIER_COMMIT -e INPUT_PULL_REQUEST_BUILD -e INPUT_EVENT_FILE -e INPUT_EVENT_NAME -e INPUT_TEST_CHANGES_LIMIT -e INPUT_CHECK_RUN_ANNOTATIONS -e INPUT_CHECK_RUN_ANNOTATIONS_BRANCH -e INPUT_SECONDS_BETWEEN_GITHUB_READS -e INPUT_SECONDS_BETWEEN_GITHUB_WRITES -e INPUT_JSON_FILE -e INPUT_JSON_THOUSANDS_SEPARATOR -e INPUT_JOB_SUMMARY -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e GITHUB_STEP_SUMMARY -e RUNNER_OS -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "$RUNNER_TEMP":"$RUNNER_TEMP" -v "$GITHUB_WORKSPACE":"$GITHUB_WORKSPACE" enricomi/publish-unit-test-result-action:latest + docker run --workdir $GITHUB_WORKSPACE --rm -e INPUT_CHECK_NAME -e INPUT_JUNIT_FILES -e INPUT_XUNIT_FILES -e INPUT_TRX_FILES -e INPUT_TIME_UNIT -e INPUT_GITHUB_TOKEN -e INPUT_GITHUB_RETRIES -e INPUT_COMMIT -e INPUT_COMMENT_TITLE -e INPUT_FAIL_ON -e INPUT_REPORT_INDIVIDUAL_RUNS -e INPUT_DEDUPLICATE_CLASSES_BY_FILE_NAME -e INPUT_IGNORE_RUNS -e INPUT_HIDE_COMMENTS -e INPUT_COMMENT_ON_PR -e INPUT_COMMENT_MODE -e INPUT_COMPARE_TO_EARLIER_COMMIT -e INPUT_PULL_REQUEST_BUILD -e INPUT_EVENT_FILE -e INPUT_EVENT_NAME -e INPUT_TEST_CHANGES_LIMIT -e INPUT_CHECK_RUN_ANNOTATIONS -e INPUT_CHECK_RUN_ANNOTATIONS_BRANCH -e INPUT_SECONDS_BETWEEN_GITHUB_READS -e INPUT_SECONDS_BETWEEN_GITHUB_WRITES -e INPUT_JSON_FILE -e INPUT_JSON_THOUSANDS_SEPARATOR -e INPUT_JOB_SUMMARY -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e GITHUB_STEP_SUMMARY -e RUNNER_OS -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "$RUNNER_TEMP":"$RUNNER_TEMP" -v "$GITHUB_WORKSPACE":"$GITHUB_WORKSPACE" enricomi/publish-unit-test-result-action:latest env: INPUT_GITHUB_TOKEN: ${{ github.token }} INPUT_CHECK_NAME: Test Results (Docker Image) @@ -423,6 +423,7 @@ jobs: check_name: Test Results (Test Files) fail_on: nothing junit_files: "test-files/junit-xml/**/*.xml" + xunit_files: "test-files/xunit/**/*.xml" trx_files: "test-files/trx/**/*.trx" json_file: "tests.json" log_level: DEBUG diff --git a/README.md b/README.md index b899a5ee..96d24fc8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ You can add this action to your GitHub workflow for ![Ubuntu Linux](https://badg uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: - junit_files: "test-results/**/*.xml" + junit_files: "test-results/junit/**/*.xml" + xunit_files: "test-results/xunit/**/*.xml" trx_files: "test-results/**/*.trx" ``` @@ -34,7 +35,8 @@ and ![Windows](https://badgen.net/badge/icon/Windows?icon=windows&label) (e.g. ` uses: EnricoMi/publish-unit-test-result-action/composite@v2 if: always() with: - junit_files: "test-results/**/*.xml" + junit_files: "test-results/junit/**/*.xml" + xunit_files: "test-results/xunit/**/*.xml" trx_files: "test-results/**/*.trx" ``` @@ -182,11 +184,11 @@ With `comment_mode: off`, the `pull-requests: write` permission is not needed. ## Configuration -Files can be selected via the `junit_files` and `trx_files` options. +Files can be selected via the `junit_files`, `xunit_files`, and `trx_files` options. They support [glob wildcards](https://docs.python.org/3/library/glob.html#glob.glob) like `*`, `**`, `?` and `[]`. The `**` wildcard matches all files and directories recursively: `./`, `./*/`, `./*/*/`, etc. -At least one of `junit_files` and `trx_files` options have to be set. +At least one of `junit_files`, `xunit_files`, and `trx_files` options have to be set. You can provide multiple file patterns, one pattern per line. Patterns starting with `!` exclude the matching files. There have to be at least one pattern starting without a `!`: @@ -202,7 +204,7 @@ The list of most notable options: |Option|Default Value|Description| |:-----|:-----:|:----------| -|`junit_files`
`trx_files`|At least one of these `*_files` must be set.|File patterns of JUnit XML and TRX test result files, respectively. Supports `*`, `**`, `?`, and `[]`. Use multiline string for multiple patterns. Patterns starting with `!` exclude the matching files. There have to be at least one pattern starting without a `!`.| +|`junit_files`
`xunit_files`
`trx_files`|At least one of these `*_files` must be set.|File patterns of JUnit XML, XUnit XML, and TRX test result files, respectively. Supports `*`, `**`, `?`, and `[]`. Use multiline string for multiple patterns. Patterns starting with `!` exclude the matching files. There have to be at least one pattern starting without a `!`.| |`check_name`|`"Test Results"`|An alternative name for the check result.| |`comment_title`|same as `check_name`|An alternative name for the pull request comment.| |`comment_mode`|`always`|The action posts comments to pull requests that are associated with the commit. Set to:
`always` - always comment
`changes` - comment when changes w.r.t. the target branch exist
`changes in failures` - when changes in the number of failures and errors exist
`changes in errors` - when changes in the number of (only) errors exist
`failures` - when failures or errors exist
`errors` - when (only) errors exist
`off` - to not create pull request comments.| diff --git a/action.yml b/action.yml index 9a286de9..5aa76711 100644 --- a/action.yml +++ b/action.yml @@ -32,6 +32,9 @@ inputs: junit_files: description: 'File patterns of JUnit XML test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' required: false + xunit_files: + description: 'File patterns of XUnit XML test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' + required: false trx_files: description: 'File patterns of TRX test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' required: false diff --git a/composite/action.yml b/composite/action.yml index 38481a3e..3ce7035b 100644 --- a/composite/action.yml +++ b/composite/action.yml @@ -32,6 +32,9 @@ inputs: junit_files: description: 'File patterns of JUnit XML test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' required: false + xunit_files: + description: 'File patterns of XUnit XML test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' + required: false trx_files: description: 'File patterns of TRX test result files. Supports *, **, ?, and []. Use multiline string for multiple patterns. Patterns starting with ! exclude the matching files. There have to be at least one pattern starting without a `!`.' required: false @@ -157,6 +160,7 @@ runs: # deprecated FILES: ${{ inputs.files }} JUNIT_FILES: ${{ inputs.junit_files }} + XUNIT_FILES: ${{ inputs.xunit_files }} TRX_FILES: ${{ inputs.trx_files }} TIME_UNIT: ${{ inputs.time_unit }} REPORT_INDIVIDUAL_RUNS: ${{ inputs.report_individual_runs }} diff --git a/python/publish/junit.py b/python/publish/junit.py index 4ae3972e..b1d016ff 100644 --- a/python/publish/junit.py +++ b/python/publish/junit.py @@ -127,6 +127,7 @@ def close(self) -> Element: def parse_junit_xml_files(files: Iterable[str], drop_testcases: bool = False, progress: Callable[[ParsedJUnitFile], ParsedJUnitFile] = lambda x: x) -> Iterable[ParsedJUnitFile]: + """Parses junit xml files.""" def parse(path: str) -> JUnitTreeOrException: """Parses a junit xml file and returns either a JUnitTree or an Exception.""" if not os.path.exists(path): @@ -172,11 +173,12 @@ def create_junitxml(filepath: str, tree: JUnitTree) -> Union[JUnitXml, JUnitXmlE for result_file, junit in junits for suite in (junit if junit._tag == "testsuites" else [junit])] - suite_tests = sum([suite.tests for result_file, suite in suites]) - suite_skipped = sum([suite.skipped + suite.disabled for result_file, suite in suites]) - suite_failures = sum([suite.failures for result_file, suite in suites]) - suite_errors = sum([suite.errors for result_file, suite in suites]) - suite_time = int(sum([suite.time for result_file, suite in suites if not math.isnan(suite.time)]) * time_factor) + suite_tests = sum([suite.tests for result_file, suite in suites if suite.tests]) + suite_skipped = sum([suite.skipped + suite.disabled for result_file, suite in suites if suite.skipped]) + suite_failures = sum([suite.failures for result_file, suite in suites if suite.failures]) + suite_errors = sum([suite.errors for result_file, suite in suites if suite.errors]) + suite_time = int(sum([suite.time for result_file, suite in suites + if suite.time and not math.isnan(suite.time)]) * time_factor) def int_opt(string: Optional[str]) -> Optional[int]: try: diff --git a/python/publish/publisher.py b/python/publish/publisher.py index 18cb16f9..0ee8440f 100644 --- a/python/publish/publisher.py +++ b/python/publish/publisher.py @@ -45,6 +45,7 @@ class Settings: fail_on_failures: bool # one of these *_files_glob must be set junit_files_glob: Optional[str] + xunit_files_glob: Optional[str] trx_files_glob: Optional[str] time_factor: float check_name: str diff --git a/python/publish/trx.py b/python/publish/trx.py index 77e3d31e..dcabc9c2 100644 --- a/python/publish/trx.py +++ b/python/publish/trx.py @@ -12,6 +12,7 @@ def parse_trx_files(files: Iterable[str], progress: Callable[[ParsedJUnitFile], ParsedJUnitFile] = lambda x: x) -> Iterable[ParsedJUnitFile]: + """Parses trx files.""" def parse(path: str) -> JUnitTreeOrException: """Parses a trx file and returns either a JUnitTree or an Exception.""" if not os.path.exists(path): diff --git a/python/publish/xslt/xunit-to-junit.xslt b/python/publish/xslt/xunit-to-junit.xslt new file mode 100644 index 00000000..6ee68d64 --- /dev/null +++ b/python/publish/xslt/xunit-to-junit.xslt @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/publish/xunit.py b/python/publish/xunit.py new file mode 100644 index 00000000..5874db27 --- /dev/null +++ b/python/publish/xunit.py @@ -0,0 +1,29 @@ +import os +import pathlib +from typing import Iterable, Callable + +from lxml import etree + +from publish.junit import JUnitTreeOrException, ParsedJUnitFile + +with (pathlib.Path(__file__).parent / 'xslt' / 'xunit-to-junit.xslt').open('r', encoding='utf-8') as r: + transform_xunit_to_junit = etree.XSLT(etree.parse(r)) + + +def parse_xunit_files(files: Iterable[str], + progress: Callable[[ParsedJUnitFile], ParsedJUnitFile] = lambda x: x) -> Iterable[ParsedJUnitFile]: + """Parses xunit files.""" + def parse(path: str) -> JUnitTreeOrException: + """Parses an xunit file and returns either a JUnitTree or an Exception.""" + if not os.path.exists(path): + return FileNotFoundError(f'File does not exist.') + if os.stat(path).st_size == 0: + return Exception(f'File is empty.') + + try: + trx = etree.parse(path) + return transform_xunit_to_junit(trx) + except BaseException as e: + return e + + return [progress((result_file, parse(result_file))) for result_file in files] diff --git a/python/publish_unit_test_results.py b/python/publish_unit_test_results.py index 3174a16d..ed934f33 100644 --- a/python/publish_unit_test_results.py +++ b/python/publish_unit_test_results.py @@ -93,13 +93,14 @@ def get_number_of_files(files: List[str]) -> str: def parse_files(settings: Settings, gha: GithubAction) -> ParsedUnitTestResultsWithCommit: # expand file globs junit_files = expand_glob(settings.junit_files_glob, gha) + xunit_files = expand_glob(settings.xunit_files_glob, gha) trx_files = expand_glob(settings.trx_files_glob, gha) elems = [] # parse files, log the progress # https://github.com/EnricoMi/publish-unit-test-result-action/issues/304 - with progress_logger(items=len(junit_files + trx_files), + with progress_logger(items=len(junit_files + xunit_files + trx_files), interval_seconds=10, progress_template='Read {progress} files in {time}', finish_template='Finished reading {observations} files in {duration}', @@ -107,6 +108,9 @@ def parse_files(settings: Settings, gha: GithubAction) -> ParsedUnitTestResultsW logger=logger) as progress: if junit_files: elems.extend(parse_junit_xml_files(junit_files, settings.ignore_runs, progress)) + if xunit_files: + from publish.xunit import parse_xunit_files + elems.extend(parse_xunit_files(xunit_files, progress)) if trx_files: from publish.trx import parse_trx_files elems.extend(parse_trx_files(trx_files, progress)) @@ -325,7 +329,7 @@ def get_settings(options: dict, gha: Optional[GithubAction] = None) -> Settings: # replace with error when deprecated FILES is removed default_junit_files_glob = None if not any([get_var(f'{flavour}_FILES', options) - for flavour in ['JUNIT', 'TRX']]): + for flavour in ['JUNIT', 'XUNIT', 'TRX']]): default_junit_files_glob = '*.xml' gha.warning(f'At least one of the *_FILES options has to be set! ' f'Falling back to deprecated default "{default_junit_files_glob}"') @@ -369,6 +373,7 @@ def get_settings(options: dict, gha: Optional[GithubAction] = None) -> Settings: fail_on_errors=fail_on_errors, fail_on_failures=fail_on_failures, junit_files_glob=get_var('JUNIT_FILES', options) or get_var('FILES', options) or default_junit_files_glob, + xunit_files_glob=get_var('XUNIT_FILES', options), trx_files_glob=get_var('TRX_FILES', options), time_factor=time_factor, check_name=check_name, diff --git a/python/test/files/junit-xml/tst/disabled.results b/python/test/files/junit-xml/tst/disabled.results index c7c08bb1..cbdb1e7c 100644 --- a/python/test/files/junit-xml/tst/disabled.results +++ b/python/test/files/junit-xml/tst/disabled.results @@ -3,7 +3,7 @@ publish.unittestresults.ParsedUnitTestResults( errors=[], suites=2, suite_tests=31, - suite_skipped=5, + suite_skipped=0, suite_failures=19, suite_errors=1, suite_time=0, diff --git a/python/test/files/update_expectations.sh b/python/test/files/update_expectations.sh index 1af705b0..8f333be2 100755 --- a/python/test/files/update_expectations.sh +++ b/python/test/files/update_expectations.sh @@ -3,5 +3,6 @@ base=$(dirname "$0") python $base/../test_junit.py +python $base/../test_xunit.py python $base/../test_trx.py diff --git a/python/test/files/xunit/README.md b/python/test/files/xunit/README.md new file mode 100644 index 00000000..c83fd6a9 --- /dev/null +++ b/python/test/files/xunit/README.md @@ -0,0 +1 @@ +[mstest/fixie.xml](https://raw.githubusercontent.com/fixie/fixie/42b43dc6cc57476958eea8b507aa9d0d72cedae6/src/Fixie.Tests/Reports/XUnitXmlReport.xml) diff --git a/python/test/files/xunit/mstest/fixie.junit-xml b/python/test/files/xunit/mstest/fixie.junit-xml new file mode 100644 index 00000000..dc08dec5 --- /dev/null +++ b/python/test/files/xunit/mstest/fixie.junit-xml @@ -0,0 +1,26 @@ + + + + + + + + Expected: System.String +Actual: System.Int32 at [genericTestClassForStackTrace].ShouldBeString[T](T genericArgument) in [fileLocation]:line # + + + + + 'Fail' failed! at [testClassForStackTrace].Fail() in [fileLocation]:line # + + + Expected: 2 +Actual: 1 at [testClassForStackTrace].FailByAssertion() in [fileLocation]:line # + + + + + + + + diff --git a/python/test/files/xunit/mstest/fixie.results b/python/test/files/xunit/mstest/fixie.results new file mode 100644 index 00000000..88ab4f45 --- /dev/null +++ b/python/test/files/xunit/mstest/fixie.results @@ -0,0 +1,93 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=7, + suite_skipped=1, + suite_failures=3, + suite_errors=0, + suite_time=8, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[genericTestClass]', + test_name='ShouldBeString', + result='success', + message=None, + content=None, + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[genericTestClass]', + test_name='ShouldBeString', + result='success', + message=None, + content=None, + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[genericTestClass]', + test_name='ShouldBeString', + result='failure', + message='Expected: System.String\nActual: System.Int32', + content='Expected: System.String\nActual: System.Int32 at ' + '[genericTestClassForStackTrace].ShouldBeString[T](T genericArgument) ' + 'in [fileLocation]:line #', + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[testClass]', + test_name='Fail', + result='failure', + message="'Fail' failed!", + content="'Fail' failed! at [testClassForStackTrace].Fail() in " + "[fileLocation]:line #", + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[testClass]', + test_name='FailByAssertion', + result='failure', + message='Expected: 2\nActual: 1', + content='Expected: 2\nActual: 1 at ' + '[testClassForStackTrace].FailByAssertion() in [fileLocation]:line #', + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[testClass]', + test_name='Pass', + result='success', + message=None, + content=None, + time=1.234 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/fixie.xml', + test_file=None, + line=None, + class_name='[testClass]', + test_name='Skip', + result='skipped', + message='⚠ Skipped with attribute.', + content=None, + time=1.234 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/fixie.xml b/python/test/files/xunit/mstest/fixie.xml new file mode 100644 index 00000000..77715ea6 --- /dev/null +++ b/python/test/files/xunit/mstest/fixie.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase1.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase1.junit-xml new file mode 100644 index 00000000..6a0d81a4 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase1.junit-xml @@ -0,0 +1,14 @@ + + + + + + Assert.True() Failure at MyProject.Tests.SampleFact.FailedTest() in c:\Jenkins\jobs\my-project\workspace\MyProject\MyProject.Tests\SampleFact.cs:line 16 + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase1.results b/python/test/files/xunit/mstest/jenkinsci/testcase1.results new file mode 100644 index 00000000..fbf10373 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase1.results @@ -0,0 +1,48 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=3, + suite_skipped=1, + suite_failures=1, + suite_errors=0, + suite_time=0, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase1.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='FailedTest', + result='failure', + message='Assert.True() Failure', + content='Assert.True() Failure at MyProject.Tests.SampleFact.FailedTest() ' + 'in ' + 'c:\\Jenkins\\jobs\\my-project\\workspace\\MyProject\\MyProject.Tests\\Sample' + 'Fact.cs:line 16', + time=0.014 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase1.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='SuccessfulTest', + result='success', + message=None, + content=None, + time=0.01 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase1.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='SkippedTest', + result='skipped', + message='On Purpose', + content=None, + time=0.0 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase1.xml b/python/test/files/xunit/mstest/jenkinsci/testcase1.xml new file mode 100644 index 00000000..cc9cab4a --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase1.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase2.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase2.junit-xml new file mode 100644 index 00000000..7f95608b --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase2.junit-xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase2.results b/python/test/files/xunit/mstest/jenkinsci/testcase2.results new file mode 100644 index 00000000..2a43cf8f --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase2.results @@ -0,0 +1,23 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=3, + suite_skipped=1, + suite_failures=1, + suite_errors=0, + suite_time=0, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase2.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='SuccessfulTestWithTrait', + result='success', + message=None, + content=None, + time=35.5236617 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase2.xml b/python/test/files/xunit/mstest/jenkinsci/testcase2.xml new file mode 100644 index 00000000..ef720197 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase3.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase3.junit-xml new file mode 100644 index 00000000..d4596aca --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase3.junit-xml @@ -0,0 +1,14 @@ + + + + + + Assert.True() Failure at MyProject.Tests.SampleFact.FailedTest() in c:\Jenkins\jobs\my-project\workspace\MyProject\MyProject.Tests\SampleFact.cs:line 16 + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase3.results b/python/test/files/xunit/mstest/jenkinsci/testcase3.results new file mode 100644 index 00000000..50507c1b --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase3.results @@ -0,0 +1,48 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=3, + suite_skipped=1, + suite_failures=1, + suite_errors=0, + suite_time=288, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase3.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='FailedTest', + result='failure', + message='Assert.True() Failure', + content='Assert.True() Failure at MyProject.Tests.SampleFact.FailedTest() ' + 'in ' + 'c:\\Jenkins\\jobs\\my-project\\workspace\\MyProject\\MyProject.Tests\\Sample' + 'Fact.cs:line 16', + time=14.0 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase3.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='SuccessfulTest', + result='success', + message=None, + content=None, + time=10.0 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase3.xml', + test_file=None, + line=None, + class_name='MyProject.Tests.SampleFact', + test_name='SkippedTest', + result='skipped', + message='On Purpose', + content=None, + time=0.0 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase3.xml b/python/test/files/xunit/mstest/jenkinsci/testcase3.xml new file mode 100644 index 00000000..21a9c533 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase3.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase4.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase4.junit-xml new file mode 100644 index 00000000..80b072ff --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase4.junit-xml @@ -0,0 +1,4 @@ + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase4.results b/python/test/files/xunit/mstest/jenkinsci/testcase4.results new file mode 100644 index 00000000..87ab69c4 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase4.results @@ -0,0 +1,11 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=0, + suite_skipped=0, + suite_failures=0, + suite_errors=0, + suite_time=0, + cases=[] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase4.xml b/python/test/files/xunit/mstest/jenkinsci/testcase4.xml new file mode 100644 index 00000000..c43c2bc7 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase4.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase5.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase5.junit-xml new file mode 100644 index 00000000..d5f5b093 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase5.junit-xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase5.results b/python/test/files/xunit/mstest/jenkinsci/testcase5.results new file mode 100644 index 00000000..53a81563 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase5.results @@ -0,0 +1,67 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=5, + suite_skipped=0, + suite_failures=0, + suite_errors=0, + suite_time=92, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase5.xml', + test_file=None, + line=None, + class_name='UnitTest.UnitTest', + test_name='UninstallApplicationIfInstalled', + result='success', + message=None, + content=None, + time=48.3914131 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase5.xml', + test_file=None, + line=None, + class_name='UnitTest.UnitTest', + test_name='InstallApplication', + result='success', + message=None, + content=None, + time=33.1446488 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase5.xml', + test_file=None, + line=None, + class_name='UnitTest.UnitTest', + test_name='CheckIfAllFilesAreThere', + result='success', + message=None, + content=None, + time=3.0399824 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase5.xml', + test_file=None, + line=None, + class_name='UnitTest.UnitTest', + test_name='CheckIfFilesWereSinged', + result='success', + message=None, + content=None, + time=4.7233194 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase5.xml', + test_file=None, + line=None, + class_name='UnitTest.UnitTest', + test_name='CheckIfAppIsRunning', + result='success', + message=None, + content=None, + time=3.0279026 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase5.xml b/python/test/files/xunit/mstest/jenkinsci/testcase5.xml new file mode 100644 index 00000000..3ab35ec0 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase5.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase6.junit-xml b/python/test/files/xunit/mstest/jenkinsci/testcase6.junit-xml new file mode 100644 index 00000000..a16cb199 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase6.junit-xml @@ -0,0 +1,15 @@ + + + + + + OK: TestProcess.Execution.ExitCode == 0 +FAIL: Output differs from reference. + Left: data\ref + Right: results\out + + + + + + diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase6.results b/python/test/files/xunit/mstest/jenkinsci/testcase6.results new file mode 100644 index 00000000..27e747b7 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase6.results @@ -0,0 +1,27 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=1, + suite_skipped=0, + suite_failures=1, + suite_errors=0, + suite_time=0, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/jenkinsci/testcase6.xml', + test_file=None, + line=None, + class_name='', + test_name='', + result='failure', + message='OK: TestProcess.Execution.ExitCode == 0\r\nFAIL: Output differs from ' + 'reference.\r\n Left: data\\ref\r\n Right: results\\out\r\n \r\n ' + ' ', + content='OK: TestProcess.Execution.ExitCode == 0\r\nFAIL: Output differs from ' + 'reference.\r\n Left: data\\ref\r\n Right: results\\out\r\n \r\n ' + ' ', + time=36.7820544 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/jenkinsci/testcase6.xml b/python/test/files/xunit/mstest/jenkinsci/testcase6.xml new file mode 100644 index 00000000..30f598a8 --- /dev/null +++ b/python/test/files/xunit/mstest/jenkinsci/testcase6.xml @@ -0,0 +1,17 @@ + + + + + + + OK: TestProcess.Execution.ExitCode == 0 +FAIL: Output differs from reference. + Left: data\ref + Right: results\out + + + + + + + \ No newline at end of file diff --git a/python/test/files/xunit/mstest/pickles.junit-xml b/python/test/files/xunit/mstest/pickles.junit-xml new file mode 100644 index 00000000..28faba47 --- /dev/null +++ b/python/test/files/xunit/mstest/pickles.junit-xml @@ -0,0 +1,26 @@ + + + + + + + + + + System.InvalidOperationException : This is a fake failure message + + at Pickles.TestHarness.xUnit.Steps.ThenTheResultShouldBePass(Int32 result) in C:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Steps.cs:line 26 + at lambda_method(Closure , IContextManager , Int32 ) + at TechTalk.SpecFlow.Bindings.MethodBinding.InvokeAction(IContextManager contextManager, Object[] arguments, ITestTracer testTracer, TimeSpan& duration) + at TechTalk.SpecFlow.Bindings.StepDefinitionBinding.Invoke(IContextManager contextManager, ITestTracer testTracer, Object[] arguments, TimeSpan& duration) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStepMatch(BindingMatch match, Object[] arguments) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(StepArgs stepArgs) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.OnAfterLastStep() + at TechTalk.SpecFlow.TestRunner.CollectScenarioErrors() + at Pickles.TestHarness.xUnit.AdditionFeature.ScenarioCleanup() in C:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Addition.feature.cs:line 0 + at Pickles.TestHarness.xUnit.AdditionFeature.FailToAddTwoNumbers() in c:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Addition.feature:line 18 + + + + + diff --git a/python/test/files/xunit/mstest/pickles.results b/python/test/files/xunit/mstest/pickles.results new file mode 100644 index 00000000..775cbf02 --- /dev/null +++ b/python/test/files/xunit/mstest/pickles.results @@ -0,0 +1,82 @@ +publish.unittestresults.ParsedUnitTestResults( + files=1, + errors=[], + suites=1, + suite_tests=4, + suite_skipped=0, + suite_failures=1, + suite_errors=0, + suite_time=0, + cases=[ + publish.unittestresults.UnitTestCase( + result_file='mstest/pickles.xml', + test_file=None, + line=None, + class_name='Pickles.TestHarness.xUnit.AdditionFeature', + test_name='AddTwoNumbers', + result='success', + message=None, + content=None, + time=0.153 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/pickles.xml', + test_file=None, + line=None, + class_name='Pickles.TestHarness.xUnit.AdditionFeature', + test_name='AddingSeveralNumbers', + result='success', + message=None, + content=None, + time=0.006 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/pickles.xml', + test_file=None, + line=None, + class_name='Pickles.TestHarness.xUnit.AdditionFeature', + test_name='AddingSeveralNumbers', + result='success', + message=None, + content=None, + time=0.003 + ), + publish.unittestresults.UnitTestCase( + result_file='mstest/pickles.xml', + test_file=None, + line=None, + class_name='Pickles.TestHarness.xUnit.AdditionFeature', + test_name='FailToAddTwoNumbers', + result='failure', + message='\n System.InvalidOperationException : This is a fake ' + 'failure message\n ', + content='\n System.InvalidOperationException : This is a fake ' + 'failure message\n \n at ' + 'Pickles.TestHarness.xUnit.Steps.ThenTheResultShouldBePass(Int32 ' + 'result) in ' + 'C:\\dev\\pickles-results-harness\\Pickles.TestHarness\\Pickles.TestHarnes' + 's.xUnit\\Steps.cs:line 26\n at lambda_method(Closure , ' + 'IContextManager , Int32 )\n at ' + 'TechTalk.SpecFlow.Bindings.MethodBinding.InvokeAction(IContextManager' + ' contextManager, Object[] arguments, ITestTracer testTracer, ' + 'TimeSpan& duration)\n at ' + 'TechTalk.SpecFlow.Bindings.StepDefinitionBinding.Invoke(IContextManag' + 'er contextManager, ITestTracer testTracer, Object[] arguments, ' + 'TimeSpan& duration)\n at ' + 'TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStepMatch' + '(BindingMatch match, Object[] arguments)\n at ' + 'TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(Step' + 'Args stepArgs)\n at ' + 'TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.OnAfterLastStep(' + ')\n at TechTalk.SpecFlow.TestRunner.CollectScenarioErrors()\n' + ' at ' + 'Pickles.TestHarness.xUnit.AdditionFeature.ScenarioCleanup() in ' + 'C:\\dev\\pickles-results-harness\\Pickles.TestHarness\\Pickles.TestHarnes' + 's.xUnit\\Addition.feature.cs:line 0\n at ' + 'Pickles.TestHarness.xUnit.AdditionFeature.FailToAddTwoNumbers() in ' + 'c:\\dev\\pickles-results-harness\\Pickles.TestHarness\\Pickles.TestHarnes' + 's.xUnit\\Addition.feature:line 18\n ', + time=0.023 + ) + ] +) \ No newline at end of file diff --git a/python/test/files/xunit/mstest/pickles.xml b/python/test/files/xunit/mstest/pickles.xml new file mode 100644 index 00000000..a90f339a --- /dev/null +++ b/python/test/files/xunit/mstest/pickles.xml @@ -0,0 +1,92 @@ + + + + + + + + + + Given I have entered 50 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(50) (0.0s) + And I have entered 70 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(70) (0.0s) + When I press add + -> done: Steps.WhenIPressAdd() (0.0s) + Then the result should be 120 on the screen + -> done: Steps.ThenTheResultShouldBePass(120) (0.0s) + + + + + + + + + Given I have entered 40 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(40) (0.0s) + And I have entered 50 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(50) (0.0s) + When I press add + -> done: Steps.WhenIPressAdd() (0.0s) + Then the result should be 90 on the screen + -> done: Steps.ThenTheResultShouldBePass(90) (0.0s) + + + + + + + + + Given I have entered 60 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(60) (0.0s) + And I have entered 70 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(70) (0.0s) + When I press add + -> done: Steps.WhenIPressAdd() (0.0s) + Then the result should be 130 on the screen + -> done: Steps.ThenTheResultShouldBePass(130) (0.0s) + + + + + + + + + Given I have entered 50 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(50) (0.0s) + And I have entered -1 into the calculator + -> done: Steps.GivenIHaveEnteredSomethingIntoTheCalculator(-1) (0.0s) + When I press add + -> done: Steps.WhenIPressAdd() (0.0s) + Then the result should be -50 on the screen + -> error: This is a fake failure message + + + + System.InvalidOperationException : This is a fake failure message + + + at Pickles.TestHarness.xUnit.Steps.ThenTheResultShouldBePass(Int32 result) in C:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Steps.cs:line 26 + at lambda_method(Closure , IContextManager , Int32 ) + at TechTalk.SpecFlow.Bindings.MethodBinding.InvokeAction(IContextManager contextManager, Object[] arguments, ITestTracer testTracer, TimeSpan& duration) + at TechTalk.SpecFlow.Bindings.StepDefinitionBinding.Invoke(IContextManager contextManager, ITestTracer testTracer, Object[] arguments, TimeSpan& duration) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStepMatch(BindingMatch match, Object[] arguments) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.ExecuteStep(StepArgs stepArgs) + at TechTalk.SpecFlow.Infrastructure.TestExecutionEngine.OnAfterLastStep() + at TechTalk.SpecFlow.TestRunner.CollectScenarioErrors() + at Pickles.TestHarness.xUnit.AdditionFeature.ScenarioCleanup() in C:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Addition.feature.cs:line 0 + at Pickles.TestHarness.xUnit.AdditionFeature.FailToAddTwoNumbers() in c:\dev\pickles-results-harness\Pickles.TestHarness\Pickles.TestHarness.xUnit\Addition.feature:line 18 + + + + + diff --git a/python/test/test_action_script.py b/python/test/test_action_script.py index ccd5f4ae..9d0f9ffc 100644 --- a/python/test/test_action_script.py +++ b/python/test/test_action_script.py @@ -134,8 +134,10 @@ def test_get_var(self): @classmethod def get_settings_no_default_files(cls, junit_files_glob=None, + xunit_files_glob=None, trx_files_glob=None) -> Settings: return cls.get_settings(junit_files_glob=junit_files_glob, + xunit_files_glob=xunit_files_glob, trx_files_glob=trx_files_glob) @staticmethod @@ -151,6 +153,7 @@ def get_settings(token='token', fail_on_errors=True, fail_on_failures=True, junit_files_glob='junit-files', + xunit_files_glob='xunit-files', trx_files_glob='trx-files', time_factor=1.0, check_name='check name', @@ -184,6 +187,7 @@ def get_settings(token='token', fail_on_errors=fail_on_errors, fail_on_failures=fail_on_failures, junit_files_glob=junit_files_glob, + xunit_files_glob=xunit_files_glob, trx_files_glob=trx_files_glob, time_factor=time_factor, check_name=check_name, @@ -248,15 +252,18 @@ def test_get_settings_github_retries(self): def test_get_settings_any_files(self): for junit in [None, 'junit-file']: - for trx in [None, 'trx-file']: - with self.subTest(junit=junit, trx=trx): - any_flavour_set = any([flavour is not None for flavour in [junit, trx]]) - expected = self.get_settings(junit_files_glob=junit if any_flavour_set else '*.xml', - trx_files_glob=trx) - warnings = None if any_flavour_set else 'At least one of the *_FILES options has to be set! ' \ - 'Falling back to deprecated default "*.xml"' - - self.do_test_get_settings(JUNIT_FILES=junit, TRX_FILES=trx, expected=expected, warning=warnings) + for xunit in [None, 'xunit-file']: + for trx in [None, 'trx-file']: + with self.subTest(junit=junit, xunit=xunit, trx=trx): + any_flavour_set = any([flavour is not None for flavour in [junit, xunit, trx]]) + expected = self.get_settings(junit_files_glob=junit if any_flavour_set else '*.xml', + xunit_files_glob=xunit, + trx_files_glob=trx) + warnings = None if any_flavour_set else 'At least one of the *_FILES options has to be set! ' \ + 'Falling back to deprecated default "*.xml"' + + self.do_test_get_settings(JUNIT_FILES=junit, XUNIT_FILES=xunit, TRX_FILES=trx, + expected=expected, warning=warnings) def test_get_settings_junit_files(self): self.do_test_get_settings_no_default_files(JUNIT_FILES='file', expected=self.get_settings_no_default_files(junit_files_glob='file')) @@ -269,6 +276,16 @@ def test_get_settings_junit_files(self): self.do_test_get_settings_no_default_files(JUNIT_FILES=None, FILES='file\nfile2', expected=self.get_settings_no_default_files(junit_files_glob='file\nfile2'), warning=['Option FILES is deprecated, please use JUNIT_FILES instead!', 'At least one of the *_FILES options has to be set! Falling back to deprecated default "*.xml"']) self.do_test_get_settings_no_default_files(JUNIT_FILES=None, FILES=None, expected=self.get_settings_no_default_files(junit_files_glob='*.xml'), warning='At least one of the *_FILES options has to be set! Falling back to deprecated default "*.xml"') + def test_get_settings_xunit_files(self): + self.do_test_get_settings_no_default_files(XUNIT_FILES='file', expected=self.get_settings_no_default_files(xunit_files_glob='file')) + self.do_test_get_settings_no_default_files(XUNIT_FILES='file\nfile2', expected=self.get_settings_no_default_files(xunit_files_glob='file\nfile2')) + self.do_test_get_settings_no_default_files(XUNIT_FILES=None, expected=self.get_settings_no_default_files(xunit_files_glob=None, junit_files_glob='*.xml'), warning='At least one of the *_FILES options has to be set! Falling back to deprecated default "*.xml"') + + def test_get_settings_trx_files(self): + self.do_test_get_settings_no_default_files(TRX_FILES='file', expected=self.get_settings_no_default_files(trx_files_glob='file')) + self.do_test_get_settings_no_default_files(TRX_FILES='file\nfile2', expected=self.get_settings_no_default_files(trx_files_glob='file\nfile2')) + self.do_test_get_settings_no_default_files(TRX_FILES=None, expected=self.get_settings_no_default_files(trx_files_glob=None, junit_files_glob='*.xml'), warning='At least one of the *_FILES options has to be set! Falling back to deprecated default "*.xml"') + def test_get_settings_time_unit(self): self.do_test_get_settings(TIME_UNIT=None, expected=self.get_settings(time_factor=1.0)) self.do_test_get_settings(TIME_UNIT='milliseconds', expected=self.get_settings(time_factor=0.001)) @@ -479,7 +496,7 @@ def do_test_get_settings_no_default_files(self, expected: Settings = get_settings.__func__(), **kwargs): options = dict(**kwargs) - for flavour in ['JUNIT', 'TRX']: + for flavour in ['JUNIT', 'XUNIT', 'TRX']: if f'{flavour}_FILES' not in kwargs: options[f'{flavour}_FILES'] = None @@ -513,6 +530,7 @@ def do_test_get_settings(self, GITHUB_REPOSITORY='repo', COMMIT='commit', # defaults to get_commit_sha(event, event_name) JUNIT_FILES='junit-files', + XUNIT_FILES='xunit-files', TRX_FILES='trx-files', COMMENT_TITLE='title', # defaults to check name COMMENT_MODE='always', @@ -797,34 +815,38 @@ def test_get_files_with_mock(self): def test_parse_files(self): gha = mock.MagicMock() settings = self.get_settings(junit_files_glob=str(test_files_path / 'junit-xml' / '**' / '*.xml'), + xunit_files_glob=str(test_files_path / 'xunit' / '**' / '*.xml'), trx_files_glob=str(test_files_path / 'trx' / '**' / '*.trx')) actual = parse_files(settings, gha) gha.warning.assert_not_called() gha.error.assert_not_called() - self.assertEqual(35, actual.files) + self.assertEqual(43, actual.files) self.assertEqual(4, len(actual.errors)) - self.assertEqual(32, actual.suites) - self.assertEqual(1397, actual.suite_tests) - self.assertEqual(96, actual.suite_skipped) - self.assertEqual(48, actual.suite_failures) + self.assertEqual(40, actual.suites) + self.assertEqual(1423, actual.suite_tests) + self.assertEqual(95, actual.suite_skipped) + self.assertEqual(56, actual.suite_failures) self.assertEqual(7, actual.suite_errors) - self.assertEqual(2392, actual.suite_time) - self.assertEqual(1387, len(actual.cases)) + self.assertEqual(2782, actual.suite_time) + self.assertEqual(1411, len(actual.cases)) self.assertEqual('commit', actual.commit) def test_parse_files_no_matches(self): gha = mock.MagicMock() with tempfile.TemporaryDirectory() as path: missing_junit = str(pathlib.Path(path) / 'junit-not-there') + missing_xunit = str(pathlib.Path(path) / 'xunit-not-there') missing_trx = str(pathlib.Path(path) / 'trx-not-there') settings = self.get_settings(junit_files_glob=missing_junit, + xunit_files_glob=missing_xunit, trx_files_glob=missing_trx) actual = parse_files(settings, gha) gha.warning.assert_has_calls([ mock.call(f'Could not find any files for {missing_junit}'), + mock.call(f'Could not find any files for {missing_xunit}'), mock.call(f'Could not find any files for {missing_trx}') ]) gha.error.assert_not_called() diff --git a/python/test/test_publisher.py b/python/test/test_publisher.py index a4fd3de9..a17ecc2c 100644 --- a/python/test/test_publisher.py +++ b/python/test/test_publisher.py @@ -106,6 +106,7 @@ def create_settings(comment_mode=comment_mode_always, fail_on_errors=True, fail_on_failures=True, junit_files_glob='*.xml', + xunit_files_glob=None, trx_files_glob=None, time_factor=1.0, check_name='Check Name', diff --git a/python/test/test_xunit.py b/python/test/test_xunit.py new file mode 100644 index 00000000..82047f07 --- /dev/null +++ b/python/test/test_xunit.py @@ -0,0 +1,39 @@ +import pathlib +import sys +import unittest +from glob import glob +from typing import List, Union + +sys.path.append(str(pathlib.Path(__file__).resolve().parent.parent)) +sys.path.append(str(pathlib.Path(__file__).resolve().parent.parent.parent)) + +from publish.junit import JUnitTree +from publish.xunit import parse_xunit_files +from test_junit import JUnitXmlParseTest + + +test_files_path = pathlib.Path(__file__).parent / 'files' / 'xunit' + + +class TestXunit(unittest.TestCase, JUnitXmlParseTest): + maxDiff = None + + @property + def test(self): + return self + + @staticmethod + def _test_files_path(): + return test_files_path + + @staticmethod + def get_test_files() -> List[str]: + return glob(str(test_files_path / '**' / '*.xml'), recursive=True) + + @staticmethod + def parse_file(filename) -> Union[JUnitTree, BaseException]: + return list(parse_xunit_files([filename]))[0][1] + + +if __name__ == "__main__": + TestXunit.update_expectations()