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

Update CI/tests to allow running proper, realistic unit tests #136

Merged
merged 16 commits into from
Feb 17, 2022
13 changes: 13 additions & 0 deletions .github/scripts/ssh_setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

ssh-keygen -t ed25519 -f ~/.ssh/my_key -N ''
cat > ~/.ssh/config <<EOF
Host eniac.local
User $USER
HostName 127.0.0.1
IdentityFile ~/.ssh/my_key
EOF
echo -n 'from="127.0.0.1" ' | cat - ~/.ssh/my_key.pub > ~/.ssh/authorized_keys
chmod og-rw ~
ls -la ~/.ssh
ssh -o 'StrictHostKeyChecking no' eniac.local id
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: CI
on:
push:
branches:
- main
pull_request:
schedule:
- cron: '0 0 * * *' # Daily “At 00:00”
Expand All @@ -24,6 +26,11 @@ jobs:
python-version: ['3.7', '3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure SSH key for localhost
run: |
.github/scripts/ssh_setup.sh
- uses: conda-incubator/setup-miniconda@v2
with:
channels: conda-forge
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/upstream-dev-ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Upstream CI
on:
push:
branches:
- main
schedule:
- cron: '0 0 * * *' # Daily “At 00:00” UTC
workflow_dispatch: # allows you to trigger the workflow run manually
Expand All @@ -22,6 +24,11 @@ jobs:
python-version: ['3.10']
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure SSH key for localhost
run: |
.github/scripts/ssh_setup.sh
- uses: conda-incubator/setup-miniconda@v2
id: conda
with:
Expand Down Expand Up @@ -49,6 +56,7 @@ jobs:
- name: Report Status
if: |
always()
&& github.ref == 'refs/heads/main'
&& (steps.conda.outcome != 'success' || steps.install.outcome != 'success' || steps.install.outcome != 'success')
uses: actions/github-script@v6
with:
Expand Down
67 changes: 37 additions & 30 deletions jupyter_forward/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from fabric import Connection
from rich.console import Console

timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')

console = Console()


Expand Down Expand Up @@ -178,29 +180,8 @@ def _launch_jupyter(self):

else:
self.run_command(check_jupyter_status)
console.rule(
f'[bold green] Checking $TMPDIR and $HOME on {self.session.host}', characters='*'
)
tmp_dir_env_status = self.run_command(command='printenv TMPDIR', exit=False)
home_dir_env_status = self.run_command(command='printenv HOME', exit=False)
check_dir_command = 'touch ${}/foobar && rm -rf ${}/foobar && echo "${} is WRITABLE" || echo "${} is NOT WRITABLE"'
if not tmp_dir_env_status.failed:
self._check_log_file_dir(check_dir_command, 'TMPDIR', '$TMPDIR')
elif not home_dir_env_status.failed:
self._check_log_file_dir(check_dir_command, 'HOME', '$HOME')
else:
tmp_dir_error_message = '$TMPDIR is not defined'
home_dir_error_message = '$HOME is not defined'
console.print(
f'[bold red]Can not determine directory for log file:\n{home_dir_error_message}\n{tmp_dir_error_message}'
)
sys.exit(1)
self.log_dir = f'{self.log_dir}/.jupyter_forward'
self.run_command(command=f'mkdir -p {self.log_dir}')
timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
self.log_file = f'{self.log_dir}/log.{timestamp}'
self.run_command(command=f'touch {self.log_file}')

self._set_log_directory()
self._set_log_file()
command = r'jupyter lab --no-browser --ip=\$(hostname -f)'
if self.notebook_dir:
command = f'{command} --notebook-dir={self.notebook_dir}'
Expand Down Expand Up @@ -243,13 +224,39 @@ def _launch_jupyter(self):
open_browser(url=self.parsed_result['url'], path=self.notebook)
self.run_command(command=f'tail -f {self.log_file}')

def _check_log_file_dir(self, check_dir_command, arg1, arg2):
_tmp_dir_status = self.run_command(
command=check_dir_command.format(arg1, arg1, arg1, arg1), exit=False
)

if 'is WRITABLE' in _tmp_dir_status.stdout.strip():
self.log_dir = arg2
def _set_log_file(self):
log_file = f'{self.log_dir}/log_{timestamp}.txt'
self.run_command(command=f'touch {log_file}')
self.log_file = log_file
console.print(f'[bold cyan]:white_check_mark: Log file is set to {log_file}')
return self

def _set_log_directory(self):
def _check_log_file_dir(directory):
check_dir_command = f'touch {directory}/foobar && rm -rf {directory}/foobar && echo "{directory} is WRITABLE" || echo "{directory} is NOT WRITABLE"'
_tmp_dir_status = self.run_command(command=check_dir_command, exit=False)
return directory if 'is WRITABLE' in _tmp_dir_status.stdout.strip() else None

console.rule(f'[bold green] Creating log file on {self.session.host}', characters='*')
log_dir = None
tmp_dir_env_status = self.run_command(command='printenv TMPDIR', exit=False)
home_dir_env_status = self.run_command(command='printenv HOME', exit=False)
if not tmp_dir_env_status.failed:
log_dir = _check_log_file_dir('$TMPDIR')
elif not home_dir_env_status.failed:
log_dir = _check_log_file_dir('$HOME')
else:
tmp_dir_error_message = '$TMPDIR is not defined'
home_dir_error_message = '$HOME is not defined'
console.print(
f'[bold red]Can not determine directory for log file:\n{home_dir_error_message}\n{tmp_dir_error_message}'
)
sys.exit(1)
log_dir = f'{log_dir}/.jupyter_forward'
self.run_command(command=f'mkdir -p {log_dir}')
console.print(f'[bold cyan]:white_check_mark: Log directory is set to {log_dir}')
self.log_dir = log_dir
return self


def open_browser(port: int = None, token: str = None, url: str = None, path=None):
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ skip=

[tool:pytest]
console_output_style = count
addopts = --cov=./ --cov-report=xml --verbose
addopts = --cov=./ --cov-report=xml --verbose -s
49 changes: 49 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import datetime
import os
import socket
from unittest import mock as mock

import pytest

import jupyter_forward
from jupyter_forward.core import is_port_available, open_browser, parse_stdout

NOT_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') is None
requires_gha = pytest.mark.skipif(NOT_GITHUB_ACTIONS, reason='requires GITHUB_ACTIONS')


@pytest.fixture(scope='session', params=[f"{os.environ['USER']}@eniac.local"])
def runner(request):
remote = jupyter_forward.RemoteRunner(request.param)
yield remote
remote.close()


@pytest.mark.parametrize(
'stdout, expected',
Expand Down Expand Up @@ -68,3 +81,39 @@ def test_open_browser(port, token, url, expected):
with mock.patch('webbrowser.open') as mockwebopen:
open_browser(port, token, url)
mockwebopen.assert_called_once_with(expected, new=2)


@requires_gha
def test_connection(runner):
USER = os.environ['USER']
assert runner.session.is_connected
assert runner.session.host == '127.0.0.1'
assert runner.session.user == USER


@requires_gha
@pytest.mark.parametrize('command', ['echo $HOME'])
def test_run_command(runner, command):
out = runner.run_command(command)
assert not out.failed
assert out.stdout.strip() == f"{os.environ['HOME']}"


@requires_gha
@pytest.mark.parametrize('command', ['echod $HOME'])
def test_run_command_failure(runner, command):
out = runner.run_command(command, exit=False)
assert out.failed
assert 'echod: command not found' in out.stdout.strip()

with pytest.raises(SystemExit):
runner.run_command(command)


@requires_gha
def test_set_logs(runner):
runner._set_log_directory()
assert runner.log_dir == '$HOME/.jupyter_forward'
runner._set_log_file()
now = datetime.datetime.now()
assert f"log_{now.strftime('%Y-%m-%dT%H')}" in runner.log_file