Skip to content

Commit

Permalink
gh-127845: Minor improvements to iOS test runner script (#127846)
Browse files Browse the repository at this point in the history
Uses symlinks to install iOS framework into testbed clone, adds a verbose mode
to the iOS runner to hide most Xcode output, adds another mechanism to disable
terminal colors, and ensures that stdout is flushed after every write.
  • Loading branch information
freakboy3742 authored Dec 12, 2024
1 parent 0cbc19d commit ba2d2fd
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 48 additions & 18 deletions iOS/testbed/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ async def log_stream_task(initial_devices):
else:
suppress_dupes = False
sys.stdout.write(line)
sys.stdout.flush()


async def xcode_test(location, simulator):
async def xcode_test(location, simulator, verbose):
# Run the test suite on the named simulator
print("Starting xcodebuild...")
args = [
"xcodebuild",
"test",
Expand All @@ -159,13 +161,17 @@ async def xcode_test(location, simulator):
"-derivedDataPath",
str(location / "DerivedData"),
]
if not verbose:
args += ["-quiet"]

async with async_process(
*args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) 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)
Expand All @@ -182,7 +188,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"
Expand All @@ -202,33 +210,48 @@ 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...")
xc_framework_path = target / "Python.xcframework"
shutil.rmtree(xc_framework_path)
shutil.copytree(framework, xc_framework_path)
print(" Installing XCFramework...", end="", flush=True)
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.relative_to(xc_framework_path.parent, walk_up=True)
)
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"
).resolve()
if sim_framework_path.is_dir():
shutil.rmtree(sim_framework_path)
else:
sim_framework_path.unlink()
sim_framework_path.symlink_to(
framework.relative_to(sim_framework_path.parent, walk_up=True)
)
shutil.rmtree(sim_framework_path)
shutil.copytree(framework, sim_framework_path)
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):
Expand All @@ -243,10 +266,11 @@ 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...")
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
Expand All @@ -256,7 +280,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:
Expand Down Expand Up @@ -315,6 +339,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("--")
Expand All @@ -330,7 +359,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":
Expand All @@ -348,6 +377,7 @@ def main():
asyncio.run(
run_testbed(
simulator=context.simulator,
verbose=context.verbose,
args=test_args,
)
)
Expand Down
5 changes: 4 additions & 1 deletion iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ba2d2fd

Please sign in to comment.