1+ #!/usr/bin/env python3
2+ import json
3+ import subprocess
4+ import os
5+ import sys
6+ import argparse
7+
8+ def run_command (command , env_vars , step_name , dry_run = False , verbose = False , quiet = False ):
9+ """Executes a shell command with the specific environment."""
10+ display_cmd = command
11+ prefix = "[EXEC]"
12+
13+ if dry_run :
14+ prefix = "[DRY-RUN]"
15+ if verbose :
16+ # Expand the variables
17+ prefix = "[EXPANDED]"
18+ for key in sorted (env_vars .keys (), key = len , reverse = True ):
19+ val = env_vars [key ]
20+ display_cmd = display_cmd .replace (f"${ key } " , val ).replace (f"${{{ key } }}" , val )
21+
22+ print (f" { prefix } { step_name } : { display_cmd } " )
23+
24+ if verbose and len (env_vars ) > 0 :
25+ for key , val in env_vars .items ():
26+ print (f" { key } ={ val } " )
27+
28+ if dry_run :
29+ return
30+
31+ try :
32+ # Merge system env with our custom env
33+ full_env = os .environ .copy ()
34+ full_env .update (env_vars )
35+
36+ stdout_dest = None
37+ stderr_dest = None
38+ if quiet :
39+ stdout_dest = subprocess .DEVNULL
40+ stderr_dest = subprocess .DEVNULL
41+
42+ subprocess .run (
43+ command ,
44+ shell = True ,
45+ env = full_env ,
46+ check = True ,
47+ executable = '/bin/bash' ,
48+ stdout = stdout_dest ,
49+ stderr = stderr_dest
50+ )
51+ except subprocess .CalledProcessError as e :
52+ print (f" [ERROR] Step '{ step_name } ' failed with exit code { e .returncode } " )
53+ raise e
54+
55+ def main ():
56+ parser = argparse .ArgumentParser (description = "Benchmark Orchestrator" )
57+ parser .add_argument ("config" , nargs = "?" , default = "/benchmark/testcases.json" , help = "Path to configuration file" )
58+ parser .add_argument ("--dry-run" , action = "store_true" , help = "Simulate execution" )
59+ parser .add_argument ("-v" , "--verbose" , action = "store_true" , help = "Enable verbose output" )
60+ parser .add_argument ("-q" , "--quiet" , action = "store_true" , help = "Suppress command output" )
61+ parser .add_argument ("-t" , "--testcase" , help = "Only run the specified testcase" )
62+ args = parser .parse_args ()
63+
64+ if not os .path .exists (args .config ):
65+ print (f"Error: { args .config } not found." )
66+ sys .exit (1 )
67+
68+ with open (args .config , 'r' ) as f :
69+ data = json .load (f )
70+
71+ global_env = data .get ("global_config" , {}).get ("env" , {})
72+
73+ for testcase in data .get ("testcases" , []):
74+ if "name" not in testcase :
75+ print ("Error: Testcase definition missing required 'name' field." )
76+ sys .exit (1 )
77+
78+ tc_name = testcase ["name" ]
79+ if args .testcase and args .testcase != tc_name :
80+ continue
81+
82+ print (f"\n ========================================" )
83+ print (f"RUNNING TESTCASE: { tc_name } " )
84+ print (f"========================================" )
85+
86+ # 1. Setup Base Environment (Global + Testcase)
87+ tc_env = global_env .copy ()
88+ tc_env ["TESTCASE" ] = tc_name
89+ tc_env .update (testcase .get ("env" , {}))
90+
91+ scripts = testcase .get ("scripts" , {})
92+
93+ # 2. Iterate EXACTLY as defined in the JSON file
94+ for step_name , step_config in scripts .items ():
95+ # Merge Step-specific Env
96+ step_env = tc_env .copy ()
97+ step_env .update (step_config .get ("env" , {}))
98+
99+ # Handle "cmds" (list) vs "cmd" (string)
100+ commands = []
101+ if "cmds" in step_config :
102+ commands = step_config ["cmds" ]
103+ elif "cmd" in step_config :
104+ commands = [step_config ["cmd" ]]
105+
106+ # Execute
107+ try :
108+ for cmd in commands :
109+ run_command (cmd , step_env , step_name , dry_run = args .dry_run , verbose = args .verbose , quiet = args .quiet )
110+ except subprocess .CalledProcessError :
111+ print (f"Aborting testcase '{ tc_name } ' due to failure in step '{ step_name } '." )
112+ break
113+
114+ if __name__ == "__main__" :
115+ main ()
0 commit comments