-
Notifications
You must be signed in to change notification settings - Fork 187
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
13 changed files
with
378 additions
and
5 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 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
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
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 |
---|---|---|
@@ -0,0 +1,143 @@ | ||
#!/usr/bin/env python | ||
""" | ||
The ModulesTest class runs the tests locally | ||
""" | ||
|
||
import logging | ||
import questionary | ||
import os | ||
import pytest | ||
import sys | ||
import rich | ||
from pathlib import Path | ||
from shutil import which | ||
|
||
import nf_core.utils | ||
from .modules_repo import ModulesRepo | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
class ModulesTest(object): | ||
""" | ||
Class to run module pytests. | ||
... | ||
Attributes | ||
---------- | ||
module_name : str | ||
name of the tool to run tests for | ||
no_prompts : bool | ||
flat indicating if prompts are used | ||
pytest_args : tuple | ||
additional arguments passed to pytest command | ||
Methods | ||
------- | ||
run(): | ||
Run test steps | ||
_check_inputs(): | ||
Check inputs. Ask for module_name if not provided and check that the directory exists | ||
_set_profile(): | ||
Set software profile | ||
_run_pytests(self): | ||
Run pytest | ||
""" | ||
|
||
def __init__( | ||
self, | ||
module_name=None, | ||
no_prompts=False, | ||
pytest_args="", | ||
): | ||
self.module_name = module_name | ||
self.no_prompts = no_prompts | ||
self.pytest_args = pytest_args | ||
|
||
def run(self): | ||
"""Run test steps""" | ||
if not self.no_prompts: | ||
log.info( | ||
"[yellow]Press enter to use default values [cyan bold](shown in brackets) [yellow]or type your own responses" | ||
) | ||
self._check_inputs() | ||
self._set_profile() | ||
self._check_profile() | ||
self._run_pytests() | ||
|
||
def _check_inputs(self): | ||
"""Do more complex checks about supplied flags.""" | ||
|
||
# Get the tool name if not specified | ||
if self.module_name is None: | ||
if self.no_prompts: | ||
raise UserWarning( | ||
"Tool name not provided and prompts deactivated. Please provide the tool name as TOOL/SUBTOOL or TOOL." | ||
) | ||
modules_repo = ModulesRepo() | ||
modules_repo.get_modules_file_tree() | ||
self.module_name = questionary.autocomplete( | ||
"Tool name:", | ||
choices=modules_repo.modules_avail_module_names, | ||
style=nf_core.utils.nfcore_question_style, | ||
).ask() | ||
module_dir = Path("modules") / self.module_name | ||
|
||
# First, sanity check that the module directory exists | ||
if not module_dir.is_dir(): | ||
raise UserWarning( | ||
f"Cannot find directory '{module_dir}'. Should be TOOL/SUBTOOL or TOOL. Are you running the tests inside the nf-core/modules main directory?" | ||
) | ||
|
||
def _set_profile(self): | ||
"""Set $PROFILE env variable. | ||
The config expects $PROFILE and Nextflow fails if it's not set. | ||
""" | ||
if os.environ.get("PROFILE") is None: | ||
os.environ["PROFILE"] = "" | ||
if self.no_prompts: | ||
log.info( | ||
"Setting environment variable '$PROFILE' to an empty string as not set.\n" | ||
"Tests will run with Docker by default. " | ||
"To use Singularity set 'export PROFILE=singularity' in your shell before running this command." | ||
) | ||
else: | ||
question = { | ||
"type": "list", | ||
"name": "profile", | ||
"message": "Choose software profile", | ||
"choices": ["Docker", "Singularity", "Conda"], | ||
} | ||
answer = questionary.unsafe_prompt([question], style=nf_core.utils.nfcore_question_style) | ||
profile = answer["profile"].lower() | ||
os.environ["PROFILE"] = profile | ||
log.info(f"Setting environment variable '$PROFILE' to '{profile}'") | ||
|
||
def _check_profile(self): | ||
"""Check if profile is available""" | ||
profile = os.environ.get("PROFILE") | ||
# Make sure the profile read from the environment is a valid Nextflow profile. | ||
valid_nextflow_profiles = ["docker", "singularity", "conda"] | ||
if profile in valid_nextflow_profiles: | ||
if not which(profile): | ||
raise UserWarning(f"Command '{profile}' not found - is it installed?") | ||
else: | ||
raise UserWarning( | ||
f"The PROFILE '{profile}' set in the shell environment is not valid.\n" | ||
f"Valid Nextflow profiles are '{', '.join(valid_nextflow_profiles)}'." | ||
) | ||
|
||
def _run_pytests(self): | ||
"""Given a module name, run tests.""" | ||
# Print nice divider line | ||
console = rich.console.Console() | ||
console.rule(self.module_name, style="black") | ||
|
||
# Set pytest arguments | ||
command_args = ["--tag", f"{self.module_name}", "--symlink", "--keep-workflow-wd", "--git-aware"] | ||
command_args += self.pytest_args | ||
|
||
# Run pytest | ||
log.info(f"Running pytest for module '{self.module_name}'") | ||
sys.exit(pytest.main(command_args)) |
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,67 @@ | ||
"""Generate the name of a BioContainers mulled image version 2.""" | ||
|
||
|
||
import logging | ||
import re | ||
from packaging.version import Version, InvalidVersion | ||
from typing import Iterable, Tuple, List | ||
|
||
import requests | ||
from galaxy.tool_util.deps.mulled.util import build_target, v2_image_name | ||
|
||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
class MulledImageNameGenerator: | ||
""" | ||
Define a service class for generating BioContainers version 2 mulled image names. | ||
Adapted from https://gist.github.com/natefoo/19cefeedd1942c30f9d88027a61b3f83. | ||
""" | ||
|
||
_split_pattern = re.compile(r"==?") | ||
|
||
@classmethod | ||
def parse_targets(cls, specifications: Iterable[str]) -> List[Tuple[str, str]]: | ||
""" | ||
Parse tool, version pairs from specification strings. | ||
Args: | ||
specifications: An iterable of strings that contain tools and their versions. | ||
""" | ||
result = [] | ||
for spec in specifications: | ||
try: | ||
tool, version = cls._split_pattern.split(spec, maxsplit=1) | ||
except ValueError: | ||
raise ValueError( | ||
f"The specification {spec} does not have the expected format <tool==version> or <tool=version>." | ||
) from None | ||
try: | ||
Version(version) | ||
except InvalidVersion: | ||
raise ValueError(f"{version} in {spec} is not a PEP440 compliant version specification.") from None | ||
result.append((tool.strip(), version.strip())) | ||
return result | ||
|
||
@classmethod | ||
def generate_image_name(cls, targets: Iterable[Tuple[str, str]], build_number: int = 0) -> str: | ||
""" | ||
Generate the name of a BioContainers mulled image version 2. | ||
Args: | ||
targets: One or more tool, version pairs of the multi-tool container image. | ||
build_number: The build number for this image. This is an incremental value that starts at zero. | ||
""" | ||
return v2_image_name([build_target(name, version) for name, version in targets], image_build=str(build_number)) | ||
|
||
@classmethod | ||
def image_exists(cls, image_name: str) -> bool: | ||
"""Check whether a given BioContainers image name exists via a call to the quay.io API.""" | ||
response = requests.get(f"https://quay.io/biocontainers/{image_name}/", allow_redirects=True) | ||
log.debug(response.text) | ||
return response.status_code == 200 |
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,4 +1,5 @@ | ||
click | ||
galaxy-tool-util | ||
GitPython | ||
jinja2 | ||
jsonschema>=3.0 | ||
|
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,27 @@ | ||
"""Test the 'modules test' command which runs module pytests.""" | ||
import os | ||
import pytest | ||
|
||
import nf_core.modules | ||
|
||
|
||
def test_modules_test_check_inputs(self): | ||
"""Test the check_inputs() function - raise UserWarning because module doesn't exist""" | ||
cwd = os.getcwd() | ||
os.chdir(self.nfcore_modules) | ||
meta_builder = nf_core.modules.ModulesTest("none", True, "") | ||
with pytest.raises(UserWarning) as excinfo: | ||
meta_builder._check_inputs() | ||
os.chdir(cwd) | ||
assert "Cannot find directory" in str(excinfo.value) | ||
|
||
|
||
def test_modules_test_no_name_no_prompts(self): | ||
"""Test the check_inputs() function - raise UserWarning prompts are deactivated and module name is not provided.""" | ||
cwd = os.getcwd() | ||
os.chdir(self.nfcore_modules) | ||
meta_builder = nf_core.modules.ModulesTest(None, True, "") | ||
with pytest.raises(UserWarning) as excinfo: | ||
meta_builder._check_inputs() | ||
os.chdir(cwd) | ||
assert "Tool name not provided and prompts deactivated." in str(excinfo.value) |
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
Oops, something went wrong.