Skip to content

Commit 4df67cd

Browse files
committed
fix(tests): session-scoped scaffold cache + timeout + dead code removal
- Add timeout=300 and returncode check to _run_release_script() to fail fast with clear output on script hangs or failures - Remove unused import specify_cli, _SOURCE_TEMPLATES, bundled_project fixture - Add session-scoped scaffolded_sh/scaffolded_ps fixtures that scaffold once per agent and reuse the output directory across all invariant tests - Reduces test_core_pack_scaffold runtime from ~175s to ~51s (3.4x faster) - Parity tests still scaffold independently for isolation
1 parent 629257e commit 4df67cd

1 file changed

Lines changed: 63 additions & 58 deletions

File tree

tests/test_core_pack_scaffold.py

Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
import pytest
4040
import yaml
4141

42-
import specify_cli
4342
from specify_cli import (
4443
AGENT_CONFIG,
4544
_TOML_AGENTS,
@@ -86,8 +85,15 @@ def _run_release_script(agent: str, script_type: str, bash: str, output_dir: Pat
8685
capture_output=True, text=True,
8786
cwd=str(_REPO_ROOT),
8887
env=env,
88+
timeout=300,
8989
)
9090

91+
if result.returncode != 0:
92+
pytest.fail(
93+
f"Release script failed with exit code {result.returncode}\n"
94+
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
95+
)
96+
9197
zip_pattern = f"spec-kit-template-{agent}-{script_type}-v0.0.0.zip"
9298
zip_path = output_dir / zip_pattern
9399
if not zip_path.exists():
@@ -102,7 +108,6 @@ def _run_release_script(agent: str, script_type: str, bash: str, output_dir: Pat
102108
# ---------------------------------------------------------------------------
103109

104110
# Number of source command templates (one per .md file in templates/commands/)
105-
_SOURCE_TEMPLATES: list[str] = []
106111

107112

108113
def _commands_dir() -> Path:
@@ -167,10 +172,32 @@ def source_template_stems() -> list[str]:
167172
return _get_source_template_stems()
168173

169174

170-
@pytest.fixture
171-
def bundled_project(tmp_path):
172-
"""Run scaffold_from_core_pack for each test; caller picks agent."""
173-
return tmp_path
175+
@pytest.fixture(scope="session")
176+
def scaffolded_sh(tmp_path_factory):
177+
"""Session-scoped cache: scaffold once per agent with script_type='sh'."""
178+
cache = {}
179+
def _get(agent: str) -> Path:
180+
if agent not in cache:
181+
project = tmp_path_factory.mktemp(f"scaffold_sh_{agent}")
182+
ok = scaffold_from_core_pack(project, agent, "sh")
183+
assert ok, f"scaffold_from_core_pack returned False for agent '{agent}'"
184+
cache[agent] = project
185+
return cache[agent]
186+
return _get
187+
188+
189+
@pytest.fixture(scope="session")
190+
def scaffolded_ps(tmp_path_factory):
191+
"""Session-scoped cache: scaffold once per agent with script_type='ps'."""
192+
cache = {}
193+
def _get(agent: str) -> Path:
194+
if agent not in cache:
195+
project = tmp_path_factory.mktemp(f"scaffold_ps_{agent}")
196+
ok = scaffold_from_core_pack(project, agent, "ps")
197+
assert ok, f"scaffold_from_core_pack returned False for agent '{agent}'"
198+
cache[agent] = project
199+
return cache[agent]
200+
return _get
174201

175202

176203
# ---------------------------------------------------------------------------
@@ -185,35 +212,29 @@ def bundled_project(tmp_path):
185212
# ---------------------------------------------------------------------------
186213

187214
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
188-
def test_scaffold_creates_specify_scripts(tmp_path, agent):
215+
def test_scaffold_creates_specify_scripts(agent, scaffolded_sh):
189216
"""scaffold_from_core_pack copies at least one script into .specify/scripts/."""
190-
project = tmp_path / "proj"
191-
ok = scaffold_from_core_pack(project, agent, "sh")
192-
assert ok, f"scaffold_from_core_pack returned False for agent '{agent}'"
217+
project = scaffolded_sh(agent)
193218

194219
scripts_dir = project / ".specify" / "scripts" / "bash"
195220
assert scripts_dir.is_dir(), f".specify/scripts/bash/ missing for agent '{agent}'"
196221
assert any(scripts_dir.iterdir()), f".specify/scripts/bash/ is empty for agent '{agent}'"
197222

198223

199224
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
200-
def test_scaffold_creates_specify_templates(tmp_path, agent):
225+
def test_scaffold_creates_specify_templates(agent, scaffolded_sh):
201226
"""scaffold_from_core_pack copies at least one page template into .specify/templates/."""
202-
project = tmp_path / "proj"
203-
ok = scaffold_from_core_pack(project, agent, "sh")
204-
assert ok
227+
project = scaffolded_sh(agent)
205228

206229
tpl_dir = project / ".specify" / "templates"
207230
assert tpl_dir.is_dir(), f".specify/templates/ missing for agent '{agent}'"
208231
assert any(tpl_dir.iterdir()), ".specify/templates/ is empty"
209232

210233

211234
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
212-
def test_scaffold_command_dir_location(tmp_path, agent, source_template_stems):
235+
def test_scaffold_command_dir_location(agent, scaffolded_sh, source_template_stems):
213236
"""Command files land in the directory declared by AGENT_CONFIG."""
214-
project = tmp_path / "proj"
215-
ok = scaffold_from_core_pack(project, agent, "sh")
216-
assert ok
237+
project = scaffolded_sh(agent)
217238

218239
cmd_dir = _expected_cmd_dir(project, agent)
219240
assert cmd_dir.is_dir(), (
@@ -226,11 +247,9 @@ def test_scaffold_command_dir_location(tmp_path, agent, source_template_stems):
226247
# ---------------------------------------------------------------------------
227248

228249
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
229-
def test_scaffold_command_file_count(tmp_path, agent, source_template_stems):
250+
def test_scaffold_command_file_count(agent, scaffolded_sh, source_template_stems):
230251
"""One command file is generated per source template for every agent."""
231-
project = tmp_path / "proj"
232-
ok = scaffold_from_core_pack(project, agent, "sh")
233-
assert ok
252+
project = scaffolded_sh(agent)
234253

235254
cmd_dir = _expected_cmd_dir(project, agent)
236255
generated = _list_command_files(cmd_dir, agent)
@@ -241,11 +260,9 @@ def test_scaffold_command_file_count(tmp_path, agent, source_template_stems):
241260

242261

243262
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
244-
def test_scaffold_command_file_names(tmp_path, agent, source_template_stems):
263+
def test_scaffold_command_file_names(agent, scaffolded_sh, source_template_stems):
245264
"""Each source template stem maps to a corresponding speckit.<stem>.<ext> file."""
246-
project = tmp_path / "proj"
247-
ok = scaffold_from_core_pack(project, agent, "sh")
248-
assert ok
265+
project = scaffolded_sh(agent)
249266

250267
cmd_dir = _expected_cmd_dir(project, agent)
251268
for stem in source_template_stems:
@@ -264,10 +281,9 @@ def test_scaffold_command_file_names(tmp_path, agent, source_template_stems):
264281
# ---------------------------------------------------------------------------
265282

266283
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
267-
def test_no_unresolved_script_placeholder(tmp_path, agent):
284+
def test_no_unresolved_script_placeholder(agent, scaffolded_sh):
268285
"""{SCRIPT} must not appear in any generated command file."""
269-
project = tmp_path / "proj"
270-
scaffold_from_core_pack(project, agent, "sh")
286+
project = scaffolded_sh(agent)
271287

272288
cmd_dir = _expected_cmd_dir(project, agent)
273289
for f in cmd_dir.rglob("*"):
@@ -279,10 +295,9 @@ def test_no_unresolved_script_placeholder(tmp_path, agent):
279295

280296

281297
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
282-
def test_no_unresolved_agent_placeholder(tmp_path, agent):
298+
def test_no_unresolved_agent_placeholder(agent, scaffolded_sh):
283299
"""__AGENT__ must not appear in any generated command file."""
284-
project = tmp_path / "proj"
285-
scaffold_from_core_pack(project, agent, "sh")
300+
project = scaffolded_sh(agent)
286301

287302
cmd_dir = _expected_cmd_dir(project, agent)
288303
for f in cmd_dir.rglob("*"):
@@ -294,10 +309,9 @@ def test_no_unresolved_agent_placeholder(tmp_path, agent):
294309

295310

296311
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
297-
def test_no_unresolved_args_placeholder(tmp_path, agent):
312+
def test_no_unresolved_args_placeholder(agent, scaffolded_sh):
298313
"""{ARGS} must not appear in any generated command file (replaced with agent-specific token)."""
299-
project = tmp_path / "proj"
300-
scaffold_from_core_pack(project, agent, "sh")
314+
project = scaffolded_sh(agent)
301315

302316
cmd_dir = _expected_cmd_dir(project, agent)
303317
for f in cmd_dir.rglob("*"):
@@ -317,14 +331,13 @@ def test_no_unresolved_args_placeholder(tmp_path, agent):
317331

318332

319333
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
320-
def test_argument_token_format(tmp_path, agent):
334+
def test_argument_token_format(agent, scaffolded_sh):
321335
"""For templates that carry an {ARGS} token:
322336
- TOML agents must emit {{args}}
323337
- Markdown agents must emit $ARGUMENTS
324338
Templates without {ARGS} (e.g. implement, plan) are skipped.
325339
"""
326-
project = tmp_path / "proj"
327-
scaffold_from_core_pack(project, agent, "sh")
340+
project = scaffolded_sh(agent)
328341

329342
cmd_dir = _expected_cmd_dir(project, agent)
330343

@@ -350,10 +363,9 @@ def test_argument_token_format(tmp_path, agent):
350363

351364

352365
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
353-
def test_path_rewrites_applied(tmp_path, agent):
366+
def test_path_rewrites_applied(agent, scaffolded_sh):
354367
"""Bare scripts/ and templates/ paths must be rewritten to .specify/ variants."""
355-
project = tmp_path / "proj"
356-
scaffold_from_core_pack(project, agent, "sh")
368+
project = scaffolded_sh(agent)
357369

358370
cmd_dir = _expected_cmd_dir(project, agent)
359371
for f in cmd_dir.rglob("*"):
@@ -374,10 +386,9 @@ def test_path_rewrites_applied(tmp_path, agent):
374386
# ---------------------------------------------------------------------------
375387

376388
@pytest.mark.parametrize("agent", sorted(_TOML_AGENTS))
377-
def test_toml_format_valid(tmp_path, agent):
389+
def test_toml_format_valid(agent, scaffolded_sh):
378390
"""TOML agents: every command file must have description and prompt fields."""
379-
project = tmp_path / "proj"
380-
scaffold_from_core_pack(project, agent, "sh")
391+
project = scaffolded_sh(agent)
381392

382393
cmd_dir = _expected_cmd_dir(project, agent)
383394
for f in cmd_dir.glob("speckit.*.toml"):
@@ -398,10 +409,9 @@ def test_toml_format_valid(tmp_path, agent):
398409

399410

400411
@pytest.mark.parametrize("agent", _MARKDOWN_AGENTS)
401-
def test_markdown_has_frontmatter(tmp_path, agent):
412+
def test_markdown_has_frontmatter(agent, scaffolded_sh):
402413
"""Markdown agents: every command file must start with valid YAML frontmatter."""
403-
project = tmp_path / "proj"
404-
scaffold_from_core_pack(project, agent, "sh")
414+
project = scaffolded_sh(agent)
405415

406416
cmd_dir = _expected_cmd_dir(project, agent)
407417
for f in _list_command_files(cmd_dir, agent):
@@ -422,11 +432,9 @@ def test_markdown_has_frontmatter(tmp_path, agent):
422432
# 6. Copilot-specific: companion .prompt.md files
423433
# ---------------------------------------------------------------------------
424434

425-
def test_copilot_companion_prompt_files(tmp_path, source_template_stems):
435+
def test_copilot_companion_prompt_files(scaffolded_sh, source_template_stems):
426436
"""Copilot: a speckit.<stem>.prompt.md companion is created for every .agent.md file."""
427-
project = tmp_path / "proj"
428-
ok = scaffold_from_core_pack(project, "copilot", "sh")
429-
assert ok
437+
project = scaffolded_sh("copilot")
430438

431439
prompts_dir = project / ".github" / "prompts"
432440
assert prompts_dir.is_dir(), ".github/prompts/ not created for copilot"
@@ -438,10 +446,9 @@ def test_copilot_companion_prompt_files(tmp_path, source_template_stems):
438446
)
439447

440448

441-
def test_copilot_prompt_file_content(tmp_path, source_template_stems):
449+
def test_copilot_prompt_file_content(scaffolded_sh, source_template_stems):
442450
"""Copilot companion .prompt.md files must reference their parent .agent.md."""
443-
project = tmp_path / "proj"
444-
scaffold_from_core_pack(project, "copilot", "sh")
451+
project = scaffolded_sh("copilot")
445452

446453
prompts_dir = project / ".github" / "prompts"
447454
for stem in source_template_stems:
@@ -457,11 +464,9 @@ def test_copilot_prompt_file_content(tmp_path, source_template_stems):
457464
# ---------------------------------------------------------------------------
458465

459466
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
460-
def test_scaffold_powershell_variant(tmp_path, agent, source_template_stems):
467+
def test_scaffold_powershell_variant(agent, scaffolded_ps, source_template_stems):
461468
"""scaffold_from_core_pack with script_type='ps' creates correct files."""
462-
project = tmp_path / "proj"
463-
ok = scaffold_from_core_pack(project, agent, "ps")
464-
assert ok
469+
project = scaffolded_ps(agent)
465470

466471
scripts_dir = project / ".specify" / "scripts" / "powershell"
467472
assert scripts_dir.is_dir(), f".specify/scripts/powershell/ missing for '{agent}'"

0 commit comments

Comments
 (0)