Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/250_quickstart.cmd
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cijoe --produce-report || true
cijoe --report-only || true
2 changes: 1 addition & 1 deletion docs/source/250_quickstart.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cijoe --produce-report
cijoe --report-only
7 changes: 4 additions & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@ Quickstart
==========

**Try it yourself!** The commands below install **cijoe**, prints a list of
examples, emits and runs one of them, produces an HTML report, and opens
it using your preferred browser if one is available.
examples, emits and runs one of them, and produces an HTML report; add
``--report-open`` to also open it in your preferred browser.

.. literalinclude:: 200_quickstart.cmd
:language: bash

**cijoe** operates silently by default, as in, it does not print output unless
errors occur. Options for customizing the behavior include:

- ``-n`` / ``--no-report``: Disable the generation and display of the HTML report.
- ``--report-open``: Open the HTML report in a browser once it is written.
- ``--report-skip``: Do not write the HTML report.
- ``-l`` / ``--log-level``: Increase verbosity by setting a higher log level.
- ``-m`` / ``--monitor``: Enable real-time monitoring of the task.

Expand Down
100 changes: 79 additions & 21 deletions src/cijoe/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def cli_produce_report(args):
if reporter.func is None:
reporter.load()

setattr(args, "report_open", args.skip_report)
setattr(args, "report_open", getattr(args, "open_report", False))

return reporter.func(args, cijoe)

Expand Down Expand Up @@ -516,6 +516,32 @@ def create_adhoc_task(args):
sys.exit(main(args))


def _resolve_report_args(args) -> int:
"""Map the deprecated report options onto --report-skip and --report-only"""

for old, new, attr in [
("-n / --no-report", "--report-skip", "report_skip"),
("-p / --produce-report", "--report-only", "report_only"),
]:
if getattr(args, f"{attr}_deprecated", False):
log.warning(f"{old} is deprecated; use {new} instead")
setattr(args, attr, True)
if getattr(args, "report_noopen_deprecated", False):
log.warning(
"-s / --skip-report is deprecated; the report is only opened with"
" --report-open"
)
for attr in ["report_skip", "report_only", "report_noopen"]:
if hasattr(args, f"{attr}_deprecated"):
delattr(args, f"{attr}_deprecated")

if getattr(args, "report_skip", False) and getattr(args, "report_only", False):
log.error("--report-skip and --report-only cannot be combined")
return errno.EINVAL

return 0


def parse_args():
"""Parse command-line interface."""

Expand Down Expand Up @@ -575,24 +601,57 @@ def parse_args():
help="Dump command output to stdout",
)
run_group.add_argument(
"--tag",
"-t",
type=str,
action="append",
help="Tags to identify a task-run."
" This will be prefixed while storing in archive",
)

report_group = parent_parser.add_argument_group(
"report", "The report written at the end of a task-run"
)
# Not 'report_open': that is core.reporter's own argument, which a task step
# may set and which is deleted from the namespace again after the step
report_group.add_argument(
"--report-open",
dest="open_report",
action="store_true",
help="Open the report in a browser once it is written",
)
report_group.add_argument(
"--report-skip",
action="store_true",
help="Do not write a report at the end of the task-run",
)
report_group.add_argument(
"--report-only",
action="store_true",
help="Write the report for the output at '-o / --output' and exit, without"
" running a task; with --report-open, also open it",
)
# Deprecated spellings of the above, mapped by _resolve_report_args()
report_group.add_argument(
"--no-report",
"-n",
dest="report_skip_deprecated",
action="store_true",
help="Skip the producing, and opening, a report at the end of the task-run",
help=argparse.SUPPRESS,
)
run_group.add_argument(
report_group.add_argument(
"--skip-report",
"-s",
action="store_false",
help="Skip the report opening at the end of the task-run",
dest="report_noopen_deprecated",
action="store_true",
help=argparse.SUPPRESS,
)
run_group.add_argument(
"--tag",
"-t",
type=str,
action="append",
help="Tags to identify a task-run."
" This will be prefixed while storing in archive",
report_group.add_argument(
"--produce-report",
"-p",
dest="report_only_deprecated",
action="store_true",
help=argparse.SUPPRESS,
)

utils_group = parent_parser.add_argument_group(
Expand All @@ -604,13 +663,6 @@ def parse_args():
action="store_true",
help="Move the output at '-o / --output' to 'cijoe-archive/YYYY-MM-DD_HH:MM:SS",
)
utils_group.add_argument(
"--produce-report",
"-p",
action="append_const",
const=1,
help="Produce report, and open it, for output at '-o / --output' and exit.",
)
utils_group.add_argument(
"--integrity-check",
"-i",
Expand Down Expand Up @@ -714,6 +766,12 @@ def parse_args():
setattr(args, "script_name", ident)
args.step = []

# Before log.basicConfig(), which sets the default level to ERROR: the
# deprecation warnings must be seen without raising the log level
err = _resolve_report_args(args)
if err:
return err, args

levels = [log.ERROR, log.INFO, log.DEBUG]
log.basicConfig(
format="%(levelname)s:%(module)s:%(funcName)s(): %(message)s",
Expand Down Expand Up @@ -750,7 +808,7 @@ def main(args=None):
if args.version:
return cli_version(args)

if args.produce_report:
if args.report_only:
return cli_produce_report(args)

if args.archive:
Expand Down Expand Up @@ -798,7 +856,7 @@ def main(args=None):

err = cli_task(args)

if not args.no_report:
if not args.report_skip:
report_err = cli_produce_report(args)
err = err if err else report_err

Expand Down
2 changes: 1 addition & 1 deletion src/cijoe/core/auxiliary/cijoe-completions
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ _cijoe_completion() {
prev="${COMP_WORDS[COMP_CWORD-1]}"

# Define the possible options for cijoe
options="--help --config -c --workflow -w --output -o --log-level -l --no-report -n --skip-report -s --tag -t --archive -a --produce-report -p --monitor -m --integrity-check -i --resources -r --example -e --version -v"
options="--help --config -c --workflow -w --output -o --log-level -l --report-open --report-skip --report-only --no-report -n --skip-report -s --tag -t --archive -a --produce-report -p --monitor -m --integrity-check -i --resources -r --example -e --version -v"

# Provide options completion if the current word starts with --
if [[ "$cur" == --* ]]; then
Expand Down
10 changes: 4 additions & 6 deletions tests/core/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ def test_run_group():
output,
f"-{loglevel}",
"--monitor",
"--no-report",
"--skip-report",
"--report-open",
"--report-skip",
"--tag",
tag,
"--archive",
"--produce-report",
"--integrity-check",
"--resources",
"--example",
Expand All @@ -46,11 +45,10 @@ def test_run_group():
assert args.output.name == output
assert len(args.log_level) == len(loglevel)
assert args.monitor
assert args.no_report
assert not args.skip_report
assert args.open_report
assert args.report_skip
assert args.tag == [tag]
assert args.archive
assert args.produce_report
assert args.integrity_check
assert args.resources
assert args.example == example
Expand Down
2 changes: 1 addition & 1 deletion tests/core/test_loglevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def run_loglevel_script(tmp_path, loglevel) -> subprocess.CompletedProcess[str]:
str(script_file),
"--config",
str(config_path),
"--no-report",
"--report-skip",
]

if loglevel:
Expand Down
60 changes: 60 additions & 0 deletions tests/core/test_report_args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""The --report-* options, and the deprecated spellings they replace"""

import errno
import logging
import sys

import pytest

from cijoe.cli.cli import parse_args


def parse(monkeypatch, *argv):
monkeypatch.setattr(sys, "argv", ["cijoe", "task.yaml", *argv])
return parse_args()


def test_defaults(monkeypatch):
err, args = parse(monkeypatch)
assert not err
assert not args.open_report
assert not args.report_skip
assert not args.report_only


def test_report_only_with_open(monkeypatch):
err, args = parse(monkeypatch, "--report-only", "--report-open")
assert not err
assert args.report_only and args.open_report


def test_skip_and_only_conflict(monkeypatch):
err, _ = parse(monkeypatch, "--report-skip", "--report-only")
assert err == errno.EINVAL


@pytest.mark.parametrize(
"old, attr",
[
("-n", "report_skip"),
("--no-report", "report_skip"),
("-p", "report_only"),
("--produce-report", "report_only"),
],
)
def test_deprecated_maps_onto_new(monkeypatch, caplog, old, attr):
with caplog.at_level(logging.WARNING):
err, args = parse(monkeypatch, old)
assert not err
assert getattr(args, attr)
assert "deprecated" in caplog.text
assert not any(name.endswith("_deprecated") for name in vars(args))


@pytest.mark.parametrize("old", ["-s", "--skip-report"])
def test_deprecated_skip_report_is_accepted_and_ignored(monkeypatch, caplog, old):
with caplog.at_level(logging.WARNING):
err, args = parse(monkeypatch, old)
assert not err
assert not args.open_report
assert "deprecated" in caplog.text
61 changes: 61 additions & 0 deletions tests/core/test_report_open.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""The report is written after a run, but only opened when asked for"""

import sys
import webbrowser

import pytest

from cijoe.cli import cli

TASK = """\
---
doc: A single step, that runs anywhere
steps:
- name: noop
run: "true"
"""


@pytest.mark.parametrize(
"extra, written, opened",
[
([], True, 0),
(["--report-open"], True, 1),
(["--report-skip"], False, 0),
(["-s"], True, 0),
],
)
def test_report_open(tmp_path, monkeypatch, extra, written, opened):
calls = []
monkeypatch.setattr(webbrowser, "open", lambda url, *a, **k: calls.append(url))
monkeypatch.chdir(tmp_path)

config = tmp_path / "cijoe-config.toml"
config.write_text("[cijoe.task]\nfail_fast = true\n")
task = tmp_path / "cijoe-task.yaml"
task.write_text(TASK)

argv = ["cijoe", "-c", str(config), str(task), "-o", str(tmp_path / "out")]
monkeypatch.setattr(sys, "argv", argv + extra)
assert cli.main() == 0

assert (tmp_path / "out" / "report.html").exists() == written
assert len(calls) == opened


@pytest.mark.parametrize("step_with", ["", " with:\n report_open: false\n"])
def test_reporter_as_a_task_step(tmp_path, monkeypatch, step_with):
"""core.reporter keeps working as a task step, with and without its argument"""

monkeypatch.setattr(webbrowser, "open", lambda url, *a, **k: None)
monkeypatch.chdir(tmp_path)

config = tmp_path / "cijoe-config.toml"
config.write_text("[cijoe.task]\nfail_fast = true\n")
task = tmp_path / "cijoe-task.yaml"
task.write_text(TASK + "- name: report\n uses: core.reporter\n" + step_with)

argv = ["cijoe", "-c", str(config), str(task), "-o", str(tmp_path / "out")]
monkeypatch.setattr(sys, "argv", argv)
assert cli.main() == 0
assert (tmp_path / "out" / "report.html").exists()
Loading