Skip to content

Commit 788a1a5

Browse files
committed
Add application testing testcases.
1 parent 1a11cbb commit 788a1a5

6 files changed

Lines changed: 159 additions & 6 deletions

File tree

.github/workflows/ApplicationTesting.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ jobs:
383383
export ENVIRONMENT_NAME="${{ matrix.envname }}"
384384
385385
cd "${{ inputs.root_directory || '.' }}"
386-
[ -n '${{ inputs.apptest_xml_artifact }}' ] && PYTEST_ARGS='--junitxml=report/unit/TestReportSummary.xml' || unset PYTEST_ARGS
386+
[ -n '${{ inputs.apptest_xml_artifact }}' ] && PYTEST_ARGS='--junitxml=${{ fromJson(inputs.apptest_report_xml).fullpath }}' || unset PYTEST_ARGS
387387
printf "%s\n" "python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }}"
388388
python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }}
389389
@@ -394,7 +394,7 @@ jobs:
394394
$env:ENVIRONMENT_NAME = "${{ matrix.envname }}"
395395
396396
cd "${{ inputs.root_directory || '.' }}"
397-
$PYTEST_ARGS = if ("${{ inputs.apptest_xml_artifact }}") { "--junitxml=report/unit/TestReportSummary.xml" } else { "" }
397+
$PYTEST_ARGS = if ("${{ inputs.apptest_xml_artifact }}") { "--junitxml=${{ fromJson(inputs.apptest_report_xml).fullpath }}" } else { "" }
398398
Write-Host "python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }}"
399399
python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }}
400400
@@ -405,8 +405,8 @@ jobs:
405405
uses: pyTooling/upload-artifact@v7
406406
with:
407407
name: ${{ inputs.apptest_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
408-
working-directory: ${{ fromJson(inputs.unittest_report_xml).directory }}
409-
path: ${{ fromJson(inputs.unittest_report_xml).filename }}
408+
working-directory: ${{ fromJson(inputs.apptest_report_xml).directory }}
409+
path: ${{ fromJson(inputs.apptest_report_xml).filename }}
410410
if-no-files-found: error
411411
retention-days: 1
412412

.github/workflows/CompletePipeline.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,8 @@ jobs:
338338
with:
339339
jobs: ${{ needs.AppTestingParams.outputs.python_jobs }}
340340
wheel: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
341-
apptest_report_xml: ${{ needs.AppTestingParams.outputs.unittest_report_xml }}
342-
apptest_xml_artifact: ${{ fromJson(needs.AppTestingParams.outputs.artifact_names).apptesting_xml }}
341+
apptest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
342+
apptest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).apptesting_xml }}
343343

344344
PublishCoverageResults:
345345
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@dev

myPackage/CLI.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ==================================================================================================================== #
2+
# _____ _ _ _ _ _ #
3+
# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / \ ___| |_(_) ___ _ __ ___ #
4+
# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | / _ \ / __| __| |/ _ \| '_ \/ __| #
5+
# | |_) | |_| || | (_) | (_) | | | | | | (_| |_ / ___ \ (__| |_| | (_) | | | \__ \ #
6+
# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_/ \_\___|\__|_|\___/|_| |_|___/ #
7+
# |_| |___/ |___/ #
8+
# ==================================================================================================================== #
9+
# Authors: #
10+
# Patrick Lehmann #
11+
# #
12+
# License: #
13+
# ==================================================================================================================== #
14+
# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany #
15+
# #
16+
# Licensed under the Apache License, Version 2.0 (the "License"); #
17+
# you may not use this file except in compliance with the License. #
18+
# You may obtain a copy of the License at #
19+
# #
20+
# http://www.apache.org/licenses/LICENSE-2.0 #
21+
# #
22+
# Unless required by applicable law or agreed to in writing, software #
23+
# distributed under the License is distributed on an "AS IS" BASIS, #
24+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25+
# See the License for the specific language governing permissions and #
26+
# limitations under the License. #
27+
# #
28+
# SPDX-License-Identifier: Apache-2.0 #
29+
# ==================================================================================================================== #
30+
#
31+
"""
32+
A module for a dummy CLI interface.
33+
"""
34+
from typing import NoReturn
35+
36+
37+
def main() -> NoReturn:
38+
print("Hello world")
39+
exit(0)

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
dataFiles={
5151
packageName: ["py.typed"]
5252
},
53+
consoleScripts={
54+
"myPackage": "myPackage.CLI:main"
55+
},
5356
debug=True
5457
)
5558
)

tests/app/AppTest.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# ==================================================================================================================== #
2+
# _____ _ _ _ _ _ #
3+
# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / \ ___| |_(_) ___ _ __ ___ #
4+
# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | / _ \ / __| __| |/ _ \| '_ \/ __| #
5+
# | |_) | |_| || | (_) | (_) | | | | | | (_| |_ / ___ \ (__| |_| | (_) | | | \__ \ #
6+
# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_/ \_\___|\__|_|\___/|_| |_|___/ #
7+
# |_| |___/ |___/ #
8+
# ==================================================================================================================== #
9+
# Authors: #
10+
# Patrick Lehmann #
11+
# #
12+
# License: #
13+
# ==================================================================================================================== #
14+
# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany #
15+
# #
16+
# Licensed under the Apache License, Version 2.0 (the "License"); #
17+
# you may not use this file except in compliance with the License. #
18+
# You may obtain a copy of the License at #
19+
# #
20+
# http://www.apache.org/licenses/LICENSE-2.0 #
21+
# #
22+
# Unless required by applicable law or agreed to in writing, software #
23+
# distributed under the License is distributed on an "AS IS" BASIS, #
24+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25+
# See the License for the specific language governing permissions and #
26+
# limitations under the License. #
27+
# #
28+
# SPDX-License-Identifier: Apache-2.0 #
29+
# ==================================================================================================================== #
30+
#
31+
from importlib.metadata import version as get_version
32+
from json import loads as json_loads, JSONDecodeError
33+
from re import IGNORECASE, MULTILINE
34+
from shutil import which
35+
from subprocess import CompletedProcess, run as subprocess_run, TimeoutExpired
36+
from sys import executable as PYTHON_EXECUTABLE
37+
from tempfile import TemporaryDirectory
38+
from typing import ClassVar
39+
from unittest import TestCase
40+
41+
42+
class Testcase(TestCase):
43+
"""
44+
Shared base class: resolves the installed console_scripts executable
45+
once per test class and provides a subprocess-invocation helper.
46+
"""
47+
48+
ENTRY_POINT_NAME: ClassVar[str] = "myPackage"
49+
executable: str
50+
51+
@classmethod
52+
def setUpClass(cls) -> None:
53+
if (resolved := which(cls.ENTRY_POINT_NAME)) is None:
54+
raise RuntimeError(
55+
f"'{cls.ENTRY_POINT_NAME}' not found on PATH. Verify the wheel "
56+
f"was installed in this environment and that setup.py's "
57+
f"entry_points {{'console_scripts': "
58+
f"['{cls.ENTRY_POINT_NAME}=myPackage.CLI:main']}} was picked "
59+
f"up correctly."
60+
)
61+
cls.executable = resolved
62+
63+
def RunEntrypoint(
64+
self,
65+
*args: str,
66+
timeout: float = 10.0,
67+
input_text: str | None = None,
68+
env: dict | None = None,
69+
cwd: str | None = None,
70+
) -> CompletedProcess:
71+
return subprocess_run(
72+
[self.executable, *args],
73+
capture_output=True,
74+
text=True,
75+
timeout=timeout,
76+
input=input_text,
77+
env=env,
78+
cwd=cwd,
79+
)
80+
81+
def RunModule(self, *args: str, timeout: float = 10.0) -> CompletedProcess:
82+
"""
83+
Invokes `python -m myPackage.CLI` directly, bypassing the
84+
console_scripts shim. Use to isolate whether a failure originates
85+
in the entry-point wiring vs. the CLI logic itself.
86+
"""
87+
return subprocess_run(
88+
[PYTHON_EXECUTABLE, "-m", "myPackage.CLI", *args],
89+
capture_output=True,
90+
text=True,
91+
timeout=timeout,
92+
)
93+
94+
def assertExitCode(self, result: CompletedProcess, expected: int) -> None:
95+
self.assertEqual(
96+
expected,
97+
result.returncode,
98+
msg=(
99+
f"args={result.args!r}\n"
100+
f"--- stdout ---\n{result.stdout}\n"
101+
f"--- stderr ---\n{result.stderr}"
102+
),
103+
)
104+
105+
106+
class Basic(Testcase):
107+
def test_NoArguments(self) -> None:
108+
result = self.RunEntrypoint()
109+
self.assertExitCode(result, 0)

tests/app/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Test Runner
2+
pytest ~= 9.1

0 commit comments

Comments
 (0)