44import os
55import sys
66import argparse
7+ from dotenv import dotenv_values
8+
9+ def resolve_env_vars (env_vars ):
10+ """Resolves environment variables with transitive substitution."""
11+ original_env = os .environ .copy ()
12+ resolved = {k : str (v ) for k , v in env_vars .items ()}
13+
14+ try :
15+ # Seed os.environ with our variables so expandvars can find them
16+ os .environ .update (resolved )
17+
18+ # Multi-pass expansion to handle transitive dependencies
19+ for _ in range (10 ):
20+ changed = False
21+ for k , v in resolved .items ():
22+ new_val = os .path .expandvars (v )
23+ if new_val != v :
24+ resolved [k ] = new_val
25+ os .environ [k ] = new_val
26+ changed = True
27+ if not changed :
28+ break
29+ return resolved
30+ finally :
31+ os .environ .clear ()
32+ os .environ .update (original_env )
733
834def run_command (command , env_vars , step_name , dry_run = False , verbose = False , quiet = False ):
935 """Executes a shell command with the specific environment."""
36+ # Ensure all environment variables are strings
37+ env_vars = {k : str (v ) for k , v in env_vars .items ()}
38+ env_vars = resolve_env_vars (env_vars )
39+
1040 display_cmd = command
1141 prefix = "[EXEC]"
1242
@@ -52,13 +82,47 @@ def run_command(command, env_vars, step_name, dry_run=False, verbose=False, quie
5282 print (f" [ERROR] Step '{ step_name } ' failed with exit code { e .returncode } " )
5383 raise e
5484
85+ def execute_scripts (scripts , env , context_name , args ):
86+ """Iterates over scripts and executes them."""
87+ for step_name , step_config in scripts .items ():
88+ # Merge Step-specific Env
89+ step_env = env .copy ()
90+ step_env .update (step_config .get ("env" , {}))
91+
92+ # Handle "cmds" (list) vs "cmd" (string)
93+ commands = []
94+ if "cmds" in step_config :
95+ commands = step_config ["cmds" ]
96+ elif "cmd" in step_config :
97+ commands = [step_config ["cmd" ]]
98+
99+ # Execute
100+ try :
101+ for cmd in commands :
102+ run_command (cmd , step_env , step_name , dry_run = args .dry_run , verbose = args .verbose , quiet = args .quiet )
103+ except subprocess .CalledProcessError :
104+ print (f"Aborting '{ context_name } ' due to failure in step '{ step_name } '." )
105+ return False
106+ return True
107+
55108def main ():
56109 parser = argparse .ArgumentParser (description = "Benchmark Orchestrator" )
57- parser .add_argument ("config" , nargs = "?" , default = "/benchmark/testcases.json" , help = "Path to configuration file" )
110+ parser .add_argument ("config" , nargs = "?" , default = "/benchmark/testcases.json" , help = "Path to configuration file (default: %(default)s)" )
111+
112+ mode_group = parser .add_mutually_exclusive_group (required = True )
113+ mode_group .add_argument ("-t" , "--testcase" , help = "Only run the specified testcase" )
114+ mode_group .add_argument ("--all" , action = "store_true" , help = "Run all testcases" )
115+ mode_group .add_argument ("--create-templates" , action = "store_true" , help = "Run template creation scripts" )
116+ mode_group .add_argument ("--show" , action = "store_true" , help = "List available testcases" )
117+
58118 parser .add_argument ("--dry-run" , action = "store_true" , help = "Simulate execution" )
59119 parser .add_argument ("-v" , "--verbose" , action = "store_true" , help = "Enable verbose output" )
60120 parser .add_argument ("-q" , "--quiet" , action = "store_true" , help = "Suppress command output" )
61- parser .add_argument ("-t" , "--testcase" , help = "Only run the specified testcase" )
121+
122+ if len (sys .argv ) == 1 :
123+ parser .print_help (sys .stderr )
124+ sys .exit (1 )
125+
62126 args = parser .parse_args ()
63127
64128 if not os .path .exists (args .config ):
@@ -68,48 +132,58 @@ def main():
68132 with open (args .config , 'r' ) as f :
69133 data = json .load (f )
70134
71- global_env = data .get ("global_config" , {}).get ("env" , {})
135+ if args .show :
136+ print ("Available Testcases:" )
137+ for tc in data .get ("testcases" , []):
138+ print (f" - { tc .get ('name' , 'unnamed' )} " )
139+ sys .exit (0 )
72140
73- for testcase in data .get ("testcases" , []):
74- if "name" not in testcase :
141+ global_env = data .get ("global_config" , {}).get ("env" , {})
142+ global_env ["ORCHESTRATOR_MODE" ] = "1"
143+
144+ # Load .env from ../.env relative to script location
145+ script_dir = os .path .dirname (os .path .abspath (__file__ ))
146+ env_path = os .path .join (script_dir , ".." , ".env" )
147+ if os .path .exists (env_path ):
148+ shell_env = dotenv_values (env_path )
149+ global_env .update (shell_env )
150+
151+ # Determine execution targets
152+ if args .create_templates :
153+ if "create_templates" not in data :
154+ print ("Warning: 'create_templates' section not found in configuration." )
155+ targets = []
156+ else :
157+ targets = [data ["create_templates" ]]
158+ else :
159+ targets = data .get ("testcases" , [])
160+
161+ for testcase in targets :
162+ if "name" in testcase :
163+ context_name = testcase ["name" ]
164+ elif args .create_templates :
165+ context_name = "create_templates"
166+ else :
75167 print ("Error: Testcase definition missing required 'name' field." )
76168 sys .exit (1 )
77169
78- tc_name = testcase ["name" ]
79- if args .testcase and args .testcase != tc_name :
170+ if not args .create_templates and args .testcase and args .testcase != context_name :
80171 continue
81172
82173 print (f"\n ========================================" )
83- print (f"RUNNING TESTCASE : { tc_name } " )
174+ print (f"RUNNING: { context_name } " )
84175 print (f"========================================" )
85176
86- # 1. Setup Base Environment (Global + Testcase)
177+ # Setup Environment
87178 tc_env = global_env .copy ()
88- tc_env ["TESTCASE" ] = tc_name
179+ if not args .create_templates :
180+ tc_env ["TESTCASE" ] = context_name
89181 tc_env .update (testcase .get ("env" , {}))
90182
91183 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
184+ if not execute_scripts (scripts , tc_env , context_name , args ):
185+ if args .create_templates :
186+ sys .exit (1 )
113187
114188if __name__ == "__main__" :
115189 main ()
0 commit comments