Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
193 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,7 @@ ENV/ | |
env.bak/ | ||
venv.bak/ | ||
venv3/ | ||
venv-pex/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,7 @@ | ||
#!/usr/bin/env python3 | ||
"""Lint contracts defined with icontract library.""" | ||
|
||
import argparse | ||
import pathlib | ||
import sys | ||
|
||
import icontract_lint | ||
|
||
|
||
def main() -> None: | ||
"""" | ||
Main routine | ||
""" | ||
parser = argparse.ArgumentParser(description=__doc__) | ||
parser.add_argument("--dont_panic", help="Retrun a zero code even if there were errors.", action='store_true') | ||
parser.add_argument("--format", help="Specify the output format.", default='verbose', choices=['verbose', 'json']) | ||
parser.add_argument("paths", help="Specify paths to check (directories and files).", nargs="+") | ||
args = parser.parse_args() | ||
|
||
assert isinstance(args.paths, list) | ||
assert all(isinstance(pth, str) for pth in args.paths) | ||
|
||
a_format = str(args.format) | ||
|
||
paths = [pathlib.Path(pth) for pth in args.paths] | ||
|
||
errors = icontract_lint.check_paths(paths=paths) | ||
|
||
if a_format == 'verbose': | ||
icontract_lint.output_verbose(errors=errors, stream=sys.stdout) | ||
elif a_format == 'json': | ||
icontract_lint.output_json(errors=errors, stream=sys.stdout) | ||
else: | ||
raise NotImplementedError("Unhandled format: {}".format(a_format)) | ||
|
||
import icontract_lint.main | ||
|
||
if __name__ == "__main__": | ||
main() | ||
icontract_lint.main.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/usr/bin/env python3 | ||
"""Lint contracts defined with icontract library.""" | ||
|
||
# This file is necessary so that we can specify the entry point for pex. | ||
|
||
import argparse | ||
import pathlib | ||
import sys | ||
from typing import List, Any, TextIO | ||
|
||
import icontract_lint | ||
|
||
|
||
class Args: | ||
"""Represent parsed command-line arguments.""" | ||
|
||
def __init__(self, args: Any) -> None: | ||
"""Initialize with arguments parsed with ``argparse``.""" | ||
assert isinstance(args.paths, list) | ||
assert all(isinstance(pth, str) for pth in args.paths) | ||
|
||
self.format = str(args.format) | ||
self.paths = [pathlib.Path(pth) for pth in args.paths] | ||
self.dont_panic = bool(args.dont_panic) | ||
|
||
|
||
def parse_args(sys_argv: List[str]) -> Args: | ||
"""Parse command-line arguments.""" | ||
parser = argparse.ArgumentParser(description=__doc__) | ||
parser.add_argument("--dont_panic", help="Retrun a zero code even if there were errors.", action='store_true') | ||
parser.add_argument("--format", help="Specify the output format.", default='verbose', choices=['verbose', 'json']) | ||
parser.add_argument("paths", help="Specify paths to check (directories and files).", nargs="+") | ||
|
||
args = parser.parse_args(sys_argv[1:]) | ||
|
||
return Args(args=args) | ||
|
||
|
||
def _main(args: Args, stream: TextIO) -> int: | ||
"""Execute the main routine.""" | ||
errors = icontract_lint.check_paths(paths=args.paths) | ||
|
||
if args.format == 'verbose': | ||
icontract_lint.output_verbose(errors=errors, stream=stream) | ||
elif args.format == 'json': | ||
icontract_lint.output_json(errors=errors, stream=stream) | ||
else: | ||
raise NotImplementedError("Unhandled format: {}".format(args.format)) | ||
|
||
if not args.dont_panic and errors: | ||
return 1 | ||
|
||
return 0 | ||
|
||
|
||
def main() -> None: | ||
"""Wrap the main routine so that it can be tested.""" | ||
args = parse_args(sys_argv=sys.argv) | ||
sys.exit(_main(args=args, stream=sys.stdout)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
#!/usr/bin/env python3 | ||
"""Test the main routine.""" | ||
import io | ||
import pathlib | ||
import sys | ||
import textwrap | ||
import unittest | ||
from typing import TextIO, cast | ||
|
||
import temppathlib | ||
|
||
import icontract_lint.main | ||
|
||
# pylint: disable=missing-docstring | ||
|
||
|
||
class TestParseArgs(unittest.TestCase): | ||
def test_single_path(self): | ||
args = icontract_lint.main.parse_args(sys_argv=['some-executable.py', '/path/to/some/file.py']) | ||
self.assertListEqual([pathlib.Path('/path/to/some/file.py')], args.paths) | ||
|
||
def test_multiple_paths(self): | ||
args = icontract_lint.main.parse_args( | ||
sys_argv=['some-executable.py', '/path/to/some/file.py', '/path/to/another/file.py']) | ||
self.assertListEqual([ | ||
pathlib.Path('/path/to/some/file.py'), | ||
pathlib.Path('/path/to/another/file.py'), | ||
], args.paths) | ||
|
||
def test_panic(self): | ||
args = icontract_lint.main.parse_args(sys_argv=['some-executable.py', '/path/to/some/file.py']) | ||
self.assertFalse(args.dont_panic) | ||
|
||
def test_dont_panic(self): | ||
args = icontract_lint.main.parse_args(sys_argv=['some-executable.py', '/path/to/some/file.py', '--dont_panic']) | ||
self.assertTrue(args.dont_panic) | ||
|
||
def test_format(self): | ||
args = icontract_lint.main.parse_args( | ||
sys_argv=['some-executable.py', '/path/to/some/file.py', "--format", "json"]) | ||
self.assertEqual("json", args.format) | ||
|
||
|
||
class sys_path_with: # pylint: disable=invalid-name,duplicate-code | ||
"""Add the path to the sys.path in the context.""" | ||
|
||
def __init__(self, path: pathlib.Path) -> None: | ||
"""Set property with the given argument.""" | ||
self.path = path | ||
|
||
def __enter__(self): | ||
"""Add the path to the ``sys.path``.""" | ||
sys.path.insert(0, self.path.as_posix()) | ||
|
||
def __exit__(self, exc_type, exc_value, traceback): | ||
"""Remove the path from the ``sys.path``.""" | ||
sys.path.remove(self.path.as_posix()) | ||
|
||
|
||
class TestMain(unittest.TestCase): | ||
# pylint: disable=protected-access | ||
TEXT = textwrap.dedent("""\ | ||
from icontract import pre | ||
@pre(lambda x: x > 0) | ||
def some_func(y: int) -> int: | ||
return y | ||
""") | ||
|
||
def test_json(self): | ||
with temppathlib.TemporaryDirectory() as tmp: | ||
pth = tmp.path / "some_module.py" | ||
pth.write_text(TestMain.TEXT) | ||
|
||
with sys_path_with(tmp.path): | ||
buf = io.StringIO() | ||
stream = cast(TextIO, buf) | ||
args = icontract_lint.main.parse_args( | ||
sys_argv=["some-executable.py", pth.as_posix(), "--format", "json"]) | ||
|
||
retcode = icontract_lint.main._main(args=args, stream=stream) | ||
|
||
self.assertEqual(1, retcode) | ||
self.assertEqual( | ||
textwrap.dedent("""\ | ||
[ | ||
{{ | ||
"identifier": "pre-invalid-arg", | ||
"description": "Condition argument(s) are missing in the function signature: x", | ||
"filename": "{pth}", | ||
"lineno": 3 | ||
}} | ||
]""".format(pth=pth.as_posix())), | ||
buf.getvalue()) | ||
|
||
def test_verbose(self): | ||
with temppathlib.TemporaryDirectory() as tmp: | ||
pth = tmp.path / "some_module.py" | ||
pth.write_text(TestMain.TEXT) | ||
|
||
with sys_path_with(tmp.path): | ||
buf = io.StringIO() | ||
stream = cast(TextIO, buf) | ||
args = icontract_lint.main.parse_args(sys_argv=["some-executable.py", pth.as_posix()]) | ||
|
||
retcode = icontract_lint.main._main(args=args, stream=stream) | ||
|
||
self.assertEqual(1, retcode) | ||
self.assertEqual( | ||
("{pth}:3: Condition argument(s) are missing in " | ||
"the function signature: x (pre-invalid-arg)\n").format(pth=pth.as_posix()), | ||
buf.getvalue()) | ||
|
||
def test_dont_panic(self): | ||
with temppathlib.TemporaryDirectory() as tmp: | ||
pth = tmp.path / "some_module.py" | ||
pth.write_text(TestMain.TEXT) | ||
|
||
with sys_path_with(tmp.path): | ||
buf = io.StringIO() | ||
stream = cast(TextIO, buf) | ||
args = icontract_lint.main.parse_args(sys_argv=["some-executable.py", pth.as_posix(), "--dont_panic"]) | ||
|
||
retcode = icontract_lint.main._main(args=args, stream=stream) | ||
|
||
self.assertEqual(0, retcode) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |