Repository: python/mypy

Issue:
scripts/mypy doesn't work when run straight from the repo
When I don't have mypy installed, running scripts/mypy from the repo doesn't work. E.g.

```
$ ./scripts/mypy -h
Traceback (most recent call last):
  File "./scripts/mypy", line 4, in <module>
    from mypy.main import main
ImportError: No module named 'mypy'
$ 
```

This is unfortunate because README.md shows this as the way to run individual unit tests:

```
$ scripts/myunit -m mypy.test.testlex -v '*backslash*'
```

Also, unit tests that invoke mypy (e.g. mypy/test/testpythoneval.py) use scripts/mypy this way. The danger exists that those tests run the mypy that's installed rather than the one in the repo.

**UPDATE:** To run an individual unit test I use this instead of what the README.md suggests:

```
$ python3 -m mypy.myunit -m mypy.test.testlex -v '*backslash*'
51 test cases run, all passed.
*** OK ***
$ 
```

But this doesn't work for testpythoneval:

```
$ python3 -m mypy.myunit -m mypy.test.testpythoneval '*testHello'
Expected:
  hello, world                                  (diff)
Actual:
  Traceback (most recent call last):            (diff)
    File "/Users/guido/src/mypy/scripts/mypy", line 4, in <module> (diff)
      from mypy.main import main                (diff)
  ImportError: No module named 'mypy'           (diff)

Alignment of first line difference:
  E: hello, world
  A: Traceback (most recent call last):
     ^
Traceback (most recent call last):
  File "/Users/guido/src/mypy/mypy/test/data.py", line 141, in run
    self.perform(self)
  File "/Users/guido/src/mypy/mypy/test/testpythoneval.py", line 102, in test_python_evaluation
    testcase.file, testcase.line))
  File "/Users/guido/src/mypy/mypy/test/helpers.py", line 86, in assert_string_arrays_equal
    raise AssertionFailure(msg)
AssertionFailure: Invalid output (mypy/test/data/pythoneval.test, line 8)

test_testpythoneval_PythonEvaluationSuite.testHello failed

1/1 test cases failed.
*** FAILURE ***
$
```

To work around this you have to set PYTHONPATH to an absolute path:

```
$ PYTHONPATH=`pwd` python3 -m mypy.myunit -m mypy.test.testpythoneval '*testHello'
1 test cases run, all passed.
*** OK ***
$ 
```

(Using `PYTHONPATH=.` doesn't work, because tests are run from a temporary directory.)



Discussion:
I have some custom scripts that automatically set PYTHONPATH, but it's a minor burden to maintain it. Scripts under `scripts/` could tweak `sys.path` to have things just work. It'd be a little ugly but could be worth it. What do you think?

At the very least README.md should be updated.

I thought of sys.path massaging too, but I worry about what would happen if the script is installed (then it probably shouldn't mess with sys.path). Maybe the answer is try: import mypy / except ImportError: update sys.path, try again.

Since we no longer use `scripts/mypy` for installation, could it modify `sys.path` to include one directory up? Then this shouldn't be an issue. Also its logic to find python needs to be fixed. When I run it I get:

```
λ scripts\mypy -h
'"C:\Users\ethanhs\Documents\mypy\scripts\..\python"' is not recognized as an internal or external command,
operable program or batch file.
```
@ethanhs Which script are you talking about? script/mypy, or script/mypy.bat? The former doesn't have any logic to find python, it just has `#!/usr/bin/env python3`. The latter is for Windows so I suppose that's what you use, but it's not so obvious from your message (unless one guesses from context that you're on Windows and are talking about the Windows script. (And what does `%~dp0` do?)

FWIW I think the README.md has been updated and no longer references this (unless you talk about the *installed* script).
I am on Windows, talking about `mypy.bat`. `%~dp0` is a macro that expands to the path of the drive letter and path to the batch file. So that is definitely incorrect for resolving python (since it is no longer installed in `C:\PythonXY\Scripts`). Since this is no longer used for install I don't think it is that important, but since it is still shipped, I think it would be good to work or be removed.
Ah, I found https://stackoverflow.com/questions/112055/what-does-d0-mean-in-a-windows-batch-file which clarified the syntax for me.

I think I am ready for both `scripts/mypy` and `scripts/mypy.bat` to be removed completely -- rather than `scripts/mypy` I just use `python3 -m mypy`.

Additional issues then while we're at it:

- There's also `scripts/myunit` and `scripts/stubgen` (the latter actually contains code to modify `sys.path`!). I think these should go as well.

- In `README.md` there's still some mention of the `Scripts` directory on Windows, that should be taken out or replaced with a working recipe.
When you delete scripts/mypy and scripts/myunit, also delete the tests for
these from add_basic() in runtests.py.

On master the main issue with removing the scripts is that `python -m mypy` breaks `stdlibsamples`:

```
Traceback (most recent call last):
  File "C:\Python36\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Python36\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\ethanhs\Documents\mypy\mypy\__main__.py", line 3, in <module>
    from mypy.main import main
  File "C:\Users\ethanhs\Documents\mypy\mypy\main.py", line 13, in <module>
    from mypy import build
  File "C:\Users\ethanhs\Documents\mypy\mypy\build.py", line 36, in <module>
    from mypy.nodes import (MODULE_REF, MypyFile, Node, ImportBase, Import, ImportFrom, ImportAll)
  File "C:\Users\ethanhs\Documents\mypy\mypy\nodes.py", line 14, in <module>
    import mypy.strconv
  File "C:\Users\ethanhs\Documents\mypy\mypy\strconv.py", line 8, in <module>
    from mypy.util import short_type, IdMapper
  File "C:\Users\ethanhs\Documents\mypy\mypy\util.py", line 4, in <module>
    import subprocess
  File "C:\Users\ethanhs\Documents\mypy\test-data\stdlib-samples\3.2\subprocess.py", line 374, in <module>
    import _subprocess
ModuleNotFoundError: No module named '_subprocess'
```

It seems to be picking up the `subprocess.py` in the `stdlibsamples` directory. I believe this happens because `-m` adds the current directory to the path.

Resolving patch:
diff --git a/runtests.py b/runtests.py
--- a/runtests.py
+++ b/runtests.py
@@ -38,7 +38,6 @@ def __init__(self, *, whitelist: List[str], blacklist: List[str],
         self.waiter = Waiter(verbosity=verbosity, limit=parallel_limit, xfail=xfail, lf=lf, ff=ff)
         self.versions = get_versions()
         self.cwd = os.getcwd()
-        self.mypy = os.path.join(self.cwd, 'scripts', 'mypy')
         self.env = dict(os.environ)
         self.coverage = coverage
 
@@ -67,7 +66,7 @@ def add_mypy_cmd(self, name: str, mypy_args: List[str], cwd: Optional[str] = Non
         full_name = 'check %s' % name
         if not self.allow(full_name):
             return
-        args = [sys.executable, self.mypy] + mypy_args
+        args = [sys.executable, '-m', 'mypy'] + mypy_args
         args.append('--show-traceback')
         args.append('--no-site-packages')
         self.waiter.add(LazySubprocess(full_name, args, cwd=cwd, env=self.env))
@@ -196,18 +195,17 @@ def add_stubs(driver: Driver) -> None:
 
 def add_stdlibsamples(driver: Driver) -> None:
     seen = set()  # type: Set[str]
-    for version in driver.versions:
-        stdlibsamples_dir = join(driver.cwd, 'test-data', 'stdlib-samples', version)
-        modules = []  # type: List[str]
-        for f in find_files(stdlibsamples_dir, prefix='test_', suffix='.py'):
-            module = file_to_module(f[len(stdlibsamples_dir) + 1:])
-            if module not in seen:
-                seen.add(module)
-                modules.append(module)
-        if modules:
-            # TODO: Remove need for --no-strict-optional
-            driver.add_mypy_modules('stdlibsamples (%s)' % (version,), modules,
-                                    cwd=stdlibsamples_dir, extra_args=['--no-strict-optional'])
+    stdlibsamples_dir = join(driver.cwd, 'test-data', 'stdlib-samples', '3.2', 'test')
+    modules = []  # type: List[str]
+    for f in find_files(stdlibsamples_dir, prefix='test_', suffix='.py'):
+        module = file_to_module(f[len(stdlibsamples_dir) + 1:])
+        if module not in seen:
+            seen.add(module)
+            modules.append(module)
+    if modules:
+        # TODO: Remove need for --no-strict-optional
+        driver.add_mypy_modules('stdlibsamples (3.2)', modules,
+                                cwd=stdlibsamples_dir, extra_args=['--no-strict-optional'])
 
 
 def add_samples(driver: Driver) -> None:


Test patch:
diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py
--- a/mypy/test/testcmdline.py
+++ b/mypy/test/testcmdline.py
@@ -49,8 +49,7 @@ def test_python_cmdline(testcase: DataDrivenTestCase) -> None:
     args.append('--show-traceback')
     args.append('--no-site-packages')
     # Type check the program.
-    fixed = [python3_path,
-             os.path.join(testcase.old_cwd, 'scripts', 'mypy')]
+    fixed = [python3_path, '-m', 'mypy']
     process = subprocess.Popen(fixed + args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
diff --git a/test-data/stdlib-samples/3.2/test/mypy.ini b/test-data/stdlib-samples/3.2/test/mypy.ini
new file mode 100644
--- /dev/null
+++ b/test-data/stdlib-samples/3.2/test/mypy.ini
@@ -0,0 +1,2 @@
+[mypy]
+mypy_path = ..