Skip to content

Commit 51d26f0

Browse files
committed
add tests
1 parent b508b3c commit 51d26f0

34 files changed

+4002
-41
lines changed
File renamed without changes.
File renamed without changes.

.github/workflows/test_review.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Test Cylc Review
2+
3+
on:
4+
pull_request:
5+
workflow_dispatch:
6+
push:
7+
branches:
8+
- master
9+
- '*.*.x'
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
env:
16+
FORCE_COLOR: 2
17+
18+
defaults:
19+
run:
20+
shell: bash -c "exec $CONDA_PREFIX/bin/bash -elo pipefail {0}"
21+
22+
jobs:
23+
test:
24+
runs-on: ${{ matrix.os }}
25+
timeout-minutes: 15
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
os: ['ubuntu-latest']
30+
python-version: ['3.12']
31+
env:
32+
PYTEST_ADDOPTS: --cov --color=yes
33+
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v5
37+
38+
- name: Patch DNS
39+
uses: cylc/release-actions/patch-dns@v1
40+
41+
- name: Install System Dependencies
42+
uses: mamba-org/setup-micromamba@v2
43+
with:
44+
cache-environment: true
45+
post-cleanup: 'all'
46+
environment-name: cylc-uiserver
47+
create-args: >-
48+
python=${{ matrix.python-version }}
49+
pip
50+
bash
51+
coreutils
52+
53+
- name: install cylc-flow
54+
uses: cylc/release-actions/install-cylc-components@v1
55+
with:
56+
cylc_flow: true
57+
cylc_flow_opts: ''
58+
59+
- name: install cylc-uiserver
60+
run: pip install -e .[all]
61+
62+
- name: Functional Tests
63+
run: |
64+
pip uninstall cylc-flow -y
65+
pip install git+https://github.com/wxtim/[email protected]
66+
67+
CONF_PATH="$HOME/.cylc/flow/8"
68+
mkdir -p "$CONF_PATH"
69+
touch "$CONF_PATH/global.cylc"
70+
ln -s "$CONF_PATH/global.cylc" "$CONF_PATH/global-tests.cylc"
71+
72+
cat >> "$CONF_PATH/global.cylc" <<__HERE__
73+
[platforms]
74+
[[_local_background_indep_tcp]]
75+
hosts = localhost
76+
install target = localhost
77+
ssh command = ssh -oBatchMode=yes -oConnectTimeout=8 -oStrictHostKeyChecking=no
78+
__HERE__
79+
80+
# Check that Cylc Review is ready to run
81+
cylc review --help || exit 1
82+
83+
./etc/bin/run-functional-tests

cylc/uiserver/jupyter_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
)
2525
from cylc.uiserver.app import USER_CONF_ROOT
2626
from cylc.uiserver.authorise import CylcAuthorizer
27-
from cylc.uiserver.ws import get_review_service
27+
from cylc.uiserver.ws import get_review_service_config
2828

2929

3030
# the command the hub should spawn (i.e. the cylc uiserver itself)
@@ -108,4 +108,4 @@
108108

109109

110110
# Setup Cylc Review
111-
c.JupyterHub.services = [get_review_service()]
111+
c.JupyterHub.services = [get_review_service_config()]

cylc/uiserver/review.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
For 'cylc review start', if 'PORT' is not specified, port 8080 is used."""
2424

2525
import cherrypy
26+
import contextlib
2627
from fnmatch import fnmatch
2728
from glob import glob
2829
import jinja2
@@ -40,15 +41,12 @@
4041
import traceback
4142
from urllib.parse import quote
4243

43-
from cylc.flow.hostuserutil import get_host
44-
from cylc.uiserver.review_dao import CylcReviewDAO
45-
from cylc.flow.task_state import (
46-
TASK_STATUSES_ORDERED,
47-
)
4844
from cylc.flow import __version__ as CYLC_VERSION
49-
from cylc.uiserver.ws import get_util_home
50-
from cylc.uiserver.review_dao import TASK_STATUS_GROUPS
45+
from cylc.flow.hostuserutil import get_host
46+
from cylc.flow.task_state import TASK_STATUSES_ORDERED
5147
from cylc.flow.workflow_files import WorkflowFiles
48+
from cylc.uiserver.review_dao import TASK_STATUS_GROUPS, CylcReviewDAO
49+
from cylc.uiserver.ws import get_util_home
5250

5351

5452
# Cylc 7 Task states.
@@ -69,7 +67,7 @@
6967
]
7068

7169

72-
class CylcReviewService(object):
70+
class CylcReviewService:
7371
"""'Cylc Review Service."""
7472

7573
NS = "cylc"
@@ -509,14 +507,12 @@ def suites(
509507
item = os.path.relpath(dirpath, user_suite_dir_root)
510508
if not any(fnmatch(item, glob_) for glob_ in name_globs):
511509
continue
512-
try:
510+
with contextlib.suppress(OSError):
513511
data["entries"].append({
514512
"name": str(item),
515513
"info": {},
516514
"last_activity_time": (
517515
self.get_last_activity_time(user, item))})
518-
except OSError:
519-
pass
520516

521517
if order == "name_asc":
522518
data["entries"].sort(key=lambda entry: entry["name"])
@@ -542,14 +538,14 @@ def suites(
542538
rosie_suite_info = os.path.join(user_suite_dir, "rose-suite.info")
543539
if os.path.isfile(rosie_suite_info):
544540
rosie_info = {}
545-
try:
546-
for line in open(rosie_suite_info, 'r').readlines():
541+
with contextlib.suppress(IOError):
542+
for line in open( # noqa: SIM115
543+
rosie_suite_info, 'r'
544+
).readlines():
547545
if not line.strip().startswith('#') and '=' in line:
548546
rosie_key, rosie_val = line.strip().split("=", 1)
549547
if rosie_key in ("project", "title"):
550548
rosie_info[rosie_key] = rosie_val
551-
except IOError:
552-
pass
553549
entry["info"].update(rosie_info)
554550

555551
data["time"] = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
@@ -600,7 +596,7 @@ def get_file(self, user, suite, path, path_in_tar=None, mode=None):
600596
text = handle.read()
601597
else:
602598
f_size = os.stat(f_name).st_size
603-
if open(f_name).read(2) == "#!":
599+
if open(f_name).read(2) == "#!": # noqa: SIM115
604600
mime = self.MIME_TEXT_PLAIN
605601
else:
606602
mime = mimetypes.guess_type(quote(f_name))[0]
@@ -614,7 +610,7 @@ def get_file(self, user, suite, path, path_in_tar=None, mode=None):
614610
):
615611
cherrypy.response.headers["Content-Type"] = mime
616612
return cherrypy.lib.static.serve_file(f_name, mime)
617-
text = open(f_name).read()
613+
text = open(f_name).read() # noqa: SIM115
618614
try:
619615
text = str(text)
620616
if mode in [None, "text"]:

cylc/uiserver/review_dao.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
"""Provide data access object for the suite runtime database."""
1717

18-
import sqlite3
18+
import contextlib
19+
from glob import glob
1920
import os
2021
from pathlib import Path
21-
import tarfile
2222
import re
23-
from glob import glob
23+
import sqlite3
2424
from sqlite3 import OperationalError
25+
import tarfile
2526

2627
from cylc.flow.rundb import CylcWorkflowDAO
27-
from cylc.flow.workflow_files import WorkflowFiles
2828
from cylc.flow.task_state import (
2929
TASK_STATUSES_ACTIVE,
3030
TASK_STATUSES_FAILURE,
3131
TASK_STATUSES_NEVER_ACTIVE,
3232
TASK_STATUSES_SUCCESS,
3333
)
34+
from cylc.flow.workflow_files import WorkflowFiles
3435

3536
TASK_STATUS_GROUPS = {
3637
"active": list(TASK_STATUSES_ACTIVE | TASK_STATUSES_NEVER_ACTIVE),
@@ -39,7 +40,7 @@
3940
}
4041

4142

42-
class CylcReviewDAO(object):
43+
class CylcReviewDAO:
4344
"""Cylc Review data access object to the suite runtime database."""
4445

4546
CYCLE_ORDERS = {"time_desc": " DESC", "time_asc": " ASC"}
@@ -455,9 +456,11 @@ def _get_job_logs(self, user_name, suite_name, entries, entry_of):
455456
"exists": True,
456457
"seq_key": None}
457458
continue
458-
if entry["cycle"] in targzip_log_cycles:
459-
if entry["cycle"] not in relevant_targzip_log_cycles:
460-
relevant_targzip_log_cycles.append(entry["cycle"])
459+
if (
460+
entry["cycle"] in targzip_log_cycles
461+
and entry["cycle"] not in relevant_targzip_log_cycles
462+
):
463+
relevant_targzip_log_cycles.append(entry["cycle"])
461464

462465
for cycle in relevant_targzip_log_cycles:
463466
path = os.path.join("log", "job-%s.tar.gz" % cycle)
@@ -561,12 +564,10 @@ def get_suite_cycles_summary(
561564
user_suite_dir = os.path.expanduser(os.path.join(
562565
prefix, os.path.join("cylc-run", suite_name)))
563566
targzip_log_cycles = []
564-
try:
567+
with contextlib.suppress(OSError):
565568
for item in os.listdir(os.path.join(user_suite_dir, "log")):
566569
if item.startswith("job-") and item.endswith(".tar.gz"):
567570
targzip_log_cycles.append(item[4:-7])
568-
except OSError:
569-
pass
570571

571572
if self.is_cylc8:
572573
# Cylc 8 has a smaller set of task states.
@@ -695,7 +696,7 @@ def get_suite_state_summary(self, user_name, suite_name):
695696
try:
696697
host = None
697698
port_str = None
698-
for line in open(port_file_path):
699+
for line in open(port_file_path): # noqa: SIM115
699700
key, value = [item.strip() for item in line.split("=", 1)]
700701
if key in ["CYLC_SUITE_HOST", "CYLC_WORKFLOW_HOST"]:
701702
host = value
@@ -710,17 +711,14 @@ def get_suite_state_summary(self, user_name, suite_name):
710711

711712
stmt = "SELECT status FROM task_states WHERE status GLOB ? LIMIT 1"
712713
stmt_args = ["*failed"]
713-
try:
714+
with contextlib.suppress(sqlite3.Error):
714715
for _ in self._db_exec(user_name, suite_name, stmt, stmt_args):
715716
ret["is_failed"] = True
716717
break
717-
except sqlite3.Error:
718-
pass # case with no task_states table.
719718
self._db_close(user_name, suite_name)
720719

721720
return ret
722721

723-
724722
def pre_select_broadcast_events(self, order=None):
725723
"""Query statement and args formation for select_broadcast_events."""
726724
form_stmt = r"SELECT time,change,point,namespace,key,value FROM %s"

cylc/uiserver/ws.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
ws_cli - Parse CLI. Start/Stop ad-hoc server.
2121
"""
2222

23+
from annotated_types import Ge
2324
import cherrypy
2425
from glob import glob
2526
import os
2627
from pathlib import Path
2728
from random import shuffle
2829
import socket
2930
from typing import Annotated
30-
from annotated_types import Ge
3131

3232
LOG_ROOT_TMPL = "~/.cylc/%(ns)s-%(util)s-%(host)s-%(port)s"
3333

@@ -77,9 +77,9 @@ def _ws_init(service_cls, port, service_root, *args, **kwargs):
7777
handle.write("pid=%d\n" % os.getpid())
7878

7979
cherrypy.config["log.access_file"] = log_root + "-access.log"
80-
open(cherrypy.config["log.access_file"], "w").close()
80+
open(cherrypy.config["log.access_file"], "w").close() # noqa: SIM115
8181
cherrypy.config["log.error_file"] = log_root + "-error.log"
82-
open(cherrypy.config["log.error_file"], "w").close()
82+
open(cherrypy.config["log.error_file"], "w").close() # noqa: SIM115
8383

8484
root = '/'
8585
if service_root != '/':
@@ -144,7 +144,7 @@ def _get_server_status(service_cls):
144144
)
145145
for filename in glob(log_root_glob):
146146
try:
147-
for line in open(filename):
147+
for line in open(filename): # noqa: SIM115
148148
key, value = line.strip().split("=", 1)
149149
ret[key] = value
150150
break
@@ -162,7 +162,7 @@ def get_util_home(*args):
162162
return str(Path(__file__).parent / '/'.join(args))
163163

164164

165-
def get_review_service(
165+
def get_review_service_config(
166166
ports: tuple[Annotated[int, Ge(0)], Annotated[int, Ge(0)]] = (8000, 8999),
167167
service_root: str = 'services/cylc'
168168
) -> dict:
@@ -194,4 +194,4 @@ def get_review_service(
194194
f"--service-root={service_root}",
195195
],
196196
"url": f"http://127.0.0.1:{port}/",
197-
}
197+
}

0 commit comments

Comments
 (0)