[Python-checkins] bpo-40280: Address more test failures on Emscripten (GH-31050)

tiran webhook-mailer at python.org
Sat Feb 5 14:52:29 EST 2022


https://github.com/python/cpython/commit/96b344c2f15cb09251018f57f19643fe20637392
commit: 96b344c2f15cb09251018f57f19643fe20637392
branch: main
author: Christian Heimes <christian at python.org>
committer: tiran <christian at python.org>
date: 2022-02-05T20:52:01+01:00
summary:

bpo-40280: Address more test failures on Emscripten (GH-31050)

Co-authored-by: Brett Cannon <brett at python.org>

files:
A Misc/NEWS.d/next/Build/2022-01-31-15-15-08.bpo-40280.r1AYNW.rst
M Lib/test/support/__init__.py
M Lib/test/support/os_helper.py
M Lib/test/test_builtin.py
M Lib/test/test_capi.py
M Lib/test/test_faulthandler.py
M Lib/test/test_fileio.py
M Lib/test/test_genericalias.py
M Lib/test/test_getpass.py
M Lib/test/test_inspect.py
M Lib/test/test_interpreters.py
M Lib/test/test_io.py
M Lib/test/test_os.py
M Lib/test/test_posix.py
M Lib/test/test_pwd.py
M Lib/test/test_pyexpat.py
M Lib/test/test_resource.py
M Lib/test/test_zipfile.py
M Modules/clinic/resource.c.h
M Modules/posixmodule.c
M Modules/resource.c
M Modules/timemodule.c
M Tools/wasm/README.md
M Tools/wasm/config.site-wasm32-emscripten
M configure
M configure.ac
M pyconfig.h.in

diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index e5eb66e9068e7..9d0d3d9c9b619 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -1278,6 +1278,8 @@ def reap_children():
     # Need os.waitpid(-1, os.WNOHANG): Windows is not supported
     if not (hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG')):
         return
+    elif not has_subprocess_support:
+        return
 
     # Reap all our dead child processes so we don't leave zombies around.
     # These hog resources and might be causing some of the buildbots to die.
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index 50aa7a7176c0a..c761d7bd3d2b6 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -502,7 +502,7 @@ def __fspath__(self):
 def fd_count():
     """Count the number of open file descriptors.
     """
-    if sys.platform.startswith(('linux', 'freebsd')):
+    if sys.platform.startswith(('linux', 'freebsd', 'emscripten')):
         try:
             names = os.listdir("/proc/self/fd")
             # Subtract one because listdir() internally opens a file
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index c6e67cc2910cf..a601a524d6eb7 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -393,6 +393,7 @@ def test_compile_top_level_await_no_coro(self):
                                 msg=f"source={source} mode={mode}")
 
 
+    @unittest.skipIf(support.is_emscripten, "socket.accept is broken")
     def test_compile_top_level_await(self):
         """Test whether code some top level await can be compiled.
 
@@ -1213,6 +1214,7 @@ def test_open_default_encoding(self):
             os.environ.clear()
             os.environ.update(old_environ)
 
+    @support.requires_subprocess()
     def test_open_non_inheritable(self):
         fileobj = open(__file__, encoding="utf-8")
         with fileobj:
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index ccf8ceda49831..089088d97a66e 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -611,6 +611,7 @@ def check_fatal_error(self, code, expected, not_expected=()):
             self.assertNotIn(name, modules)
         self.assertEqual(len(modules), total)
 
+    @support.requires_subprocess()
     def test_fatal_error(self):
         # By default, stdlib extension modules are ignored,
         # but not test modules.
@@ -880,6 +881,7 @@ class Test_testinternalcapi(unittest.TestCase):
                     if name.startswith('test_'))
 
 
+ at support.requires_subprocess()
 class PyMemDebugTests(unittest.TestCase):
     PYTHONMALLOC = 'debug'
     # '0x04c06e0' or '04C06E0'
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index daacdeef5bc80..8d106daaf6520 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -19,6 +19,9 @@
 except ImportError:
     _testcapi = None
 
+if not support.has_subprocess_support:
+    raise unittest.SkipTest("test module requires subprocess")
+
 TIMEOUT = 0.5
 MS_WINDOWS = (os.name == 'nt')
 
diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py
index 4269b0e53f56d..e4984d3cd559e 100644
--- a/Lib/test/test_fileio.py
+++ b/Lib/test/test_fileio.py
@@ -9,7 +9,7 @@
 from weakref import proxy
 from functools import wraps
 
-from test.support import cpython_only, swap_attr, gc_collect
+from test.support import cpython_only, swap_attr, gc_collect, is_emscripten
 from test.support.os_helper import (TESTFN, TESTFN_UNICODE, make_bad_fd)
 from test.support.warnings_helper import check_warnings
 from collections import UserList
@@ -373,7 +373,7 @@ def testAbles(self):
             self.assertEqual(f.isatty(), False)
             f.close()
 
-            if sys.platform != "win32":
+            if sys.platform != "win32" and not is_emscripten:
                 try:
                     f = self.FileIO("/dev/tty", "a")
                 except OSError:
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index 706cc5ea1af2f..d311281c578a2 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -24,14 +24,20 @@
 from fileinput import FileInput
 from itertools import chain
 from http.cookies import Morsel
-from multiprocessing.managers import ValueProxy
-from multiprocessing.pool import ApplyResult
+try:
+    from multiprocessing.managers import ValueProxy
+    from multiprocessing.pool import ApplyResult
+    from multiprocessing.queues import SimpleQueue as MPSimpleQueue
+except ImportError:
+    # _multiprocessing module is optional
+    ValueProxy = None
+    ApplyResult = None
+    MPSimpleQueue = None
 try:
     from multiprocessing.shared_memory import ShareableList
 except ImportError:
     # multiprocessing.shared_memory is not available on e.g. Android
     ShareableList = None
-from multiprocessing.queues import SimpleQueue as MPSimpleQueue
 from os import DirEntry
 from re import Pattern, Match
 from types import GenericAlias, MappingProxyType, AsyncGeneratorType
@@ -79,13 +85,14 @@ class BaseTest(unittest.TestCase):
                      Queue, SimpleQueue,
                      _AssertRaisesContext,
                      SplitResult, ParseResult,
-                     ValueProxy, ApplyResult,
                      WeakSet, ReferenceType, ref,
-                     ShareableList, MPSimpleQueue,
+                     ShareableList,
                      Future, _WorkItem,
                      Morsel]
     if ctypes is not None:
         generic_types.extend((ctypes.Array, ctypes.LibraryLoader))
+    if ValueProxy is not None:
+        generic_types.extend((ValueProxy, ApplyResult, MPSimpleQueue))
 
     def test_subscriptable(self):
         for t in self.generic_types:
diff --git a/Lib/test/test_getpass.py b/Lib/test/test_getpass.py
index 3452e46213a76..98ecec94336e3 100644
--- a/Lib/test/test_getpass.py
+++ b/Lib/test/test_getpass.py
@@ -28,6 +28,9 @@ def test_username_priorities_of_env_values(self, environ):
             getpass.getuser()
         except ImportError: # in case there's no pwd module
             pass
+        except KeyError:
+            # current user has no pwd entry
+            pass
         self.assertEqual(
             environ.get.call_args_list,
             [mock.call(x) for x in ('LOGNAME', 'USER', 'LNAME', 'USERNAME')])
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index a553431bdccfb..29589a726768f 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -788,6 +788,7 @@ def test_nested_class_definition_inside_function(self):
         self.assertSourceEqual(mod2.cls213, 218, 222)
         self.assertSourceEqual(mod2.cls213().func219(), 220, 221)
 
+    @unittest.skipIf(support.is_emscripten, "socket.accept is broken")
     def test_nested_class_definition_inside_async_function(self):
         import asyncio
         self.addCleanup(asyncio.set_event_loop_policy, None)
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
index 6266aa7c33b32..48c7119fff1cf 100644
--- a/Lib/test/test_interpreters.py
+++ b/Lib/test/test_interpreters.py
@@ -5,7 +5,8 @@
 import unittest
 import time
 
-import _xxsubinterpreters as _interpreters
+from test.support import import_helper
+_interpreters = import_helper.import_module('_xxsubinterpreters')
 from test.support import interpreters
 
 
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index a10611abb13f4..e9abd153a3e8c 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -76,6 +76,10 @@ def _default_chunk_size():
     with open(__file__, "r", encoding="latin-1") as f:
         return f._CHUNK_SIZE
 
+requires_alarm = unittest.skipUnless(
+    hasattr(signal, "alarm"), "test requires signal.alarm()"
+)
+
 
 class MockRawIOWithoutRead:
     """A RawIO implementation without read(), so as to exercise the default
@@ -4435,12 +4439,15 @@ def _read():
                 if e.errno != errno.EBADF:
                     raise
 
+    @requires_alarm
     def test_interrupted_write_unbuffered(self):
         self.check_interrupted_write(b"xy", b"xy", mode="wb", buffering=0)
 
+    @requires_alarm
     def test_interrupted_write_buffered(self):
         self.check_interrupted_write(b"xy", b"xy", mode="wb")
 
+    @requires_alarm
     def test_interrupted_write_text(self):
         self.check_interrupted_write("xy", b"xy", mode="w", encoding="ascii")
 
@@ -4472,9 +4479,11 @@ def on_alarm(*args):
             wio.close()
             os.close(r)
 
+    @requires_alarm
     def test_reentrant_write_buffered(self):
         self.check_reentrant_write(b"xy", mode="wb")
 
+    @requires_alarm
     def test_reentrant_write_text(self):
         self.check_reentrant_write("xy", mode="w", encoding="ascii")
 
@@ -4502,10 +4511,12 @@ def alarm_handler(sig, frame):
             os.close(w)
             os.close(r)
 
+    @requires_alarm
     def test_interrupted_read_retry_buffered(self):
         self.check_interrupted_read_retry(lambda x: x.decode('latin1'),
                                           mode="rb")
 
+    @requires_alarm
     def test_interrupted_read_retry_text(self):
         self.check_interrupted_read_retry(lambda x: x,
                                           mode="r", encoding="latin1")
@@ -4578,9 +4589,11 @@ def alarm2(sig, frame):
                 if e.errno != errno.EBADF:
                     raise
 
+    @requires_alarm
     def test_interrupted_write_retry_buffered(self):
         self.check_interrupted_write_retry(b"x", mode="wb")
 
+    @requires_alarm
     def test_interrupted_write_retry_text(self):
         self.check_interrupted_write_retry("x", mode="w", encoding="latin1")
 
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 84c27f346c340..660691579c163 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -992,6 +992,7 @@ def _empty_mapping(self):
     @unittest.skipUnless(unix_shell and os.path.exists(unix_shell),
                          'requires a shell')
     @unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()")
+    @support.requires_subprocess()
     def test_update2(self):
         os.environ.clear()
         os.environ.update(HELLO="World")
@@ -1002,6 +1003,7 @@ def test_update2(self):
     @unittest.skipUnless(unix_shell and os.path.exists(unix_shell),
                          'requires a shell')
     @unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()")
+    @support.requires_subprocess()
     def test_os_popen_iter(self):
         with os.popen("%s -c 'echo \"line1\nline2\nline3\"'"
                       % unix_shell) as popen:
@@ -1173,6 +1175,8 @@ def test_iter_error_when_changing_os_environ_values(self):
     def _test_underlying_process_env(self, var, expected):
         if not (unix_shell and os.path.exists(unix_shell)):
             return
+        elif not support.has_subprocess_support:
+            return
 
         with os.popen(f"{unix_shell} -c 'echo ${var}'") as popen:
             value = popen.read().strip()
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 974edd766cc80..5cc04fd46dddb 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -184,7 +184,7 @@ def test_truncate(self):
         posix.truncate(os_helper.TESTFN, 0)
 
     @unittest.skipUnless(getattr(os, 'execve', None) in os.supports_fd, "test needs execve() to support the fd parameter")
-    @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+    @support.requires_fork()
     def test_fexecve(self):
         fp = os.open(sys.executable, os.O_RDONLY)
         try:
@@ -199,7 +199,7 @@ def test_fexecve(self):
 
 
     @unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()")
-    @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+    @support.requires_fork()
     def test_waitid(self):
         pid = os.fork()
         if pid == 0:
@@ -209,7 +209,7 @@ def test_waitid(self):
             res = posix.waitid(posix.P_PID, pid, posix.WEXITED)
             self.assertEqual(pid, res.si_pid)
 
-    @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+    @support.requires_fork()
     def test_register_at_fork(self):
         with self.assertRaises(TypeError, msg="Positional args not allowed"):
             os.register_at_fork(lambda: None)
@@ -1056,6 +1056,7 @@ def test_getgrouplist(self):
 
     @unittest.skipUnless(hasattr(os, 'getegid'), "test needs os.getegid()")
     @unittest.skipUnless(hasattr(os, 'popen'), "test needs os.popen()")
+    @support.requires_subprocess()
     def test_getgroups(self):
         with os.popen('id -G 2>/dev/null') as idg:
             groups = idg.read().strip()
@@ -1481,7 +1482,7 @@ def test_unlink_dir_fd(self):
                 self.addCleanup(posix.unlink, fullname)
                 raise
 
-    @unittest.skipUnless(os.mkfifo in os.supports_dir_fd, "test needs dir_fd support in os.mkfifo()")
+    @unittest.skipUnless(hasattr(os, 'mkfifo') and os.mkfifo in os.supports_dir_fd, "test needs dir_fd support in os.mkfifo()")
     def test_mkfifo_dir_fd(self):
         with self.prepare() as (dir_fd, name, fullname):
             try:
diff --git a/Lib/test/test_pwd.py b/Lib/test/test_pwd.py
index f8f12571ca90e..c789326425be3 100644
--- a/Lib/test/test_pwd.py
+++ b/Lib/test/test_pwd.py
@@ -69,7 +69,7 @@ def test_errors(self):
 
         allnames = list(bynames.keys())
         namei = 0
-        fakename = allnames[namei]
+        fakename = allnames[namei] if allnames else "invaliduser"
         while fakename in bynames:
             chars = list(fakename)
             for i in range(len(chars)):
diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
index b2b4dea060532..6e578458a2509 100644
--- a/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py
@@ -12,7 +12,7 @@
 from xml.parsers import expat
 from xml.parsers.expat import errors
 
-from test.support import sortdict
+from test.support import sortdict, is_emscripten
 
 
 class SetAttributeTest(unittest.TestCase):
@@ -466,7 +466,10 @@ def test_exception(self):
                                        "pyexpat.c", "StartElement")
             self.check_traceback_entry(entries[2],
                                        "test_pyexpat.py", "StartElementHandler")
-            if sysconfig.is_python_build() and not (sys.platform == 'win32' and platform.machine() == 'ARM'):
+            if (sysconfig.is_python_build()
+                and not (sys.platform == 'win32' and platform.machine() == 'ARM')
+                and not is_emscripten
+            ):
                 self.assertIn('call_with_frame("StartElement"', entries[1][3])
 
 
diff --git a/Lib/test/test_resource.py b/Lib/test/test_resource.py
index f2642c6ba181d..317e7ca8f8c85 100644
--- a/Lib/test/test_resource.py
+++ b/Lib/test/test_resource.py
@@ -98,6 +98,7 @@ def test_fsize_toobig(self):
             except (OverflowError, ValueError):
                 pass
 
+    @unittest.skipUnless(hasattr(resource, "getrusage"), "needs getrusage")
     def test_getrusage(self):
         self.assertRaises(TypeError, resource.getrusage)
         self.assertRaises(TypeError, resource.getrusage, 42, 42)
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index e226dd741d7a7..de2dd33f43660 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -22,7 +22,9 @@
 from test.support import script_helper
 from test.support import (findfile, requires_zlib, requires_bz2,
                           requires_lzma, captured_stdout, requires_subprocess)
-from test.support.os_helper import TESTFN, unlink, rmtree, temp_dir, temp_cwd
+from test.support.os_helper import (
+    TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count
+)
 
 
 TESTFN2 = TESTFN + "2"
@@ -2539,14 +2541,14 @@ def test_write_after_read(self):
     def test_many_opens(self):
         # Verify that read() and open() promptly close the file descriptor,
         # and don't rely on the garbage collector to free resources.
+        startcount = fd_count()
         self.make_test_archive(TESTFN2)
         with zipfile.ZipFile(TESTFN2, mode="r") as zipf:
             for x in range(100):
                 zipf.read('ones')
                 with zipf.open('ones') as zopen1:
                     pass
-        with open(os.devnull, "rb") as f:
-            self.assertLess(f.fileno(), 100)
+        self.assertEqual(startcount, fd_count())
 
     def test_write_while_reading(self):
         with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_DEFLATED) as zipf:
diff --git a/Misc/NEWS.d/next/Build/2022-01-31-15-15-08.bpo-40280.r1AYNW.rst b/Misc/NEWS.d/next/Build/2022-01-31-15-15-08.bpo-40280.r1AYNW.rst
new file mode 100644
index 0000000000000..bb4878c6b0ac2
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2022-01-31-15-15-08.bpo-40280.r1AYNW.rst
@@ -0,0 +1,9 @@
+Fix wasm32-emscripten test failures and platform issues.
+- Disable syscalls that are not supported or don't work, e.g.
+  wait, getrusage, prlimit, mkfifo, mknod, setres[gu]id, setgroups.
+- Use fd_count to cound open fds.
+- Add more checks for subprocess and fork.
+- Add workarounds for missing _multiprocessing and failing socket.accept().
+- Enable bzip2.
+- Disable large file support.
+- Disable signal.alarm.
diff --git a/Modules/clinic/resource.c.h b/Modules/clinic/resource.c.h
index 32c092ad7a94a..f31f7e8265e79 100644
--- a/Modules/clinic/resource.c.h
+++ b/Modules/clinic/resource.c.h
@@ -2,6 +2,8 @@
 preserve
 [clinic start generated code]*/
 
+#if defined(HAVE_GETRUSAGE)
+
 PyDoc_STRVAR(resource_getrusage__doc__,
 "getrusage($module, who, /)\n"
 "--\n"
@@ -29,6 +31,8 @@ resource_getrusage(PyObject *module, PyObject *arg)
     return return_value;
 }
 
+#endif /* defined(HAVE_GETRUSAGE) */
+
 PyDoc_STRVAR(resource_getrlimit__doc__,
 "getrlimit($module, resource, /)\n"
 "--\n"
@@ -160,7 +164,11 @@ resource_getpagesize(PyObject *module, PyObject *Py_UNUSED(ignored))
     return return_value;
 }
 
+#ifndef RESOURCE_GETRUSAGE_METHODDEF
+    #define RESOURCE_GETRUSAGE_METHODDEF
+#endif /* !defined(RESOURCE_GETRUSAGE_METHODDEF) */
+
 #ifndef RESOURCE_PRLIMIT_METHODDEF
     #define RESOURCE_PRLIMIT_METHODDEF
 #endif /* !defined(RESOURCE_PRLIMIT_METHODDEF) */
-/*[clinic end generated code: output=ad190fb33d647d1e input=a9049054013a1b77]*/
+/*[clinic end generated code: output=9ce1886c129eb2f3 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 7b5c3ef575565..ea0435d7d52e6 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -881,7 +881,7 @@ _Py_Gid_Converter(PyObject *obj, gid_t *p)
 #define _PyLong_FromDev PyLong_FromLongLong
 
 
-#if defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV)
+#if (defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV)) || defined(HAVE_DEVICE_MACROS)
 static int
 _Py_Dev_Converter(PyObject *obj, void *p)
 {
@@ -890,7 +890,7 @@ _Py_Dev_Converter(PyObject *obj, void *p)
         return 0;
     return 1;
 }
-#endif /* HAVE_MKNOD && HAVE_MAKEDEV */
+#endif /* (HAVE_MKNOD && HAVE_MAKEDEV) || HAVE_DEVICE_MACROS */
 
 
 #ifdef AT_FDCWD
diff --git a/Modules/resource.c b/Modules/resource.c
index 0d69c2983b4d1..d8bba2e39847a 100644
--- a/Modules/resource.c
+++ b/Modules/resource.c
@@ -78,6 +78,7 @@ get_resource_state(PyObject *module)
 
 static struct PyModuleDef resourcemodule;
 
+#ifdef HAVE_GETRUSAGE
 /*[clinic input]
 resource.getrusage
 
@@ -134,6 +135,7 @@ resource_getrusage_impl(PyObject *module, int who)
 
     return result;
 }
+#endif
 
 static int
 py2rlimit(PyObject *limits, struct rlimit *rl_out)
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 35b8e14e82711..4b7500aabded0 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1282,7 +1282,7 @@ _PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
 #endif
 
     /* getrusage(RUSAGE_SELF) */
-#if defined(HAVE_SYS_RESOURCE_H)
+#if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRUSAGE)
     struct rusage ru;
 
     if (getrusage(RUSAGE_SELF, &ru) == 0) {
diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md
index f59b876b11a74..1cdaa4efffb89 100644
--- a/Tools/wasm/README.md
+++ b/Tools/wasm/README.md
@@ -1,7 +1,11 @@
 # Python WebAssembly (WASM) build
 
+**WARNING: WASM support is highly experimental! Lots of features are not working yet.**
+
 This directory contains configuration and helpers to facilitate cross
-compilation of CPython to WebAssembly (WASM).
+compilation of CPython to WebAssembly (WASM). For now we support
+*wasm32-emscripten* builds for modern browser and for *Node.js*. It's not
+possible to build for *wasm32-wasi* out-of-the-box yet.
 
 ## wasm32-emscripten build
 
@@ -22,16 +26,14 @@ popd
 ### Fetch and build additional emscripten ports
 
 ```shell
-embuilder build zlib
+embuilder build zlib bzip2
 ```
 
-### Cross compile to wasm32-emscripten
-
-For browser:
+### Cross compile to wasm32-emscripten for browser
 
 ```shell
-mkdir -p builddir/emscripten
-pushd builddir/emscripten
+mkdir -p builddir/emscripten-browser
+pushd builddir/emscripten-browser
 
 CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \
   emconfigure ../../configure -C \
@@ -41,11 +43,27 @@ CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \
     --with-build-python=$(pwd)/../build/python
 
 emmake make -j$(nproc)
+popd
+```
+
+Serve `python.html` with a local webserver and open the file in a browser.
+
+```shell
+emrun builddir/emscripten-browser/python.html
+```
+
+or
+
+```shell
+python3 -m http.server
 ```
 
-For node:
+### Cross compile to wasm32-emscripten for node
 
 ```
+mkdir -p builddir/emscripten-node
+pushd builddir/emscripten-node
+
 CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \
   emconfigure ../../configure -C \
     --host=wasm32-unknown-emscripten \
@@ -54,18 +72,70 @@ CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \
     --with-build-python=$(pwd)/../build/python
 
 emmake make -j$(nproc)
+popd
 ```
 
-### Test in browser
-
-Serve `python.html` with a local webserver and open the file in a browser.
-
-```shell
-emrun python.html
 ```
-
-or
-
-```shell
-python3 -m http.server
+node --experimental-wasm-threads --experimental-wasm-bulk-memory builddir/emscripten-node/python.js
 ```
+
+## wasm32-emscripten limitations and issues
+
+- Most stdlib modules with a dependency on external libraries are missing:
+  ``ctypes``, ``readline``, ``sqlite3``, ``ssl``, and more.
+- Shared extension modules are not implemented yet. All extension modules
+  are statically linked into the main binary.
+- Processes are not supported. System calls like fork, popen, and subprocess
+  fail with ``ENOSYS`` or ``ENOSUP``.
+- Blocking sockets are not available and non-blocking sockets don't work
+  correctly, e.g. ``socket.accept`` crashes the runtime. ``gethostbyname``
+  does not resolve to a real IP address. IPv6 is not available.
+- The ``select`` module is limited. ``select.select()`` crashes the runtime
+  due to lack of exectfd support.
+- The ``*at`` variants of functions (e.g. ``openat``) are not available.
+  The ``dir_fd`` argument of *os* module functions can't be used.
+- Signal support is limited. ``signal.alarm``, ``itimer``, ``sigaction``
+  are not available or do not work correctly. ``SIGTERM`` exits the runtime.
+- Most user, group, and permission related function and modules are not
+  supported or don't work as expected, e.g.``pwd`` module, ``grp`` module,
+  ``os.setgroups``, ``os.chown``, and so on.
+- Offset and iovec I/O functions (e.g. ``os.pread``, ``os.preadv``) are not
+  available.
+- ``os.mknod`` and ``os.mkfifo``
+  [don't work](https://github.com/emscripten-core/emscripten/issues/16158)
+  and are disabled.
+- Large file support crashes the runtime and is disabled.
+- ``mmap`` module is unstable. flush (``msync``) can crash the runtime.
+- Resource-related functions like ``os.nice`` and most functions of the
+  ``resource`` module are not available.
+- Some time and datetime features are broken. ``strftime`` and ``strptime``
+  have known bugs, e.g.
+  [%% quoting](https://github.com/emscripten-core/emscripten/issues/16155),
+  [%U off-by-one](https://github.com/emscripten-core/emscripten/issues/16156).
+  Extended glibc formatting features are not available.
+- ``locales`` module is affected by musl libc issues,
+  [bpo-46390](https://bugs.python.org/issue46390).
+- ``uuid`` module is affected by
+  [memory leak](https://github.com/emscripten-core/emscripten/issues/16081)
+  and crasher in Emscripten's ``freeaddrinfo``,
+- Recursive ``glob`` leaks file descriptors.
+- Python's object allocator ``obmalloc`` is disabled by default.
+- ``ensurepip`` is not available.
+
+### wasm32-emscripten in browsers
+
+- The bundled stdlib is limited. Network-related modules,
+  distutils, multiprocessing, dbm, tests and similar modules
+  are not shipped. All other modules are bundled as pre-compiled
+  ``pyc`` files.
+- Threading is not supported.
+
+### wasm32-emscripten in node
+
+Node builds use ``NODERAWFS``, ``USE_PTHREADS`` and ``PROXY_TO_PTHREAD``
+linker options.
+
+- Node RawFS allows direct access to the host file system.
+- pthread support requires WASM threads and SharedArrayBuffer (bulk memory).
+  The runtime keeps a pool of web workers around. Each web worker uses
+  several file descriptors (eventfd, epoll, pipe).
diff --git a/Tools/wasm/config.site-wasm32-emscripten b/Tools/wasm/config.site-wasm32-emscripten
index 413506bbc9abd..98991b462446f 100644
--- a/Tools/wasm/config.site-wasm32-emscripten
+++ b/Tools/wasm/config.site-wasm32-emscripten
@@ -27,9 +27,6 @@ ac_cv_func_prlimit=no
 # unsupported syscall, https://github.com/emscripten-core/emscripten/issues/13393
 ac_cv_func_shutdown=no
 
-# breaks build, see https://github.com/ethanhs/python-wasm/issues/16
-ac_cv_lib_bz2_BZ2_bzCompress=no
-
 # clock_nanosleep() causes time.sleep() to sleep forever.
 # nanosleep() works correctly
 ac_cv_func_clock_nanosleep=no
@@ -66,6 +63,11 @@ ac_cv_func_pwritev=no
 ac_cv_func_pipe2=no
 ac_cv_func_nice=no
 ac_cv_func_setitimer=no
+# unsupported syscall: __syscall_prlimit64
+ac_cv_func_prlimit=no
+# unsupported syscall: __syscall_getrusage
+ac_cv_func_getrusage=no
+ac_cv_func_posix_fallocate=no
 
 # Syscalls that resulted in a segfault
 ac_cv_func_utimensat=no
@@ -78,6 +80,20 @@ ac_cv_header_sys_ioctl_h=no
 ac_cv_func_openpty=no
 ac_cv_func_forkpty=no
 
+# mkfifo and mknod are broken, create regular file
+ac_cv_func_mkfifo=no
+ac_cv_func_mkfifoat=no
+ac_cv_func_mknod=no
+ac_cv_func_mknodat=no
+
+# always fails with permission error
+ac_cv_func_setgroups=no
+ac_cv_func_setresuid=no
+ac_cv_func_setresgid=no
+
+# alarm signal is not delivered, may need a callback into the event loop?
+ac_cv_func_alarm=no
+
 # To use dlopen, you need to use Emscripten's linking support,
-# see https://github.com/emscripten-core/emscripten/wiki/Linking)
+# see https://emscripten.org/docs/compiling/Dynamic-Linking.html
 ac_cv_func_dlopen=no
diff --git a/configure b/configure
index 9097c0514b57c..49d5abeac3024 100755
--- a/configure
+++ b/configure
@@ -9844,14 +9844,30 @@ _ACEOF
 $as_echo_n "checking whether to enable large file support... " >&6; }
 if test "$ac_cv_sizeof_off_t" -gt "$ac_cv_sizeof_long" -a \
 	"$ac_cv_sizeof_long_long" -ge "$ac_cv_sizeof_off_t"; then
+  have_largefile_support="yes"
+else
+  have_largefile_support="no"
+fi
+case $ac_sys_system in #(
+  Emscripten) :
+    have_largefile_support="no"
+ ;; #(
+  *) :
+     ;;
+esac
+if test "x$have_largefile_support" = xyes; then :
+
 
 $as_echo "#define HAVE_LARGEFILE_SUPPORT 1" >>confdefs.h
 
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
+
 else
+
   { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
 $as_echo "no" >&6; }
+
 fi
 
 # The cast to long int works around a bug in the HP C Compiler
@@ -13751,7 +13767,7 @@ for ac_func in  \
   gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \
   getgrnam_r getgrouplist getgroups getitimer getloadavg getlogin \
   getpeername getpgid getpid getppid getpriority _getpty \
-  getpwent getpwnam_r getpwuid_r getresgid getresuid getsid getspent \
+  getpwent getpwnam_r getpwuid_r getresgid getresuid getrusage getsid getspent \
   getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \
   lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
   mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
diff --git a/configure.ac b/configure.ac
index b613c18dbb3c5..83dd854248b42 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2554,15 +2554,24 @@ AC_CHECK_SIZEOF(off_t, [], [
 AC_MSG_CHECKING(whether to enable large file support)
 if test "$ac_cv_sizeof_off_t" -gt "$ac_cv_sizeof_long" -a \
 	"$ac_cv_sizeof_long_long" -ge "$ac_cv_sizeof_off_t"; then
+  have_largefile_support="yes"
+else
+  have_largefile_support="no"
+fi
+dnl LFS does not work with Emscripten 3.1
+AS_CASE([$ac_sys_system],
+  [Emscripten], [have_largefile_support="no"]
+)
+AS_VAR_IF([have_largefile_support], [yes], [
   AC_DEFINE(HAVE_LARGEFILE_SUPPORT, 1,
   [Defined to enable large file support when an off_t is bigger than a long
    and long long is at least as big as an off_t. You may need
    to add some flags for configuration and compilation to enable this mode.
    (For Solaris and Linux, the necessary defines are already defined.)])
   AC_MSG_RESULT(yes)
-else
+], [
   AC_MSG_RESULT(no)
-fi
+])
 
 AC_CHECK_SIZEOF(time_t, [], [
 #ifdef HAVE_SYS_TYPES_H
@@ -4144,7 +4153,7 @@ AC_CHECK_FUNCS([ \
   gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \
   getgrnam_r getgrouplist getgroups getitimer getloadavg getlogin \
   getpeername getpgid getpid getppid getpriority _getpty \
-  getpwent getpwnam_r getpwuid_r getresgid getresuid getsid getspent \
+  getpwent getpwnam_r getpwuid_r getresgid getresuid getrusage getsid getspent \
   getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \
   lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
   mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 02569c49543b6..a1bf9502e9268 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -537,6 +537,9 @@
 /* Define to 1 if you have the `getresuid' function. */
 #undef HAVE_GETRESUID
 
+/* Define to 1 if you have the `getrusage' function. */
+#undef HAVE_GETRUSAGE
+
 /* Define to 1 if you have the `getsid' function. */
 #undef HAVE_GETSID
 



More information about the Python-checkins mailing list