33CLI for the Sharpy Dogfooding Tool.
44
55Usage:
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
1110import argparse
1514
1615from .config import Config
1716from .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"\n Converted: { 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+
150342def 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
0 commit comments