Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
464 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
""" Tests for rendering CLI options and arguments """ | ||
|
||
from collections import OrderedDict | ||
import pytest | ||
from ubiquerg import build_cli_extra, powerset, VersionInHelpParser, convert_value | ||
import argparse | ||
|
||
|
||
def pytest_generate_tests(metafunc): | ||
""" Test case generation and parameterization for this module """ | ||
if "ordwrap" in metafunc.fixturenames: | ||
metafunc.parametrize("ordwrap", [tuple, OrderedDict]) | ||
|
||
|
||
def build_parser(): | ||
""" | ||
Example argument parser, needed solely for testing purposes. | ||
This example parser was copied from looper/__init__.py | ||
:return argparse.ArgumentParser | ||
""" | ||
|
||
banner = "%(prog)s - Loop through samples and submit pipelines." | ||
additional_description = "For subcommand-specific options, " \ | ||
"type: '%(prog)s <subcommand> -h'" | ||
additional_description += "\nhttps://github.com/pepkit/looper" | ||
|
||
parser = VersionInHelpParser(prog="looper", description=banner, | ||
epilog=additional_description, version=0.1) | ||
|
||
# Logging control | ||
parser.add_argument("--logfile", dest="logfile", | ||
help="Optional output file for looper logs (default: %(default)s)") | ||
parser.add_argument("--verbosity", dest="verbosity", type=int, | ||
choices=range(4), | ||
help="Choose level of verbosity (default: %(default)s)") | ||
parser.add_argument("--logging-level", dest="logging_level", | ||
help=argparse.SUPPRESS) | ||
parser.add_argument("--dbg", dest="dbg", action="store_true", | ||
help="Turn on debug mode (default: %(default)s)") | ||
parser.add_argument("--env", dest="env", default=None, | ||
help="Environment variable that points to the DIVCFG file. " | ||
"(default: DIVCFG)") | ||
parser.add_argument("--dotfile-template", action="store_true", | ||
help="Print out a looper dotfile template and exit") | ||
|
||
# Individual subcommands | ||
msg_by_cmd = {"run": "Main Looper function: Submit jobs for samples.", | ||
"rerun": "Resubmit jobs with failed flags.", | ||
"runp": "Submit jobs for a project.", | ||
"summarize": "Summarize statistics of project samples.", | ||
"destroy": "Remove all files of the project.", | ||
"check": "Checks flag status of current runs.", | ||
"clean": "Runs clean scripts to remove intermediate " | ||
"files of already processed jobs."} | ||
|
||
subparsers = parser.add_subparsers(dest="command") | ||
|
||
def add_subparser(cmd): | ||
message = msg_by_cmd[cmd] | ||
return subparsers.add_parser(cmd, description=message, help=message) | ||
|
||
# Run and rerun command | ||
run_subparser = add_subparser("run") | ||
rerun_subparser = add_subparser("rerun") | ||
collate_subparser = add_subparser("runp") | ||
for subparser in [run_subparser, rerun_subparser, collate_subparser]: | ||
subparser.add_argument("--ignore-flags", dest="ignore_flags", | ||
default=False, action="store_true", | ||
help="Ignore run status flags? Default: False. " | ||
"By default, pipelines will not be submitted if a pypiper " | ||
"flag file exists marking the run (e.g. as " | ||
"'running' or 'failed'). Set this option to ignore flags " | ||
"and submit the runs anyway. Default=False") | ||
subparser.add_argument("-t", "--time-delay", dest="time_delay", | ||
type=int, default=0, | ||
help="Time delay in seconds between job submissions.") | ||
subparser.add_argument("-p", "--package", | ||
help="Name of computing resource package to use") | ||
subparser.add_argument("--compute", | ||
help="Specification of individual computing resource settings; " | ||
"separate setting name/key from value with equals sign, " | ||
"and separate key-value pairs from each other by comma") | ||
subparser.add_argument("--limit", dest="limit", default=None, | ||
type=int, help="Limit to n samples.") | ||
subparser.add_argument("-a", "--pipeline-args", | ||
dest="pipeline_args", default="", | ||
help="arguments to pass to a pipline") | ||
subparser.add_argument("-s", "--settings", dest="settings", | ||
default="", | ||
help="path to a YAML-formatted settings file used to populate " | ||
"the command template") | ||
for subparser in [run_subparser, rerun_subparser]: | ||
# Note that defaults for otherwise numeric lump parameters are | ||
# set to | ||
# null by default so that the logic that parses their values may | ||
# distinguish between explicit 0 and lack of specification. | ||
subparser.add_argument("--lump", default=None, | ||
type=float, | ||
help="Maximum total input file size for a lump/batch of " | ||
"commands in a single job (in GB)") | ||
subparser.add_argument("--lumpn", default=None, | ||
type=int, | ||
help="Number of individual scripts grouped into " | ||
"single submission") | ||
|
||
# Other commands | ||
summarize_subparser = add_subparser("summarize") | ||
destroy_subparser = add_subparser("destroy") | ||
check_subparser = add_subparser("check") | ||
clean_subparser = add_subparser("clean") | ||
|
||
check_subparser.add_argument("-A", "--all-folders", | ||
action="store_true", default=False, | ||
help="Check status for all project's output folders, not just " | ||
"those for samples specified in the config file used. " | ||
"Default=False") | ||
|
||
for subparser in [destroy_subparser, clean_subparser]: | ||
subparser.add_argument("--force-yes", action="store_true", | ||
default=False, | ||
help="Provide upfront confirmation of destruction intent, " | ||
"to skip console query. Default=False") | ||
|
||
# Common arguments | ||
for subparser in [run_subparser, rerun_subparser, summarize_subparser, | ||
destroy_subparser, check_subparser, clean_subparser, | ||
collate_subparser]: | ||
subparser.add_argument("config_file", nargs="?", | ||
help="Project configuration file (YAML).") | ||
# subparser.add_argument( | ||
# "-c", "--config", required=False, default=None, | ||
# dest="looper_config", help="Looper configuration file ( | ||
# YAML).") | ||
subparser.add_argument("--pipeline-interfaces", dest="pifaces", | ||
nargs="+", action='append', | ||
help="Path to a pipeline interface file") | ||
subparser.add_argument("--file-checks", dest="file_checks", | ||
action="store_true", | ||
help="Perform input file checks. Default=True.") | ||
subparser.add_argument("-d", "--dry-run", dest="dry_run", | ||
action="store_true", | ||
help="Don't actually submit the jobs. Default=False") | ||
|
||
fetch_samples_group = subparser.add_argument_group("select samples", | ||
"This group of arguments lets you specify samples to use by " | ||
"exclusion OR inclusion of the samples attribute values.") | ||
fetch_samples_group.add_argument("--selector-attribute", | ||
dest="selector_attribute", default="toggle", | ||
help="Specify the attribute for samples exclusion OR inclusion") | ||
protocols = fetch_samples_group.add_mutually_exclusive_group() | ||
protocols.add_argument("--selector-exclude", nargs='*', | ||
dest="selector_exclude", | ||
help="Operate only on samples that either lack this attribute " | ||
"value or for which this value is not in this collection.") | ||
protocols.add_argument("--selector-include", nargs='*', | ||
dest="selector_include", | ||
help="Operate only on samples associated with these attribute " | ||
"values; if not provided, all samples are used.") | ||
subparser.add_argument("--amendments", dest="amendments", nargs="+", | ||
help="Name of amendment(s) to use, as designated in the " | ||
"project's configuration file") | ||
|
||
return parser, msg_by_cmd | ||
|
||
|
||
@pytest.mark.parametrize(["optargs", "expected"], [ | ||
([("-X", None), ("--revert", 1), ("-O", "outfile"), | ||
("--execute-locally", None), ("-I", ["file1", "file2"])], | ||
"-X --revert 1 -O outfile --execute-locally -I file1 file2") | ||
]) | ||
def test_build_cli_extra(optargs, expected, ordwrap): | ||
""" Check that CLI optargs are rendered as expected. """ | ||
observed = build_cli_extra(ordwrap(optargs)) | ||
print("expected: {}".format(expected)) | ||
print("observed: {}".format(observed)) | ||
assert expected == observed | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"optargs", powerset([(None, "a"), (1, "one")], nonempty=True)) | ||
def test_illegal_cli_extra_input_is_exceptional(optargs, ordwrap): | ||
""" Non-string keys are illegal and cause a TypeError. """ | ||
with pytest.raises(TypeError): | ||
build_cli_extra(ordwrap(optargs)) | ||
|
||
|
||
def test_dests_by_subparser_return_type(): | ||
""" Check if the return type is dict of lists keyed by subcommand name """ | ||
parser, msgs_by_cmd = build_parser() | ||
assert isinstance(parser.dests_by_subparser(), dict) | ||
assert all([isinstance(v, list) | ||
for k, v in parser.dests_by_subparser().items()]) | ||
assert all([k in parser.dests_by_subparser() | ||
for k in list(msgs_by_cmd.keys())]) | ||
|
||
@pytest.mark.parametrize("subcmd", | ||
list(build_parser()[0].dests_by_subparser().keys())) | ||
def test_dests_by_subparser_specific_subcmd(subcmd): | ||
""" | ||
Should return only specified section of the | ||
return if subparser specified | ||
""" | ||
parser, _ = build_parser() | ||
assert isinstance(parser.dests_by_subparser(subcmd), dict) | ||
assert subcmd in parser.dests_by_subparser(subcmd).keys() | ||
assert isinstance(parser.dests_by_subparser(subcmd)[subcmd], list) | ||
|
||
|
||
def test_dests_by_subparser_toplevel_actions(): | ||
""" | ||
Should return only top level dests, which is a list | ||
""" | ||
parser, _ = build_parser() | ||
tld = parser.dests_by_subparser(top_level=True) | ||
assert isinstance(tld, list) | ||
assert len(tld) == 6 | ||
|
||
|
||
def test_arg_defaults_return_type(): | ||
""" Check if the return type is dict of dicts keyed by subcommand name """ | ||
parser, msgs_by_cmd = build_parser() | ||
assert isinstance(parser.arg_defaults(), dict) | ||
assert all([isinstance(v, dict) | ||
for k, v in parser.arg_defaults().items()]) | ||
assert all([k in parser.arg_defaults() | ||
for k in list(msgs_by_cmd.keys())]) | ||
|
||
|
||
@pytest.mark.parametrize("subcmd", | ||
list(build_parser()[0].dests_by_subparser().keys())) | ||
def test_arg_defaults_specific_subcmd(subcmd): | ||
""" | ||
Should return only specified section of the | ||
return if subparser specified | ||
""" | ||
parser, _ = build_parser() | ||
assert isinstance(parser.arg_defaults(subcmd), dict) | ||
assert subcmd in parser.arg_defaults(subcmd).keys() | ||
assert isinstance(parser.arg_defaults(subcmd)[subcmd], dict) | ||
|
||
|
||
def test_arg_defaults_toplevel_actions(): | ||
""" | ||
Should return only top level defaults, which is a dict | ||
""" | ||
parser, _ = build_parser() | ||
tld = parser.arg_defaults(top_level=True) | ||
assert isinstance(tld, dict) | ||
assert len(tld) == 6 | ||
|
||
|
||
def test_arg_defaults_unqiue(): | ||
""" | ||
Should return only dict of unique kv pairs, not keyed by subcommands | ||
""" | ||
parser, _ = build_parser() | ||
tld = parser.arg_defaults(unique=True) | ||
assert isinstance(tld, dict) | ||
assert len(tld) == 17 | ||
assert len(set(tld.keys())) == len(tld.keys()) | ||
|
||
|
||
@pytest.mark.parametrize(["input", "output"], | ||
[(None, None), ("None", None), ("1", 1), ("0.1", 0.1), | ||
("True", True), ("False", False)]) | ||
def test_value_conversion(input, output): | ||
assert convert_value(input) == output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = "0.5.1" | ||
__version__ = "0.5.2-dev" |
Oops, something went wrong.