summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2024-11-17 23:43:41 (GMT)
committerGitHub <noreply@github.com>2024-11-17 23:43:41 (GMT)
commit500a4712bb42355eeb785ed5b9d71507384d18bc (patch)
tree437b029c9cc73574c4d261bd928937da263708aa
parent0c5c80928c476ac0dcb9a053b15a562af899cfba (diff)
downloadcpython-500a4712bb42355eeb785ed5b9d71507384d18bc.zip
cpython-500a4712bb42355eeb785ed5b9d71507384d18bc.tar.gz
cpython-500a4712bb42355eeb785ed5b9d71507384d18bc.tar.bz2
gh-126167: Modify iOS Testbed to read arguments from Info.plist (#126169)
Modify iOS Testbed to read arguments from Info.plist.
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2024-10-30-13-59-07.gh-issue-126167.j5cCWE.rst2
-rw-r--r--iOS/README.rst14
-rw-r--r--iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj10
-rw-r--r--iOS/testbed/iOSTestbed/app/README7
-rw-r--r--iOS/testbed/iOSTestbed/app_packages/README7
-rw-r--r--iOS/testbed/iOSTestbed/iOSTestbed-Info.plist14
-rw-r--r--iOS/testbed/iOSTestbedTests/iOSTestbedTests.m80
7 files changed, 106 insertions, 28 deletions
diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-10-30-13-59-07.gh-issue-126167.j5cCWE.rst b/Misc/NEWS.d/next/Tools-Demos/2024-10-30-13-59-07.gh-issue-126167.j5cCWE.rst
new file mode 100644
index 0000000..338160e
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2024-10-30-13-59-07.gh-issue-126167.j5cCWE.rst
@@ -0,0 +1,2 @@
+The iOS testbed was modified so that it can be used by third-party projects
+for testing purposes.
diff --git a/iOS/README.rst b/iOS/README.rst
index 4d7c344..e33455e 100644
--- a/iOS/README.rst
+++ b/iOS/README.rst
@@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
index d57cfc3..6819ac0 100644
--- a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
+++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
@@ -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 */
@@ -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 */
@@ -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 */,
@@ -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;
};
@@ -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 */
diff --git a/iOS/testbed/iOSTestbed/app/README b/iOS/testbed/iOSTestbed/app/README
new file mode 100644
index 0000000..af22c68
--- /dev/null
+++ b/iOS/testbed/iOSTestbed/app/README
@@ -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.
diff --git a/iOS/testbed/iOSTestbed/app_packages/README b/iOS/testbed/iOSTestbed/app_packages/README
new file mode 100644
index 0000000..42d7fde
--- /dev/null
+++ b/iOS/testbed/iOSTestbed/app_packages/README
@@ -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.
diff --git a/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist
index e2aa460..a582f42 100644
--- a/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist
+++ b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist
@@ -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>
diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
index 9bf502a..db00d43 100644
--- a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
+++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m
@@ -9,30 +9,38 @@
- (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);
@@ -50,7 +58,7 @@
// 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;
@@ -83,7 +91,7 @@
}
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);
@@ -98,11 +106,47 @@
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(@"---------------------------------------------------------------------------");