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

More coverage #1067

Merged
merged 16 commits into from
Nov 12, 2022
3 changes: 2 additions & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ jobs:
pip check
- name: Run the tests
run: |
pytest -vv --integration_tests=true tests
hatch run cov:integration


integration_check: # This job does nothing and is only used for the branch protection
if: always()
Expand Down
21 changes: 4 additions & 17 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install texlive-plain-generic inkscape texlive-xetex
sudo apt-get install xvfb x11-utils libxkbcommon-x11-0
pip install pandoc
sudo apt-get install xvfb x11-utils libxkbcommon-x11-0 pandoc
- name: Run the tests
if: ${{ !startsWith(matrix.python-version, 'pypy') && !startsWith(matrix.os, 'windows') }}
run: hatch run cov:test -W default || hatch run cov:test -W default --lf
run: hatch run cov:test -W default || hatch run test:test -W default --lf
- name: Run the tests on pypy and windows
if: ${{ startsWith(matrix.python-version, 'pypy') || startsWith(matrix.os, 'windows') }}
run: hatch run test:test -W default || hatch run test:test -W default --lf
Expand Down Expand Up @@ -71,20 +70,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: pre-commit/[email protected]
with:
extra_args: --all-files --hook-stage=manual
- name: Help message if pre-commit fail
if: ${{ failure() }}
run: |
echo "You can install pre-commit hooks to automatically run formatting"
echo "on each commit with:"
echo " pre-commit install"
echo "or you can run by hand on staged files with"
echo " pre-commit run"
echo "or after-the-fact on already committed files with"
echo " pre-commit run --all-files --hook-stage=manual"
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
- uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1

test_docs:
name: Test Docs
Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ coverage:
project:
default:
target: auto
threshold: 10
threshold: 1
patch:
default:
target: 0%
7 changes: 5 additions & 2 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,6 @@ def shutdown_server(server_info, timeout=5, log=None):

url = server_info["url"]
pid = server_info["pid"]

try:
shutdown_url = urljoin(url, "api/shutdown")
if log:
Expand Down Expand Up @@ -2891,7 +2890,11 @@ def list_running_servers(runtime_dir=None, log=None):
for file_name in os.listdir(runtime_dir):
if re.match("jpserver-(.+).json", file_name):
with open(os.path.join(runtime_dir, file_name), encoding="utf-8") as f:
info = json.load(f)
# Handle race condition where file is being written.
try:
info = json.load(f)
except json.JSONDecodeError:
continue

# Simple check whether that process is really still running
# Also remove leftover files from IPython 2.x without a pid field
Expand Down
17 changes: 16 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ dependencies = ["coverage", "pytest-cov"]
[tool.hatch.envs.cov.env-vars]
ARGS = "-vv --cov jupyter_server --cov-branch --cov-report term-missing:skip-covered"
[tool.hatch.envs.cov.scripts]
test = "python -m pytest $ARGS --cov-fail-under 70 {args}"
test = "python -m pytest $ARGS --cov-fail-under 75 {args}"
integration = "python -m pytest $ARGS --integration_tests=true {args}"

[tool.hatch.version]
path = "jupyter_server/_version.py"
Expand Down Expand Up @@ -145,6 +146,20 @@ filterwarnings = [
"module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"class .*\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]

[tool.jupyter-releaser.hooks]
before-build-python = ["npm install", "npm run build"]

Expand Down
65 changes: 64 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import os
import socket
import subprocess
import sys
import warnings
from pathlib import Path
from unittest.mock import patch

import pytest
from traitlets.tests.utils import check_help_all_output

from jupyter_server.utils import is_namespace_package, url_escape, url_unescape
from jupyter_server.utils import (
check_pid,
check_version,
is_namespace_package,
path2url,
run_sync_in_loop,
samefile_simple,
to_api_path,
unix_socket_in_use,
url2path,
url_escape,
url_unescape,
)


def test_help_output():
Expand Down Expand Up @@ -59,3 +76,49 @@ def test_is_namespace_package_no_spec():

assert is_namespace_package("dummy") is None
mocked_spec.assert_called_once_with("dummy")


@pytest.mark.skipif(os.name == "nt", reason="Paths are annoying on Windows")
def test_path_utils(tmp_path):
path = str(tmp_path)
assert os.path.basename(path2url(path)) == os.path.basename(path)

url = path2url(path)
assert path.endswith(url2path(url))

assert samefile_simple(path, path)

assert to_api_path(path, os.path.dirname(path)) == os.path.basename(path)


def test_check_version():
assert check_version("1.0.2", "1.0.1")
assert not check_version("1.0.0", "1.0.1")
assert check_version(1.0, "1.0.1")


def test_check_pid():
proc = subprocess.Popen([sys.executable])
proc.kill()
proc.wait()
check_pid(proc.pid)


async def test_run_sync_in_loop():
async def foo():
pass

with warnings.catch_warnings():
warnings.simplefilter("ignore")
await run_sync_in_loop(foo())


@pytest.mark.skipif(os.name != "posix", reason="Requires unix sockets")
def test_unix_socket_in_use(tmp_path):
root_tmp_dir = Path("/tmp").resolve()
server_address = os.path.join(root_tmp_dir, os.path.basename(tmp_path))
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(server_address)
sock.listen(0)
assert unix_socket_in_use(server_address)
sock.close()
37 changes: 31 additions & 6 deletions tests/unix_sockets/test_serverapp_integration.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import os
import stat
import subprocess
import sys
import time

import pytest

from jupyter_server.serverapp import list_running_servers, shutdown_server
from jupyter_server.utils import urlencode_unix_socket, urlencode_unix_socket_path

# Skip this module if on Windows. Unix sockets are not available on Windows.
pytestmark = pytest.mark.skipif(
sys.platform.startswith("win"), reason="Unix sockets are not available on Windows."
)

import os
import subprocess
import time

from jupyter_server.utils import urlencode_unix_socket, urlencode_unix_socket_path


def _cleanup_process(proc):
proc.wait()
Expand Down Expand Up @@ -171,3 +171,28 @@ def test_launch_socket_collision(jp_unix_socket_file):
_ensure_stopped()

_cleanup_process(p1)


@pytest.mark.integration_test
def test_shutdown_server(jp_environ):
# Start a server in another process
# Stop that server
import subprocess

from jupyter_client.connect import LocalPortCache

port = LocalPortCache().find_available_port("localhost")
p = subprocess.Popen(["jupyter-server", f"--port={port}"])
servers = []
while 1:
servers = list(list_running_servers())
if len(servers):
break
time.sleep(0.1)
while 1:
try:
shutdown_server(servers[0])
break
except ConnectionRefusedError:
time.sleep(0.1)
p.wait()