Skip to content

Commit

Permalink
Merge bfc1eda into 5d44a50
Browse files Browse the repository at this point in the history
  • Loading branch information
jidicula committed Nov 15, 2020
2 parents 5d44a50 + bfc1eda commit 4a26c57
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 6 deletions.
13 changes: 7 additions & 6 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from shimmingtoolbox import __dir_testing__
from shimmingtoolbox.cli.download_data import download_data
from shimmingtoolbox.cli.check_env import check_dcm2niix_installation, check_prelude_installation

logger = logging.getLogger(__name__)

Expand All @@ -35,17 +36,17 @@ def test_data_path_fixture():

@pytest.fixture(params=[pytest.param(0, marks=pytest.mark.prelude)])
def test_prelude_installation():
# note that subprocess.check_call() returns 0 on success, so it must be
# negated for the assertion.
assert not subprocess.check_call(['which', 'prelude'])
# note that check_prelude_installation() returns 0 on success, so it must
# be negated for the assertion.
assert not check_prelude_installation()
return


@pytest.fixture(params=[pytest.param(0, marks=pytest.mark.dcm2niix)])
def test_dcm2niix_installation():
# note that subprocess.check_call() returns 0 on success, so it must be
# negated for the assertion.
assert not subprocess.check_call(['which', 'dcm2niix'])
# note that check_dcm2niix_installation() returns 0 on success, so it must
# be negated for the assertion.
assert not check_dcm2niix_installation()
return


Expand Down
3 changes: 3 additions & 0 deletions docs/source/6_api_reference/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ misc

.. automodule:: shimmingtoolbox.cli.download_data
:members:

.. automodule:: shimmingtoolbox.cli.check_env
:members:
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
'console_scripts': [
"st_download_data=shimmingtoolbox.cli.download_data:download_data",
"st_dicom_to_nifti=shimmingtoolbox.cli.dicom_to_nifti:dicom_to_nifti_cli",
"st_check_dependencies=shimmingtoolbox.cli.check_env:check_dependencies",
"st_dump_env_info=shimmingtoolbox.cli.check_env:dump_env_info"
]
},
packages=find_packages(exclude=["docs"]),
Expand All @@ -35,6 +37,7 @@
"scipy~=1.5.0",
"tqdm",
"matplotlib~=3.1.2",
"psutil~=5.7.3",
"pytest~=4.6.3",
"pytest-cov~=2.5.1",
],
Expand Down
171 changes: 171 additions & 0 deletions shimmingtoolbox/cli/check_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file provides 2 CLI entrypoints and associated helper functions:
# - a tool to check dependency installation, availability in PATH, and version
# - a tool to dump details about the environment and shimmingtoolbox
# installation

import click
import subprocess
import os
import platform
import psutil

from typing import Dict, Tuple, List

CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])


@click.command(
context_settings=CONTEXT_SETTINGS,
help="Verify that the dependencies are installed and available to the toolbox."
)
def check_dependencies():
"""Verifies dependencies are installed by calling helper functions and
formatting output accordingly.
"""
# 0 indicates prelude is installed.
prelude_exit_code: int = check_prelude_installation()
# negating condition because 0 indicates prelude is installed.
if not prelude_exit_code:
print(get_prelude_version())
else:
print(f"Error {prelude_exit_code}: prelude is not installed or not in your PATH.")

# 0 indicates dcm2niix is installed.
dcm2niix_exit_code: int = check_dcm2niix_installation()
# negating condition because 0 indicates dcm2niix is installed.
if not dcm2niix_exit_code:
print(get_dcm2niix_version())
else:
print(f"Error {dcm2niix_exit_code}: dcm2niix is not installed or not in your PATH.")

return


def check_prelude_installation() -> int:
"""Checks that ``prelude`` is installed.
This function calls ``which prelude`` and checks the exit code to verify
that ``prelude`` is installed.
Returns:
int: Exit code. 0 on success, nonzero on failure.
"""

return subprocess.check_call(['which', 'prelude'])


def check_dcm2niix_installation() -> int:
"""Checks that ``dcm2niix`` is installed.
This function calls ``which dcm2niix`` and checks the exit code to verify
that ``dcm2niix`` is installed.
Returns:
int: Exit code. 0 on success, nonzero on failure.
"""
return subprocess.check_call(['which', 'dcm2niix'])


def get_prelude_version() -> str:
"""Gets the ``prelude`` installation version.
This function calls ``prelude --help`` and parses the output to obtain the
installation version.
Returns:
str: Version of the ``prelude`` installation.
"""
# `prelude --help` returns an error code and output is in stderr
prelude_help = subprocess.run(["prelude", "--help"], capture_output=True, encoding="utf-8")
# If the behaviour of FSL prelude changes to output help in stdout with a
# 0 exit code, this function must fail loudly so we can update its
# behaviour accordingly:
assert prelude_help.returncode != 0
# we're capturing stderr instead of stdout
help_output: str = prelude_help.stderr.rstrip()
# remove beginning newline and drop help info to keep version info
version: str = help_output.split("\n\n")[0].replace("\n","", 1)
return version


def get_dcm2niix_version() -> str:
"""Gets the ``dcm2niix`` installation version.
This function calls ``dcm2niix --version`` and captures the output to
obtain the installation version.
Returns:
str: Version of the ``dcm2niix`` installation.
"""
# `dcm2niix --version` returns an error code and output is in stderr
dcm2niix_version: str = subprocess.run(["dcm2niix", "--version"], capture_output=True, encoding="utf-8")
# If the behaviour of dcm2niix changes to output help with a 0 exit code,
# this function must fail loudly so we can update its behaviour
# accordingly:
assert dcm2niix_version.returncode != 0
version_output: str = dcm2niix_version.stdout.rstrip()
return version_output


@click.command(
context_settings=CONTEXT_SETTINGS,
help="Dumps environment and package details into stdout for debugging purposes."
)
def dump_env_info():
"""Dumps environment and package details into stdout for debugging purposes
by calling helper functions to retrieve these details.
"""
env_info = get_env_info()
pkg_version = get_pkg_info()

print(f"ENVIRONMENT INFO:\n{env_info}\n\nPACKAGE INFO:\n{pkg_version}")
return


def get_env_info() -> str:
"""Gets information about the environment.
This function gets information about the operating system, the host
machine hardware, Python version & implementation, and Python location.
Returns:
str: A multiline string containing environment info.
"""

os_name = os.name
cpu_arch = platform.machine()
platform_release = platform.release()
platform_system = platform.system()
platform_version = platform.version()
python_full_version = platform.python_version()
python_implementation = platform.python_implementation()

cpu_usage = f"CPU cores: Available: {psutil.cpu_count()}, Used by ITK functions: {int(os.getenv('ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS', 0))}"
ram = psutil.virtual_memory()
factor_MB = 1024 * 1024
ram_usage = f'RAM: Total: {ram.total // factor_MB}MB, Used: {ram.used // factor_MB}MB, Available: {ram.available // factor_MB}MB'

env_info = (f"{os_name} {cpu_arch}\n" +
f"{platform_system} {platform_release}\n" +
f"{platform_version}\n" +
f"{python_implementation} {python_full_version}\n\n" +
f"{cpu_usage}\n" +
f"{ram_usage}"
)
return env_info


def get_pkg_info() -> str:
"""Gets package version.
This function gets the version of shimming-toolbox.
Returns:
str: The version number of the shimming-toolbox installation.
"""
import shimmingtoolbox as st
pkg_version = st.__version__
return pkg_version
69 changes: 69 additions & 0 deletions test/cli/test_check_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest

import re
from click.testing import CliRunner
import shimmingtoolbox.cli.check_env as st_ce

@pytest.mark.dcm2niix
@pytest.mark.prelude
def test_check_dependencies(test_dcm2niix_installation, test_prelude_installation):
runner = CliRunner()

result = runner.invoke(st_ce.check_dependencies)
assert result.exit_code == 0


def test_dump_env_info():
runner = CliRunner()

result = runner.invoke(st_ce.dump_env_info)
assert result.exit_code == 0


def test_check_prelude_installation():
"""Tests that the function returns an exit code as expected, does not test
the exit code value itself, so it does not depend on prelude being
installed.
"""
check_prelude_installation_exit = st_ce.check_prelude_installation()
assert isinstance(check_prelude_installation_exit, int)


def test_check_dcm2niix_installation():
"""Tests that the function returns an exit code as expected, does not test
the exit code value itself, so it does not depend on dcm2niix being
installed.
"""
check_dcm2niix_installation_exit = st_ce.check_dcm2niix_installation()
assert isinstance(check_dcm2niix_installation_exit, int)


@pytest.mark.prelude
def test_get_prelude_version(test_prelude_installation):
"""Checks prelude version output for expected structure.
"""
prelude_version_info = st_ce.get_prelude_version()
version_regex = r"Part of FSL.*\nprelude.*\nPhase.*\nCopyright.*"
assert re.search(version_regex, prelude_version_info)


@pytest.mark.dcm2niix
def test_get_dcm2niix_version(test_dcm2niix_installation):
"""Checks dcm2niix version output for expected structure.
"""
dcm2niix_version_info = st_ce.get_dcm2niix_version()
version_regex = r"Chris.*\nv\d\.\d.\d{8}"
assert re.search(version_regex, dcm2niix_version_info)


def test_get_env_info():
env_info = st_ce.get_env_info()
assert isinstance(env_info, str)


def test_get_pkg_info():
pkg_info = st_ce.get_pkg_info()
version_regex = r"\d*\.\d*\.\d*"
assert re.search(version_regex, pkg_info)

0 comments on commit 4a26c57

Please sign in to comment.