1010"""
1111
1212import os
13+ import shutil
1314import subprocess
1415import sys
1516
@@ -34,6 +35,39 @@ def _cli_script(setup, args):
3435 return f"{ setup } \n import sys\n sys.argv = { args !r} \n from nf_core.__main__ import run_nf_core\n run_nf_core()"
3536
3637
38+ @pytest .fixture (scope = "session" )
39+ def generated_pipeline (tmp_path_factory ):
40+ """Render a real pipeline scaffold once, reused by the realistic benchmarks.
41+
42+ Generation is expensive and identical for every consumer, so it happens a
43+ single time per session and outside any measured rounds. Consumers that
44+ mutate the pipeline (e.g. installing a module) must work on a copy so the
45+ shared scaffold stays pristine and the suite is order-independent.
46+ """
47+ from nf_core .pipelines .create .create import PipelineCreate
48+
49+ pipeline_dir = tmp_path_factory .mktemp ("pipeline_fixture" ) / "pipeline"
50+ PipelineCreate ("benchmark" , "benchmark pipeline" , "nf-core" , outdir = pipeline_dir , no_git = True ).init_pipeline ()
51+ return pipeline_dir
52+
53+
54+ @pytest .fixture (scope = "session" )
55+ def modules_cache ():
56+ """Ensure a local nf-core/modules clone exists, without ever fetching.
57+
58+ Constructing ``ModulesRepo`` clones the remote into ``NFCORE_DIR`` only if it
59+ is not already present; ``no_pull_global`` suppresses the ``git fetch`` on an
60+ existing clone. In CI the clone is warmed by an earlier, network-enabled
61+ workflow step, so this fixture (and every measured round) runs offline. When
62+ run locally against a cold cache it performs the one-off clone here, outside
63+ the measured rounds.
64+ """
65+ from nf_core .modules .modules_repo import ModulesRepo
66+
67+ ModulesRepo .no_pull_global = True
68+ ModulesRepo ()
69+
70+
3771def test_import_main_startup (benchmark ):
3872 """Wall-time cost of importing the CLI entry point in a fresh interpreter."""
3973 _benchmark_command (benchmark , IMPORT_CMD )
@@ -71,16 +105,32 @@ def test_pipelines_create_startup(benchmark, tmp_path):
71105 _benchmark_command (benchmark , [sys .executable , "-c" , _cli_script (setup , args )])
72106
73107
74- def test_modules_install_startup (benchmark , tmp_path ):
75- """Start module installation, stubbing only the remote clone and resulting writes."""
76- args = ["nf-core" , "modules" , "install" , "fastp" , "-d" , str (tmp_path )]
77- setup = """import nf_core.modules.install as install_module
78- install_module.ModuleInstall = type(
79- "ModuleInstall",
80- (),
81- {
82- "__init__": lambda self, *args, **kwargs: None,
83- "install": lambda self, *args, **kwargs: True,
84- },
85- )"""
108+ def test_modules_install_startup (benchmark , generated_pipeline , modules_cache , tmp_path ):
109+ """Install a real module into a real pipeline, offline.
110+
111+ The pipeline scaffold (``generated_pipeline``) and the nf-core/modules clone
112+ (``modules_cache``) are both prepared once, outside the measured rounds. The
113+ rounds then run the real install (git checkout + file copy + modules.json
114+ write) against a private copy of the scaffold. ``no_pull_global`` suppresses
115+ the per-round ``git fetch`` and ``--force`` avoids the re-install prompt, so
116+ every round is deterministic and network-free.
117+ """
118+ target = tmp_path / "pipeline"
119+ shutil .copytree (generated_pipeline , target )
120+ args = ["nf-core" , "modules" , "install" , "fastp" , "-d" , str (target ), "--force" ]
121+ setup = "from nf_core.modules.modules_repo import ModulesRepo\n ModulesRepo.no_pull_global = True"
122+ _benchmark_command (benchmark , [sys .executable , "-c" , _cli_script (setup , args )])
123+
124+
125+ def test_pipelines_schema_lint_startup (benchmark , generated_pipeline ):
126+ """Lint a real, freshly generated pipeline schema.
127+
128+ The schema fixture is rendered once by ``generated_pipeline`` (outside the
129+ measured rounds); the benchmark then runs the real lint (JSON parse +
130+ JSON-Schema validation + default-param checks). Only ``fetch_wf_config`` is
131+ stubbed to skip the ``nextflow config`` JVM subprocess, which would
132+ otherwise dominate wall time and require Nextflow on the runner.
133+ """
134+ args = ["nf-core" , "pipelines" , "schema" , "lint" , str (generated_pipeline )]
135+ setup = "import nf_core.utils\n nf_core.utils.fetch_wf_config = lambda *args, **kwargs: {}"
86136 _benchmark_command (benchmark , [sys .executable , "-c" , _cli_script (setup , args )])
0 commit comments