Skip to content

Commit

Permalink
gh-126167: Modify iOS Testbed to read arguments from Info.plist (#126169
Browse files Browse the repository at this point in the history
)

Modify iOS Testbed to read arguments from Info.plist.
  • Loading branch information
freakboy3742 authored Nov 17, 2024
1 parent 0c5c809 commit 500a471
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The iOS testbed was modified so that it can be used by third-party projects
for testing purposes.
14 changes: 7 additions & 7 deletions iOS/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,13 @@ Running specific tests
^^^^^^^^^^^^^^^^^^^^^^

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
this limitation, the arguments that would normally be passed as command line
arguments are configured as a static string at the start of the XCTest method
``- (void)testPython`` in ``iOSTestbedTests.m``. To pass an argument to the test
suite, add a a string to the ``argv`` definition. These arguments will be passed
to the test suite as if they had been passed to ``python -m test`` at the
command line.
pass in command line arguments to configure test suite operation. To work
around this limitation, the arguments that would normally be passed as command
line arguments are configured as part of the ``iOSTestbed-Info.plist`` file
that is used to configure the iOS testbed app. In this file, the ``TestArgs``
key is an array containing the arguments that would be passed to ``python -m``
on the command line (including ``test`` in position 0, the name of the test
module to be executed).

Disabling automated breakpoints
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
10 changes: 9 additions & 1 deletion iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
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, ); }; };
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 */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -66,6 +68,8 @@
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>"; };
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 */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -111,6 +115,8 @@
607A66142B0EFA380010BFC8 /* iOSTestbed */ = {
isa = PBXGroup;
children = (
608619552CB7819B00F46182 /* app */,
608619532CB77BA900F46182 /* app_packages */,
607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
607A66152B0EFA380010BFC8 /* AppDelegate.h */,
Expand Down Expand Up @@ -223,7 +229,9 @@
files = (
607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */,
608619562CB7819B00F46182 /* app in Resources */,
607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
608619542CB77BA900F46182 /* app_packages in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -273,7 +281,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
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 */

Expand Down
7 changes: 7 additions & 0 deletions iOS/testbed/iOSTestbed/app/README
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions iOS/testbed/iOSTestbed/app_packages/README
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 12 additions & 2 deletions iOS/testbed/iOSTestbed/iOSTestbed-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,18 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MainModule</key>
<string>ios</string>
<key>TestArgs</key>
<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>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand Down
80 changes: 62 additions & 18 deletions iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,38 @@ @implementation iOSTestbedTests


- (void)testPython {
// 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`
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.
const char **argv;
int exit_code;
int failed;
PyStatus status;
PyPreConfig preconfig;
PyConfig config;
PyObject *sys_module;
PyObject *sys_path_attr;
NSArray *test_args;
NSString *python_home;
NSString *path;
wchar_t *wtmp_str;

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.
NSLog(@"Configuring isolated Python...");
PyPreConfig_InitIsolatedConfig(&preconfig);
Expand All @@ -50,7 +58,7 @@ - (void)testPython {
// Ensure that signal handlers are installed
config.install_signal_handlers = 1;
// 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.
// config.verbose = 1;

Expand Down Expand Up @@ -83,7 +91,7 @@ - (void)testPython {
}

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)) {
XCTFail(@"Unable to configure argc/argv: %s", status.err_msg);
PyConfig_Clear(&config);
Expand All @@ -98,11 +106,47 @@ - (void)testPython {
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
NSLog(@"---------------------------------------------------------------------------");

exit_code = Py_RunMain();
XCTAssertEqual(exit_code, 0, @"Python test suite did not pass");
XCTAssertEqual(exit_code, 0, @"Test suite did not pass");

NSLog(@"---------------------------------------------------------------------------");

Expand Down

0 comments on commit 500a471

Please sign in to comment.