Skip to content

Commit

Permalink
Merge dd740d6 into 356d88d
Browse files Browse the repository at this point in the history
  • Loading branch information
mristin committed Sep 21, 2018
2 parents 356d88d + dd740d6 commit 8452099
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -90,6 +90,7 @@ ENV/
env.bak/
venv.bak/
venv3/
venv-pex/

# Spyder project settings
.spyderproject
Expand Down
36 changes: 2 additions & 34 deletions bin/pyicontract-lint
@@ -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()
59 changes: 59 additions & 0 deletions icontract_lint/main.py
@@ -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))
2 changes: 1 addition & 1 deletion pylint.rc
Expand Up @@ -7,5 +7,5 @@ generated-members=bottle\.request\.forms\.decode,bottle\.request\.query\.decode
max-line-length=120

[MESSAGES CONTROL]
disable=too-few-public-methods,abstract-class-little-used,len-as-condition,bad-continuation,bad-whitespace
disable=too-few-public-methods,abstract-class-little-used,len-as-condition,bad-continuation,bad-whitespace,duplicate-code

130 changes: 130 additions & 0 deletions tests/test_main.py
@@ -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()

0 comments on commit 8452099

Please sign in to comment.