Skip to content

Commit abc7547

Browse files
authored
Merge #543 from wookayin/fix-windows
Fix broken tests on Windows due to EOF error and other minor bugs
2 parents 260a0b9 + 056f6f9 commit abc7547

File tree

6 files changed

+86
-49
lines changed

6 files changed

+86
-49
lines changed

.github/workflows/test.yml

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,31 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
python-version: ['3.10', '3.11', '3.12-dev']
30+
python-version: ['3.12', '3.11', '3.10', '3.9', '3.8', '3.7']
3131
os: ['ubuntu-latest', 'macos-latest', 'windows-latest']
32+
exclude:
33+
- os: 'ubuntu-latest'
34+
python-version: '3.7'
3235
include:
33-
- os: ubuntu-20.04
34-
python-version: '3.7'
35-
NIGHTLY: nvim-linux64.tar.gz
36-
NVIM_BIN_PATH: nvim-linux64/bin
37-
EXTRACT: tar xzf
38-
- os: ubuntu-latest
39-
python-version: '3.8'
40-
NIGHTLY: nvim-linux64.tar.gz
41-
NVIM_BIN_PATH: nvim-linux64/bin
42-
EXTRACT: tar xzf
43-
- os: ubuntu-latest
44-
python-version: '3.9'
45-
NIGHTLY: nvim-linux64.tar.gz
46-
NVIM_BIN_PATH: nvim-linux64/bin
47-
EXTRACT: tar xzf
48-
- os: ubuntu-latest
49-
NIGHTLY: nvim-linux64.tar.gz
50-
NVIM_BIN_PATH: nvim-linux64/bin
51-
EXTRACT: tar xzf
52-
- os: macos-latest
53-
NIGHTLY: nvim-macos.tar.gz
54-
NVIM_BIN_PATH: nvim-macos/bin
55-
EXTRACT: tar xzf
56-
- os: windows-latest
57-
NIGHTLY: nvim-win64.zip
58-
NVIM_BIN_PATH: nvim-win64/bin
59-
EXTRACT: unzip
36+
- os: 'ubuntu-20.04'
37+
python-version: '3.7'
38+
NIGHTLY: nvim-linux64.tar.gz
39+
NVIM_BIN_PATH: nvim-linux64/bin
40+
EXTRACT: tar xzf
41+
- os: 'ubuntu-latest'
42+
NIGHTLY: nvim-linux64.tar.gz
43+
NVIM_BIN_PATH: nvim-linux64/bin
44+
EXTRACT: tar xzf
45+
- os: 'macos-latest'
46+
NIGHTLY: nvim-macos.tar.gz
47+
NVIM_BIN_PATH: nvim-macos/bin
48+
EXTRACT: tar xzf
49+
- os: 'windows-latest'
50+
NIGHTLY: nvim-win64.zip
51+
NVIM_BIN_PATH: nvim-win64/bin
52+
EXTRACT: unzip
6053

54+
name: "test (python ${{ matrix.python-version }}, ${{ matrix.os }})"
6155
runs-on: ${{ matrix.os }}
6256
steps:
6357
- uses: actions/checkout@v3
@@ -66,12 +60,6 @@ jobs:
6660
cache: 'pip'
6761
python-version: ${{ matrix.python-version }}
6862

69-
- name: install neovim
70-
run: |
71-
curl -LO 'https://github.com/neovim/neovim/releases/download/nightly/${{ matrix.NIGHTLY }}'
72-
${{ matrix.EXTRACT }} ${{ matrix.NIGHTLY }}
73-
echo '${{ runner.os }}'
74-
7563
- name: update path (bash)
7664
if: runner.os != 'Windows'
7765
run: echo "$(pwd)/${{ matrix.NVIM_BIN_PATH }}" >> $GITHUB_PATH
@@ -80,16 +68,27 @@ jobs:
8068
if: runner.os == 'Windows'
8169
run: echo "$(pwd)/${{ matrix.NVIM_BIN_PATH }}" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
8270

71+
- name: install neovim
72+
run: |
73+
curl -LO 'https://github.com/neovim/neovim/releases/download/nightly/${{ matrix.NIGHTLY }}'
74+
${{ matrix.EXTRACT }} ${{ matrix.NIGHTLY }}
75+
echo '${{ runner.os }}'
76+
nvim --version
77+
8378
- name: install dependencies
8479
run: |
8580
python3 -m pip install -U pip
8681
python3 -m pip install tox tox-gh-actions
8782
83+
- name: check neovim
84+
run: |
85+
python3 -m pip install -e . # install pynvim
86+
nvim --headless --clean -c 'checkhealth | %+print | q'
87+
8888
- name: test with tox
8989
run: |
9090
echo $PATH
9191
which nvim
92-
nvim --version
9392
which -a python3
9493
python3 --version
9594
tox run

pynvim/msgpack_rpc/event_loop/asyncio.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import sys
1515
from collections import deque
1616
from signal import Signals
17-
from typing import Any, Callable, Deque, List
17+
from typing import Any, Callable, Deque, List, Optional
1818

1919
from pynvim.msgpack_rpc.event_loop.base import BaseEventLoop
2020

@@ -37,6 +37,8 @@ class AsyncioEventLoop(BaseEventLoop, asyncio.Protocol,
3737
"""`BaseEventLoop` subclass that uses `asyncio` as a backend."""
3838

3939
_queued_data: Deque[bytes]
40+
if os.name != 'nt':
41+
_child_watcher: Optional['asyncio.AbstractChildWatcher']
4042

4143
def connection_made(self, transport):
4244
"""Used to signal `asyncio.Protocol` of a successful connection."""
@@ -58,12 +60,17 @@ def data_received(self, data: bytes) -> None:
5860

5961
def pipe_connection_lost(self, fd, exc):
6062
"""Used to signal `asyncio.SubprocessProtocol` of a lost connection."""
63+
debug("pipe_connection_lost: fd = %s, exc = %s", fd, exc)
64+
if os.name == 'nt' and fd == 2: # stderr
65+
# On windows, ignore piped stderr being closed immediately (#505)
66+
return
6167
self._on_error(exc.args[0] if exc else 'EOF')
6268

6369
def pipe_data_received(self, fd, data):
6470
"""Used to signal `asyncio.SubprocessProtocol` of incoming data."""
6571
if fd == 2: # stderr fd number
66-
self._on_stderr(data)
72+
# Ignore stderr message, log only for debugging
73+
debug("stderr: %s", str(data))
6774
elif self._on_data:
6875
self._on_data(data)
6976
else:
@@ -78,6 +85,7 @@ def _init(self) -> None:
7885
self._queued_data = deque()
7986
self._fact = lambda: self
8087
self._raw_transport = None
88+
self._child_watcher = None
8189

8290
def _connect_tcp(self, address: str, port: int) -> None:
8391
coroutine = self._loop.create_connection(self._fact, address, port)
@@ -145,6 +153,9 @@ def _close(self) -> None:
145153
if self._raw_transport is not None:
146154
self._raw_transport.close()
147155
self._loop.close()
156+
if self._child_watcher is not None:
157+
self._child_watcher.close()
158+
self._child_watcher = None
148159

149160
def _threadsafe_call(self, fn: Callable[[], Any]) -> None:
150161
self._loop.call_soon_threadsafe(fn)

test/conftest.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
"""Configs for pytest."""
2+
3+
import gc
14
import json
25
import os
6+
from typing import Generator
37

48
import pytest
59

@@ -9,7 +13,10 @@
913

1014

1115
@pytest.fixture
12-
def vim() -> pynvim.Nvim:
16+
def vim() -> Generator[pynvim.Nvim, None, None]:
17+
"""Create an embedded, sub-process Nvim fixture instance."""
18+
editor: pynvim.Nvim
19+
1320
child_argv = os.environ.get('NVIM_CHILD_ARGV')
1421
listen_address = os.environ.get('NVIM_LISTEN_ADDRESS')
1522
if child_argv is None and listen_address is None:
@@ -28,4 +35,13 @@ def vim() -> pynvim.Nvim:
2835
assert listen_address is not None and listen_address != ''
2936
editor = pynvim.attach('socket', path=listen_address)
3037

31-
return editor
38+
try:
39+
yield editor
40+
41+
finally:
42+
# Ensure all internal resources (pipes, transports, etc.) are always
43+
# closed properly. Otherwise, during GC finalizers (__del__) will raise
44+
# "Event loop is closed" error.
45+
editor.close()
46+
47+
gc.collect() # force-run GC, to early-detect potential leakages

test/test_host.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@
1111

1212
def test_host_imports(vim):
1313
h = ScriptHost(vim)
14-
assert h.module.__dict__['vim']
15-
assert h.module.__dict__['vim'] == h.legacy_vim
16-
assert h.module.__dict__['sys']
14+
try:
15+
assert h.module.__dict__['vim']
16+
assert h.module.__dict__['vim'] == h.legacy_vim
17+
assert h.module.__dict__['sys']
18+
finally:
19+
h.teardown()
1720

1821

1922
def test_host_import_rplugin_modules(vim):
2023
# Test whether a Host can load and import rplugins (#461).
2124
# See also $VIMRUNTIME/autoload/provider/pythonx.vim.
2225
h = Host(vim)
26+
2327
plugins: Sequence[str] = [ # plugin paths like real rplugins
2428
os.path.join(__PATH__, "./fixtures/simple_plugin/rplugin/python3/simple_nvim.py"),
2529
os.path.join(__PATH__, "./fixtures/module_plugin/rplugin/python3/mymodule/"),
@@ -56,7 +60,10 @@ def test_host_async_error(vim):
5660

5761
def test_legacy_vim_eval(vim):
5862
h = ScriptHost(vim)
59-
assert h.legacy_vim.eval('1') == '1'
60-
assert h.legacy_vim.eval('v:null') is None
61-
assert h.legacy_vim.eval('v:true') is True
62-
assert h.legacy_vim.eval('v:false') is False
63+
try:
64+
assert h.legacy_vim.eval('1') == '1'
65+
assert h.legacy_vim.eval('v:null') is None
66+
assert h.legacy_vim.eval('v:true') is True
67+
assert h.legacy_vim.eval('v:false') is False
68+
finally:
69+
h.teardown()

test/test_vim.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
import os
32
import tempfile
43
from typing import Any
@@ -31,7 +30,10 @@ def test_command(vim: Nvim) -> None:
3130
assert os.path.isfile(fname)
3231
with open(fname) as f:
3332
assert f.read() == 'testing\npython\napi\n'
34-
os.unlink(fname)
33+
try:
34+
os.unlink(fname)
35+
except OSError:
36+
pass # on windows, this can be flaky; ignore it
3537

3638

3739
def test_command_output(vim: Nvim) -> None:

tox.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ deps =
2929
# setenv =
3030
# cov: PYTEST_ADDOPTS=--cov=. {env:PYTEST_ADDOPTS:}
3131
# passenv = PYTEST_ADDOPTS
32+
33+
# Note: Use python instead of python3 due to tox-dev/tox#2801
3234
commands =
33-
python3 -m pytest --color yes -s -vv {posargs}
35+
python -m pytest --color yes -s --timeout 5 -vv {posargs}
3436

3537
[testenv:checkqa]
3638
deps =

0 commit comments

Comments
 (0)