diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -69,3 +69,4 @@ PCbuild/*.suo PCbuild/Win32-temp-* PCbuild/x64-temp-* __pycache__ +Modules/_testembed diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -2,6 +2,7 @@ # these are all functions _testcapi exports whose name begins with 'test_'. from __future__ import with_statement +import os import random import subprocess import sys @@ -141,8 +142,38 @@ class Test6012(unittest.TestCase): def test(self): self.assertEqual(_testcapi.argparsing("Hello", "World"), 1) + +class EmbeddingTest(unittest.TestCase): + + def test_subinterps(self): + # XXX only tested under Unix checkouts + basepath = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + oldcwd = os.getcwd() + # This is needed otherwise we get a fatal error: + # "Py_Initialize: Unable to get the locale encoding + # LookupError: no codec search functions registered: can't find encoding" + os.chdir(basepath) + try: + exe = os.path.join(basepath, "Modules", "_testembed") + if not os.path.exists(exe): + self.skipTest("%r doesn't exist" % exe) + p = subprocess.Popen([exe], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (out, err) = p.communicate() + self.assertEqual(p.returncode, 0, + "bad returncode %d, stderr is %r" % + (p.returncode, err)) + if support.verbose: + print() + print(out.decode('latin1')) + print(err.decode('latin1')) + finally: + os.chdir(oldcwd) + + def test_main(): - support.run_unittest(CAPITest) + support.run_unittest(CAPITest, TestPendingCalls, Test6012, EmbeddingTest) for name in dir(_testcapi): if name.startswith('test_'): @@ -177,8 +208,6 @@ def test_main(): t.start() t.join() - support.run_unittest(TestPendingCalls, Test6012) - if __name__ == "__main__": test_main() diff --git a/Makefile.pre.in b/Makefile.pre.in --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -394,7 +394,7 @@ LIBRARY_OBJS= \ # Default target all: build_all -build_all: $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks +build_all: $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks Modules/_testembed # Compile a binary with gcc profile guided optimization. profile-opt: @@ -539,6 +539,9 @@ Modules/Setup: $(srcdir)/Modules/Setup.d echo "-----------------------------------------------"; \ fi +Modules/_testembed: Modules/_testembed.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY) + $(LINKCC) $(PY_LDFLAGS) $(LINKFORSHARED) -o $@ Modules/_testembed.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS) $(LDLAST) + ############################################################################ # Special rules for object files diff --git a/Modules/_testembed.c b/Modules/_testembed.c new file mode 100644 --- /dev/null +++ b/Modules/_testembed.c @@ -0,0 +1,52 @@ +#include +#include + +void print_subinterp(void) +{ + /* Just output some debug stuff */ + PyThreadState *ts = PyThreadState_Get(); + printf("interp %p, thread state %p: ", ts->interp, ts); + fflush(stdout); + PyRun_SimpleString( + "import sys;" + "print('id(modules) =', id(sys.modules));" + "sys.stdout.flush()" + ); +} + +int main(int argc, char *argv[]) +{ + PyThreadState *mainstate, *substate; + PyGILState_STATE gilstate; + int i, j; + + for (i=0; i<3; i++) { + printf("--- Pass %d ---\n", i); + /* HACK: the "./" at front avoids a search along the PATH in + Modules/getpath.c */ + Py_SetProgramName(L"./_testembed"); + Py_Initialize(); + mainstate = PyThreadState_Get(); + + PyEval_InitThreads(); + PyEval_ReleaseThread(mainstate); + + gilstate = PyGILState_Ensure(); + print_subinterp(); + PyThreadState_Swap(NULL); + + for (j=0; j<3; j++) { + substate = Py_NewInterpreter(); + print_subinterp(); + Py_EndInterpreter(substate); + } + + PyThreadState_Swap(mainstate); + print_subinterp(); + PyGILState_Release(gilstate); + + PyEval_RestoreThread(mainstate); + Py_Finalize(); + } + return 0; +}