@@ -65,15 +65,11 @@ def filter_output(lines: list[str]) -> list[str]:
6565 result .append (cleaned )
6666 return result
6767
68- def is_successful (output : str ) -> bool :
69- return END_MARKER in output and PASSED_MARKER in output and FAILED_MARKER not in output
70-
7168# ──────────────────────────────────────────────
7269# Portable Godot
7370# ──────────────────────────────────────────────
7471
75- def setup_temp_portable_godot ():
76- original_path = Path (ORIGINAL_GODOT ).resolve ()
72+ def setup_temp_portable_godot ( original_path :Path ):
7773 if not original_path .is_file ():
7874 print (f"Warning: Original Godot not found — using as-is." )
7975 return ORIGINAL_GODOT
@@ -178,12 +174,12 @@ def run_godot(
178174
179175 if verbose :
180176 print (full_output .rstrip ())
181- print (f"→ Exit code: { exit_code } " )
177+ print (f"\n { '─' * 10 } { desc } - exit: { exit_code :#x } { '─' * 10 } " )
182178
183179 if timeout :
184180 return 124 , "TIMEOUT" , f'After { timeout_sec } s' , full_output
185181
186- return exit_code , "DONE" , f'Exit code: { exit_code } ' , full_output
182+ return exit_code , "DONE" , f'Exit code: { exit_code :#x } ' , full_output
187183
188184 except Exception as exc :
189185 stdout = stdout_path .read_text ("utf-8" , errors = "replace" )
@@ -215,50 +211,98 @@ def pre_import_project(godot_bin: str, verbose: bool = False):
215211 return exit_code != 0
216212
217213
218- def run_tests ( mode : str , godot_bin : str , verbose : bool = False ) -> bool :
219- success = True
214+ def run_integration_tests ( godot_bin : str , verbose : bool = False ) -> bool :
215+ print ( "→ Unit/Integration Tests" , end = ' ' , flush = True )
220216
221- if mode in ("unit" , "full" ):
222- print ("→ Unit/Integration Tests" , end = ' ' , flush = True )
217+ args = [
218+ "--path" , str (GODOT_PROJECT ),
219+ "--debug" , "--headless" , "--quit" ]
220+ exitcode , strcode , msg , output = run_godot (
221+ args , "Unit/Integration tests" , godot_bin , verbose = verbose
222+ )
223223
224- args = ["--path" , str (GODOT_PROJECT ), "--debug" , "--headless" , "--quit" ]
225- exitcode , strcode , msg , output = run_godot (
226- args , "Unit tests" , godot_bin , verbose = verbose
227- )
224+ def is_successful (output : str ) -> bool :
225+ return END_MARKER in output and PASSED_MARKER in output and FAILED_MARKER not in output
228226
229- if not verbose :
230- print (f"[ { strcode } ]" , end = ' ' )
231- print (f"- { msg } " if msg else '' )
227+ if not verbose :
228+ print (f"[ { strcode } ]" , end = ' ' )
229+ print (f"- { msg } " if msg else '' )
232230
233- if exitcode == 127 :
234- print ("→ Unit phase: TIMEOUT" )
235- success = False
236- elif not is_successful (output ):
237- print ("→ Unit phase: FAILED" )
238- success = False
231+ if exitcode == 127 :
232+ print ("→ Unit phase: TIMEOUT" )
233+ return False
234+ elif not is_successful (output ):
235+ print ("→ Unit phase: FAILED" )
236+ return False
237+ else :
238+ return True
239239
240- return success
241240
241+ def generate_extension_docs (godot_bin : str , verbose : bool = False ) -> bool :
242+ print ("→ GDExtension XML DocGen" , end = ' ' , flush = True )
243+
244+ # Run from inside project/ (demo/), pointing --doctool at ../
245+ args = [
246+ "--path" , str (PROJECT_DIR ),
247+ "--doctool" , ".." , "--gdextension-docs" ,
248+ "--headless" , "--quit" ,
249+ ]
250+ exitcode , strcode , msg , output = run_godot (
251+ args , "GDExtension XML DocGen" , godot_bin , verbose = verbose
252+ )
253+
254+ # print the completion of the non verbose line.
255+ if not verbose :
256+ print (f"[ { strcode } ]" , end = ' ' )
257+ print (f"- { msg } " if msg else '' )
258+
259+ if strcode == 'TIMEOUT' :
260+ if verbose : print ("→ DocGen phase: TIMEOUT" )
261+ return False
262+
263+ doc_path = (PROJECT_DIR .parent / "doc_classes" ).resolve ()
264+ if doc_path .exists ():
265+ xml_files = list (doc_path .glob ('*.xml' ))
266+ if len (xml_files ) > 0 :
267+ if verbose :
268+ print (f"→ DocGen doc_classes/ created at: { doc_path } ({ len (xml_files )} XML files)" )
269+ for file in xml_files : print (file )
270+ return True
271+ if verbose : print ("→ Warning: DocGen Command succeeded but no doc_classes/*.xml found" )
272+ return False
273+ else :
274+ print ("→ DocGen phase: FAILED" )
275+ return False
242276
243277# ──────────────────────────────────────────────
244278# Main
245279# ──────────────────────────────────────────────
246280
247281def main ():
248282 parser = argparse .ArgumentParser (description = "Run godot-cpp test suite" )
249- parser .add_argument ("--unit-only" , action = "store_const" , const = "unit" , dest = "mode" )
283+ parser .add_argument ("--tests-only" , action = "store_const" , const = "unit" , dest = "mode" ,
284+ help = "Only run the integration tests (skip doc xml generation)" )
285+ parser .add_argument ("--docs-only" , action = "store_const" , const = "docs" , dest = "mode" ,
286+ help = "Only generate GDExtension XML documentation (skip tests)" )
250287 parser .add_argument ("--verbose" , action = "store_true" , default = False ,
251288 help = "Show full unfiltered Godot output" )
252289 parser .add_argument ("--quiet" , action = "store_true" , default = False ,
253290 help = "Only exit code (0=success, >0=failure); no output" )
291+ parser .add_argument ("--editor-bin" , default = ORIGINAL_GODOT ,
292+ help = "Path to Godot editor binary for --doctool (default: same as test Godot)" )
254293 args = parser .parse_args ()
255294
256295 # store a reference to print
257296 original_print = builtins .print
258297
298+ godot_path = Path (ORIGINAL_GODOT ).resolve ()
299+ editor_path = Path (args .editor_bin ).resolve ()
300+ # TODO test godot bin to make sure its ok.
301+
259302 mode = args .mode or "full"
260303 verbose = args .verbose
261304
305+
262306 if args .quiet :
263307 def silent (* _args , ** _kwargs ):
264308 pass
@@ -277,14 +321,19 @@ def silent(*_args, **_kwargs):
277321 print (f"Mode: { mode } " )
278322 print (f"Verbose: { verbose } \n " )
279323
280- godot_bin = setup_temp_portable_godot ()
281- # TODO test godot bin to make sure its ok.
324+ godot_bin = setup_temp_portable_godot (godot_path )
282325
283326 pre_clean = cleanup_godot_cache (verbose = verbose )
284-
285327 pre_import = pre_import_project (godot_bin , verbose = verbose )
328+ # NOTE: the above arent strictly necessary , and the import will always fail anyway.
286329
287- success = run_tests (mode , godot_bin , verbose = verbose )
330+ success = True
331+ if mode in ("unit" , "full" ) and success :
332+ success = run_integration_tests (godot_bin , verbose = verbose )
333+
334+ if mode in ("docs" , "full" ) and success :
335+ editor_bin = godot_bin if editor_path == godot_path else setup_temp_portable_godot (editor_path )
336+ success = generate_extension_docs (editor_bin , verbose = verbose )
288337
289338 cleanup_temp_portable ()
290339
@@ -300,3 +349,25 @@ def silent(*_args, **_kwargs):
300349
301350if __name__ == "__main__" :
302351 main ()
352+
353+ #
354+ # try:
355+ # godot_bin = setup_temp_portable_godot() # still use temp copy for tests
356+ #
357+ # if args.generate_docs_only:
358+ # # Standalone mode — use possibly different editor binary
359+ # editor_bin = args.godot_editor
360+ # success = generate_extension_docs(editor_bin)
361+ # else:
362+ # # Normal test run
363+ # all_passed = run_tests(mode, godot_bin)
364+ #
365+ # if all_passed and mode == "full": # only do docs in full mode, after tests pass
366+ # print("\nAll tests passed → generating GDExtension docs as final step...")
367+ # success = generate_extension_docs(godot_bin) # reuse test Godot binary
368+ # all_passed = all_passed and success
369+ # else:
370+ # success = all_passed # no docs run → status is just tests
371+ #
372+ # finally:
373+ # cleanup_temp_portable()
0 commit comments