10000 bpo-44131: Test Py_FrozenMain() by vstinner · Pull Request #26126 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-44131: Test Py_FrozenMain() #26126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Doc/library/token-list.inc linguist-generated=true
Include/token.h linguist-generated=true
Lib/token.py linguist-generated=true
Parser/token.c linguist-generated=true
Programs/test_frozenmain.h linguist-generated=true

# Language aware diff headers
# https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,21 @@ def test_unicode_id_init(self):
# when Python is initialized multiples times.
self.run_embedded_interpreter("test_unicode_id_init")

# See bpo-44133
@unittest.skipIf(os.name == 'nt',
'Py_FrozenMain is not exported on Windows')
def test_frozenmain(self):
out, err = self.run_embedded_interpreter("test_frozenmain")
exe = os.path.realpath('./argv0')
expected = textwrap.dedent(f"""
Frozen Hello World
sys.argv ['./argv0', '-E', 'arg1', 'arg2']
config program_name: ./argv0
config executable: {exe}
config use_environment: 1
""").lstrip()
self.assertEqual(out, expected)


class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
# Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
Expand Down
12 changes: 10 additions & 2 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,14 @@ Makefile Modules/config.c: Makefile.pre \
@mv config.c Modules
@echo "The Makefile was updated, you may need to re-run make."

regen-test-frozenmain: $(BUILDPYTHON)
# Regenerate Programs/test_frozenmain.h
# from Programs/test_frozenmain.py
# using Programs/freeze_test_frozenmain.py
$(RUNSHARED) ./$(BUILDPYTHON) Programs/freeze_test_frozenmain.py Programs/test_frozenmain.h

Programs/test_frozenmain.h: Programs/freeze_test_frozenmain.py Programs/test_frozenmain.py
$(MAKE) regen-test-frozenmain

Programs/_testembed: Programs/_testembed.o $(LIBRARY_DEPS)
$(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS)
Expand Down Expand Up @@ -763,7 +771,7 @@ regen-limited-abi: all

regen-all: regen-opcode regen-opcode-targets regen-typeslots \
regen-token regen-ast regen-keyword regen-importlib clinic \
regen-pegen-metaparser regen-pegen regen-frozen
regen-pegen-metaparser regen-pegen regen-frozen regen-test-frozenmain
@echo
@echo "Note: make regen-stdlib-module-names and autoconf should be run manually"

Expand Down Expand Up @@ -794,7 +802,7 @@ Modules/getpath.o: $(srcdir)/Modules/getpath.c Makefile
Programs/python.o: $(srcdir)/Programs/python.c
$(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c

Programs/_testembed.o: $(srcdir)/Programs/_testembed.c
Programs/_testembed.o: $(srcdir)/Programs/_testembed.c Programs/test_frozenmain.h
$(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/_testembed.c

Modules/_sre.o: $(srcdir)/Modules/_sre.c $(srcdir)/Modules/sre.h $(srcdir)/Modules/sre_constants.h $(srcdir)/Modules/sre_lib.h
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add test_frozenmain to test_embed to test the :c:func:`Py_FrozenMain` C
function. Patch by Victor Stinner.
72 changes: 63 additions & 9 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
_Py_COMP_DIAG_PUSH
_Py_COMP_DIAG_IGNORE_DEPR_DECLS


static void error(const char *msg)
{
fprintf(stderr, "ERROR: %s\n", msg);
fflush(stderr);
}


static void _testembed_Py_Initialize(void)
{
Py_SetProgramName(PROGRAM_NAME);
Expand Down Expand Up @@ -239,7 +247,7 @@ static void bpo20891_thread(void *lockp)

PyGILState_STATE state = PyGILState_Ensure();
if (!PyGILState_Check()) {
fprintf(stderr, "PyGILState_Check failed!");
error("PyGILState_Check failed!");
abort();
}

Expand All @@ -259,15 +267,15 @@ static int test_bpo20891(void)
crash. */
PyThread_type_lock lock = PyThread_allocate_lock();
if (!lock) {
fprintf(stderr, "PyThread_allocate_lock failed!");
error("PyThread_allocate_lock failed!");
return 1;
}

_testembed_Py_Initialize();

unsigned long thrd = PyThread_start_new_thread(bpo20891_thread, &lock);
if (thrd == PYTHREAD_INVALID_THREAD_ID) {
fprintf(stderr, "PyThread_start_new_thread failed!");
error("PyThread_start_new_thread failed!");
return 1;
}
PyThread_acquire_lock(lock, WAIT_LOCK);
Expand Down Expand Up @@ -1397,12 +1405,12 @@ static int test_init_setpath(void)
{
char *env = getenv("TESTPATH");
if (!env) {
fprintf(stderr, "missing TESTPATH env var\n");
error("missing TESTPATH env var");
return 1;
}
wchar_t *path = Py_DecodeLocale(env, NULL);
if (path == NULL) {
fprintf(stderr, "failed to decode TESTPATH\n");
error("failed to decode TESTPATH");
return 1;
}
Py_SetPath(path);
Expand Down Expand Up @@ -1430,12 +1438,12 @@ static int test_init_setpath_config(void)

char *env = getenv("TESTPATH");
if (!env) {
fprintf(stderr, "missing TESTPATH env var\n");
error("missing TESTPATH env var");
return 1;
}
wchar_t *path = Py_DecodeLocale(env, NULL);
if (path == NULL) {
fprintf(stderr, "failed to decode TESTPATH\n");
error("failed to decode TESTPATH");
return 1;
}
Py_SetPath(path);
Expand All @@ -1459,12 +1467,12 @@ static int test_init_setpythonhome(void)
{
char *env = getenv("TESTHOME");
if (!env) {
fprintf(stderr, "missing TESTHOME env var\n");
error("missing TESTHOME env var");
return 1;
}
wchar_t *home = Py_DecodeLocale(env, NULL);
if (home == NULL) {
fprintf(stderr, "failed to decode TESTHOME\n");
error("failed to decode TESTHOME");
return 1;
}
Py_SetPythonHome(home);
Expand Down Expand Up @@ -1726,6 +1734,48 @@ static int test_unicode_id_init(void)
}


#ifndef MS_WINDOWS
#include "test_frozenmain.h" // M_test_frozenmain

static int test_frozenmain(void)
{
// Get "_frozen_importlib" and "_frozen_importlib_external"
// from PyImport_FrozenModules
const struct _frozen *importlib = NULL, *importlib_external = NULL;
for (const struct _frozen *mod = PyImport_FrozenModules; mod->name != NULL; mod++) {
if (strcmp(mod->name, "_frozen_importlib") == 0) {
importlib = mod;
}
else if (strcmp(mod->name, "_frozen_importlib_external") == 0) {
importlib_external = mod;
}
}
if (importlib == NULL || importlib_external == NULL) {
error("cannot find frozen importlib and importlib_external");
return 1;
}

static struct _frozen frozen_modules[4] = {
{0, 0, 0}, // importlib
{0, 0, 0}, // importlib_external
{"__main__", M_test_frozenmain, sizeof(M_test_frozenmain)},
{0, 0, 0} // sentinel
};
frozen_modules[0] = *importlib;
frozen_modules[1] = *importlib_external;

char* argv[] = {
"./argv0",
"-E",
"arg1",
"arg2",
};
PyImport_FrozenModules = frozen_modules;
return Py_FrozenMain(Py_ARRAY_LENGTH(argv), argv);
}
#endif // !MS_WINDOWS


// List frozen modules.
// Command used by Tools/scripts/generate_stdlib_module_names.py script.
static int list_frozen(void)
Expand Down Expand Up @@ -1811,11 +1861,15 @@ static struct TestCase TestCases[] = {
{"test_audit_run_stdin", test_audit_run_stdin},

{"test_unicode_id_init", test_unicode_id_init},
#ifndef MS_WINDOWS
{"test_frozenmain", test_frozenmain},
#endif

{"list_frozen", list_frozen},
{NULL, NULL}
};


int main(int argc, char *argv[])
{
if (argc > 1) {
Expand Down
48 changes: 48 additions & 0 deletions Programs/freeze_test_frozenmain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import marshal
import tokenize
import os.path
import sys

PROGRAM_DIR = os.path.dirname(__file__)
SRC_DIR = os.path.dirname(PROGRAM_DIR)


def writecode(fp, mod, data):
print('unsigned char M_%s[] = {' % mod, file=fp)
indent = ' ' * 4
for i in range(0, len(data), 16):
print(indent, file=fp, end='')
for c in bytes(data[i:i+16]):
print('%d,' % c, file=fp, end='')
print('', file=fp)
print('};', file=fp)


def dump(fp, filename, name):
# Strip the directory to get reproducible marshal dump
code_filename = os.path.basename(filename)

with tokenize.open(filename) as source_fp:
source = source_fp.read()
code = compile(source, code_filename, 'exec')

data = marshal.dumps(code)
writecode(fp, name, data)


def main():
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} filename")
sys.exit(1)
filename = sys.argv[1]

with open(filename, "w") as fp:
print("// Auto-generated by Programs/freeze_test_frozenmain.py", file=fp)
frozenmain = os.path.join(PROGRAM_DIR, 'test_frozenmain.py')
dump(fp, frozenmain, 'test_frozenmain')

print(f"{filename} written")


if __name__ == "__main__":
main()
30 changes: 30 additions & 0 deletions Programs/test_frozenmain.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Programs/test_frozenmain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import sys
import _testinternalcapi

print("Frozen Hello World")
print("sys.argv", sys.argv)
config = _testinternalcapi.get_configs()['config']
print(f"config program_name: {config['program_name']}")
print(f"config executable: {config['executable']}")
print(f"config use_environment: {config['use_environment']}")
26 changes: 16 additions & 10 deletions Python/frozenmain.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/* Python interpreter main program for frozen scripts */

#include "Python.h"
Expand Down Expand Up @@ -43,10 +42,12 @@ Py_FrozenMain(int argc, char **argv)
PyConfig_InitPythonConfig(&config);
config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */

if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') {
inspect = 1;
if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
}
if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') {
unbuffered = 1;
}

if (unbuffered) {
setbuf(stdin, (char *)NULL);
Expand All @@ -65,8 +66,9 @@ Py_FrozenMain(int argc, char **argv)
argv_copy[i] = Py_DecodeLocale(argv[i], NULL);
argv_copy2[i] = argv_copy[i];
if (!argv_copy[i]) {
fprintf(stderr, "Unable to decode the command line argument #%i\n",
i + 1);
fprintf(stderr,
"Unable to decode the command line argument #%i\n",
i + 1);
argc = i;
goto error;
}
Expand Down Expand Up @@ -97,24 +99,28 @@ Py_FrozenMain(int argc, char **argv)
PyWinFreeze_ExeInit();
#endif

if (Py_VerboseFlag)
if (Py_VerboseFlag) {
fprintf(stderr, "Python %s\n%s\n",
Py_GetVersion(), Py_GetCopyright());
Py_GetVersion(), Py_GetCopyright());
}

PySys_SetArgv(argc, argv_copy);

n = PyImport_ImportFrozenModule("__main__");
if (n == 0)
if (n == 0) {
Py_FatalError("the __main__ module is not frozen");
}
if (n < 0) {
PyErr_Print();
sts = 1;
}
else
else {
sts = 0;
}

if (inspect && isatty((int)fileno(stdin)))
if (inspect && isatty((int)fileno(stdin))) {
sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
}

#ifdef MS_WINDOWS
PyWinFreeze_ExeTerm();
Expand Down
20 changes: 9 additions & 11 deletions Tools/freeze/makefreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,12 @@ def makefreeze(base, dict, debug=0, entry_point=None, fail_import=()):
# Write a C initializer for a module containing the frozen python code.
# The array is called M_<mod>.

def writecode(outfp, mod, str):
outfp.write('unsigned char M_%s[] = {' % mod)
for i in range(0, len(str), 16):
outfp.write('\n\t')
for c in bytes(str[i:i+16]):
outfp.write('%d,' % c)
outfp.write('\n};\n')

## def writecode(outfp, mod, str):
## outfp.write('unsigned char M_%s[%d] = "%s";\n' % (mod, len(str),
## '\\"'.join(map(lambda s: repr(s)[1:-1], str.split('"')))))
def writecode(fp, mod, data):
print('unsigned char M_%s[] = {' % mod, file=fp)
indent = ' ' * 4
for i in range(0, len(data), 16):
print(indent, file=fp, end='')
for c in bytes(data[i:i+16]):
print('%d,' % c, file=fp, end='')
print('', file=fp)
print('};', file=fp)
0