Skip to content

Commit

Permalink
# This is a combination of 5 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

add better tools

# The commit message #2 will be skipped:

# add 3dcalc test

# The commit message #3 will be skipped:

# add 3dinfo test

# The commit message #4 will be skipped:

# in a state of chasis

# The commit message #5 will be skipped:

# fix bug in output directory
  • Loading branch information
leej3 committed May 6, 2019
1 parent bacf551 commit bc308ed
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 63 deletions.
91 changes: 45 additions & 46 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@


def get_output_dir():
return Path(pytest.config.rootdir) / ("output_" + CURRENT_TIME)
return Path(pytest.config.rootdir) / "output_of_tests" / ("output_" + CURRENT_TIME)


def get_test_dir_path():
Expand Down Expand Up @@ -68,85 +68,83 @@ def get_test_dir():
return test_data_dir


@pytest.fixture(scope="module")
def get_current_test_name():
return os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]


@pytest.fixture(scope="function")
def data(request):
test_data_dir = get_test_dir()
test_name = get_current_test_name()
module_data_dir = get_test_dir()

# Read values from calling module:
# Set module specific values:
data_paths = request.module.data_paths
test_outdir = get_output_dir() / Path(request.module.__file__).stem.replace(
module_outdir = get_output_dir() / Path(request.module.__file__).stem.replace(
"test_", ""
)
test_logdir = module_outdir / get_current_test_name() / "captured_output"

# Make appropriate directories if they don't already exist
if not test_outdir.parent.exists():
test_outdir.parent.mkdir(exist_ok=True)
if not test_outdir.exists():
test_outdir.mkdir(exist_ok=True)

# Define output for calling module and get data as required:
out_dict = {
k: misc.process_path_obj(v, test_data_dir) for k, v in data_paths.items()
k: misc.process_path_obj(v, module_data_dir) for k, v in data_paths.items()
}

# Define output for calling module and get data as required:
out_dict.update(
{
"test_data_dir": test_data_dir,
"outdir": test_outdir,
"module_data_dir": module_data_dir,
"outdir": module_outdir / get_current_test_name(),
"logdir": test_logdir,
"comparison_dir": get_comparison_dir_path(),
"test_name": test_name,
}
)
return namedtuple("DataTuple", out_dict.keys())(**out_dict)


@pytest.fixture(scope="module")
@pytest.fixture(scope="function")
def run_cmd():
def command_runner(
cmd,
current_vars={},
add_env_vars={},
merge_error_with_output=False,
workdir=None,
cmd, current_vars, add_env_vars={}, merge_error_with_output=False, workdir=None
):
"""run_cmd is initialized for all test functions that list it as an
argument. It is used as a callable function to run command line arguments.
The cmd string may require a formatting step where the values contained in
'current_vars' are injected into the command string.
Technical note: check_cmd is not a standard function. It is a
Technical note: run_cmd is not a standard function. It is a
module-scoped pytest fixture that returns a callable function
(command_runner) that takes the arguments from the user writing a test.
Args:
cmd (str): A string that requires execution and error checking.
Variables will be substituted into the string as required. Following
python's f-strings syntax, variables are wrapped in braces.
current_vars (dict, optional): If variable substitution is required,
the user must provide a dictionary of variables for this substitution
process. Passing the dictionary from 'locals()' is the most convenient
method of doing this.
current_vars (dict): The current variables in the test function
scope (accessed by getting the values returned by 'locals()') must
be provided to this callable. Among other things, this uses the
data fixture to perform variable substitution for the command
string
Returns:
subprocess.CompletedProcess: An object that among other useful
attributes contains stdout, stderror of the executed command
"""
# Get the data object created by the data test fixture
data = current_vars.get("data", None)
if not data:
raise ValueError(
"When using run_cmd you should use the data fixture for "
"the test and pass the local variables accessed by "
"calling 'locals' to the run_cmd callable using the "
"'current_vars' argument "
)

# Set working directory for command execution if not set explicitly
if not workdir:
workdir = Path.cwd()

base_outdir = get_output_dir()
# try to extract the name of the calling function to label captured
# sys output from the command execution.
calling_function = inspect.stack()[1].function
if not calling_function.startswith("test_"):
calling_function = "unknown"
log_file_path = (
base_outdir
/ "captured_output"
/ (calling_function + "_" + cmd.split()[0] + ".log")
)
log_file_path.parent.mkdir(exist_ok=True)
# Define log file paths
stdout_log = data.logdir / (data.test_name + "_stdout.log")
stderr_log = Path(str(stdout_log).replace("_stdout", "_stderr"))

# Set environment variables for the command execution
os.environ["OMP_NUM_THREADS"] = "1"
Expand All @@ -156,24 +154,25 @@ def command_runner(
# Tidy whitespace and sub variables into command
cmd = " ".join(cmd.format(**current_vars).split())
print(cmd)
# Make the appropriate output directories
os.makedirs(stdout_log.parent)

with misc.remember_cwd():
os.chdir(workdir)
# Execute the command and log output
if merge_error_with_output:
proc = subprocess.run(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
log_file_path.write_text(proc.stdout.decode("utf-8"))
else:
proc = subprocess.run(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
log_file_path.with_suffix(".out.log").write_text(
proc.stdout.decode("utf-8")
)
log_file_path.with_suffix(".err.log").write_text(
proc.stderr.decode("utf-8")
)
# log the output
stdout_log.write_text(proc.stdout.decode("utf-8"))
err_text = proc.stderr.decode("utf-8")
if err_text:
stderr_log.write_text(err_text)

# Raise error if there was a non-zero exit code.
proc.check_returncode()
Expand Down
19 changes: 14 additions & 5 deletions tests/scripts/test_3dClustSim.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .utils.misc import is_omp
from .utils import tools

# check for omp compilation
OMP = is_omp("3dAllineate")
Expand All @@ -20,7 +21,7 @@

def test_3dClustSim_basic(data, run_cmd):
seedval = 31416
outfile = data.outdir / ("clust_sim_out")
outfile_prefix = data.outdir / ("clust_sim_out")
cmd = """
3dClustSim
-nxyz 16 8 4
Expand All @@ -29,11 +30,19 @@ def test_3dClustSim_basic(data, run_cmd):
-acf 0.7 3 3
-LOTS
-seed {seedval}
-prefix {outfile}
-prefix {outfile_prefix}
"""
run_cmd(cmd, locals())

proc_1 = run_cmd(cmd, locals())

# If compiled with OpenMP, additionally run the command with 2 threads
if OMP:
outfile = outfile.parent / (outfile.name + "_with_omp")
run_cmd(cmd, locals(), add_env_vars={"OMP_NUM_THREADS": "2"})
outfile_prefix = outfile_prefix.parent / (outfile_prefix.name + "_with_omp")
proc_2 = run_cmd(cmd, locals(), add_env_vars={"OMP_NUM_THREADS": "2"})

# Test all outputs match
tools.assert_all_files_equal(
data,
kwargs_1d={"rtol": 0.15},
kwargs_log={"ignore_patterns": ["Clock time", "but max simulated alpha="]},
)
5 changes: 4 additions & 1 deletion tests/scripts/test_3dToutcount.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# define data
from .utils import tools

data_paths = {"epi": "AFNI_data6/roi_demo/func_slim+orig.HEAD"}


Expand All @@ -14,4 +16,5 @@ def test_3dToutcount_basic(data, run_cmd):
-legendre {data.epi}
"""

run_cmd(cmd, locals())
proc = run_cmd(cmd, locals())
tools.assert_proc_text_has(proc, stderr_has=["3dToutcount: AFNI version="])
21 changes: 21 additions & 0 deletions tests/scripts/test_3dcalc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from .utils import tools
import filecmp

# Define Data
data_paths = {
"anatomical": "mini_data/anat_3mm.nii.gz",
"no_skull": "mini_data/anat_3mm_no_skull_zero_padded.nii.gz",
}


def test_3dcalc_basic(data, run_cmd):
outfile = data.outdir / "outside_brain.nii.gz"
cmd = """
3dcalc -a {data.anatomical} -b {data.no_skull} -expr 'a*not(b)' -prefix {outfile}
"""

proc = run_cmd(cmd, current_vars=locals())

tools.assert_all_files_equal(
data, kwargs_log={"append_to_ignored": ["Output dataset"]}
)
3 changes: 3 additions & 0 deletions tests/scripts/test_3dcopy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .utils import tools
import filecmp

# Define Data
Expand All @@ -11,3 +12,5 @@ def test_3dcopy_basic(data, run_cmd):
"""

proc = run_cmd(cmd, current_vars=locals())

tools.assert_all_files_equal(data, kwargs_scans={"data_kwargs": {"atol": 1e-6}})
15 changes: 15 additions & 0 deletions tests/scripts/test_3dinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .utils import tools
import filecmp

# Define Data
data_paths = {"anatomical": "mini_data/anat_3mm.nii.gz"}


def test_3dinfo_basic(data, run_cmd):
cmd = """
3dinfo {data.anatomical}
"""

proc = run_cmd(cmd, current_vars=locals())

tools.assert_all_files_equal(data)
15 changes: 10 additions & 5 deletions tests/scripts/test_3dttest++.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path
import shutil

from .utils import tools

base_path = Path("old_test_data_repo") / "3dttest++"
data_paths = {
Expand All @@ -20,13 +20,18 @@ def test_3dttest__plus____plus___basic(data, run_cmd):
copied_Zovar = data.outdir / data.Zovar
set_a = " ".join([str(f) for f in data.set_a])
set_b = " ".join([str(f) for f in data.set_b])
zov_out = data.outdir / "TTest_zov.nii.gz"
tov_out = data.outdir / "TTest_tov.nii.gz"

cmd = """
3dttest++ -setA {set_a} -setB {set_b} -prefix TTest_zov -covariates {copied_Zovar}
3dttest++ -setA {set_a} -setB {set_b} -prefix {zov_out} -covariates {copied_Zovar}
"""
run_cmd(cmd, locals(), workdir=data.Tovar.parent)
proc_1 = run_cmd(cmd, locals(), workdir=data.Tovar.parent)

cmd = """
3dttest++ -setA {set_a} -setB {set_b} -prefix TTest_tov -covariates {copied_Tovar}
3dttest++ -setA {set_a} -setB {set_b} -prefix {tov_out} -covariates {copied_Tovar}
"""
run_cmd(cmd, locals(), workdir=data.Tovar.parent)
proc_2 = run_cmd(cmd, locals(), workdir=data.Tovar.parent)

# test outputs if above commands ran
tools.assert_scans_equal(data.comparison_dir, [zov_out, tov_out])
20 changes: 14 additions & 6 deletions tests/scripts/test_afni_proc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import os
import shutil
import subprocess
import pytest
from pathlib import Path
from .utils import tools

ft_dir = Path("AFNI_data6/FT_analysis/FT")
data_paths = {
Expand Down Expand Up @@ -52,7 +49,12 @@ def test_handout_realcase2(data, run_cmd):
"""
run_cmd(cmd, locals(), workdir=data.outdir)

assert Path(f"{data.outdir}/proc.{subj}").exists()
# test outputs if above commands ran
tools.assert_all_files_equal(
data,
text_file_patterns=[".FT"],
kwargs_text_files={"ignore_patterns": ["auto-gener"]},
)


def test_handout_realcase3(data, run_cmd):
Expand All @@ -72,4 +74,10 @@ def test_handout_realcase3(data, run_cmd):
"""
run_cmd(cmd, locals(), workdir=data.outdir)

assert Path(f"{data.outdir}/proc.{subj}").exists()
# test outputs if above commands ran

tools.assert_all_files_equal(
data,
text_file_patterns=[".FT"],
kwargs_text_files={"ignore_patterns": ["auto-gener"]},
)
Loading

0 comments on commit bc308ed

Please sign in to comment.