]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-126167: Modify iOS Testbed to read arguments from Info.plist (#126169)
authorRussell Keith-Magee <russell@keith-magee.com>
Sun, 17 Nov 2024 23:43:41 +0000 (07:43 +0800)
committerGitHub <noreply@github.com>
Sun, 17 Nov 2024 23:43:41 +0000 (07:43 +0800)
Modify iOS Testbed to read arguments from Info.plist.

Misc/NEWS.d/next/Tools-Demos/2024-10-30-13-59-07.gh-issue-126167.j5cCWE.rst [new file with mode: 0644]
iOS/README.rst
iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj
iOS/testbed/iOSTestbed/app/README [new file with mode: 0644]
iOS/testbed/iOSTestbed/app_packages/README [new file with mode: 0644]
iOS/testbed/iOSTestbed/iOSTestbed-Info.plist
iOS/testbed/iOSTestbedTests/iOSTestbedTests.m

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 (file)
index 0000000..338160e
--- /dev/null
@@ -0,0 +1,2 @@
+The iOS testbed was modified so that it can be used by third-party projects
+for testing purposes.
index 4d7c344d5e9e17d7f91e682150f4a20831fabfdf..e33455eef8f44af089210b6240a8c340e15f7aa2 100644 (file)
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index d57cfc3dbe03048eb495522ca3c3768e47218fe7..6819ac0eeed95fcedd0f30d45c4571d272614b17 100644 (file)
@@ -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 */
                607A66142B0EFA380010BFC8 /* iOSTestbed */ = {
                        isa = PBXGroup;
                        children = (
+                               608619552CB7819B00F46182 /* app */,
+                               608619532CB77BA900F46182 /* app_packages */,
                                607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
                                607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
                                607A66152B0EFA380010BFC8 /* AppDelegate.h */,
                        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;
                };
                        );
                        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 (file)
index 0000000..af22c68
--- /dev/null
@@ -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 (file)
index 0000000..42d7fde
--- /dev/null
@@ -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.
index e2aa460b6fd5eeb0c25aaf93ea2322767aaa3c7d..a582f42a21278386ead97552bd9f0a6606681c9c 100644 (file)
                <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>
index 9bf502a808eb886896f71ce2070019974727f969..db00d43da85cbc7a97a679cc0ef595d64315cbd9 100644 (file)
@@ -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);
         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(@"---------------------------------------------------------------------------");