gh-126167: Modify iOS Testbed to read arguments from Info.plist (#126169)

Modify iOS Testbed to read arguments from Info.plist.
This commit is contained in:
Russell Keith-Magee 2024-11-18 07:43:41 +08:00 committed by GitHub
parent 0c5c80928c
commit 500a4712bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 106 additions and 28 deletions

View File

@ -0,0 +1,2 @@
The iOS testbed was modified so that it can be used by third-party projects
for testing purposes.

View File

@ -351,13 +351,13 @@ Running specific tests
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
As the test suite is being executed on an iOS simulator, it is not possible to As the test suite is being executed on an iOS simulator, it is not possible to
pass in command line arguments to configure test suite operation. To work around pass in command line arguments to configure test suite operation. To work
this limitation, the arguments that would normally be passed as command line around this limitation, the arguments that would normally be passed as command
arguments are configured as a static string at the start of the XCTest method line arguments are configured as part of the ``iOSTestbed-Info.plist`` file
``- (void)testPython`` in ``iOSTestbedTests.m``. To pass an argument to the test that is used to configure the iOS testbed app. In this file, the ``TestArgs``
suite, add a a string to the ``argv`` definition. These arguments will be passed key is an array containing the arguments that would be passed to ``python -m``
to the test suite as if they had been passed to ``python -m test`` at the on the command line (including ``test`` in position 0, the name of the test
command line. module to be executed).
Disabling automated breakpoints Disabling automated breakpoints
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -17,6 +17,8 @@
607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; };
608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -66,6 +68,8 @@
607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = "<group>"; }; 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = "<group>"; };
607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = "<group>"; }; 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = "<group>"; };
607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = "<group>"; }; 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = "<group>"; };
608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = "<group>"; };
608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -111,6 +115,8 @@
607A66142B0EFA380010BFC8 /* iOSTestbed */ = { 607A66142B0EFA380010BFC8 /* iOSTestbed */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
608619552CB7819B00F46182 /* app */,
608619532CB77BA900F46182 /* app_packages */,
607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
607A66152B0EFA380010BFC8 /* AppDelegate.h */, 607A66152B0EFA380010BFC8 /* AppDelegate.h */,
@ -223,7 +229,9 @@
files = ( files = (
607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */,
608619562CB7819B00F46182 /* app in Resources */,
607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
608619542CB77BA900F46182 /* app_packages in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -273,7 +281,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n"; shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n";
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */

View File

@ -0,0 +1,7 @@
This folder can contain any Python application code.
During the build, any binary modules found in this folder will be processed into
iOS Framework form.
When the test suite runs, this folder will be on the PYTHONPATH, and will be the
working directory for the test suite.

View File

@ -0,0 +1,7 @@
This folder can be a target for installing any Python dependencies needed by the
test suite.
During the build, any binary modules found in this folder will be processed into
iOS Framework form.
When the test suite runs, this folder will be on the PYTHONPATH.

View File

@ -41,8 +41,18 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>MainModule</key> <key>TestArgs</key>
<string>ios</string> <array>
<string>test</string> <!-- Invoke "python -m test" -->
<string>-uall</string> <!-- Enable all resources -->
<string>--single-process</string> <!-- always run all tests sequentially in a single process -->
<string>--rerun</string> <!-- Re-run failed tests in verbose mode -->
<string>-W</string> <!-- Display test output on failure -->
<!-- To run a subset of tests, add the test names below; e.g.,
<string>test_os</string>
<string>test_sys</string>
-->
</array>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>

View File

@ -9,30 +9,38 @@
- (void)testPython { - (void)testPython {
// Arguments to pass into the test suite runner. const char **argv;
// argv[0] must identify the process; any subsequent arg
// will be handled as if it were an argument to `python -m test`
const char *argv[] = {
"iOSTestbed", // argv[0] is the process that is running.
"-uall", // Enable all resources
"--single-process", // always run all tests sequentially in a single process
"--rerun", // Re-run failed tests in verbose mode
"-W", // Display test output on failure
// To run a subset of tests, add the test names below; e.g.,
// "test_os",
// "test_sys",
};
// Start a Python interpreter.
int exit_code; int exit_code;
int failed;
PyStatus status; PyStatus status;
PyPreConfig preconfig; PyPreConfig preconfig;
PyConfig config; PyConfig config;
PyObject *sys_module;
PyObject *sys_path_attr;
NSArray *test_args;
NSString *python_home; NSString *python_home;
NSString *path;
wchar_t *wtmp_str; wchar_t *wtmp_str;
NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
// Disable all color, as the Xcode log can't display color
setenv("NO_COLOR", "1", true);
// Arguments to pass into the test suite runner.
// argv[0] must identify the process; any subsequent arg
// will be handled as if it were an argument to `python -m test`
test_args = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"TestArgs"];
if (test_args == NULL) {
NSLog(@"Unable to identify test arguments.");
}
argv = malloc(sizeof(char *) * ([test_args count] + 1));
argv[0] = "iOSTestbed";
for (int i = 1; i < [test_args count]; i++) {
argv[i] = [[test_args objectAtIndex:i] UTF8String];
}
NSLog(@"Test command: %@", test_args);
// Generate an isolated Python configuration. // Generate an isolated Python configuration.
NSLog(@"Configuring isolated Python..."); NSLog(@"Configuring isolated Python...");
PyPreConfig_InitIsolatedConfig(&preconfig); PyPreConfig_InitIsolatedConfig(&preconfig);
@ -50,7 +58,7 @@
// Ensure that signal handlers are installed // Ensure that signal handlers are installed
config.install_signal_handlers = 1; config.install_signal_handlers = 1;
// Run the test module. // Run the test module.
config.run_module = Py_DecodeLocale("test", NULL); config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL);
// For debugging - enable verbose mode. // For debugging - enable verbose mode.
// config.verbose = 1; // config.verbose = 1;
@ -83,7 +91,7 @@
} }
NSLog(@"Configure argc/argv..."); NSLog(@"Configure argc/argv...");
status = PyConfig_SetBytesArgv(&config, sizeof(argv) / sizeof(char *), (char**) argv); status = PyConfig_SetBytesArgv(&config, [test_args count], (char**) argv);
if (PyStatus_Exception(status)) { if (PyStatus_Exception(status)) {
XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
PyConfig_Clear(&config); PyConfig_Clear(&config);
@ -98,11 +106,47 @@
return; return;
} }
sys_module = PyImport_ImportModule("sys");
if (sys_module == NULL) {
XCTFail(@"Could not import sys module");
return;
}
sys_path_attr = PyObject_GetAttrString(sys_module, "path");
if (sys_path_attr == NULL) {
XCTFail(@"Could not access sys.path");
return;
}
// Add the app packages path
path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
NSLog(@"App packages path: %@", path);
wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
if (failed) {
XCTFail(@"Unable to add app packages to sys.path");
return;
}
PyMem_RawFree(wtmp_str);
path = [NSString stringWithFormat:@"%@/app", resourcePath, nil];
NSLog(@"App path: %@", path);
wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
failed = PyList_Insert(sys_path_attr, 0, PyUnicode_FromString([path UTF8String]));
if (failed) {
XCTFail(@"Unable to add app to sys.path");
return;
}
PyMem_RawFree(wtmp_str);
// Ensure the working directory is the app folder.
chdir([path UTF8String]);
// Start the test suite. Print a separator to differentiate Python startup logs from app logs // Start the test suite. Print a separator to differentiate Python startup logs from app logs
NSLog(@"---------------------------------------------------------------------------"); NSLog(@"---------------------------------------------------------------------------");
exit_code = Py_RunMain(); exit_code = Py_RunMain();
XCTAssertEqual(exit_code, 0, @"Python test suite did not pass"); XCTAssertEqual(exit_code, 0, @"Test suite did not pass");
NSLog(@"---------------------------------------------------------------------------"); NSLog(@"---------------------------------------------------------------------------");