// port/ScriptLib CPythonLauncher on the embedded interpreter (批次 2P step 2), driven exactly as // 40250 UserInterface.cpp does it: Main() creates the launcher, calls Create(), and RunMainScript() // then sets __DEBUG__ and __COMMAND_LINE__ with RunLine before RunFile("system.py") reads the script // out of the registered packs (UserInterface.cpp:241-353, 419-434). // // The script layer's own modules (initapp, initpack, initwndMgr, ...) arrive with the 2V0 slice and the // stdlib with step 3, so system.py is expected to stop at its first import ("No module named app") — // what this test proves is the launcher: the interpreter starts, RunLine/RunMemoryTextFile/ // RunCompiledFile run code, and RunFile reaches the real script through CEterPackManager. // // port_python_launcher_test <40250 Client dir> // // Exit 77 (ctest SKIP) when the client is missing, unless MT_ASSETS_STRICT=1, which fails instead. #include "ScriptLib/StdAfx.h" #include "ScriptLib/PythonLauncher.h" #include "EterPack/EterPackManager.h" #include "../src/platform/UserInterface/UserInterface.h" #include #include #include #include #include #include static int g_failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \ ++g_failures; \ } \ } while (0) // The dictionary the launcher runs everything in, and the globals it leaves behind there. static PyObject* main_dict() { return PyModule_GetDict(PyImport_AddModule((char*) "__main__")); } static long main_long(const char* name) { PyObject* main_module = PyImport_AddModule((char*) "__main__"); PyObject* value = PyDict_GetItemString(PyModule_GetDict(main_module), name); return value && PyInt_Check(value) ? PyInt_AsLong(value) : -1; } static std::string main_string(const char* name) { PyObject* main_module = PyImport_AddModule((char*) "__main__"); PyObject* value = PyDict_GetItemString(PyModule_GetDict(main_module), name); return value && PyString_Check(value) ? PyString_AS_STRING(value) : ""; } int main(int argc, char** argv) { const char* strict = std::getenv("MT_ASSETS_STRICT"); const bool is_strict = strict && std::string(strict) == "1"; const std::string client = argc > 1 ? argv[1] : ""; if (client.empty() || chdir(client.c_str()) != 0 || access("pack/Index", 0) != 0) { std::fprintf(stderr, "port_python_launcher_test: no 40250 Client/pack at '%s'\n", client.c_str()); return is_strict ? 1 : 77; } PackSingletons(); CHECK(PackInitialize("pack")); // Host policy, set before the launcher's ctor runs Py_Initialize: 40250 shipped a Python with a real // prefix on disk, the embedded one has none, so `import site` would fail at startup. The flags are the // ones tools/py_embed_android/main.c uses; step 3 moves them, and the stdlib on sys.path, into the // platform layer that every host shares. Py_NoSiteFlag = 1; Py_IgnoreEnvironmentFlag = 1; Py_FrozenFlag = 1; Py_DontWriteBytecodeFlag = 1; Py_SetProgramName((char*) "eter.python"); // CPythonLauncher::Create's default program name { CPythonLauncher launcher; // ctor runs Py_Initialize, as 40250 Main() does on the stack CHECK(Py_IsInitialized()); CHECK(launcher.Create()); // RegisterDebugFlag / RegisterCommandLine, verbatim from RunMainScript. CHECK(launcher.RunLine("__DEBUG__ = 0")); CHECK(launcher.RunLine("__COMMAND_LINE__ = \"\"")); CHECK(main_long("__DEBUG__") == 0); CHECK(main_string("__COMMAND_LINE__") == ""); // Create() adds TRUE/FALSE to __builtin__, which the 40250 scripts use unqualified. CHECK(launcher.RunLine("__ok__ = (TRUE, FALSE)")); CHECK(launcher.RunLine("assert TRUE == 1 and FALSE == 0")); // A failing line reports through Traceback(), which fetches (and so clears) the exception. CHECK(!launcher.RunLine("raise RuntimeError('port test')")); CHECK(PyErr_Occurred() == NULL); // GetError() reads the pending exception's value with neither normalization nor a NULL check, // so it is only defined while an error really is pending and was set with a string value — after // RunLine it never is, because Traceback() got there first. Kept verbatim: it is what 40250 does. PyErr_SetString(PyExc_RuntimeError, "port test error"); CHECK(std::string(launcher.GetError()) == "port test error"); PyErr_Clear(); // RunMemoryTextFile wraps the text in exec(compile(...)) after dropping CR, so a CRLF script // from the pack compiles and the file name it is given reaches the code object. const char crlf_script[] = "__mem__ = 1\r\ndef f():\r\n\treturn f.func_code.co_filename\r\n__mem__ = f()\r\n"; CHECK(launcher.RunMemoryTextFile("crlf.py", sizeof(crlf_script) - 1, crlf_script)); CHECK(main_string("__mem__") == "crlf.py"); // RunCompiledFile reads a .pyc through the ported PythonMarshal readers: magic, timestamp, // then the code object. const std::string pyc = std::string(std::getenv("TMPDIR") ? std::getenv("TMPDIR") : "/tmp") + "/port_python_launcher_test.pyc"; PyObject* code = Py_CompileString("__pyc__ = 7\n", "compiled.py", Py_file_input); CHECK(code != NULL); if (code) { FILE* fp = fopen(pyc.c_str(), "wb"); CHECK(fp != NULL); if (fp) { PyMarshal_WriteLongToFile(PyImport_GetMagicNumber(), fp, Py_MARSHAL_VERSION); PyMarshal_WriteLongToFile(0, fp, Py_MARSHAL_VERSION); // mtime, ignored on read PyMarshal_WriteObjectToFile(code, fp, Py_MARSHAL_VERSION); fclose(fp); CHECK(launcher.RunCompiledFile(pyc.c_str())); CHECK(main_long("__pyc__") == 7); } Py_DECREF(code); std::remove(pyc.c_str()); } CHECK(!launcher.RunCompiledFile("no/such.pyc")); // RunFile pulls the script out of the packs. system.py is the 40250 entry point; without the // stdlib (step 3) and the C++ modules (2V0) it stops at its first import, which is still proof // that the pack read path and the compile reached Python. { CMappedFile file; const VOID* data = NULL; CHECK(CEterPackManager::Instance().Get(file, "system.py", &data)); CHECK(file.Size() > 0); // Where it stops, spelled out: RunFile reports through Traceback(), which fetches the // exception and so leaves nothing behind, and LogBoxf is a platform stub. Running the same // source here once names it: today "No module named app", i.e. system.py compiles and runs // until the first of the C++ modules RunMainScript registers (initapp, 2V0 slice) — the // stdlib imports behind it come with step 3. std::string source((const char*) data, file.Size()); source.erase(std::remove(source.begin(), source.end(), '\r'), source.end()); PyObject* result = PyRun_String(source.c_str(), Py_file_input, main_dict(), main_dict()); CHECK(result == NULL); if (result) Py_DECREF(result); else { PyObject *exc = NULL, *value = NULL, *tb = NULL; PyErr_Fetch(&exc, &value, &tb); PyErr_NormalizeException(&exc, &value, &tb); PyObject* text = value ? PyObject_Str(value) : NULL; std::printf("system.py stops at %s: %s\n", exc ? ((PyTypeObject*) exc)->tp_name : "?", text ? PyString_AsString(text) : ""); CHECK(exc == PyExc_ImportError); Py_XDECREF(text); Py_XDECREF(exc); Py_XDECREF(value); Py_XDECREF(tb); } } // Whatever it stops at, RunLine's Traceback() has already reported and cleared it, so there is // nothing left for GetError() to read here either. const bool ran = launcher.RunFile("system.py"); CHECK(!ran); CHECK(PyErr_Occurred() == NULL); CHECK(!launcher.RunFile("no/such/script.py")); // not in any pack: no file, no exception launcher.Clear(); // Main() calls Clear() explicitly before the launcher leaves scope } CHECK(!Py_IsInitialized()); if (g_failures) std::fprintf(stderr, "%d failure(s)\n", g_failures); else std::printf("port_python_launcher_test: PASS\n"); return g_failures ? 1 : 0; }