File: iOSTestbedTests.m

package info (click to toggle)
python3.14 3.14.0~rc1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 126,824 kB
  • sloc: python: 745,274; ansic: 713,752; xml: 31,250; sh: 5,822; cpp: 4,063; makefile: 1,988; objc: 787; lisp: 502; javascript: 136; asm: 75; csh: 12
file content (193 lines) | stat: -rw-r--r-- 6,558 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#import <XCTest/XCTest.h>
#import <Python/Python.h>

@interface iOSTestbedTests : XCTestCase

@end

@implementation iOSTestbedTests


- (void)testPython {
    const char **argv;
    int exit_code;
    int failed;
    PyStatus status;
    PyPreConfig preconfig;
    PyConfig config;
    PyObject *app_packages_path;
    PyObject *method_args;
    PyObject *result;
    PyObject *site_module;
    PyObject *site_addsitedir_attr;
    PyObject *sys_module;
    PyObject *sys_path_attr;
    NSArray *test_args;
    NSString *python_home;
    NSString *path;
    wchar_t *wtmp_str;

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

    // Set some other common environment indicators to disable color, as the
    // Xcode log can't display color. Stdout will report that it is *not* a
    // TTY.
    setenv("NO_COLOR", "1", true);
    setenv("PYTHON_COLORS", "0", 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);
    PyConfig_InitIsolatedConfig(&config);

    // Configure the Python interpreter:
    // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale.
    // See https://docs.python.org/3/library/os.html#python-utf-8-mode.
    preconfig.utf8_mode = 1;
    // Use the system logger for stdout/err
    config.use_system_logger = 1;
    // Don't buffer stdio. We want output to appears in the log immediately
    config.buffered_stdio = 0;
    // Don't write bytecode; we can't modify the app bundle
    // after it has been signed.
    config.write_bytecode = 0;
    // Ensure that signal handlers are installed
    config.install_signal_handlers = 1;
    // Run the test module.
    config.run_module = Py_DecodeLocale([[test_args objectAtIndex:0] UTF8String], NULL);
    // For debugging - enable verbose mode.
    // config.verbose = 1;

    NSLog(@"Pre-initializing Python runtime...");
    status = Py_PreInitialize(&preconfig);
    if (PyStatus_Exception(status)) {
        XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg);
        PyConfig_Clear(&config);
        return;
    }

    // Set the home for the Python interpreter
    python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil];
    NSLog(@"PythonHome: %@", python_home);
    wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL);
    status = PyConfig_SetString(&config, &config.home, wtmp_str);
    if (PyStatus_Exception(status)) {
        XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg);
        PyConfig_Clear(&config);
        return;
    }
    PyMem_RawFree(wtmp_str);

    // Read the site config
    status = PyConfig_Read(&config);
    if (PyStatus_Exception(status)) {
        XCTFail(@"Unable to read site config: %s", status.err_msg);
        PyConfig_Clear(&config);
        return;
    }

    NSLog(@"Configure argc/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;
    }

    NSLog(@"Initializing Python runtime...");
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg);
        PyConfig_Clear(&config);
        return;
    }

    // Add app_packages as a site directory. This both adds to sys.path,
    // and ensures that any .pth files in that directory will be executed.
    site_module = PyImport_ImportModule("site");
    if (site_module == NULL) {
        XCTFail(@"Could not import site module");
        return;
    }

    site_addsitedir_attr = PyObject_GetAttrString(site_module, "addsitedir");
    if (site_addsitedir_attr == NULL || !PyCallable_Check(site_addsitedir_attr)) {
        XCTFail(@"Could not access site.addsitedir");
        return;
    }

    path = [NSString stringWithFormat:@"%@/app_packages", resourcePath, nil];
    NSLog(@"App packages path: %@", path);
    wtmp_str = Py_DecodeLocale([path UTF8String], NULL);
    app_packages_path = PyUnicode_FromWideChar(wtmp_str, wcslen(wtmp_str));
    if (app_packages_path == NULL) {
        XCTFail(@"Could not convert app_packages path to unicode");
        return;
    }
    PyMem_RawFree(wtmp_str);

    method_args = Py_BuildValue("(O)", app_packages_path);
    if (method_args == NULL) {
        XCTFail(@"Could not create arguments for site.addsitedir");
        return;
    }

    result = PyObject_CallObject(site_addsitedir_attr, method_args);
    if (result == NULL) {
        XCTFail(@"Could not add app_packages directory using site.addsitedir");
        return;
    }

    // Add test code to sys.path
    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;
    }

    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, @"Test suite did not pass");

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

    Py_Finalize();
}


@end