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

Return after a for in a try on Windows incorrectly marked as missing #959

Closed
Peilonrayz opened this issue Mar 18, 2020 · 18 comments
Closed
Labels
bug Something isn't working not our bug The problem was elsewhere
Milestone

Comments

@Peilonrayz
Copy link

Peilonrayz commented Mar 18, 2020

If you have a return after a for in a try block, the return is incorrectly said to not be hit.
This is effecting my Windows, however it does not seem to effect Linux installs.

Below is an MVCE of the commands and file to setup the environment to reproduce this.

               $ mkdir example
               $ cd example
       example $ python -m virtualenv venv
       example $ & ./venv/Scripts/activate
(venv) example $ pip install pytest coverage>5.0.0
Successfully installed atomicwrites-1.3.0 attrs-19.3.0 colorama-0.4.3 coverage-5.0.4 more-itertools-8.2.0 packaging-20.3 pluggy-0.13.1 py-1.8.1 pyparsing-2.4.6 pytest-5.4.1 six-1.14.0 wcwidth-0.1.8
(venv) example $ vim test_example.py
(venv) example $ coverage run --omit venv/* -m pytest
================================================= test session starts =================================================
platform win32 -- Python 3.8.0, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: example
collected 1 item

test_example.py .                                                                                                [100%]

================================================== 1 passed in 0.04s ==================================================
(venv) example $ coverage report -m
Name              Stmts   Miss  Cover   Missing
-----------------------------------------------
test_example.py      10      1    90%   5

test_example.py

def fn(values):
    try:
        for _ in values:
            raise KeyboardInterrupt()
        return 0
    except KeyboardInterrupt:
        return 130


def test_fn():
    assert fn([]) == 0
    assert fn([...]) == 130
System information
(venv) example> python -V
Python 3.8.0
(venv) example> coverage debug sys
-- sys -------------------------------------------------------
                        version: 5.0.4
                       coverage: example\venv\lib\site-packages\coverage\__init__.py
                         tracer: -none-
                        CTracer: available
           plugins.file_tracers: -none-
            plugins.configurers: -none-
      plugins.context_switchers: -none-
              configs_attempted: .coveragerc
                                 setup.cfg
                                 tox.ini
                                 pyproject.toml
                   configs_read: -none-
                    config_file: None
                config_contents: -none-
                      data_file: -none-
                         python: 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)]
                       platform: Windows-10-10.0.18362-SP0
                 implementation: CPython
                     executable: example\venv\scripts\python.exe
                   def_encoding: utf-8
                    fs_encoding: utf-8
                            pid: 195048
                            cwd: example
                           path: example\venv\Scripts\coverage.exe
                                 example\venv\scripts\python38.zip
                                 C:\Program Files\Python\3.8.0\DLLs
                                 C:\Program Files\Python\3.8.0\lib
                                 C:\Program Files\Python\3.8.0
                                 example\venv
                                 example\venv\lib\site-packages
                    environment: -none-
                   command_line: example\venv\Scripts\coverage debug sys
                sqlite3_version: 2.6.0
         sqlite3_sqlite_version: 3.28.0
             sqlite3_temp_store: 0
        sqlite3_compile_options: COMPILER=msvc-1916
                                 ENABLE_FTS4
                                 ENABLE_FTS5
                                 THREADSAFE=1
(venv) example> pip freeze
atomicwrites==1.3.0
attrs==19.3.0
colorama==0.4.3
coverage==5.0.4
more-itertools==8.2.0
packaging==20.3
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.6
pytest==5.4.1
six==1.14.0
wcwidth==0.1.8
@nedbat
Copy link
Owner

nedbat commented Mar 19, 2020

I don't understand why Windows would have anything to do with this. On Mac, I can't reproduce the issue. I get 100% coverage.

@Peilonrayz
Copy link
Author

Peilonrayz commented Mar 19, 2020

This has been reproduce on:

  • 3.8.0-2 (all available Windows 3.8 versions) on my main PC. These were all standard 64 bit CPython installs from their website.
  • 3.8.0 on a second box. (Same system info as my other box)
  • I also got another person to test this on their Windows box, inside a VM, and they also reproduced this.
Other person's system information
(venv) example $ python --version
Python 3.8.2
(venv) example $ pip list
Package        Version
-------------- -------
atomicwrites   1.3.0
attrs          19.3.0
colorama       0.4.3
coverage       5.0.4
more-itertools 8.2.0
packaging      20.3
pip            20.0.2
pluggy         0.13.1
py             1.8.1
pyparsing      2.4.6
pytest         5.4.1
setuptools     46.0.0
six            1.14.0
wcwidth        0.1.8
wheel          0.34.2

@graingert
Copy link
Contributor

I can reproduce this

@graingert
Copy link
Contributor

graingert commented Apr 11, 2020

Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32 win10

https://usercontent.irccloud-cdn.com/file/Suugb9xM/.coverage

@nedbat
Copy link
Owner

nedbat commented Aug 30, 2020

Here is dis output from Windows: https://gist.github.com/bensimner/463858949e4a34dde1edce7489027f49 I get the same dis output on Mac, so I don't understand how the execution could differ.

@Peilonrayz
Copy link
Author

Would comparing .coverage files between systems help? Are there other logs/outputs we can get to help narrow down where the bug is coming from? Do the different stages each have output we can use for this? Would the output with --debug used help?

@Peilonrayz
Copy link
Author

Peilonrayz commented Aug 30, 2020

If I am able to learn how to get VMs to work on Linux then I'd be happy to compare the outputs. But I know very little about coverage as you've made the usage super simple. If you could help me find any and all debugging output then I'd be happy to try and track down the origin of the bug. But I'm not too sure how much I'd be able to help otherwise.

@matham
Copy link

matham commented Aug 30, 2020

This seems to be Python 3.8 specific on Windows. I.e.:

$ coverage run  -m pytest
============================================================================================================= test session starts ============================================================================================================= 
platform win32 -- Python 3.8.0, pytest-6.0.1, py-1.9.0, pluggy-0.13.0
rootdir: G:\Python\libs\example
collected 1 item

test_example.py .                                                                                                                                                                                                                        [100%]

============================================================================================================== 1 passed in 0.06s ==============================================================================================================

$ coverage report -m
Name              Stmts   Miss  Cover   Missing
-----------------------------------------------
test_example.py      10      1    90%   5

Vs.:

$ coverage run  -m pytest
============================================================================================================= test session starts ============================================================================================================= 
platform win32 -- Python 3.7.3, pytest-5.4.2, py-1.8.0, pluggy-0.12.0
rootdir: G:\Python\libs\example
plugins: anyio-1.3.1, cov-2.9.0, timeout-1.3.4, trio-0.6.0
collected 1 item

test_example.py .                                                                                                                                                                                                                        [100%]

============================================================================================================== 1 passed in 0.06s ==============================================================================================================

$ coverage report -m
Name              Stmts   Miss  Cover   Missing
-----------------------------------------------
test_example.py      10      0   100%

Both are with:

$ pip show coverage
Name: coverage
Version: 5.2.1

$ pip show pytest
Name: pytest
Version: 6.0.1

@matham
Copy link

matham commented Aug 30, 2020

And disassembly is different:

Python 3.8 disassembly
```
$ python -m dis test_example.py
  1           0 LOAD_CONST               0 (<code object fn at 0x0000019421162BE0, file "test_example.py", line 1>)
              2 LOAD_CONST               1 ('fn')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (fn)

 10           8 LOAD_CONST               2 (<code object test_fn at 0x0000019421162C90, file "test_example.py", line 10>)
             10 LOAD_CONST               3 ('test_fn')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               1 (test_fn)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

Disassembly of <code object fn at 0x0000019421162BE0, file "test_example.py", line 1>:
  2           0 SETUP_FINALLY           22 (to 24)

  3           2 LOAD_FAST                0 (values)
              4 GET_ITER
        >>    6 FOR_ITER                10 (to 18)
              8 STORE_FAST               1 (_)

  4          10 LOAD_GLOBAL              0 (KeyboardInterrupt)
             12 CALL_FUNCTION            0
             14 RAISE_VARARGS            1
             16 JUMP_ABSOLUTE            6

  5     >>   18 POP_BLOCK
             20 LOAD_CONST               1 (0)
             22 RETURN_VALUE

  6     >>   24 DUP_TOP
             26 LOAD_GLOBAL              0 (KeyboardInterrupt)
             28 COMPARE_OP              10 (exception match)
             30 POP_JUMP_IF_FALSE       44
             32 POP_TOP
             34 POP_TOP
             36 POP_TOP

  7          38 POP_EXCEPT
             40 LOAD_CONST               2 (130)
             42 RETURN_VALUE
        >>   44 END_FINALLY
             46 LOAD_CONST               0 (None)
             48 RETURN_VALUE

Disassembly of <code object test_fn at 0x0000019421162C90, file "test_example.py", line 10>:
 11           0 LOAD_GLOBAL              0 (fn)
              2 BUILD_LIST               0
              4 CALL_FUNCTION            1
              6 LOAD_CONST               1 (0)
              8 COMPARE_OP               2 (==)
             10 POP_JUMP_IF_TRUE        16
             12 LOAD_GLOBAL              1 (AssertionError)
             14 RAISE_VARARGS            1

 12     >>   16 LOAD_GLOBAL              0 (fn)
             18 LOAD_CONST               2 (Ellipsis)
             20 BUILD_LIST               1
             22 CALL_FUNCTION            1
             24 LOAD_CONST               3 (130)
             26 COMPARE_OP               2 (==)
             28 POP_JUMP_IF_TRUE        34
             30 LOAD_GLOBAL              1 (AssertionError)
             32 RAISE_VARARGS            1
        >>   34 LOAD_CONST               0 (None)
             36 RETURN_VALUE
```

vs.

Python 3.7 disassembly
```
$ python -m dis test_example.py
  1           0 LOAD_CONST               0 (<code object fn at 0x000001FD5FB03F60, file "test_example.py", line 1>)
              2 LOAD_CONST               1 ('fn')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (fn)

 10           8 LOAD_CONST               2 (<code object test_fn at 0x000001FD5FAF5780, file "test_example.py", line 10>)
             10 LOAD_CONST               3 ('test_fn')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               1 (test_fn)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

Disassembly of <code object fn at 0x000001FD5FB03F60, file "test_example.py", line 1>:
  2           0 SETUP_EXCEPT            24 (to 26)

  3           2 SETUP_LOOP              18 (to 22)
              4 LOAD_FAST                0 (values)
              6 GET_ITER
        >>    8 FOR_ITER                10 (to 20)
             10 STORE_FAST               1 (_)

  4          12 LOAD_GLOBAL              0 (KeyboardInterrupt)
             14 CALL_FUNCTION            0
             16 RAISE_VARARGS            1
             18 JUMP_ABSOLUTE            8
        >>   20 POP_BLOCK

  5     >>   22 LOAD_CONST               1 (0)
             24 RETURN_VALUE

  6     >>   26 DUP_TOP
             28 LOAD_GLOBAL              0 (KeyboardInterrupt)
             30 COMPARE_OP              10 (exception match)
             32 POP_JUMP_IF_FALSE       44
             34 POP_TOP
             36 POP_TOP
             38 POP_TOP

  7          40 LOAD_CONST               2 (130)
             42 RETURN_VALUE
        >>   44 END_FINALLY
             46 LOAD_CONST               0 (None)
             48 RETURN_VALUE

Disassembly of <code object test_fn at 0x000001FD5FAF5780, file "test_example.py", line 10>:
 11           0 LOAD_GLOBAL              0 (fn)
              2 BUILD_LIST               0
              4 CALL_FUNCTION            1
              6 LOAD_CONST               1 (0)
              8 COMPARE_OP               2 (==)
             10 POP_JUMP_IF_TRUE        16
             12 LOAD_GLOBAL              1 (AssertionError)
             14 RAISE_VARARGS            1

 12     >>   16 LOAD_GLOBAL              0 (fn)
             18 LOAD_CONST               2 (Ellipsis)
             20 BUILD_LIST               1
             22 CALL_FUNCTION            1
             24 LOAD_CONST               3 (130)
             26 COMPARE_OP               2 (==)
             28 POP_JUMP_IF_TRUE        34
             30 LOAD_GLOBAL              1 (AssertionError)
             32 RAISE_VARARGS            1
        >>   34 LOAD_CONST               0 (None)
             36 RETURN_VALUE
```

@Peilonrayz
Copy link
Author

Peilonrayz commented Aug 30, 2020

@matham Yes this seems to effect Python 3.8.x on Windows only. (I have not tested 3.9+) Your 3.8 disassembly is the exact same as I'm getting on Python 3.8.5 on Linux.

@ammaraskar
Copy link

ammaraskar commented Aug 30, 2020

Reposting here, as Ned alluded to in https://discuss.python.org/t/same-python-version-different-optimizations-on-different-os/5098, this is a CPython bug. In particular, it relates to the opcode prediction in the interpreter which is different on Windows vs compilers that support computed GOTOs.

@nedbat
Copy link
Owner

nedbat commented Aug 30, 2020

I've created https://bugs.python.org/issue41670 for this.

@Peilonrayz
Copy link
Author

Nice. Since there is a duplicate and this doesn't seem to be a coverage.py issue, I'm going to close this.

@graingert
Copy link
Contributor

graingert commented Aug 30, 2020

@Peilonrayz the duplicate was already closed, this is the last remaining ticket in this project relating to this issue

@nedbat
Copy link
Owner

nedbat commented Aug 31, 2020

@ammaraskar had an interesting idea about how coverage.py could perhaps adapt to this, so I'm re-opening.

@ammaraskar
Copy link

There is now a fix for this upstream in CPython, could someone try this on the latest alpha and see if it re-creates: https://www.python.org/downloads/release/python-3100a1/

@Peilonrayz
Copy link
Author

I have tried this with 3.10.0a1 and did not reproduce my issue.

@nedbat
Copy link
Owner

nedbat commented Oct 11, 2020

I'll close this since Python 3.10 fixes it.

@nedbat nedbat closed this as completed Oct 11, 2020
@nedbat nedbat added the not our bug The problem was elsewhere label Oct 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working not our bug The problem was elsewhere
Projects
None yet
Development

No branches or pull requests

5 participants