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

Unbuffer runner test results #418

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
26 changes: 11 additions & 15 deletions nextstrain/cli/command/check_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from .. import config
from ..argparse import SKIP_AUTO_DEFAULT_IN_HELP, runner_module_argument
from ..types import Options
from ..util import colored, check_for_new_version, runner_name, runner_tests_ok, print_runner_tests
from ..util import colored, check_for_new_version, runner_name, runner_tests_ok, stream_and_print_results
from ..runner import all_runners, all_runners_by_name, default_runner # noqa: F401 (it's wrong; we use it in run())


Expand Down Expand Up @@ -84,37 +84,33 @@ def run(opts: Options) -> int:
# Run and collect our runners' self-tests
print("Testing your setup…")

runner_tests = [
runner_tests = (
(runner, runner.test_setup())
for runner in opts.runners
]
)

runner_status = {
runner: runner_tests_ok(tests)
for runner, tests in runner_tests
}
supported_runners = []

# Print test results. The first print() separates results from the
# previous header or stderr output, making it easier to read.
print()

for runner, tests in runner_tests:
if runner_status[runner]:
print(colored("blue", "#"), "Checking %s…" % (runner_name(runner)))

tests = stream_and_print_results(tests)

runner_status = runner_tests_ok(tests)
if runner_status:
supported = success("supported")
supported_runners.append(runner)
else:
supported = failure("not supported")

print(colored("blue", "#"), "%s is %s" % (runner_name(runner), supported))
print_runner_tests(tests)
print()

# Print overall status.
supported_runners = [
runner
for runner, status_ok in runner_status.items()
if status_ok
]

if supported_runners:
print("Supported Nextstrain runtimes:", ", ".join(success(runner_name(r)) for r in supported_runners))

Expand Down
8 changes: 4 additions & 4 deletions nextstrain/cli/command/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

from .. import config, console
from ..argparse import runner_module_argument
from ..util import colored, runner_name, runner_tests_ok, print_runner_tests
from ..types import Options
from ..util import colored, runner_name, runner_tests_ok, stream_and_print_results
from ..types import Options, RunnerTestResults
from ..runner import all_runners_by_name, configured_runner, default_runner # noqa: F401 (it's wrong; we use it in run())


Expand Down Expand Up @@ -70,9 +70,9 @@ def run(opts: Options) -> int:
print(heading(f"Checking setup…"))

if not opts.dry_run:
tests = opts.runner.test_setup()
tests: RunnerTestResults = opts.runner.test_setup()

print_runner_tests(tests)
tests = stream_and_print_results(tests)

if not runner_tests_ok(tests):
print()
Expand Down
14 changes: 6 additions & 8 deletions nextstrain/cli/runner/ambient.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,14 @@ def runnable(*argv) -> bool:
return False


return [
('snakemake is installed and runnable',
shutil.which("snakemake") is not None and runnable("snakemake", "--version")),
yield ('snakemake is installed and runnable',
shutil.which("snakemake") is not None and runnable("snakemake", "--version"))

('augur is installed and runnable',
shutil.which("augur") is not None and runnable("augur", "--version")),
yield ('augur is installed and runnable',
shutil.which("augur") is not None and runnable("augur", "--version"))

('auspice is installed and runnable',
shutil.which("auspice") is not None and runnable("auspice", "--version")),
]
yield ('auspice is installed and runnable',
shutil.which("auspice") is not None and runnable("auspice", "--version"))


def set_default_config() -> None:
Expand Down
14 changes: 6 additions & 8 deletions nextstrain/cli/runner/aws_batch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,16 +560,14 @@ def test_setup() -> RunnerTestResults:
"""
Check that necessary AWS resources exist.
"""
return [
('job description "%s" exists' % DEFAULT_JOB,
jobs.definition_exists(DEFAULT_JOB)),
yield ('job description "%s" exists' % DEFAULT_JOB,
jobs.definition_exists(DEFAULT_JOB))

('job queue "%s" exists' % DEFAULT_QUEUE,
jobs.queue_exists(DEFAULT_QUEUE)),
yield ('job queue "%s" exists' % DEFAULT_QUEUE,
jobs.queue_exists(DEFAULT_QUEUE))

('S3 bucket "%s" exists' % DEFAULT_S3_BUCKET,
s3.bucket_exists(DEFAULT_S3_BUCKET)),
]
yield ('S3 bucket "%s" exists' % DEFAULT_S3_BUCKET,
s3.bucket_exists(DEFAULT_S3_BUCKET))


def set_default_config() -> None:
Expand Down
33 changes: 15 additions & 18 deletions nextstrain/cli/runner/conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,30 +422,27 @@ def runnable(*argv) -> bool:

support = test_support()

yield from support

if not runner_tests_ok(support):
return support
pass

if not PREFIX_BIN.exists():
return [
*support,
("runtime appears set up\n\n"
"The Conda runtime appears supported but not yet set up.\n"
"Try running `nextstrain setup conda` first.", False),
]
elif not PREFIX_BIN.exists():
yield ("runtime appears set up\n\n"
"The Conda runtime appears supported but not yet set up.\n"
"Try running `nextstrain setup conda` first.", False)

return [
*support,
("runtime appears set up", True),
else:
yield ("runtime appears set up", True)

('snakemake is installed and runnable',
which_finds_our("snakemake") and runnable("snakemake", "--version")),
yield ('snakemake is installed and runnable',
which_finds_our("snakemake") and runnable("snakemake", "--version"))

('augur is installed and runnable',
which_finds_our("augur") and runnable("augur", "--version")),
yield ('augur is installed and runnable',
which_finds_our("augur") and runnable("augur", "--version"))

('auspice is installed and runnable',
which_finds_our("auspice") and runnable("auspice", "--version")),
]
yield ('auspice is installed and runnable',
which_finds_our("auspice") and runnable("auspice", "--version"))


def test_support() -> RunnerTestResults:
Expand Down
27 changes: 14 additions & 13 deletions nextstrain/cli/runner/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,19 +381,20 @@ def test_image_version():

return [(msg, status)]

return [
('docker is installed',
shutil.which("docker") is not None),
('docker run works',
test_run()),
*test_memory_limit(),
*test_image_version(),

# Rosetta 2 is optional, so convert False (fail) → None (warning)
*[(msg, None if status is False else status)
for msg, status
in test_rosetta_enabled("Rosetta 2 is enabled for faster execution (optional)")],
]
yield ('docker is installed',
shutil.which("docker") is not None)

yield ('docker run works',
test_run())

yield from test_memory_limit()

yield from test_image_version()

# Rosetta 2 is optional, so convert False (fail) → None (warning)
yield from ((msg, None if status is False else status)
for msg, status
in test_rosetta_enabled("Rosetta 2 is enabled for faster execution (optional)"))


def set_default_config() -> None:
Expand Down
16 changes: 8 additions & 8 deletions nextstrain/cli/runner/singularity.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,14 @@ def test_run():
else:
return True

return [
("singularity is installed",
shutil.which("singularity") is not None),
(f"singularity version {singularity_version()} ≥ {SINGULARITY_MINIMUM_VERSION} ({APPTAINER_MINIMUM_VERSION} for Apptainer)",
singularity_version_at_least(SINGULARITY_MINIMUM_VERSION, apptainer=APPTAINER_MINIMUM_VERSION)),
("singularity works",
test_run()),
]
yield ("singularity is installed",
shutil.which("singularity") is not None)

yield (f"singularity version {singularity_version()} ≥ {SINGULARITY_MINIMUM_VERSION} ({APPTAINER_MINIMUM_VERSION} for Apptainer)",
singularity_version_at_least(SINGULARITY_MINIMUM_VERSION, apptainer=APPTAINER_MINIMUM_VERSION))

yield ("singularity works",
test_run())


def set_default_config() -> None:
Expand Down
2 changes: 1 addition & 1 deletion nextstrain/cli/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

RunnerSetupStatus = Optional[bool]

RunnerTestResults = List['RunnerTestResult']
RunnerTestResults = Iterable['RunnerTestResult']
RunnerTestResult = Tuple[str, 'RunnerTestResultStatus']
RunnerTestResultStatus: TypeAlias = Union[bool, None, EllipsisType]

Expand Down
10 changes: 7 additions & 3 deletions nextstrain/cli/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,12 @@ def runner_tests_ok(tests: RunnerTestResults) -> bool:
return False not in [result for test, result in tests]


def print_runner_tests(tests: RunnerTestResults):
def stream_and_print_results(tests: RunnerTestResults) -> RunnerTestResults:
"""
Prints a formatted version of the return value of a runner's
``test_setup()``.
Iterates through and prints runner test results while streaming them
forward.

Messages will only be printed if the generator is consumed downstream.
"""
success = partial(colored, "green")
failure = partial(colored, "red")
Expand All @@ -612,6 +614,8 @@ def print_runner_tests(tests: RunnerTestResults):

print(status.get(result, str(result)) + ":", formatted_description)

yield (description, result)


def test_rosetta_enabled(msg: str = "Rosetta 2 is enabled") -> RunnerTestResults:
"""
Expand Down
Loading