@@ -164,7 +164,8 @@ def _load_config(self) -> Dict[str, Any]:
164164 "common_dir" : "tests/common" ,
165165 },
166166 "debug" : {
167- "default_type" : "gdb" , # or "lldb", "python"
167+ "default_type" : "gdb" , # or "lldb", "python", "rust"
168+ "show_test_build_hint" : True , # 是否在失败时显示 TEST_BUILD 环境变量设置提示
168169 },
169170 }
170171 with open (config_path , "rb" ) as f :
@@ -194,6 +195,7 @@ def debug_config(self) -> Dict[str, Any]:
194195 "debug" ,
195196 {
196197 "default_type" : "gdb" ,
198+ "show_test_build_hint" : True ,
197199 },
198200 )
199201
@@ -2221,15 +2223,13 @@ def _print_summary(self, total_score: float, max_score: float) -> None:
22212223 )
22222224 self .console .print ()
22232225 self .console .print (summary )
2224- self .console .print ()
22252226
22262227 def _print_basic_summary (self , total_score : float , max_score : float ) -> None :
22272228 self .console .print ()
22282229 self .console .print (
22292230 f"Total Score: { total_score :.1f} /{ max_score :.1f} "
22302231 f"({ total_score / max_score * 100 :.1f} %)"
22312232 )
2232- self .console .print ()
22332233
22342234
22352235class VSCodeConfigGenerator :
@@ -2341,6 +2341,59 @@ def _generate_launch_config(
23412341 "preLaunchTask" : f"build-{ test_case .path .name } " ,
23422342 }
23432343 ]
2344+ elif debug_type == "rust" :
2345+ base_name = f"Debug { test_case .meta ['name' ]} - Step { failed_step .get ('name' , 'failed step' )} "
2346+ configs = []
2347+
2348+ # Add CodeLLDB configuration for Rust
2349+ configs .append ({
2350+ "name" : f"{ base_name } (CodeLLDB)" ,
2351+ "type" : "lldb" ,
2352+ "request" : "launch" ,
2353+ "program" : program ,
2354+ "args" : args ,
2355+ "cwd" : cwd ,
2356+ "sourceLanguages" : ["rust" ],
2357+ "internalConsoleOptions" : "neverOpen" ,
2358+ "preLaunchTask" : f"build-{ test_case .path .name } " ,
2359+ "env" : {
2360+ "RUST_BACKTRACE" : "1"
2361+ }
2362+ })
2363+
2364+ # Add GDB configuration for Rust
2365+ configs .append ({
2366+ "name" : f"{ base_name } (GDB)" ,
2367+ "type" : "cppdbg" ,
2368+ "request" : "launch" ,
2369+ "program" : program ,
2370+ "args" : args ,
2371+ "stopOnEntry" : True ,
2372+ "cwd" : cwd ,
2373+ "environment" : [
2374+ {
2375+ "name" : "RUST_BACKTRACE" ,
2376+ "value" : "1"
2377+ }
2378+ ],
2379+ "internalConsoleOptions" : "neverOpen" ,
2380+ "MIMode" : "gdb" ,
2381+ "setupCommands" : [
2382+ {
2383+ "description" : "Enable pretty-printing for gdb" ,
2384+ "text" : "-enable-pretty-printing" ,
2385+ "ignoreFailures" : True ,
2386+ },
2387+ {
2388+ "description" : "Enable Rust pretty-printing" ,
2389+ "text" : "set language rust" ,
2390+ "ignoreFailures" : True ,
2391+ }
2392+ ],
2393+ "preLaunchTask" : f"build-{ test_case .path .name } " ,
2394+ })
2395+
2396+ return configs
23442397 else :
23452398 raise ValueError (f"Unsupported debug type: { debug_type } " )
23462399
@@ -2582,6 +2635,10 @@ def _print_debug_instructions(
25822635 self .console .print (" - C/C++: ms-vscode.cpptools" )
25832636 elif debug_type == "python" :
25842637 self .console .print (" - Python: ms-python.python" )
2638+ elif debug_type == "rust" :
2639+ self .console .print (" - rust-analyzer: rust-lang.rust-analyzer" )
2640+ self .console .print (" - CodeLLDB: vadimcn.vscode-lldb" )
2641+ self .console .print (" - C/C++ (optional, for GDB): ms-vscode.cpptools" )
25852642 self .console .print (" c. Press F5 or use the Run and Debug view" )
25862643 self .console .print (
25872644 " d. Select the auto-generated configuration for this test"
@@ -2594,6 +2651,11 @@ def _print_debug_instructions(
25942651 self .console .print (" - Continue (F5)" )
25952652 self .console .print (" - Inspect variables in the Variables view" )
25962653 self .console .print (" - Use Debug Console for expressions" )
2654+ if debug_type == "rust" :
2655+ self .console .print ("\n 4. Rust-specific debug features:" )
2656+ self .console .print (" - RUST_BACKTRACE=1 is enabled for better error messages" )
2657+ self .console .print (" - CodeLLDB provides native Rust debugging experience" )
2658+ self .console .print (" - GDB configuration is also available as a backup option" )
25972659 self .console .print (
25982660 "\n Note: The test will be automatically rebuilt before debugging starts"
25992661 )
@@ -3058,6 +3120,28 @@ def get_current_shell() -> str:
30583120 # 默认返回bash
30593121 return "bash"
30603122
3123+ def get_last_failed_tests (history_file : Path | str ) -> List [Any ]:
3124+ """Get the last failed tests from the history file
3125+
3126+ Args:
3127+ history_file (Path): The path to the history file
3128+
3129+ Returns:
3130+ List[Any]: The last failed tests
3131+ """
3132+ if isinstance (history_file , str ):
3133+ history_file = Path (history_file )
3134+
3135+ if not history_file .exists ():
3136+ return []
3137+
3138+ with open (history_file , "r" , encoding = "utf-8" ) as f :
3139+ history = json .load (f )
3140+ if not history :
3141+ return []
3142+
3143+ last_result = history [- 1 ]
3144+ return filter (lambda x : x ["status" ] != "PASS" , last_result ["tests" ])
30613145
30623146def main ():
30633147 parser = argparse .ArgumentParser (description = "Grade student submissions" )
@@ -3135,123 +3219,24 @@ def main():
31353219 args = parser .parse_args ()
31363220
31373221 try :
3138- # 如果是获取上次失败测试点的模式
31393222 if args .get_last_failed :
31403223 try :
3141- history_file = Path (".test_history" )
3142- if not history_file .exists ():
3143- print ("No test history found" , file = sys .stderr )
3144- sys .exit (1 )
3145-
3146- with open (history_file , "r" , encoding = "utf-8" ) as f :
3147- history = json .load (f )
3148- if not history :
3149- print ("Test history is empty" , file = sys .stderr )
3150- sys .exit (1 )
3151-
3152- # 获取最近一次的测试结果
3153- last_result = history [- 1 ]
3154-
3155- # 查找第一个失败的测试点
3156- for test in last_result ["tests" ]:
3157- if test ["status" ] != "PASS" :
3158- # 获取shell类型
3159- shell_type = args .shell or get_current_shell ()
3160-
3161- # 根据不同shell类型生成相应的命令
3162- if shell_type == "fish" :
3163- print (f"set -x TEST_BUILD { test ['build_path' ]} " )
3164- else : # bash 或 zsh
3165- print (f"export TEST_BUILD={ test ['build_path' ]} " )
3166- sys .exit (0 )
3167-
3224+ failed_tests = get_last_failed_tests (".test_history" )
3225+ if not failed_tests :
31683226 print ("No failed test found in last run" , file = sys .stderr )
31693227 sys .exit (1 )
3228+
3229+ first_failed_test = failed_tests [0 ]
3230+ shell_type = args .shell or get_current_shell ()
31703231
3171- except Exception as e :
3172- print (f"Error reading test history: { str (e )} " , file = sys .stderr )
3173- sys .exit (1 )
3174-
3175- # 如果是重新运行失败测试点的模式
3176- if args .rerun_failed :
3177- try :
3178- history_file = Path (".test_history" )
3179- if not history_file .exists ():
3180- print ("No test history found" , file = sys .stderr )
3181- sys .exit (1 )
3182-
3183- with open (history_file , "r" , encoding = "utf-8" ) as f :
3184- history = json .load (f )
3185- if not history :
3186- print ("Test history is empty" , file = sys .stderr )
3187- sys .exit (1 )
3188-
3189- # 获取最近一次的测试结果
3190- last_result = history [- 1 ]
3191-
3192- # 获取所有失败的测试点路径
3193- failed_paths = []
3194- for test in last_result ["tests" ]:
3195- if test ["status" ] != "PASS" :
3196- failed_paths .append (Path (test ["path" ]))
3197-
3198- if not failed_paths :
3199- print ("No failed test found in last run" , file = sys .stderr )
3200- sys .exit (0 )
3201-
3202- # 直接传入失败测试点的路径
3203- grader = Grader (json_output = args .json )
3204- grader .runner = TestRunner (
3205- grader .config , grader .console , verbose = args .verbose
3206- )
3207- grader .run_all_tests (specific_paths = failed_paths )
3208-
3209- # 检查是否所有测试都通过
3210- total_score = sum (
3211- result .score for result in grader .results .values ()
3212- )
3213- max_score = sum (
3214- test .meta ["score" ]
3215- for test in grader ._load_test_cases (specific_paths = failed_paths )
3216- )
3217- percentage = (total_score / max_score * 100 ) if max_score > 0 else 0
3218-
3219- # 如果需要写入结果文件
3220- if args .write_result :
3221- with open (".autograder_result" , "w" ) as f :
3222- f .write (f"{ percentage :.2f} " )
3223-
3224- # 只要有测试点失败,输出提示信息
3225- if total_score < max_score :
3226- if not args .json :
3227- console = Console ()
3228- shell_type = args .shell or get_current_shell ()
3229-
3230- console .print (
3231- "\n [bold yellow]To set TEST_BUILD environment variable to the failed test case's build directory:[/bold yellow]"
3232- )
3233-
3234- if shell_type == "fish" :
3235- console .print (
3236- "$ [bold green]python3 grader.py -l | source[/bold green]"
3237- )
3238- else :
3239- console .print (
3240- '$ [bold green]eval "$(python3 grader.py -l)"[/bold green]'
3241- )
3242- else :
3243- shell_type = args .shell or get_current_shell ()
3244- print (
3245- "\n To set TEST_BUILD to the first failed test case's build directory, run:"
3246- )
3247- if shell_type == "fish" :
3248- print ("python3 grader.py -l | source" )
3249- else :
3250- print ('eval "$(python3 grader.py -l)"' )
3251-
3252- # 只要不是0分就通过
3253- sys .exit (0 if percentage > 0 else 1 )
3254-
3232+ # 根据不同shell类型生成相应的命令
3233+ if shell_type == "fish" :
3234+ print (f"set -x TEST_BUILD { first_failed_test ['build_path' ]} " )
3235+ else : # bash 或 zsh
3236+ print (f"export TEST_BUILD={ first_failed_test ['build_path' ]} " )
3237+
3238+ sys .exit (0 )
3239+
32553240 except Exception as e :
32563241 print (f"Error reading test history: { str (e )} " , file = sys .stderr )
32573242 sys .exit (1 )
@@ -3271,11 +3256,27 @@ def main():
32713256 verbose = args .verbose ,
32723257 generate_vscode = args .vscode ,
32733258 vscode_no_merge = args .vscode_no_merge ,
3274- compare = args .compare , # 传递对拍参数
3275- )
3276- total_score , max_score = grader .run_all_tests (
3277- args .test , prefix_match = args .prefix , group = args .group
3259+ compare = args .compare ,
32783260 )
3261+
3262+ if args .rerun_failed :
3263+ try :
3264+ failed_tests = get_last_failed_tests (".test_history" )
3265+ if not failed_tests :
3266+ print ("No failed test found in last run" , file = sys .stderr )
3267+ sys .exit (1 )
3268+
3269+ failed_paths = [Path (test ["path" ]) for test in failed_tests ]
3270+ total_score , max_score = grader .run_all_tests (specific_paths = failed_paths )
3271+
3272+ except Exception as e :
3273+ print (f"Error reading test history: { str (e )} " , file = sys .stderr )
3274+ sys .exit (1 )
3275+
3276+ else :
3277+ total_score , max_score = grader .run_all_tests (
3278+ args .test , prefix_match = args .prefix , group = args .group
3279+ )
32793280
32803281 # 如果是dry-run模式,直接退出
32813282 if args .dry_run :
@@ -3289,32 +3290,21 @@ def main():
32893290 f .write (f"{ percentage :.2f} " )
32903291
32913292 # 如果有测试点失败,输出提示信息
3292- if total_score < max_score :
3293- if not args .json :
3294- console = Console ()
3295- shell_type = args .shell or get_current_shell ()
3293+ if total_score < max_score and not args .json and grader .config .debug_config .get ("show_test_build_hint" , True ):
3294+ shell_type = args .shell or get_current_shell ()
32963295
3297- console .print (
3298- "\n [bold yellow]To set TEST_BUILD environment variable to the failed test case's build directory:[/bold yellow]"
3299- )
3296+ grader . console .print (
3297+ "\n [bold yellow]To set TEST_BUILD environment variable to the failed test case's build directory:[/bold yellow]"
3298+ )
33003299
3301- if shell_type == "fish" :
3302- console .print (
3303- "$ [bold green]python3 grader.py -l | source[/bold green]"
3304- )
3305- else :
3306- console .print (
3307- '$ [bold green]eval "$(python3 grader.py -l)"[/bold green]'
3308- )
3300+ if shell_type == "fish" :
3301+ grader .console .print (
3302+ "$ [bold green]python3 grader.py -l | source[/bold green]"
3303+ )
33093304 else :
3310- shell_type = args .shell or get_current_shell ()
3311- print (
3312- "\n To set TEST_BUILD to the first failed test case's build directory, run:"
3305+ grader .console .print (
3306+ '$ [bold green]eval "$(python3 grader.py -l)"[/bold green]'
33133307 )
3314- if shell_type == "fish" :
3315- print ("python3 grader.py -l | source" )
3316- else :
3317- print ('eval "$(python3 grader.py -l)"' )
33183308
33193309 # 只要不是0分就通过
33203310 sys .exit (0 if percentage > 0 else 1 )
0 commit comments