Skip to content

Commit c7b5b1a

Browse files
authored
tests: consolidate cli runner (#806)
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 01c1354 commit c7b5b1a

File tree

7 files changed

+296
-450
lines changed

7 files changed

+296
-450
lines changed

cyclonedx_py/_internal/cli.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import sys
2020
from argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter
2121
from itertools import chain
22-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, TextIO, Type
22+
from typing import TYPE_CHECKING, Any, Dict, NoReturn, Optional, Sequence, TextIO, Type, Union
2323

2424
from cyclonedx.model import Property
2525
from cyclonedx.output import make_outputter
@@ -256,7 +256,7 @@ def __call__(self,
256256
self._write(output, outfile)
257257

258258

259-
def run(*, argv: Optional[List[str]] = None, **kwargs: Any) -> int:
259+
def run(*, argv: Optional[Sequence[str]] = None, **kwargs: Any) -> Union[int, NoReturn]:
260260
arg_co = ArgumentParser(add_help=False)
261261
arg_co.add_argument('-v', '--verbose',
262262
help='Increase the verbosity of messages'
@@ -267,12 +267,12 @@ def run(*, argv: Optional[List[str]] = None, **kwargs: Any) -> int:
267267
default=0)
268268
arg_parser = Command.make_argument_parser(**kwargs, sco=arg_co)
269269
del arg_co, kwargs
270-
args = vars(arg_parser.parse_args(argv))
270+
args = vars(arg_parser.parse_args(argv)) # may exit -> raise `SystemExit`
271271
if args['command'] is None:
272272
# print the "help" page on error, instead of printing "usage" page
273273
# this is done to have a better user experience.
274274
arg_parser.print_help()
275-
return 1
275+
sys.exit(1)
276276
del arg_parser, argv
277277

278278
ll = (logging.WARNING, logging.INFO, logging.DEBUG)[min(2, args.pop('verbosity'))]

tests/integration/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,26 @@
1414
#
1515
# SPDX-License-Identifier: Apache-2.0
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
19+
from contextlib import redirect_stderr, redirect_stdout
20+
from io import BytesIO, StringIO, TextIOWrapper
21+
from typing import Any, Optional
22+
from unittest.mock import patch
23+
24+
from cyclonedx_py._internal.cli import run as _run_cli
25+
26+
27+
def run_cli(*args: str, inp: Optional[Any] = None) -> (int, str, str):
28+
with StringIO() as err, StringIO() as out:
29+
err.name = '<fakeerr>'
30+
out.name = '<fakeout>'
31+
with redirect_stderr(err), redirect_stdout(out):
32+
with patch('sys.stdin', TextIOWrapper(inp or BytesIO())):
33+
try:
34+
c_res = _run_cli(argv=args)
35+
except SystemExit as e:
36+
c_res = e.code
37+
c_out = out.getvalue()
38+
c_err = err.getvalue()
39+
return c_res, c_out, c_err

tests/integration/test_cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@
2121
from unittest import TestCase
2222

2323
from cyclonedx_py import __version__
24+
from tests.integration import run_cli
2425

2526

26-
class TestPipenv(TestCase):
27+
class TestCli(TestCase):
2728

28-
def test_call_as_module(self) -> None:
29+
def test_version_as_module(self) -> None:
2930
# show that this thing is callable as a module
3031
# show that the version is the one expected
3132
res = run( # nosec:B603
3233
(executable, '-m', 'cyclonedx_py', '--version'),
3334
capture_output=True, encoding='utf8', shell=False)
3435
self.assertEqual(0, res.returncode, '\n'.join((res.stdout, res.stderr)))
3536
self.assertIn(__version__, res.stdout)
37+
38+
def test_version(self) -> None:
39+
res, out, err = run_cli('--version')
40+
self.assertEqual(0, res, '\n'.join((out, err)))
41+
self.assertIn(__version__, out)

tests/integration/test_cli_environment.py

Lines changed: 82 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
import random
19-
from contextlib import redirect_stderr, redirect_stdout
2019
from glob import glob
21-
from io import StringIO
2220
from os import name as os_name
2321
from os.path import basename, dirname, join
2422
from subprocess import run # nosec:B404
@@ -29,8 +27,8 @@
2927
from cyclonedx.schema import OutputFormat, SchemaVersion
3028
from ddt import ddt, named_data
3129

32-
from cyclonedx_py._internal.cli import run as run_cli
3330
from tests import INFILES_DIRECTORY, INIT_TESTBEDS, SUPPORTED_OF_SV, SnapshotMixin, make_comparable
31+
from tests.integration import run_cli
3432

3533
initfiles = glob(join(INFILES_DIRECTORY, 'environment', '*', 'init.py'))
3634
test_data = tuple(
@@ -68,19 +66,13 @@ def setUpClass(cls) -> None:
6866
)
6967
def test_fails_with_python_not_found(self, wrong_python: str, expected_error: str) -> None:
7068
_, _, sv, of = random.choice(test_data) # nosec B311
71-
with StringIO() as err, StringIO() as out:
72-
err.name = '<fakeerr>'
73-
out.name = '<fakeout>'
74-
with redirect_stderr(err), redirect_stdout(out):
75-
res = run_cli(argv=[
76-
'environment',
77-
'-vvv',
78-
f'--sv={sv.to_version()}',
79-
f'--of={of.name}',
80-
'--outfile=-',
81-
wrong_python])
82-
err = err.getvalue()
83-
out = out.getvalue()
69+
res, out, err = run_cli(
70+
'environment',
71+
'-vvv',
72+
f'--sv={sv.to_version()}',
73+
f'--of={of.name}',
74+
'--outfile=-',
75+
wrong_python)
8476
self.assertNotEqual(0, res, err)
8577
self.assertIn(expected_error, err)
8678

@@ -91,76 +83,52 @@ def test_fails_with_python_not_found(self, wrong_python: str, expected_error: st
9183
@skipIf(os_name == 'nt', 'cannot run on win')
9284
def test_fails_with_python_unexpected(self, wrong_python: str, expected_error: str) -> None:
9385
_, _, sv, of = random.choice(test_data) # nosec B311
94-
with StringIO() as err, StringIO() as out:
95-
err.name = '<fakeerr>'
96-
out.name = '<fakeout>'
97-
with redirect_stderr(err), redirect_stdout(out):
98-
res = run_cli(argv=[
99-
'environment',
100-
'-vvv',
101-
f'--sv={sv.to_version()}',
102-
f'--of={of.name}',
103-
'--outfile=-',
104-
wrong_python])
105-
err = err.getvalue()
106-
out = out.getvalue()
86+
res, out, err = run_cli(
87+
'environment',
88+
'-vvv',
89+
f'--sv={sv.to_version()}',
90+
f'--of={of.name}',
91+
'--outfile=-',
92+
wrong_python)
10793
self.assertNotEqual(0, res, err)
10894
self.assertIn(expected_error, err)
10995

11096
def test_with_pyproject_not_found(self) -> None:
11197
_, projectdir, sv, of = random.choice(test_data) # nosec B311
112-
with StringIO() as err, StringIO() as out:
113-
err.name = '<fakeerr>'
114-
out.name = '<fakeout>'
115-
with redirect_stderr(err), redirect_stdout(out):
116-
res = run_cli(argv=[
117-
'environment',
118-
'-vvv',
119-
'--sv', sv.to_version(),
120-
'--of', of.name,
121-
'--outfile=-',
122-
'--pyproject=something-that-must-not-exist.testing',
123-
projectdir
124-
])
125-
err = err.getvalue()
126-
out = out.getvalue()
98+
res, out, err = run_cli(
99+
'environment',
100+
'-vvv',
101+
'--sv', sv.to_version(),
102+
'--of', of.name,
103+
'--outfile=-',
104+
'--pyproject=something-that-must-not-exist.testing',
105+
projectdir
106+
)
127107
self.assertNotEqual(0, res, err)
128108
self.assertIn('Could not open pyproject file: something-that-must-not-exist.testing', err)
129109

130110
def test_with_current_python(self) -> None:
131111
sv = SchemaVersion.V1_6
132112
of = random.choice((OutputFormat.XML, OutputFormat.JSON)) # nosec B311
133-
with StringIO() as err, StringIO() as out:
134-
err.name = '<fakeerr>'
135-
out.name = '<fakeout>'
136-
with redirect_stderr(err), redirect_stdout(out):
137-
res = run_cli(argv=[
138-
'environment',
139-
'-vvv',
140-
'--sv', sv.to_version(),
141-
'--of', of.name,
142-
'--output-reproducible',
143-
'--outfile=-',
144-
# no project dir -> search in current python
145-
])
146-
err = err.getvalue()
147-
sbom1 = out.getvalue()
113+
res, sbom1, err = run_cli(
114+
'environment',
115+
'-vvv',
116+
'--sv', sv.to_version(),
117+
'--of', of.name,
118+
'--output-reproducible',
119+
'--outfile=-',
120+
# no project dir -> search in current python
121+
)
148122
self.assertEqual(0, res, err)
149-
with StringIO() as err, StringIO() as out:
150-
err.name = '<fakeerr>'
151-
out.name = '<fakeout>'
152-
with redirect_stderr(err), redirect_stdout(out):
153-
res = run_cli(argv=[
154-
'environment',
155-
'-vvv',
156-
'--sv', sv.to_version(),
157-
'--of', of.name,
158-
'--output-reproducible',
159-
'--outfile=-',
160-
executable # explicitly current python
161-
])
162-
err = err.getvalue()
163-
sbom2 = out.getvalue()
123+
res, sbom2, err = run_cli(
124+
'environment',
125+
'-vvv',
126+
'--sv', sv.to_version(),
127+
'--of', of.name,
128+
'--output-reproducible',
129+
'--outfile=-',
130+
executable # explicitly current python
131+
)
164132
self.assertEqual(0, res, err)
165133
self.assertEqual(
166134
make_comparable(sbom1, of),
@@ -169,85 +137,61 @@ def test_with_current_python(self) -> None:
169137

170138
@named_data(*test_data)
171139
def test_plain_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None:
172-
with StringIO() as err, StringIO() as out:
173-
err.name = '<fakeerr>'
174-
out.name = '<fakeout>'
175-
with redirect_stderr(err), redirect_stdout(out):
176-
res = run_cli(argv=[
177-
'environment',
178-
'-vvv',
179-
'--sv', sv.to_version(),
180-
'--of', of.name,
181-
'--output-reproducible',
182-
'--outfile=-',
183-
'--pyproject', join(projectdir, 'pyproject.toml'),
184-
join(projectdir, '.venv')])
185-
err = err.getvalue()
186-
out = out.getvalue()
140+
res, out, err = run_cli(
141+
'environment',
142+
'-vvv',
143+
'--sv', sv.to_version(),
144+
'--of', of.name,
145+
'--output-reproducible',
146+
'--outfile=-',
147+
'--pyproject', join(projectdir, 'pyproject.toml'),
148+
join(projectdir, '.venv'))
187149
self.assertEqual(0, res, err)
188150
self.assertEqualSnapshot(out, 'plain', projectdir, sv, of)
189151

190152
@named_data(*test_data_file_filter('pep639'))
191153
def test_pep639_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None:
192-
with StringIO() as err, StringIO() as out:
193-
err.name = '<fakeerr>'
194-
out.name = '<fakeout>'
195-
with redirect_stderr(err), redirect_stdout(out):
196-
res = run_cli(argv=[
197-
'environment',
198-
'-vvv',
199-
'--sv', sv.to_version(),
200-
'--of', of.name,
201-
'--output-reproducible',
202-
'--outfile=-',
203-
'--pyproject', join(projectdir, 'pyproject.toml'),
204-
'--PEP-639',
205-
join(projectdir, '.venv')])
206-
err = err.getvalue()
207-
out = out.getvalue()
154+
res, out, err = run_cli(
155+
'environment',
156+
'-vvv',
157+
'--sv', sv.to_version(),
158+
'--of', of.name,
159+
'--output-reproducible',
160+
'--outfile=-',
161+
'--pyproject', join(projectdir, 'pyproject.toml'),
162+
'--PEP-639',
163+
join(projectdir, '.venv'))
208164
self.assertEqual(0, res, err)
209165
self.assertEqualSnapshot(out, 'pep639', projectdir, sv, of)
210166

211167
@named_data(*test_data_file_filter('pep639'))
212168
def test_pep639_texts_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None:
213-
with StringIO() as err, StringIO() as out:
214-
err.name = '<fakeerr>'
215-
out.name = '<fakeout>'
216-
with redirect_stderr(err), redirect_stdout(out):
217-
res = run_cli(argv=[
218-
'environment',
219-
'-vvv',
220-
'--sv', sv.to_version(),
221-
'--of', of.name,
222-
'--output-reproducible',
223-
'--outfile=-',
224-
'--pyproject', join(projectdir, 'pyproject.toml'),
225-
'--PEP-639',
226-
'--gather-license-texts',
227-
join(projectdir, '.venv')])
228-
err = err.getvalue()
229-
out = out.getvalue()
169+
res, out, err = run_cli(
170+
'environment',
171+
'-vvv',
172+
'--sv', sv.to_version(),
173+
'--of', of.name,
174+
'--output-reproducible',
175+
'--outfile=-',
176+
'--pyproject', join(projectdir, 'pyproject.toml'),
177+
'--PEP-639',
178+
'--gather-license-texts',
179+
join(projectdir, '.venv'))
230180
self.assertEqual(0, res, err)
231181
self.assertEqualSnapshot(out, 'pep639-texts', projectdir, sv, of)
232182

233183
@named_data(*test_data_file_filter('pep639'))
234184
def test_texts_as_expected(self, projectdir: str, sv: SchemaVersion, of: OutputFormat) -> None:
235-
with StringIO() as err, StringIO() as out:
236-
err.name = '<fakeerr>'
237-
out.name = '<fakeout>'
238-
with redirect_stderr(err), redirect_stdout(out):
239-
res = run_cli(argv=[
240-
'environment',
241-
'-vvv',
242-
'--sv', sv.to_version(),
243-
'--of', of.name,
244-
'--output-reproducible',
245-
'--outfile=-',
246-
'--pyproject', join(projectdir, 'pyproject.toml'),
247-
'--gather-license-texts',
248-
join(projectdir, '.venv')])
249-
err = err.getvalue()
250-
out = out.getvalue()
185+
res, out, err = run_cli(
186+
'environment',
187+
'-vvv',
188+
'--sv', sv.to_version(),
189+
'--of', of.name,
190+
'--output-reproducible',
191+
'--outfile=-',
192+
'--pyproject', join(projectdir, 'pyproject.toml'),
193+
'--gather-license-texts',
194+
join(projectdir, '.venv'))
251195
self.assertEqual(0, res, err)
252196
self.assertEqualSnapshot(out, 'texts', projectdir, sv, of)
253197

0 commit comments

Comments
 (0)