Skip to content

Commit 740e9ab

Browse files
[3.13] gh-127845: Minor improvements to iOS test runner script (GH-127846) (#127892)
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. (cherry picked from commit ba2d2fd) Co-authored-by: Russell Keith-Magee <[email protected]>
1 parent d3d478e commit 740e9ab

File tree

3 files changed

+53
-20
lines changed

3 files changed

+53
-20
lines changed

Makefile.pre.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2084,7 +2084,7 @@ testios:
20842084
$(PYTHON_FOR_BUILD) $(srcdir)/iOS/testbed clone --framework $(PYTHONFRAMEWORKPREFIX) "$(XCFOLDER)"
20852085

20862086
# Run the testbed project
2087-
$(PYTHON_FOR_BUILD) "$(XCFOLDER)" run -- test -uall --single-process --rerun -W
2087+
$(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W
20882088

20892089
# Like test, but using --slow-ci which enables all test resources and use
20902090
# longer timeout. Run an optional pybuildbot.identify script to include

iOS/testbed/__main__.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,12 @@ async def log_stream_task(initial_devices):
141141
else:
142142
suppress_dupes = False
143143
sys.stdout.write(line)
144+
sys.stdout.flush()
144145

145146

146-
async def xcode_test(location, simulator):
147+
async def xcode_test(location, simulator, verbose):
147148
# Run the test suite on the named simulator
149+
print("Starting xcodebuild...")
148150
args = [
149151
"xcodebuild",
150152
"test",
@@ -159,13 +161,17 @@ async def xcode_test(location, simulator):
159161
"-derivedDataPath",
160162
str(location / "DerivedData"),
161163
]
164+
if not verbose:
165+
args += ["-quiet"]
166+
162167
async with async_process(
163168
*args,
164169
stdout=subprocess.PIPE,
165170
stderr=subprocess.STDOUT,
166171
) as process:
167172
while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
168173
sys.stdout.write(line)
174+
sys.stdout.flush()
169175

170176
status = await asyncio.wait_for(process.wait(), timeout=1)
171177
exit(status)
@@ -182,7 +188,9 @@ def clone_testbed(
182188
sys.exit(10)
183189

184190
if framework is None:
185-
if not (source / "Python.xcframework/ios-arm64_x86_64-simulator/bin").is_dir():
191+
if not (
192+
source / "Python.xcframework/ios-arm64_x86_64-simulator/bin"
193+
).is_dir():
186194
print(
187195
f"The testbed being cloned ({source}) does not contain "
188196
f"a simulator framework. Re-run with --framework"
@@ -202,33 +210,48 @@ def clone_testbed(
202210
)
203211
sys.exit(13)
204212

205-
print("Cloning testbed project...")
206-
shutil.copytree(source, target)
213+
print("Cloning testbed project:")
214+
print(f" Cloning {source}...", end="", flush=True)
215+
shutil.copytree(source, target, symlinks=True)
216+
print(" done")
207217

208218
if framework is not None:
209219
if framework.suffix == ".xcframework":
210-
print("Installing XCFramework...")
211-
xc_framework_path = target / "Python.xcframework"
212-
shutil.rmtree(xc_framework_path)
213-
shutil.copytree(framework, xc_framework_path)
220+
print(" Installing XCFramework...", end="", flush=True)
221+
xc_framework_path = (target / "Python.xcframework").resolve()
222+
if xc_framework_path.is_dir():
223+
shutil.rmtree(xc_framework_path)
224+
else:
225+
xc_framework_path.unlink()
226+
xc_framework_path.symlink_to(
227+
framework.relative_to(xc_framework_path.parent, walk_up=True)
228+
)
229+
print(" done")
214230
else:
215-
print("Installing simulator Framework...")
231+
print(" Installing simulator framework...", end="", flush=True)
216232
sim_framework_path = (
217233
target / "Python.xcframework" / "ios-arm64_x86_64-simulator"
234+
).resolve()
235+
if sim_framework_path.is_dir():
236+
shutil.rmtree(sim_framework_path)
237+
else:
238+
sim_framework_path.unlink()
239+
sim_framework_path.symlink_to(
240+
framework.relative_to(sim_framework_path.parent, walk_up=True)
218241
)
219-
shutil.rmtree(sim_framework_path)
220-
shutil.copytree(framework, sim_framework_path)
242+
print(" done")
221243
else:
222-
print("Using pre-existing iOS framework.")
244+
print(" Using pre-existing iOS framework.")
223245

224246
for app_src in apps:
225-
print(f"Installing app {app_src.name!r}...")
247+
print(f" Installing app {app_src.name!r}...", end="", flush=True)
226248
app_target = target / f"iOSTestbed/app/{app_src.name}"
227249
if app_target.is_dir():
228250
shutil.rmtree(app_target)
229251
shutil.copytree(app_src, app_target)
252+
print(" done")
230253

231-
print(f"Testbed project created in {target}")
254+
print(f"Successfully cloned testbed: {target.resolve()}")
232255

233256

234257
def update_plist(testbed_path, args):
@@ -243,10 +266,11 @@ def update_plist(testbed_path, args):
243266
plistlib.dump(info, f)
244267

245268

246-
async def run_testbed(simulator: str, args: list[str]):
269+
async def run_testbed(simulator: str, args: list[str], verbose: bool=False):
247270
location = Path(__file__).parent
248-
print("Updating plist...")
271+
print("Updating plist...", end="", flush=True)
249272
update_plist(location, args)
273+
print(" done.")
250274

251275
# Get the list of devices that are booted at the start of the test run.
252276
# The simulator started by the test suite will be detected as the new
@@ -256,7 +280,7 @@ async def run_testbed(simulator: str, args: list[str]):
256280
try:
257281
async with asyncio.TaskGroup() as tg:
258282
tg.create_task(log_stream_task(initial_devices))
259-
tg.create_task(xcode_test(location, simulator))
283+
tg.create_task(xcode_test(location, simulator=simulator, verbose=verbose))
260284
except* MySystemExit as e:
261285
raise SystemExit(*e.exceptions[0].args) from None
262286
except* subprocess.CalledProcessError as e:
@@ -315,6 +339,11 @@ def main():
315339
default="iPhone SE (3rd Generation)",
316340
help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')",
317341
)
342+
run.add_argument(
343+
"-v", "--verbose",
344+
action="store_true",
345+
help="Enable verbose output",
346+
)
318347

319348
try:
320349
pos = sys.argv.index("--")
@@ -330,7 +359,7 @@ def main():
330359
clone_testbed(
331360
source=Path(__file__).parent,
332361
target=Path(context.location),
333-
framework=Path(context.framework) if context.framework else None,
362+
framework=Path(context.framework).resolve() if context.framework else None,
334363
apps=[Path(app) for app in context.apps],
335364
)
336365
elif context.subcommand == "run":
@@ -348,6 +377,7 @@ def main():
348377
asyncio.run(
349378
run_testbed(
350379
simulator=context.simulator,
380+
verbose=context.verbose,
351381
args=test_args,
352382
)
353383
)

iOS/testbed/iOSTestbedTests/iOSTestbedTests.m

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ - (void)testPython {
2424

2525
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
2626

27-
// Disable all color, as the Xcode log can't display color
27+
// Set some other common environment indicators to disable color, as the
28+
// Xcode log can't display color. Stdout will report that it is *not* a
29+
// TTY.
2830
setenv("NO_COLOR", "1", true);
31+
setenv("PY_COLORS", "0", true);
2932

3033
// Arguments to pass into the test suite runner.
3134
// argv[0] must identify the process; any subsequent arg

0 commit comments

Comments
 (0)