From 21a5845e8ae78ce44ef6cc52dff3a7a558eb9db2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 10 Dec 2024 12:47:49 +0800 Subject: [PATCH 1/6] Use symlinks to install iOS framework into testbed clone. --- iOS/testbed/__main__.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py index 22570ee0f3ed04..6ae2cf79210cb0 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py @@ -202,33 +202,44 @@ def clone_testbed( ) sys.exit(13) - print("Cloning testbed project...") - shutil.copytree(source, target) + print("Cloning testbed project:") + print(f" Cloning {source}...", end="", flush=True) + shutil.copytree(source, target, symlinks=True) + print(" done") if framework is not None: if framework.suffix == ".xcframework": - print("Installing XCFramework...") + print(" Installing XCFramework...", end="", flush=True) xc_framework_path = target / "Python.xcframework" - shutil.rmtree(xc_framework_path) - shutil.copytree(framework, xc_framework_path) + if xc_framework_path.is_dir(): + shutil.rmtree(xc_framework_path) + else: + xc_framework_path.unlink() + xc_framework_path.symlink_to(framework) + print(" done") else: - print("Installing simulator Framework...") + print(" Installing simulator framework...", end="", flush=True) sim_framework_path = ( target / "Python.xcframework" / "ios-arm64_x86_64-simulator" ) - shutil.rmtree(sim_framework_path) - shutil.copytree(framework, sim_framework_path) + if sim_framework_path.is_dir(): + shutil.rmtree(sim_framework_path) + else: + sim_framework_path.unlink() + sim_framework_path.symlink_to(framework) + print(" done") else: - print("Using pre-existing iOS framework.") + print(" Using pre-existing iOS framework.") for app_src in apps: - print(f"Installing app {app_src.name!r}...") + print(f" Installing app {app_src.name!r}...", end="", flush=True) app_target = target / f"iOSTestbed/app/{app_src.name}" if app_target.is_dir(): shutil.rmtree(app_target) shutil.copytree(app_src, app_target) + print(" done") - print(f"Testbed project created in {target}") + print(f"Successfully cloned testbed: {target.resolve()}") def update_plist(testbed_path, args): From 181a15b12c249eab842467478ae4eb2db26d8ac2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 10 Dec 2024 12:50:09 +0800 Subject: [PATCH 2/6] Add a verbose mode to the iOS runner to hide most Xcode output. --- Makefile.pre.in | 2 +- iOS/testbed/__main__.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 7b66802147dc3a..3e880f7800fccf 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2169,7 +2169,7 @@ testios: $(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)" # Run the testbed project - $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run -- test -uall --single-process --rerun -W + $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W # Like test, but using --slow-ci which enables all test resources and use # longer timeout. Run an optional pybuildbot.identify script to include diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py index 6ae2cf79210cb0..991333965572db 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py @@ -143,7 +143,7 @@ async def log_stream_task(initial_devices): sys.stdout.write(line) -async def xcode_test(location, simulator): +async def xcode_test(location, simulator, verbose): # Run the test suite on the named simulator args = [ "xcodebuild", @@ -159,6 +159,9 @@ async def xcode_test(location, simulator): "-derivedDataPath", str(location / "DerivedData"), ] + if not verbose: + args += ["-quiet"] + async with async_process( *args, stdout=subprocess.PIPE, @@ -254,7 +257,7 @@ def update_plist(testbed_path, args): plistlib.dump(info, f) -async def run_testbed(simulator: str, args: list[str]): +async def run_testbed(simulator: str, args: list[str], verbose: bool=False): location = Path(__file__).parent print("Updating plist...") update_plist(location, args) @@ -267,7 +270,7 @@ async def run_testbed(simulator: str, args: list[str]): try: async with asyncio.TaskGroup() as tg: tg.create_task(log_stream_task(initial_devices)) - tg.create_task(xcode_test(location, simulator)) + tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose)) except* MySystemExit as e: raise SystemExit(*e.exceptions[0].args) from None except* subprocess.CalledProcessError as e: @@ -326,6 +329,11 @@ def main(): default="iPhone SE (3rd Generation)", help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')", ) + run.add_argument( + "-v", "--verbose", + action="store_true", + help="Enable verbose output", + ) try: pos = sys.argv.index("--") @@ -359,6 +367,7 @@ def main(): asyncio.run( run_testbed( simulator=context.simulator, + verbose=context.verbose, args=test_args, ) ) From 76cc6b73090ac6f5b223c63b72cf035363905155 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 10 Dec 2024 13:00:27 +0800 Subject: [PATCH 3/6] More output cleanups. --- iOS/testbed/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py index 991333965572db..90e70a5db91887 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py @@ -145,6 +145,7 @@ async def log_stream_task(initial_devices): async def xcode_test(location, simulator, verbose): # Run the test suite on the named simulator + print("Starting xcodebuild...") args = [ "xcodebuild", "test", @@ -259,8 +260,9 @@ def update_plist(testbed_path, args): async def run_testbed(simulator: str, args: list[str], verbose: bool=False): location = Path(__file__).parent - print("Updating plist...") + print("Updating plist...", end="", flush=True) update_plist(location, args) + print(" done.") # Get the list of devices that are booted at the start of the test run. # The simulator started by the test suite will be detected as the new From 2f6b254b036e3354fb0ceb863202c75e935247ea Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 12 Dec 2024 08:27:53 +0800 Subject: [PATCH 4/6] Add PY_COLORS to the list of environmental markers. --- iOS/testbed/iOSTestbedTests/iOSTestbedTests.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m index ac78456a61e65e..6db38253396c8d 100644 --- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m +++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m @@ -24,8 +24,11 @@ - (void)testPython { NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; - // Disable all color, as the Xcode log can't display color + // Set some other common environment indicators to disable color, as the + // Xcode log can't display color. Stdout will report that it is *not* a + // TTY. setenv("NO_COLOR", "1", true); + setenv("PY_COLORS", "0", true); // Arguments to pass into the test suite runner. // argv[0] must identify the process; any subsequent arg From 3a13d5667c98cb9a8ce51db2f0a1df46e54962a6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 12 Dec 2024 11:27:47 +0800 Subject: [PATCH 5/6] Correct handling of deep cross-build paths. --- iOS/testbed/__main__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py index 90e70a5db91887..d555e0840199cb 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py @@ -186,7 +186,9 @@ def clone_testbed( sys.exit(10) if framework is None: - if not (source / "Python.xcframework/ios-arm64_x86_64-simulator/bin").is_dir(): + if not ( + source / "Python.xcframework/ios-arm64_x86_64-simulator/bin" + ).is_dir(): print( f"The testbed being cloned ({source}) does not contain " f"a simulator framework. Re-run with --framework" @@ -214,23 +216,27 @@ def clone_testbed( if framework is not None: if framework.suffix == ".xcframework": print(" Installing XCFramework...", end="", flush=True) - xc_framework_path = target / "Python.xcframework" + xc_framework_path = (target / "Python.xcframework").resolve() if xc_framework_path.is_dir(): shutil.rmtree(xc_framework_path) else: xc_framework_path.unlink() - xc_framework_path.symlink_to(framework) + xc_framework_path.symlink_to( + framework.relative_to(xc_framework_path.parent, walk_up=True) + ) print(" done") else: print(" Installing simulator framework...", end="", flush=True) sim_framework_path = ( target / "Python.xcframework" / "ios-arm64_x86_64-simulator" - ) + ).resolve() if sim_framework_path.is_dir(): shutil.rmtree(sim_framework_path) else: sim_framework_path.unlink() - sim_framework_path.symlink_to(framework) + sim_framework_path.symlink_to( + framework.relative_to(sim_framework_path.parent, walk_up=True) + ) print(" done") else: print(" Using pre-existing iOS framework.") @@ -351,7 +357,7 @@ def main(): clone_testbed( source=Path(__file__).parent, target=Path(context.location), - framework=Path(context.framework) if context.framework else None, + framework=Path(context.framework).resolve() if context.framework else None, apps=[Path(app) for app in context.apps], ) elif context.subcommand == "run": From 0651ecffeb626c104684bb3a3ac6beaabbeda7f4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 12 Dec 2024 11:34:55 +0800 Subject: [PATCH 6/6] Ensure stdout is flushed after every write. --- iOS/testbed/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py index d555e0840199cb..068272835a5b95 100644 --- a/iOS/testbed/__main__.py +++ b/iOS/testbed/__main__.py @@ -141,6 +141,7 @@ async def log_stream_task(initial_devices): else: suppress_dupes = False sys.stdout.write(line) + sys.stdout.flush() async def xcode_test(location, simulator, verbose): @@ -170,6 +171,7 @@ async def xcode_test(location, simulator, verbose): ) as process: while line := (await process.stdout.readline()).decode(*DECODE_ARGS): sys.stdout.write(line) + sys.stdout.flush() status = await asyncio.wait_for(process.wait(), timeout=1) exit(status)