Skip to content

Commit

Permalink
Merge branch 'dev' into feature/python-typing/list.py
Browse files Browse the repository at this point in the history
  • Loading branch information
kedhammar authored Oct 18, 2023
2 parents 94704fa + 8d0e1f3 commit c699bb5
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 20 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@

### Modules

- Added stub test creation to `create_test_yml` ([#2476](https://github.com/nf-core/tools/pull/2476))

### Subworkflows

- Added stub test creation to `create_test_yml` ([#2476](https://github.com/nf-core/tools/pull/2476))

### General

# [v2.10 - Nickel Ostrich](https://github.com/nf-core/tools/releases/tag/2.10) + [2023-09-25]
Expand Down Expand Up @@ -45,6 +49,7 @@
### Linting

- Add new command `nf-core subworkflows lint` ([#2379](https://github.com/nf-core/tools/pull/2379))
- Check for existence of test profile ([#2478](https://github.com/nf-core/tools/pull/2478))

### Modules

Expand Down
33 changes: 33 additions & 0 deletions nf_core/lint/nextflow_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ def nextflow_config(self):
lint:
nextflow_config: False
**The configuration should contain the following or the test will fail:**
* A ``test`` configuration profile should exist.
"""
passed = []
warned = []
Expand Down Expand Up @@ -312,4 +317,32 @@ def nextflow_config(self):
)
)

# Check for the availability of the "test" configuration profile by parsing nextflow.config
with open(os.path.join(self.wf_path, "nextflow.config"), "r") as f:
content = f.read()

# Remove comments
cleaned_content = re.sub(r"//.*", "", content)
cleaned_content = re.sub(r"/\*.*?\*/", "", content, flags=re.DOTALL)

match = re.search(r"\bprofiles\s*{", cleaned_content)
if not match:
failed.append("nextflow.config does not contain `profiles` scope, but `test` profile is required")
else:
# Extract profiles scope content and check for test profile
start = match.end()
end = start
brace_count = 1
while brace_count > 0 and end < len(content):
if cleaned_content[end] == "{":
brace_count += 1
elif cleaned_content[end] == "}":
brace_count -= 1
end += 1
profiles_content = cleaned_content[start : end - 1].strip()
if re.search(r"\btest\s*{", profiles_content):
passed.append("nextflow.config contains configuration profile `test`")
else:
failed.append("nextflow.config does not contain configuration profile `test`")

return {"passed": passed, "warned": warned, "failed": failed, "ignored": ignored}
28 changes: 19 additions & 9 deletions nf_core/modules/test_yml_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,19 @@ def build_all_tests(self):
"""
Go over each entry point and build structure
"""

# Build the other tests
for entry_point in self.entry_points:
ep_test = self.build_single_test(entry_point)
if ep_test:
self.tests.append(ep_test)

def build_single_test(self, entry_point):
# Build the stub test
stub_test = self.build_single_test(self.entry_points[0], stub=True)
if stub_test:
self.tests.append(stub_test)

def build_single_test(self, entry_point, stub=False):
"""Given the supplied cli flags, prompt for any that are missing.
Returns: Test command
Expand All @@ -158,6 +165,8 @@ def build_single_test(self, entry_point):
"tags": [],
"files": [],
}
stub_option = " -stub" if stub else ""
stub_name = " stub" if stub else ""

# Print nice divider line
console = rich.console.Console()
Expand All @@ -166,7 +175,7 @@ def build_single_test(self, entry_point):
log.info(f"Building test meta for entry point '{entry_point}'")

while ep_test["name"] == "":
default_val = f"{self.module_name.replace('/', ' ')} {entry_point}"
default_val = f"{self.module_name.replace('/', ' ')} {entry_point}{stub_name}"
if self.no_prompts:
ep_test["name"] = default_val
else:
Expand All @@ -178,6 +187,7 @@ def build_single_test(self, entry_point):
default_val = (
f"nextflow run ./tests/modules/{self.org}/{self.module_name} -entry {entry_point} "
f"-c ./tests/config/nextflow.config"
f"{stub_option}"
)
if self.no_prompts:
ep_test["command"] = default_val
Expand All @@ -200,7 +210,7 @@ def build_single_test(self, entry_point):
).strip()
ep_test["tags"] = [t.strip() for t in prompt_tags.split(",")]

ep_test["files"] = self.get_md5_sums(ep_test["command"])
ep_test["files"] = self.get_md5_sums(ep_test["command"], stub=stub)

return ep_test

Expand All @@ -227,7 +237,7 @@ def _md5(self, fname):
md5sum = hash_md5.hexdigest()
return md5sum

def create_test_file_dict(self, results_dir, is_repeat=False):
def create_test_file_dict(self, results_dir, is_repeat=False, stub=False):
"""Walk through directory and collect md5 sums"""
test_files = []
for root, _, files in os.walk(results_dir, followlinks=True):
Expand All @@ -236,13 +246,13 @@ def create_test_file_dict(self, results_dir, is_repeat=False):
# add the key here so that it comes first in the dict
test_file = {"path": file_path}
# Check that this isn't an empty file
if self.check_if_empty_file(file_path):
if self.check_if_empty_file(file_path) and not stub:
if not is_repeat:
self.errors.append(f"Empty file found! '{os.path.basename(file_path)}'")
# Add the md5 anyway, linting should fail later and can be manually removed if needed.
# Originally we skipped this if empty, but then it's too easy to miss the warning.
# Equally, if a file is legitimately empty we don't want to prevent this from working.
if filename != "versions.yml":
if filename != "versions.yml" and not stub:
# Only add md5sum if the file is not versions.yml
file_md5 = self._md5(file_path)
test_file["md5sum"] = file_md5
Expand All @@ -254,7 +264,7 @@ def create_test_file_dict(self, results_dir, is_repeat=False):

return test_files

def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None):
def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None, stub=False):
"""
Recursively go through directories and subdirectories
and generate tuples of (<file_path>, <md5sum>)
Expand All @@ -276,11 +286,11 @@ def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None):
log.error(f"Directory '{results_dir}' does not exist")
results_dir = None

test_files = self.create_test_file_dict(results_dir=results_dir)
test_files = self.create_test_file_dict(results_dir=results_dir, stub=stub)

# If test was repeated, compare the md5 sums
if results_dir_repeat:
test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat, is_repeat=True)
test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat, is_repeat=True, stub=stub)

# Compare both test.yml files
for i in range(len(test_files)):
Expand Down
32 changes: 21 additions & 11 deletions nf_core/subworkflows/test_yml_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,19 @@ def build_all_tests(self):
"""
Go over each entry point and build structure
"""

# Build the other tests
for entry_point in self.entry_points:
ep_test = self.build_single_test(entry_point)
if ep_test:
self.tests.append(ep_test)

def build_single_test(self, entry_point):
# Build the stub test
stub_test = self.build_single_test(self.entry_points[0], stub=True)
if stub_test:
self.tests.append(stub_test)

def build_single_test(self, entry_point, stub=False):
"""Given the supplied cli flags, prompt for any that are missing.
Returns: Test command
Expand All @@ -161,6 +168,8 @@ def build_single_test(self, entry_point):
"tags": [],
"files": [],
}
stub_option = " -stub" if stub else ""
stub_name = " stub" if stub else ""

# Print nice divider line
console = rich.console.Console()
Expand All @@ -169,14 +178,14 @@ def build_single_test(self, entry_point):
log.info(f"Building test meta for entry point '{entry_point}'")

while ep_test["name"] == "":
default_val = f"{self.subworkflow} {entry_point}"
default_val = f"{self.subworkflow} {entry_point}{stub_name}"
if self.no_prompts:
ep_test["name"] = default_val
else:
ep_test["name"] = rich.prompt.Prompt.ask("[violet]Test name", default=default_val).strip()

while ep_test["command"] == "":
default_val = f"nextflow run ./tests/subworkflows/{self.modules_repo.repo_path}/{self.subworkflow} -entry {entry_point} -c ./tests/config/nextflow.config"
default_val = f"nextflow run ./tests/subworkflows/{self.modules_repo.repo_path}/{self.subworkflow} -entry {entry_point} -c ./tests/config/nextflow.config{stub_option}"
if self.no_prompts:
ep_test["command"] = default_val
else:
Expand All @@ -195,7 +204,7 @@ def build_single_test(self, entry_point):
).strip()
ep_test["tags"] = [t.strip() for t in prompt_tags.split(",")]

ep_test["files"] = self.get_md5_sums(ep_test["command"])
ep_test["files"] = self.get_md5_sums(ep_test["command"], stub=stub)

return ep_test

Expand Down Expand Up @@ -244,7 +253,7 @@ def _md5(self, fname):
md5sum = hash_md5.hexdigest()
return md5sum

def create_test_file_dict(self, results_dir, is_repeat=False):
def create_test_file_dict(self, results_dir, is_repeat=False, stub=False):
"""Walk through directory and collect md5 sums"""
test_files = []
for root, _, files in os.walk(results_dir, followlinks=True):
Expand All @@ -256,14 +265,15 @@ def create_test_file_dict(self, results_dir, is_repeat=False):
# add the key here so that it comes first in the dict
test_file = {"path": file_path}
# Check that this isn't an empty file
if self.check_if_empty_file(file_path):
if self.check_if_empty_file(file_path) and not stub:
if not is_repeat:
self.errors.append(f"Empty file found! '{os.path.basename(file_path)}'")
# Add the md5 anyway, linting should fail later and can be manually removed if needed.
# Originally we skipped this if empty, but then it's too easy to miss the warning.
# Equally, if a file is legitimately empty we don't want to prevent this from working.
file_md5 = self._md5(file_path)
test_file["md5sum"] = file_md5
if not stub:
file_md5 = self._md5(file_path)
test_file["md5sum"] = file_md5
# Switch out the results directory path with the expected 'output' directory
test_file["path"] = file_path.replace(results_dir, "output")
test_files.append(test_file)
Expand All @@ -272,7 +282,7 @@ def create_test_file_dict(self, results_dir, is_repeat=False):

return test_files

def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None):
def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None, stub=False):
"""
Recursively go through directories and subdirectories
and generate tuples of (<file_path>, <md5sum>)
Expand All @@ -294,11 +304,11 @@ def get_md5_sums(self, command, results_dir=None, results_dir_repeat=None):
log.error(f"Directory '{results_dir}' does not exist")
results_dir = None

test_files = self.create_test_file_dict(results_dir=results_dir)
test_files = self.create_test_file_dict(results_dir=results_dir, stub=stub)

# If test was repeated, compare the md5 sums
if results_dir_repeat:
test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat, is_repeat=True)
test_files_repeat = self.create_test_file_dict(results_dir=results_dir_repeat, is_repeat=True, stub=stub)

# Compare both test.yml files
for i in range(len(test_files)):
Expand Down
20 changes: 20 additions & 0 deletions tests/lint/nextflow_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import os
import re

import nf_core.create
import nf_core.lint

Expand Down Expand Up @@ -33,3 +36,20 @@ def test_nextflow_config_dev_in_release_mode_failed(self):
result = lint_obj.nextflow_config()
assert len(result["failed"]) > 0
assert len(result["warned"]) == 0


def test_nextflow_config_missing_test_profile_failed(self):
"""Test failure if config file does not contain `test` profile."""
new_pipeline = self._make_pipeline_copy()
# Change the name of the test profile so there is no such profile
nf_conf_file = os.path.join(new_pipeline, "nextflow.config")
with open(nf_conf_file, "r") as f:
content = f.read()
fail_content = re.sub(r"\btest\b", "testfail", content)
with open(nf_conf_file, "w") as f:
f.write(fail_content)
lint_obj = nf_core.lint.PipelineLint(new_pipeline)
lint_obj._load_pipeline_config()
result = lint_obj.nextflow_config()
assert len(result["failed"]) > 0
assert len(result["warned"]) == 0
1 change: 1 addition & 0 deletions tests/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def test_sphinx_md_files(self):
test_nextflow_config_bad_name_fail,
test_nextflow_config_dev_in_release_mode_failed,
test_nextflow_config_example_pass,
test_nextflow_config_missing_test_profile_failed,
)
from .lint.version_consistency import test_version_consistency

Expand Down

0 comments on commit c699bb5

Please sign in to comment.