Skip to content

Commit 3acff69

Browse files
committed
dogfood: Add conversion to file-based integ tests
1 parent 5e73326 commit 3acff69

5 files changed

Lines changed: 648 additions & 19 deletions

File tree

build_tools/sharpy_dogfood/cli.py

Lines changed: 212 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
CLI for the Sharpy Dogfooding Tool.
44
55
Usage:
6-
python -m sharpy_dogfood [options]
7-
python -m sharpy_dogfood --iterations 5
8-
python -m sharpy_dogfood --output-dir ./my_output
6+
python -m sharpy_dogfood [run] [options] # Run dogfood iterations
7+
python -m sharpy_dogfood convert [options] # Convert outputs to integration tests
98
"""
109

1110
import argparse
@@ -15,10 +14,11 @@
1514

1615
from .config import Config
1716
from .orchestrator import DogfoodOrchestrator
17+
from .convert import convert_dogfood_to_test, convert_all_dogfood_outputs
1818

1919

20-
def parse_args() -> argparse.Namespace:
21-
"""Parse command line arguments."""
20+
def create_parser() -> argparse.ArgumentParser:
21+
"""Create the argument parser with subcommands."""
2222
parser = argparse.ArgumentParser(
2323
description="Sharpy Dogfooding Tool - Generate, validate, compile and verify Sharpy code",
2424
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -28,16 +28,39 @@ def parse_args() -> argparse.Namespace:
2828
python -m sharpy_dogfood
2929
3030
# Run 5 iterations
31-
python -m sharpy_dogfood --iterations 5
31+
python -m sharpy_dogfood run --iterations 5
32+
33+
# Convert all successful outputs to integration tests
34+
python -m sharpy_dogfood convert --all
3235
33-
# Specify output directory
34-
python -m sharpy_dogfood --output-dir ./dogfood_results
36+
# Convert a specific dogfood output
37+
python -m sharpy_dogfood convert 20260117_001155_success_simple_class_0001
3538
36-
# Use specific project root
37-
python -m sharpy_dogfood --project-root /path/to/sharpy
39+
# Convert with custom test name and category
40+
python -m sharpy_dogfood convert <dir> --name my_test --category classes
3841
""",
3942
)
4043

44+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
45+
46+
# Run subcommand (also the default)
47+
run_parser = subparsers.add_parser("run", help="Run dogfood iterations")
48+
_add_run_arguments(run_parser)
49+
50+
# Convert subcommand
51+
convert_parser = subparsers.add_parser(
52+
"convert", help="Convert dogfood outputs to integration tests"
53+
)
54+
_add_convert_arguments(convert_parser)
55+
56+
# Add run arguments to main parser for backward compatibility (no subcommand)
57+
_add_run_arguments(parser)
58+
59+
return parser
60+
61+
62+
def _add_run_arguments(parser: argparse.ArgumentParser) -> None:
63+
"""Add arguments for the run command."""
4164
parser.add_argument(
4265
"-n",
4366
"--iterations",
@@ -93,23 +116,97 @@ def parse_args() -> argparse.Namespace:
93116
help="Check configuration without running iterations",
94117
)
95118

96-
return parser.parse_args()
97119

120+
def _add_convert_arguments(parser: argparse.ArgumentParser) -> None:
121+
"""Add arguments for the convert command."""
122+
parser.add_argument(
123+
"directory",
124+
nargs="?",
125+
help="Specific dogfood output directory to convert (e.g., 20260117_success_...)",
126+
)
98127

99-
async def main(args: argparse.Namespace | None = None) -> int:
100-
"""Main entry point."""
101-
if args is None:
102-
args = parse_args()
128+
parser.add_argument(
129+
"--all",
130+
"-a",
131+
action="store_true",
132+
dest="convert_all",
133+
help="Convert all dogfood outputs",
134+
)
135+
136+
parser.add_argument(
137+
"--include-failures",
138+
action="store_true",
139+
help="Also convert failed compilations as error tests",
140+
)
141+
142+
parser.add_argument(
143+
"--name",
144+
type=str,
145+
help="Override test name (without extension)",
146+
)
147+
148+
parser.add_argument(
149+
"--category",
150+
type=str,
151+
help="Override test category (subfolder in TestFixtures)",
152+
)
153+
154+
parser.add_argument(
155+
"--force",
156+
"-f",
157+
action="store_true",
158+
help="Overwrite existing test files",
159+
)
160+
161+
parser.add_argument(
162+
"--dogfood-dir",
163+
type=Path,
164+
help="Path to dogfood_output directory (default: auto-detected)",
165+
)
166+
167+
parser.add_argument(
168+
"--test-fixtures-dir",
169+
type=Path,
170+
help="Path to TestFixtures directory (default: auto-detected)",
171+
)
103172

173+
parser.add_argument(
174+
"--project-root",
175+
type=Path,
176+
help="Path to the Sharpy project root (default: auto-detected)",
177+
)
178+
179+
180+
def parse_args() -> argparse.Namespace:
181+
"""Parse command line arguments."""
182+
parser = create_parser()
183+
args = parser.parse_args()
184+
185+
# Handle case where no subcommand is given (backward compatibility)
186+
if args.command is None:
187+
# Check if any run-specific args were given
188+
if hasattr(args, "iterations"):
189+
args.command = "run"
190+
else:
191+
# Show help if no command and no args
192+
parser.print_help()
193+
sys.exit(0)
194+
195+
return args
196+
197+
198+
async def run_dogfood(args: argparse.Namespace) -> int:
199+
"""Run the dogfooding process."""
104200
# Build configuration
105201
config = Config()
106202

107-
if args.project_root:
203+
if hasattr(args, "project_root") and args.project_root:
108204
config.project_root = args.project_root.resolve()
109205

110-
if args.output_dir:
206+
if hasattr(args, "output_dir") and args.output_dir:
111207
config.output_dir = args.output_dir
112208
config.issues_dir = args.output_dir / "issues"
209+
config.successes_dir = args.output_dir / "successes"
113210

114211
config.max_iterations = args.iterations
115212
config.generation_timeout = args.generation_timeout
@@ -125,6 +222,7 @@ async def main(args: argparse.Namespace | None = None) -> int:
125222
print(f"Project root: {config.project_root}", file=sys.stderr)
126223
print(f"Output dir: {config.output_dir}", file=sys.stderr)
127224
print(f"Issues dir: {config.issues_dir}", file=sys.stderr)
225+
print(f"Successes dir: {config.successes_dir}", file=sys.stderr)
128226
print(f"Iterations: {config.max_iterations}", file=sys.stderr)
129227
print(
130228
f"Timeouts: gen={config.generation_timeout}s, compile={config.compilation_timeout}s, exec={config.execution_timeout}s",
@@ -147,6 +245,100 @@ async def main(args: argparse.Namespace | None = None) -> int:
147245
return await orchestrator.run(args.iterations)
148246

149247

248+
def run_convert(args: argparse.Namespace) -> int:
249+
"""Convert dogfood outputs to integration tests."""
250+
# Determine paths
251+
config = Config()
252+
if hasattr(args, "project_root") and args.project_root:
253+
config.project_root = args.project_root.resolve()
254+
255+
dogfood_dir = (
256+
args.dogfood_dir if args.dogfood_dir else config.project_root / "dogfood_output"
257+
)
258+
test_fixtures_dir = (
259+
args.test_fixtures_dir
260+
if args.test_fixtures_dir
261+
else config.project_root / "src/Sharpy.Compiler.Tests/Integration/TestFixtures"
262+
)
263+
264+
# Validate paths
265+
if not dogfood_dir.exists():
266+
print(
267+
f"Error: Dogfood output directory not found: {dogfood_dir}", file=sys.stderr
268+
)
269+
return 1
270+
271+
if not test_fixtures_dir.exists():
272+
print(f"Creating TestFixtures directory: {test_fixtures_dir}", file=sys.stderr)
273+
test_fixtures_dir.mkdir(parents=True, exist_ok=True)
274+
275+
print(f"Dogfood directory: {dogfood_dir}", file=sys.stderr)
276+
print(f"TestFixtures directory: {test_fixtures_dir}", file=sys.stderr)
277+
print("=" * 40, file=sys.stderr)
278+
279+
if args.convert_all:
280+
# Convert all outputs
281+
converted, failed = convert_all_dogfood_outputs(
282+
dogfood_dir,
283+
test_fixtures_dir,
284+
include_failures=args.include_failures,
285+
force=args.force,
286+
)
287+
print(f"\nConverted: {converted}, Failed: {failed}", file=sys.stderr)
288+
return 0 if failed == 0 else 1
289+
290+
elif args.directory:
291+
# Convert a specific directory
292+
# Try to find it in successes or issues
293+
specific_dir = None
294+
for subdir in ["successes", "issues"]:
295+
candidate = dogfood_dir / subdir / args.directory
296+
if candidate.exists():
297+
specific_dir = candidate
298+
break
299+
300+
# Also try the raw path
301+
if not specific_dir:
302+
candidate = Path(args.directory)
303+
if candidate.exists():
304+
specific_dir = candidate
305+
306+
if not specific_dir:
307+
print(f"Error: Directory not found: {args.directory}", file=sys.stderr)
308+
print(
309+
f"Searched in: {dogfood_dir}/successes/, {dogfood_dir}/issues/",
310+
file=sys.stderr,
311+
)
312+
return 1
313+
314+
result = convert_dogfood_to_test(
315+
specific_dir,
316+
test_fixtures_dir,
317+
category=args.category,
318+
test_name=args.name,
319+
force=args.force,
320+
)
321+
return 0 if result else 1
322+
323+
else:
324+
print(
325+
"Error: Specify a directory or use --all to convert all outputs",
326+
file=sys.stderr,
327+
)
328+
return 1
329+
330+
331+
async def main(args: argparse.Namespace | None = None) -> int:
332+
"""Main entry point."""
333+
if args is None:
334+
args = parse_args()
335+
336+
if args.command == "convert":
337+
return run_convert(args)
338+
else:
339+
return await run_dogfood(args)
340+
341+
150342
def cli_main() -> None:
151343
"""Entry point for the CLI."""
152344
try:
@@ -157,6 +349,9 @@ def cli_main() -> None:
157349
sys.exit(130)
158350
except Exception as e:
159351
print(f"Fatal error: {e}", file=sys.stderr)
352+
import traceback
353+
354+
traceback.print_exc(file=sys.stderr)
160355
sys.exit(1)
161356

162357

build_tools/sharpy_dogfood/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class Config(BaseConfig):
6464
# Output configuration
6565
output_dir: Path = field(default_factory=lambda: Path("dogfood_output"))
6666
issues_dir: Path = field(default_factory=lambda: Path("dogfood_output/issues"))
67+
successes_dir: Path = field(
68+
default_factory=lambda: Path("dogfood_output/successes")
69+
)
6770

6871
# Execution limits
6972
max_iterations: int = 10
@@ -130,9 +133,11 @@ def ensure_dirs(self) -> None:
130133
# Make paths absolute relative to project root
131134
self.output_dir = self.project_root / self.output_dir
132135
self.issues_dir = self.project_root / self.issues_dir
136+
self.successes_dir = self.project_root / self.successes_dir
133137

134138
self.output_dir.mkdir(parents=True, exist_ok=True)
135139
self.issues_dir.mkdir(parents=True, exist_ok=True)
140+
self.successes_dir.mkdir(parents=True, exist_ok=True)
136141

137142
@classmethod
138143
def from_file(cls, path: Path) -> "Config":

0 commit comments

Comments
 (0)