Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new command modules test #1526

Merged
merged 25 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion nf_core/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
{
"name": "Developing new modules",
"commands": ["create", "create-test-yml", "lint", "bump-versions", "mulled"],
"commands": ["create", "create-test-yml", "lint", "bump-versions", "mulled", "test"],
},
],
}
Expand Down Expand Up @@ -709,6 +709,26 @@ def mulled(specifications, build_number):
sys.exit(1)


# nf-core modules test
@modules.command("test")
@click.pass_context
@click.argument("tool", type=str, required=False, metavar="<tool> or <tool/subtool>")
@click.option("-p", "--no-prompts", is_flag=True, default=False, help="Use defaults without prompting")
@click.option("-a", "--pytest_args", type=str, required=False, multiple=True, help="Additional pytest arguments")
def test_module(ctx, tool, no_prompts, pytest_args):
"""
Run module tests locally.

Given the name of a module, runs the Nextflow test command.
"""
try:
meta_builder = nf_core.modules.ModulesTest(tool, no_prompts, pytest_args)
meta_builder.run()
except UserWarning as e:
log.critical(e)
sys.exit(1)


# nf-core schema subcommands
@nf_core_cli.group()
def schema():
Expand Down
1 change: 1 addition & 0 deletions nf_core/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from .remove import ModuleRemove
from .info import ModuleInfo
from .mulled import MulledImageNameGenerator
from .module_test import ModulesTest
126 changes: 126 additions & 0 deletions nf_core/modules/module_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python
"""
The ModulesTest class runs the tests locally
"""

import logging
import questionary
import os
import pytest
import sys
import rich

import nf_core.utils
from .modules_repo import ModulesRepo

log = logging.getLogger(__name__)


class ModulesTest(object):
"""
Class to run module pytests.

...

Attributes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm being picky, these are for the __init__() def really, so you could probably move them there. Unless this is some kind of Pythonic way of documenting stuff that I'm not aware of. Low prio though.

----------
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():
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
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,
fabianegli marked this conversation as resolved.
Show resolved Hide resolved
no_prompts=False,
pytest_args="",
):
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
self.module_name = module_name
self.no_prompts = no_prompts
self.pytest_args = pytest_args
self.module_dir = None

ggabernet marked this conversation as resolved.
Show resolved Hide resolved
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.__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(
f"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(
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
"Tool name:",
choices=modules_repo.modules_avail_module_names,
style=nf_core.utils.nfcore_question_style,
).ask()
self.module_dir = os.path.join("modules", *self.module_name.split("/"))
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved

# First, sanity check that the module directory exists
if not os.path.isdir(self.module_dir):
raise UserWarning(f"Cannot find directory '{self.module_dir}'. Should be TOOL/SUBTOOL or TOOL")

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 env var '$PROFILE' to an empty string as not set.\n"
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
"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"],
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
}
answer = questionary.unsafe_prompt([question], style=nf_core.utils.nfcore_question_style)
profile = answer["profile"].lower()
if profile in ["singularity", "conda"]:
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
os.environ["PROFILE"] = profile
log.info(f"Setting env var '$PROFILE' to '{profile}'")

def __run_pytests(self):
"""Given a module name, run tests."""
# Print nice divider line
console = rich.console.Console()
console.print("[black]" + "─" * console.width)
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved

# Set pytest arguments
command_args = ["--tag", f"{self.module_name}", "--symlink", "--keep-workflow-wd"]
mirpedrol marked this conversation as resolved.
Show resolved Hide resolved
command_args += self.pytest_args

# Run pytest
log.info(f"Running pytest for module '{self.module_name}'")
sys.exit(pytest.main(command_args))
16 changes: 16 additions & 0 deletions tests/modules/module_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""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)
4 changes: 4 additions & 0 deletions tests/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ def test_modulesrepo_class(self):
test_modules_bump_versions_fail,
test_modules_bump_versions_fail_unknown_version,
)

from .modules.module_test import (
test_modules_test_check_inputs,
)