Skip to content

Commit

Permalink
test defaults for command callability; tweak implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
vreuter committed Jul 2, 2019
1 parent d7764b4 commit 1c3ba86
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 9 deletions.
24 changes: 15 additions & 9 deletions pypiper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def check_shell_asterisk(cmd):

def check_all_commands(
cmds,
get_bad_result=lambda bads: Exception("{} uncallable commands: "),
get_bad_result=lambda bads: Exception("{} uncallable commands: {}".format(len(bads), bads)),
handle=None):
"""
Determine whether all commands are callable
Expand All @@ -243,25 +243,25 @@ def check_all_commands(
isn't a single-argument function
"""
bads = determine_uncallable(cmds)
if not bads:
return True
if handle is None:
def handle(res):
if isinstance(res, Exception):
raise res
print("Command check result: {}".format(res))
elif not hasattr(handle, "__call__") or not 1 == len(get_fun_sig(handle).args):
raise TypeError("Command check error handler must be a one-arg function")
if bads:
handle(get_bad_result(bads))
return False
return True
handle(get_bad_result(bads))
return False


def determine_uncallable(
commands, transformations=(
(lambda f: isinstance(f, str) and
os.path.isfile(expandpath(f)) and
expandpath(f).endswith(".jar"),
lambda f: "java -jar {}".format(expandpath(f)))
lambda f: "java -jar {}".format(expandpath(f))),
), accumulate=False):
"""
Determine which commands are not callable.
Expand All @@ -282,7 +282,11 @@ def determine_uncallable(
"""
commands = [commands] if isinstance(commands, str) else commands
if transformations:
if not isinstance(transformations, Iterable) or isinstance(transformations, str):
if not isinstance(transformations, Iterable) or \
isinstance(transformations, str) or \
not all(map(lambda func_pair: isinstance(func_pair, tuple)
and len(func_pair) == 2,
transformations)):
raise TypeError(
"Transformations argument should be a collection of pairs; got "
"{} ({})".format(transformations, type(transformations).__name__))
Expand All @@ -298,15 +302,17 @@ def finalize(cmd):
"If transformations are unordered, non-accumulation of "
"effects may lead to nondeterministic behavior.")
def finalize(cmd):
print("Transformations: {}".format(transformations))
for p, t in transformations:
if p(cmd):
cmd = t(cmd)
return cmd

else:
finalize = lambda cmd: cmd
return list(filter(lambda _, cmd: not is_command_callable(cmd),
map(lambda c: (c, finalize(c)), commands)))
return [(orig, used) for orig, used in
map(lambda c: (c, finalize(c)), commands)
if not is_command_callable(used)]


def split_by_pipes_nonnested(cmd):
Expand Down
1 change: 1 addition & 0 deletions requirements/reqs-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pytest>=4.2.1
hypothesis
coveralls>=1.1
pytest-cov==2.6.1
veracitools
94 changes: 94 additions & 0 deletions tests/utils_tests/test_check_command_callability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
""" Tests for checking a collection of commands for callability """

import mock
import os
import pytest
from pypiper import utils as piper_utils
from veracitools import ExpectContext

__author__ = "Vince Reuter"
__email__ = "vreuter@virginia.edu"


EXTENSIONS = [".py", ".rb", ".sh", ".java", ".jar", ".pl", ".o", ".R", ".r",
".cpp", ".c", ".hs", ".scala", ".class"]


def _touch(f):
""" 'touch' the given file.
:param str f: filepath to create
"""
with open(f, 'w'):
print("touch: {}".format(f))


def _make_exec(f):
"""
'touch' a file and set exec bit.
:param str f: path to create
"""
import subprocess
_touch(f)
subprocess.check_call(["chmod", "+x", f])


def pytest_generate_tests(metafunc):
""" Dynamic test case generation and parameterization for this module """
if "str_list_monad" in metafunc.fixturenames:
metafunc.parametrize("str_list_monad", [lambda s: s, lambda s: [s]])


@pytest.mark.parametrize("filename", ["testfile" + x for x in EXTENSIONS])
@pytest.mark.parametrize(["setup", "pretest", "exp_miss"], [
(lambda _: None,
lambda f: not os.path.exists(f),
lambda _: True),
(_touch,
lambda f: os.path.isfile(f) and not os.access(f, os.X_OK),
lambda f: not f.endswith(".jar")),
(_make_exec,
lambda f: os.path.isfile(f) and os.access(f, os.X_OK),
lambda _: False)
])
def test_callability_checker_defaults(tmpdir, filename, setup, pretest, exp_miss):
cmd = os.path.join(tmpdir.strpath, filename)
setup(cmd)
assert pretest(cmd)
extra_commands = ["this-is-not-a-program", "man", "ls"]
expected = ["this-is-not-a-program"]
if exp_miss(cmd):
expected.append(cmd)
observed = [c for c, _ in piper_utils.determine_uncallable([cmd] + extra_commands)]
print("expected: {}".format(expected))
print("observed: {}".format(observed))
assert len(expected) == len(observed)
assert set(expected) == set(observed)


@pytest.mark.parametrize(
["uncall_result", "expectation"],
[([], True), ([("noncmd", "noncmd")], TypeError)])
@pytest.mark.parametrize("handler", [lambda: True, "not-a-function"])
def test_check_all_bad_handler_is_type_error_iff_uncallability_exists(
uncall_result, str_list_monad, handler, expectation):
""" Invalid handler evaluation is conditional having >= 1 uncallable command. """
cmd = "noncmd"
with mock.patch.object(piper_utils, "determine_uncallable",
return_value=uncall_result), \
ExpectContext(expectation, piper_utils.check_all_commands) as check:
check(cmds=str_list_monad(cmd), handle=handler)


@pytest.mark.parametrize(["create_result", "expected"], [
(lambda bads: Exception("{} bad commands: {}".format(len(bads), bads)), Exception),
(lambda bads: "{} bad commands: {}".format(len(bads), bads), False)
])
def test_check_all_result_is_conjunction(create_result, expected, str_list_monad):
""" Even one uncallable means result is False or an Exception occurs. """
cmd = "noncmd"
with mock.patch.object(piper_utils, "determine_uncallable",
return_value=[(cmd, cmd)]), \
ExpectContext(expected, piper_utils.check_all_commands) as check:
check(cmds=str_list_monad(cmd), get_bad_result=create_result)

0 comments on commit 1c3ba86

Please sign in to comment.