Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor testpep561 and add test to address #5767 #5782

Merged
merged 22 commits into from
Oct 17, 2018
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2b69726
add testcase which closes #5767
chrisphilip322 Oct 12, 2018
92e7a7d
Only return ns_ancestors in namespace-package mode
Oct 12, 2018
8bee0a3
Might as well use belt and suspenders
Oct 12, 2018
7a33cb3
use windows friendly tempfiles
chrisphilip322 Oct 12, 2018
a0a4959
add test to repro #5784
chrisphilip322 Oct 12, 2018
102cf3b
remove bad testcase
chrisphilip322 Oct 12, 2018
9c577e6
fix typing warning
chrisphilip322 Oct 12, 2018
146c26b
add test to actually reproduce #5784
chrisphilip322 Oct 13, 2018
6122776
Better fix -- roll back the previous attempts in favor of isdir() check
Oct 13, 2018
b02e6db
enhance new test case
chrisphilip322 Oct 13, 2018
7e1e3b2
Merge branch 'fix-regression' of github.com:gvanrossum/mypy into enha…
chrisphilip322 Oct 13, 2018
fa6d6ab
address flake8
chrisphilip322 Oct 13, 2018
ad6952e
add credits to the c extension test file
chrisphilip322 Oct 13, 2018
c166b16
update test to provide actual coverage
chrisphilip322 Oct 14, 2018
f826d15
Merge branch 'master' of https://github.com/python/mypy into enhance-…
chrisphilip322 Oct 14, 2018
f511005
reorganize test files
chrisphilip322 Oct 14, 2018
2cced84
add missing files
chrisphilip322 Oct 14, 2018
e7ab97c
edit expected messages
chrisphilip322 Oct 14, 2018
7513cae
Add test_typedpkg_egg_editable() for completeness
gvanrossum Oct 15, 2018
73c3557
remove unneeded line breaks
chrisphilip322 Oct 15, 2018
62e6f09
Merge branch 'master' of https://github.com/python/mypy into enhance-…
chrisphilip322 Oct 17, 2018
5826e0c
s/ep/prog and s/init/create
chrisphilip322 Oct 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add testcase which closes #5767
  • Loading branch information
chrisphilip322 committed Oct 12, 2018
commit 2b69726e1668b7e8c37f830efffe66ece09cd9c4
210 changes: 140 additions & 70 deletions mypy/test/testpep561.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from contextlib import contextmanager
from enum import Enum
import os
import sys
import tempfile
from typing import Tuple, List, Generator, Optional
from typing import Tuple, List, Generator, Optional, Mapping, Any
from unittest import TestCase, main

import mypy.api
Expand All @@ -21,9 +22,8 @@
reveal_type(a)
"""

NAMESPACE_PROGRAM = """
from typedpkg_nested.nested_package.nested_module import nested_func
from typedpkg_namespace.alpha.alpha_module import alpha_func
_NAMESPACE_PROGRAM = """
{import_style}

nested_func("abc")
alpha_func(False)
Expand All @@ -33,26 +33,79 @@
"""


def check_mypy_run(cmd_line: List[str],
python_executable: str = sys.executable,
expected_out: str = '',
expected_err: str = '',
expected_returncode: int = 1,
venv_dir: Optional[str] = None) -> None:
"""Helper to run mypy and check the output."""
if venv_dir is not None:
old_dir = os.getcwd()
os.chdir(venv_dir)
try:
if python_executable != sys.executable:
cmd_line.append('--python-executable={}'.format(python_executable))
out, err, returncode = mypy.api.run(cmd_line)
assert out == expected_out, err
assert err == expected_err, out
assert returncode == expected_returncode, returncode
finally:
class NamespaceProgramImportStyle(Enum):
from_import = """\
from typedpkg_nested.nested_package.nested_module import nested_func
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike having those long module names. If you give each of them names of 3-5 letters it will become more readable (and you won't have to break long lines with \ below). Also a comment may be in order explaining why some things must go together on one line (IIUC it's so that the line numbers in the error messages are consistent?)

from typedpkg_namespace.alpha.alpha_module import alpha_func"""
import_as = """\
import typedpkg_nested.nested_package.nested_module as nm; nested_func = nm.nested_func
import typedpkg_namespace.alpha.alpha_module as am; alpha_func = am.alpha_func"""
regular_import = """\
import typedpkg_nested.nested_package.nested_module; \
nested_func = typedpkg_nested.nested_package.nested_module.nested_func
import typedpkg_namespace.alpha.alpha_module; \
alpha_func = typedpkg_namespace.alpha.alpha_module.alpha_func"""


class SimpleProgramMessage(Enum):
msg_dne = "{tempfile}:3: error: Module 'typedpkg' has no attribute 'dne'"
msg_list = "{tempfile}:5: error: Revealed type is 'builtins.list[builtins.str]'"
msg_tuple = "{tempfile}:5: error: Revealed type is 'builtins.tuple[builtins.str]'"


class NamespaceProgramMessage(Enum):
bool_str = ('{tempfile}:8: error: Argument 1 to "nested_func" has incompatible type '
'"bool"; expected "str"')
int_bool = ('{tempfile}:9: error: Argument 1 to "alpha_func" has incompatible type '
'"int"; expected "bool"')


def create_namespace_program_source(import_style: NamespaceProgramImportStyle) -> str:
return _NAMESPACE_PROGRAM.format(import_style=import_style.value)


class ExampleProgram(object):
def __init__(self, source_code: str) -> None:
self._source_code = source_code

self._temp_file = None # type: Any

def init(self) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: rename to create? init doesn't really tell me what it is doing, create implies it is doing setup work and creating the test case.

self._temp_file = tempfile.NamedTemporaryFile(mode='w+')
self._temp_file.write(self._source_code)
self._temp_file.flush()

def cleanup(self) -> None:
if self._temp_file:
self._temp_file.close()

def build_msg(self, *msgs: Enum) -> str:
return '\n'.join(
msg.value.format(tempfile=self._temp_file.name)
for msg in msgs
) + '\n'

def check_mypy_run(self,
python_executable: str,
expected_out: List[Enum],
expected_err: str = '',
expected_returncode: int = 1,
venv_dir: Optional[str] = None) -> None:
"""Helper to run mypy and check the output."""
cmd_line = [self._temp_file.name]
if venv_dir is not None:
os.chdir(old_dir)
old_dir = os.getcwd()
os.chdir(venv_dir)
try:
if python_executable != sys.executable:
cmd_line.append('--python-executable={}'.format(python_executable))
out, err, returncode = mypy.api.run(cmd_line)
assert out == self.build_msg(*expected_out), err
assert err == expected_err, out
assert returncode == expected_returncode, returncode
finally:
if venv_dir is not None:
os.chdir(old_dir)


class TestPEP561(TestCase):
Expand Down Expand Up @@ -102,127 +155,144 @@ def install_package(self, pkg: str,
self.fail('\n'.join(lines))

def setUp(self) -> None:
self.temp_file_dir = tempfile.TemporaryDirectory()
self.tempfile = os.path.join(self.temp_file_dir.name, 'simple.py')
with open(self.tempfile, 'w+') as file:
file.write(SIMPLE_PROGRAM)
self.namespace_tempfile = os.path.join(self.temp_file_dir.name, 'namespace_program.py')
with open(self.namespace_tempfile, 'w+') as file:
file.write(NAMESPACE_PROGRAM)

self.msg_dne = \
"{}:3: error: Module 'typedpkg' has no attribute 'dne'\n".format(self.tempfile)
self.msg_list = \
"{}:5: error: Revealed type is 'builtins.list[builtins.str]'\n".format(self.tempfile)
self.msg_tuple = \
"{}:5: error: Revealed type is 'builtins.tuple[builtins.str]'\n".format(self.tempfile)

self.namespace_msg_bool_str = (
'{0}:8: error: Argument 1 to "nested_func" has incompatible type "bool"; '
'expected "str"\n'.format(self.namespace_tempfile))
self.namespace_msg_int_bool = (
'{0}:9: error: Argument 1 to "alpha_func" has incompatible type "int"; '
'expected "bool"\n'.format(self.namespace_tempfile))
self.simple_example_program = ExampleProgram(SIMPLE_PROGRAM)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variable names are all too long -- when skimming all I see is blah_blah_blah_blah. I don't think they all need to end in _example_program, and you can abbreviate namespace as ns.

self.from_namespace_example_program = ExampleProgram(
create_namespace_program_source(NamespaceProgramImportStyle.from_import))
self.import_as_namespace_example_program = ExampleProgram(
create_namespace_program_source(NamespaceProgramImportStyle.from_import))
self.regular_import_namespace_example_program = ExampleProgram(
create_namespace_program_source(NamespaceProgramImportStyle.from_import))

def tearDown(self) -> None:
self.temp_file_dir.cleanup()
self.simple_example_program.cleanup()
self.from_namespace_example_program.cleanup()

def test_get_pkg_dirs(self) -> None:
"""Check that get_package_dirs works."""
dirs = get_site_packages_dirs(sys.executable)
assert dirs

def test_typedpkg_stub_package(self) -> None:
self.simple_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg-stubs', python_executable)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
python_executable,
expected_out=self.msg_dne + self.msg_list,
[SimpleProgramMessage.msg_dne,
SimpleProgramMessage.msg_list],
venv_dir=venv_dir,
)

def test_typedpkg(self) -> None:
self.simple_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg', python_executable)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
python_executable,
expected_out=self.msg_tuple,
[SimpleProgramMessage.msg_tuple],
venv_dir=venv_dir,
)

def test_stub_and_typed_pkg(self) -> None:
self.simple_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg', python_executable)
self.install_package('typedpkg-stubs', python_executable)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
python_executable,
expected_out=self.msg_list,
[SimpleProgramMessage.msg_list],
venv_dir=venv_dir,
)

def test_typedpkg_stubs_python2(self) -> None:
self.simple_example_program.init()
python2 = try_find_python2_interpreter()
if python2:
with self.virtualenv(python2) as venv:
venv_dir, py2 = venv
self.install_package('typedpkg-stubs', py2)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
py2,
expected_out=self.msg_dne + self.msg_list,
[SimpleProgramMessage.msg_dne,
SimpleProgramMessage.msg_list],
venv_dir=venv_dir,
)

def test_typedpkg_python2(self) -> None:
self.simple_example_program.init()
python2 = try_find_python2_interpreter()
if python2:
with self.virtualenv(python2) as venv:
venv_dir, py2 = venv
self.install_package('typedpkg', py2)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
py2,
expected_out=self.msg_tuple,
[SimpleProgramMessage.msg_tuple],
venv_dir=venv_dir,
)

def test_typedpkg_egg(self) -> None:
self.simple_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg', python_executable, use_pip=False)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
python_executable,
expected_out=self.msg_tuple,
[SimpleProgramMessage.msg_tuple],
venv_dir=venv_dir,
)

def test_typedpkg_editable(self) -> None:
self.simple_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg', python_executable, editable=True)
check_mypy_run(
[self.tempfile],
self.simple_example_program.check_mypy_run(
python_executable,
[SimpleProgramMessage.msg_tuple],
venv_dir=venv_dir,
)

def test_nested_and_namespace_from_import(self) -> None:
self.from_namespace_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg_nested', python_executable)
self.install_package('typedpkg_namespace-alpha', python_executable)
self.from_namespace_example_program.check_mypy_run(
python_executable,
[NamespaceProgramMessage.bool_str,
NamespaceProgramMessage.int_bool],
venv_dir=venv_dir,
)

def test_nested_and_namespace_import_as(self) -> None:
self.import_as_namespace_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg_nested', python_executable)
self.install_package('typedpkg_namespace-alpha', python_executable)
self.import_as_namespace_example_program.check_mypy_run(
python_executable,
expected_out=self.msg_tuple,
[NamespaceProgramMessage.bool_str,
NamespaceProgramMessage.int_bool],
venv_dir=venv_dir,
)

def test_nested_and_namespace(self) -> None:
def test_nested_and_namespace_regular_import(self) -> None:
# This test case addresses https://github.com/python/mypy/issues/5767
self.regular_import_namespace_example_program.init()
with self.virtualenv() as venv:
venv_dir, python_executable = venv
self.install_package('typedpkg_nested', python_executable)
self.install_package('typedpkg_namespace-alpha', python_executable)
check_mypy_run(
[self.namespace_tempfile],
self.regular_import_namespace_example_program.check_mypy_run(
python_executable,
expected_out=self.namespace_msg_bool_str + self.namespace_msg_int_bool,
[NamespaceProgramMessage.bool_str,
NamespaceProgramMessage.int_bool],
venv_dir=venv_dir,
)

Expand Down