We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Soundness error in python2js_buffer
This bug goes back at least to version 0.15.0. It's been messing up my error handling code.
First:
```python
import numpy as np
x = np.array([['string1', 'string2'], ['string3', 'string4']])
```
Second: type `x<enter>1<enter>x<enter>1<enter>`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `conftest.py`
Content:
```
1 """
2 Various common utilities for testing.
3 """
4 
5 import contextlib
6 import multiprocessing
7 import textwrap
8 import tempfile
9 import time
10 import os
11 import pathlib
12 import queue
13 import sys
14 import shutil
15 
16 ROOT_PATH = pathlib.Path(__file__).parents[0].resolve()
17 TEST_PATH = ROOT_PATH / "src" / "tests"
18 BUILD_PATH = ROOT_PATH / "build"
19 
20 sys.path.append(str(ROOT_PATH))
21 
22 from pyodide_build._fixes import _selenium_is_connectable  # noqa: E402
23 import selenium.webdriver.common.utils  # noqa: E402
24 
25 # XXX: Temporary fix for ConnectionError in selenium
26 
27 selenium.webdriver.common.utils.is_connectable = _selenium_is_connectable
28 
29 try:
30     import pytest
31 
32     def pytest_addoption(parser):
33         group = parser.getgroup("general")
34         group.addoption(
35             "--build-dir",
36             action="store",
37             default=BUILD_PATH,
38             help="Path to the build directory",
39         )
40         group.addoption(
41             "--run-xfail",
42             action="store_true",
43             help="If provided, tests marked as xfail will be run",
44         )
45 
46 
47 except ImportError:
48     pytest = None  # type: ignore
49 
50 
51 class JavascriptException(Exception):
52     def __init__(self, msg, stack):
53         self.msg = msg
54         self.stack = stack
55 
56     def __str__(self):
57         if self.stack:
58             return self.msg + "\n\n" + self.stack
59         else:
60             return self.msg
61 
62 
63 class SeleniumWrapper:
64     JavascriptException = JavascriptException
65 
66     def __init__(
67         self, server_port, server_hostname="127.0.0.1", server_log=None, build_dir=None
68     ):
69         if build_dir is None:
70             build_dir = BUILD_PATH
71 
72         self.driver = self.get_driver()
73         self.server_port = server_port
74         self.server_hostname = server_hostname
75         self.server_log = server_log
76 
77         if not (pathlib.Path(build_dir) / "test.html").exists():
78             # selenium does not expose HTTP response codes
79             raise ValueError(
80                 f"{(build_dir / 'test.html').resolve()} " f"does not exist!"
81             )
82         self.driver.get(f"http://{server_hostname}:{server_port}/test.html")
83         self.run_js("Error.stackTraceLimit = Infinity")
84         self.run_js_async("await languagePluginLoader")
85 
86     @property
87     def logs(self):
88         logs = self.driver.execute_script("return window.logs")
89         if logs is not None:
90             return "\n".join(str(x) for x in logs)
91         else:
92             return ""
93 
94     def clean_logs(self):
95         self.driver.execute_script("window.logs = []")
96 
97     def run(self, code):
98         return self.run_js("return pyodide.runPython({!r})".format(code))
99 
100     def run_async(self, code):
101         return self.run_js_async("return pyodide.runPythonAsync({!r})".format(code))
102 
103     def run_js(self, code):
104         if isinstance(code, str) and code.startswith("\n"):
105             # we have a multiline string, fix indentation
106             code = textwrap.dedent(code)
107         wrapper = """
108             Error.stackTraceLimit = Infinity;
109             let run = () => { %s }
110             try {
111                 return [0, run()]
112             } catch (e) {
113                 return [1, e.toString(), e.stack];
114             }
115             """
116 
117         retval = self.driver.execute_script(wrapper % code)
118 
119         if retval[0] == 0:
120             return retval[1]
121         else:
122             raise JavascriptException(retval[1], retval[2])
123 
124     def run_js_async(self, code):
125         if isinstance(code, str) and code.startswith("\n"):
126             # we have a multiline string, fix indentation
127             code = textwrap.dedent(code)
128 
129         wrapper = """
130             let cb = arguments[arguments.length - 1];
131             let run = async () => { %s }
132             (async () => {
133                 try {{
134                     cb([0, await run()]);
135                 }} catch (e) {{
136                     cb([1, e.toString(), e.stack]);
137                 }}
138             })()
139         """
140 
141         retval = self.driver.execute_async_script(wrapper % code)
142 
143         if retval[0] == 0:
144             return retval[1]
145         else:
146             raise JavascriptException(retval[1], retval[2])
147 
148     def run_webworker(self, code):
149         if isinstance(code, str) and code.startswith("\n"):
150             # we have a multiline string, fix indentation
151             code = textwrap.dedent(code)
152 
153         return self.run_js_async(
154             """
155             let worker = new Worker( '{}' );
156             worker.postMessage({{ python: {!r} }});
157             return new Promise((res, rej) => {{
158                 worker.onerror = e => rej(e);
159                 worker.onmessage = e => {{
160                     if (e.data.results) {{
161                        res(e.data.results);
162                     }} else {{
163                        rej(e.data.error);
164                     }}
165                 }};
166             }})
167             """.format(
168                 f"http://{self.server_hostname}:{self.server_port}/webworker_dev.js",
169                 code,
170             )
171         )
172 
173     def load_package(self, packages):
174         self.run_js_async("await pyodide.loadPackage({!r})".format(packages))
175 
176     @property
177     def urls(self):
178         for handle in self.driver.window_handles:
179             self.driver.switch_to.window(handle)
180             yield self.driver.current_url
181 
182 
183 class FirefoxWrapper(SeleniumWrapper):
184 
185     browser = "firefox"
186 
187     def get_driver(self):
188         from selenium.webdriver import Firefox
189         from selenium.webdriver.firefox.options import Options
190 
191         options = Options()
192         options.add_argument("-headless")
193 
194         return Firefox(executable_path="geckodriver", options=options)
195 
196 
197 class ChromeWrapper(SeleniumWrapper):
198 
199     browser = "chrome"
200 
201     def get_driver(self):
202         from selenium.webdriver import Chrome
203         from selenium.webdriver.chrome.options import Options
204 
205         options = Options()
206         options.add_argument("--headless")
207         options.add_argument("--no-sandbox")
208 
209         return Chrome(options=options)
210 
211 
212 if pytest is not None:
213 
214     @pytest.fixture(params=["firefox", "chrome"])
215     def selenium_standalone(request, web_server_main):
216         server_hostname, server_port, server_log = web_server_main
217         if request.param == "firefox":
218             cls = FirefoxWrapper
219         elif request.param == "chrome":
220             cls = ChromeWrapper
221         selenium = cls(
222             build_dir=request.config.option.build_dir,
223             server_port=server_port,
224             server_hostname=server_hostname,
225             server_log=server_log,
226         )
227         try:
228             yield selenium
229         finally:
230             print(selenium.logs)
231             selenium.driver.quit()
232 
233     @pytest.fixture(params=["firefox", "chrome"], scope="module")
234     def _selenium_cached(request, web_server_main):
235         # Cached selenium instance. This is a copy-paste of
236         # selenium_standalone to avoid fixture scope issues
237         server_hostname, server_port, server_log = web_server_main
238         if request.param == "firefox":
239             cls = FirefoxWrapper
240         elif request.param == "chrome":
241             cls = ChromeWrapper
242         selenium = cls(
243             build_dir=request.config.option.build_dir,
244             server_port=server_port,
245             server_hostname=server_hostname,
246             server_log=server_log,
247         )
248         try:
249             yield selenium
250         finally:
251             selenium.driver.quit()
252 
253     @pytest.fixture
254     def selenium(_selenium_cached):
255         # selenium instance cached at the module level
256         try:
257             _selenium_cached.clean_logs()
258             yield _selenium_cached
259         finally:
260             print(_selenium_cached.logs)
261 
262 
263 @pytest.fixture(scope="session")
264 def web_server_main(request):
265     """Web server that serves files in the build/ directory"""
266     with spawn_web_server(request.config.option.build_dir) as output:
267         yield output
268 
269 
270 @pytest.fixture(scope="session")
271 def web_server_secondary(request):
272     """Secondary web server that serves files build/ directory"""
273     with spawn_web_server(request.config.option.build_dir) as output:
274         yield output
275 
276 
277 @pytest.fixture(scope="session")
278 def web_server_tst_data(request):
279     """Web server that serves files in the src/tests/data/ directory"""
280     with spawn_web_server(TEST_PATH / "data") as output:
281         yield output
282 
283 
284 @contextlib.contextmanager
285 def spawn_web_server(build_dir=None):
286 
287     if build_dir is None:
288         build_dir = BUILD_PATH
289 
290     tmp_dir = tempfile.mkdtemp()
291     log_path = pathlib.Path(tmp_dir) / "http-server.log"
292     q = multiprocessing.Queue()
293     p = multiprocessing.Process(target=run_web_server, args=(q, log_path, build_dir))
294 
295     try:
296         p.start()
297         port = q.get()
298         hostname = "127.0.0.1"
299 
300         print(
301             f"Spawning webserver at http://{hostname}:{port} "
302             f"(see logs in {log_path})"
303         )
304         yield hostname, port, log_path
305     finally:
306         q.put("TERMINATE")
307         p.join()
308         shutil.rmtree(tmp_dir)
309 
310 
311 def run_web_server(q, log_filepath, build_dir):
312     """Start the HTTP web server
313 
314     Parameters
315     ----------
316     q : Queue
317       communication queue
318     log_path : pathlib.Path
319       path to the file where to store the logs
320     """
321     import http.server
322     import socketserver
323 
324     os.chdir(build_dir)
325 
326     log_fh = log_filepath.open("w", buffering=1)
327     sys.stdout = log_fh
328     sys.stderr = log_fh
329 
330     test_prefix = "/src/tests/"
331 
332     class Handler(http.server.SimpleHTTPRequestHandler):
333         def log_message(self, format_, *args):
334             print(
335                 "[%s] source: %s:%s - %s"
336                 % (self.log_date_time_string(), *self.client_address, format_ % args)
337             )
338 
339         def end_headers(self):
340             # Enable Cross-Origin Resource Sharing (CORS)
341             self.send_header("Access-Control-Allow-Origin", "*")
342             super().end_headers()
343 
344     with socketserver.TCPServer(("", 0), Handler) as httpd:
345         host, port = httpd.server_address
346         print(f"Starting webserver at http://{host}:{port}")
347         httpd.server_name = "test-server"
348         httpd.server_port = port
349         q.put(port)
350 
351         def service_actions():
352             try:
353                 if q.get(False) == "TERMINATE":
354                     print("Stopping server...")
355                     sys.exit(0)
356             except queue.Empty:
357                 pass
358 
359         httpd.service_actions = service_actions
360         httpd.serve_forever()
361 
362 
363 if (
364     __name__ == "__main__"
365     and multiprocessing.current_process().name == "MainProcess"
366     and not hasattr(sys, "_pytest_session")
367 ):
368     with spawn_web_server():
369         # run forever
370         while True:
371             time.sleep(1)
372
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/conftest.py b/conftest.py
--- a/conftest.py
+++ b/conftest.py
@@ -108,7 +108,18 @@
             Error.stackTraceLimit = Infinity;
             let run = () => { %s }
             try {
-                return [0, run()]
+                let result = run();
+                if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){
+                    try {
+                        pyodide._module._pythonexc2js();
+                    } catch(e){
+                        console.error(`Python exited with error flag set! Error was:\n{e.message}`);
+                        // Don't put original error message in new one: we want
+                        // "pytest.raises(xxx, match=msg)" to fail
+                        throw new Error(`Python exited with error flag set!`);
+                    }
+                }
+                return [0, result]
             } catch (e) {
                 return [1, e.toString(), e.stack];
             }
@@ -130,11 +141,22 @@
             let cb = arguments[arguments.length - 1];
             let run = async () => { %s }
             (async () => {
-                try {{
-                    cb([0, await run()]);
-                }} catch (e) {{
+                try {
+                    let result = await run();
+                    if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){
+                        try {
+                            pyodide._module._pythonexc2js();
+                        } catch(e){
+                            console.error(`Python exited with error flag set! Error was:\n{e.message}`);
+                            // Don't put original error message in new one: we want
+                            // "pytest.raises(xxx, match=msg)" to fail
+                            throw new Error(`Python exited with error flag set!`);
+                        }
+                    }
+                    cb([0, result]);
+                } catch (e) {
                     cb([1, e.toString(), e.stack]);
-                }}
+                }
             })()
         """

{"golden_diff": "diff --git a/conftest.py b/conftest.py\n--- a/conftest.py\n+++ b/conftest.py\n@@ -108,7 +108,18 @@\n             Error.stackTraceLimit = Infinity;\n             let run = () => { %s }\n             try {\n-                return [0, run()]\n+                let result = run();\n+                if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){\n+                    try {\n+                        pyodide._module._pythonexc2js();\n+                    } catch(e){\n+                        console.error(`Python exited with error flag set! Error was:\\n{e.message}`);\n+                        // Don't put original error message in new one: we want\n+                        // \"pytest.raises(xxx, match=msg)\" to fail\n+                        throw new Error(`Python exited with error flag set!`);\n+                    }\n+                }\n+                return [0, result]\n             } catch (e) {\n                 return [1, e.toString(), e.stack];\n             }\n@@ -130,11 +141,22 @@\n             let cb = arguments[arguments.length - 1];\n             let run = async () => { %s }\n             (async () => {\n-                try {{\n-                    cb([0, await run()]);\n-                }} catch (e) {{\n+                try {\n+                    let result = await run();\n+                    if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){\n+                        try {\n+                            pyodide._module._pythonexc2js();\n+                        } catch(e){\n+                            console.error(`Python exited with error flag set! Error was:\\n{e.message}`);\n+                            // Don't put original error message in new one: we want\n+                            // \"pytest.raises(xxx, match=msg)\" to fail\n+                            throw new Error(`Python exited with error flag set!`);\n+                        }\n+                    }\n+                    cb([0, result]);\n+                } catch (e) {\n                     cb([1, e.toString(), e.stack]);\n-                }}\n+                }\n             })()\n         \"\"\"\n", "issue": "Soundness error in python2js_buffer\nThis bug goes back at least to version 0.15.0. It's been messing up my error handling code.\r\nFirst:\r\n```python\r\nimport numpy as np\r\nx = np.array([['string1', 'string2'], ['string3', 'string4']])\r\n```\r\nSecond: type `x<enter>1<enter>x<enter>1<enter>`.\n", "before_files": [{"content": "\"\"\"\nVarious common utilities for testing.\n\"\"\"\n\nimport contextlib\nimport multiprocessing\nimport textwrap\nimport tempfile\nimport time\nimport os\nimport pathlib\nimport queue\nimport sys\nimport shutil\n\nROOT_PATH = pathlib.Path(__file__).parents[0].resolve()\nTEST_PATH = ROOT_PATH / \"src\" / \"tests\"\nBUILD_PATH = ROOT_PATH / \"build\"\n\nsys.path.append(str(ROOT_PATH))\n\nfrom pyodide_build._fixes import _selenium_is_connectable  # noqa: E402\nimport selenium.webdriver.common.utils  # noqa: E402\n\n# XXX: Temporary fix for ConnectionError in selenium\n\nselenium.webdriver.common.utils.is_connectable = _selenium_is_connectable\n\ntry:\n    import pytest\n\n    def pytest_addoption(parser):\n        group = parser.getgroup(\"general\")\n        group.addoption(\n            \"--build-dir\",\n            action=\"store\",\n            default=BUILD_PATH,\n            help=\"Path to the build directory\",\n        )\n        group.addoption(\n            \"--run-xfail\",\n            action=\"store_true\",\n            help=\"If provided, tests marked as xfail will be run\",\n        )\n\n\nexcept ImportError:\n    pytest = None  # type: ignore\n\n\nclass JavascriptException(Exception):\n    def __init__(self, msg, stack):\n        self.msg = msg\n        self.stack = stack\n\n    def __str__(self):\n        if self.stack:\n            return self.msg + \"\\n\\n\" + self.stack\n        else:\n            return self.msg\n\n\nclass SeleniumWrapper:\n    JavascriptException = JavascriptException\n\n    def __init__(\n        self, server_port, server_hostname=\"127.0.0.1\", server_log=None, build_dir=None\n    ):\n        if build_dir is None:\n            build_dir = BUILD_PATH\n\n        self.driver = self.get_driver()\n        self.server_port = server_port\n        self.server_hostname = server_hostname\n        self.server_log = server_log\n\n        if not (pathlib.Path(build_dir) / \"test.html\").exists():\n            # selenium does not expose HTTP response codes\n            raise ValueError(\n                f\"{(build_dir / 'test.html').resolve()} \" f\"does not exist!\"\n            )\n        self.driver.get(f\"http://{server_hostname}:{server_port}/test.html\")\n        self.run_js(\"Error.stackTraceLimit = Infinity\")\n        self.run_js_async(\"await languagePluginLoader\")\n\n    @property\n    def logs(self):\n        logs = self.driver.execute_script(\"return window.logs\")\n        if logs is not None:\n            return \"\\n\".join(str(x) for x in logs)\n        else:\n            return \"\"\n\n    def clean_logs(self):\n        self.driver.execute_script(\"window.logs = []\")\n\n    def run(self, code):\n        return self.run_js(\"return pyodide.runPython({!r})\".format(code))\n\n    def run_async(self, code):\n        return self.run_js_async(\"return pyodide.runPythonAsync({!r})\".format(code))\n\n    def run_js(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n        wrapper = \"\"\"\n            Error.stackTraceLimit = Infinity;\n            let run = () => { %s }\n            try {\n                return [0, run()]\n            } catch (e) {\n                return [1, e.toString(), e.stack];\n            }\n            \"\"\"\n\n        retval = self.driver.execute_script(wrapper % code)\n\n        if retval[0] == 0:\n            return retval[1]\n        else:\n            raise JavascriptException(retval[1], retval[2])\n\n    def run_js_async(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n\n        wrapper = \"\"\"\n            let cb = arguments[arguments.length - 1];\n            let run = async () => { %s }\n            (async () => {\n                try {{\n                    cb([0, await run()]);\n                }} catch (e) {{\n                    cb([1, e.toString(), e.stack]);\n                }}\n            })()\n        \"\"\"\n\n        retval = self.driver.execute_async_script(wrapper % code)\n\n        if retval[0] == 0:\n            return retval[1]\n        else:\n            raise JavascriptException(retval[1], retval[2])\n\n    def run_webworker(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n\n        return self.run_js_async(\n            \"\"\"\n            let worker = new Worker( '{}' );\n            worker.postMessage({{ python: {!r} }});\n            return new Promise((res, rej) => {{\n                worker.onerror = e => rej(e);\n                worker.onmessage = e => {{\n                    if (e.data.results) {{\n                       res(e.data.results);\n                    }} else {{\n                       rej(e.data.error);\n                    }}\n                }};\n            }})\n            \"\"\".format(\n                f\"http://{self.server_hostname}:{self.server_port}/webworker_dev.js\",\n                code,\n            )\n        )\n\n    def load_package(self, packages):\n        self.run_js_async(\"await pyodide.loadPackage({!r})\".format(packages))\n\n    @property\n    def urls(self):\n        for handle in self.driver.window_handles:\n            self.driver.switch_to.window(handle)\n            yield self.driver.current_url\n\n\nclass FirefoxWrapper(SeleniumWrapper):\n\n    browser = \"firefox\"\n\n    def get_driver(self):\n        from selenium.webdriver import Firefox\n        from selenium.webdriver.firefox.options import Options\n\n        options = Options()\n        options.add_argument(\"-headless\")\n\n        return Firefox(executable_path=\"geckodriver\", options=options)\n\n\nclass ChromeWrapper(SeleniumWrapper):\n\n    browser = \"chrome\"\n\n    def get_driver(self):\n        from selenium.webdriver import Chrome\n        from selenium.webdriver.chrome.options import Options\n\n        options = Options()\n        options.add_argument(\"--headless\")\n        options.add_argument(\"--no-sandbox\")\n\n        return Chrome(options=options)\n\n\nif pytest is not None:\n\n    @pytest.fixture(params=[\"firefox\", \"chrome\"])\n    def selenium_standalone(request, web_server_main):\n        server_hostname, server_port, server_log = web_server_main\n        if request.param == \"firefox\":\n            cls = FirefoxWrapper\n        elif request.param == \"chrome\":\n            cls = ChromeWrapper\n        selenium = cls(\n            build_dir=request.config.option.build_dir,\n            server_port=server_port,\n            server_hostname=server_hostname,\n            server_log=server_log,\n        )\n        try:\n            yield selenium\n        finally:\n            print(selenium.logs)\n            selenium.driver.quit()\n\n    @pytest.fixture(params=[\"firefox\", \"chrome\"], scope=\"module\")\n    def _selenium_cached(request, web_server_main):\n        # Cached selenium instance. This is a copy-paste of\n        # selenium_standalone to avoid fixture scope issues\n        server_hostname, server_port, server_log = web_server_main\n        if request.param == \"firefox\":\n            cls = FirefoxWrapper\n        elif request.param == \"chrome\":\n            cls = ChromeWrapper\n        selenium = cls(\n            build_dir=request.config.option.build_dir,\n            server_port=server_port,\n            server_hostname=server_hostname,\n            server_log=server_log,\n        )\n        try:\n            yield selenium\n        finally:\n            selenium.driver.quit()\n\n    @pytest.fixture\n    def selenium(_selenium_cached):\n        # selenium instance cached at the module level\n        try:\n            _selenium_cached.clean_logs()\n            yield _selenium_cached\n        finally:\n            print(_selenium_cached.logs)\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_main(request):\n    \"\"\"Web server that serves files in the build/ directory\"\"\"\n    with spawn_web_server(request.config.option.build_dir) as output:\n        yield output\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_secondary(request):\n    \"\"\"Secondary web server that serves files build/ directory\"\"\"\n    with spawn_web_server(request.config.option.build_dir) as output:\n        yield output\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_tst_data(request):\n    \"\"\"Web server that serves files in the src/tests/data/ directory\"\"\"\n    with spawn_web_server(TEST_PATH / \"data\") as output:\n        yield output\n\n\n@contextlib.contextmanager\ndef spawn_web_server(build_dir=None):\n\n    if build_dir is None:\n        build_dir = BUILD_PATH\n\n    tmp_dir = tempfile.mkdtemp()\n    log_path = pathlib.Path(tmp_dir) / \"http-server.log\"\n    q = multiprocessing.Queue()\n    p = multiprocessing.Process(target=run_web_server, args=(q, log_path, build_dir))\n\n    try:\n        p.start()\n        port = q.get()\n        hostname = \"127.0.0.1\"\n\n        print(\n            f\"Spawning webserver at http://{hostname}:{port} \"\n            f\"(see logs in {log_path})\"\n        )\n        yield hostname, port, log_path\n    finally:\n        q.put(\"TERMINATE\")\n        p.join()\n        shutil.rmtree(tmp_dir)\n\n\ndef run_web_server(q, log_filepath, build_dir):\n    \"\"\"Start the HTTP web server\n\n    Parameters\n    ----------\n    q : Queue\n      communication queue\n    log_path : pathlib.Path\n      path to the file where to store the logs\n    \"\"\"\n    import http.server\n    import socketserver\n\n    os.chdir(build_dir)\n\n    log_fh = log_filepath.open(\"w\", buffering=1)\n    sys.stdout = log_fh\n    sys.stderr = log_fh\n\n    test_prefix = \"/src/tests/\"\n\n    class Handler(http.server.SimpleHTTPRequestHandler):\n        def log_message(self, format_, *args):\n            print(\n                \"[%s] source: %s:%s - %s\"\n                % (self.log_date_time_string(), *self.client_address, format_ % args)\n            )\n\n        def end_headers(self):\n            # Enable Cross-Origin Resource Sharing (CORS)\n            self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n            super().end_headers()\n\n    with socketserver.TCPServer((\"\", 0), Handler) as httpd:\n        host, port = httpd.server_address\n        print(f\"Starting webserver at http://{host}:{port}\")\n        httpd.server_name = \"test-server\"\n        httpd.server_port = port\n        q.put(port)\n\n        def service_actions():\n            try:\n                if q.get(False) == \"TERMINATE\":\n                    print(\"Stopping server...\")\n                    sys.exit(0)\n            except queue.Empty:\n                pass\n\n        httpd.service_actions = service_actions\n        httpd.serve_forever()\n\n\nif (\n    __name__ == \"__main__\"\n    and multiprocessing.current_process().name == \"MainProcess\"\n    and not hasattr(sys, \"_pytest_session\")\n):\n    with spawn_web_server():\n        # run forever\n        while True:\n            time.sleep(1)\n", "path": "conftest.py"}], "after_files": [{"content": "\"\"\"\nVarious common utilities for testing.\n\"\"\"\n\nimport contextlib\nimport multiprocessing\nimport textwrap\nimport tempfile\nimport time\nimport os\nimport pathlib\nimport queue\nimport sys\nimport shutil\n\nROOT_PATH = pathlib.Path(__file__).parents[0].resolve()\nTEST_PATH = ROOT_PATH / \"src\" / \"tests\"\nBUILD_PATH = ROOT_PATH / \"build\"\n\nsys.path.append(str(ROOT_PATH))\n\nfrom pyodide_build._fixes import _selenium_is_connectable  # noqa: E402\nimport selenium.webdriver.common.utils  # noqa: E402\n\n# XXX: Temporary fix for ConnectionError in selenium\n\nselenium.webdriver.common.utils.is_connectable = _selenium_is_connectable\n\ntry:\n    import pytest\n\n    def pytest_addoption(parser):\n        group = parser.getgroup(\"general\")\n        group.addoption(\n            \"--build-dir\",\n            action=\"store\",\n            default=BUILD_PATH,\n            help=\"Path to the build directory\",\n        )\n        group.addoption(\n            \"--run-xfail\",\n            action=\"store_true\",\n            help=\"If provided, tests marked as xfail will be run\",\n        )\n\n\nexcept ImportError:\n    pytest = None  # type: ignore\n\n\nclass JavascriptException(Exception):\n    def __init__(self, msg, stack):\n        self.msg = msg\n        self.stack = stack\n\n    def __str__(self):\n        if self.stack:\n            return self.msg + \"\\n\\n\" + self.stack\n        else:\n            return self.msg\n\n\nclass SeleniumWrapper:\n    JavascriptException = JavascriptException\n\n    def __init__(\n        self, server_port, server_hostname=\"127.0.0.1\", server_log=None, build_dir=None\n    ):\n        if build_dir is None:\n            build_dir = BUILD_PATH\n\n        self.driver = self.get_driver()\n        self.server_port = server_port\n        self.server_hostname = server_hostname\n        self.server_log = server_log\n\n        if not (pathlib.Path(build_dir) / \"test.html\").exists():\n            # selenium does not expose HTTP response codes\n            raise ValueError(\n                f\"{(build_dir / 'test.html').resolve()} \" f\"does not exist!\"\n            )\n        self.driver.get(f\"http://{server_hostname}:{server_port}/test.html\")\n        self.run_js(\"Error.stackTraceLimit = Infinity\")\n        self.run_js_async(\"await languagePluginLoader\")\n\n    @property\n    def logs(self):\n        logs = self.driver.execute_script(\"return window.logs\")\n        if logs is not None:\n            return \"\\n\".join(str(x) for x in logs)\n        else:\n            return \"\"\n\n    def clean_logs(self):\n        self.driver.execute_script(\"window.logs = []\")\n\n    def run(self, code):\n        return self.run_js(\"return pyodide.runPython({!r})\".format(code))\n\n    def run_async(self, code):\n        return self.run_js_async(\"return pyodide.runPythonAsync({!r})\".format(code))\n\n    def run_js(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n        wrapper = \"\"\"\n            Error.stackTraceLimit = Infinity;\n            let run = () => { %s }\n            try {\n                let result = run();\n                if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){\n                    try {\n                        pyodide._module._pythonexc2js();\n                    } catch(e){\n                        console.error(`Python exited with error flag set! Error was:\\n{e.message}`);\n                        // Don't put original error message in new one: we want\n                        // \"pytest.raises(xxx, match=msg)\" to fail\n                        throw new Error(`Python exited with error flag set!`);\n                    }\n                }\n                return [0, result]\n            } catch (e) {\n                return [1, e.toString(), e.stack];\n            }\n            \"\"\"\n\n        retval = self.driver.execute_script(wrapper % code)\n\n        if retval[0] == 0:\n            return retval[1]\n        else:\n            raise JavascriptException(retval[1], retval[2])\n\n    def run_js_async(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n\n        wrapper = \"\"\"\n            let cb = arguments[arguments.length - 1];\n            let run = async () => { %s }\n            (async () => {\n                try {\n                    let result = await run();\n                    if(pyodide && pyodide._module && pyodide._module._PyErr_Occurred()){\n                        try {\n                            pyodide._module._pythonexc2js();\n                        } catch(e){\n                            console.error(`Python exited with error flag set! Error was:\\n{e.message}`);\n                            // Don't put original error message in new one: we want\n                            // \"pytest.raises(xxx, match=msg)\" to fail\n                            throw new Error(`Python exited with error flag set!`);\n                        }\n                    }\n                    cb([0, result]);\n                } catch (e) {\n                    cb([1, e.toString(), e.stack]);\n                }\n            })()\n        \"\"\"\n\n        retval = self.driver.execute_async_script(wrapper % code)\n\n        if retval[0] == 0:\n            return retval[1]\n        else:\n            raise JavascriptException(retval[1], retval[2])\n\n    def run_webworker(self, code):\n        if isinstance(code, str) and code.startswith(\"\\n\"):\n            # we have a multiline string, fix indentation\n            code = textwrap.dedent(code)\n\n        return self.run_js_async(\n            \"\"\"\n            let worker = new Worker( '{}' );\n            worker.postMessage({{ python: {!r} }});\n            return new Promise((res, rej) => {{\n                worker.onerror = e => rej(e);\n                worker.onmessage = e => {{\n                    if (e.data.results) {{\n                       res(e.data.results);\n                    }} else {{\n                       rej(e.data.error);\n                    }}\n                }};\n            }})\n            \"\"\".format(\n                f\"http://{self.server_hostname}:{self.server_port}/webworker_dev.js\",\n                code,\n            )\n        )\n\n    def load_package(self, packages):\n        self.run_js_async(\"await pyodide.loadPackage({!r})\".format(packages))\n\n    @property\n    def urls(self):\n        for handle in self.driver.window_handles:\n            self.driver.switch_to.window(handle)\n            yield self.driver.current_url\n\n\nclass FirefoxWrapper(SeleniumWrapper):\n\n    browser = \"firefox\"\n\n    def get_driver(self):\n        from selenium.webdriver import Firefox\n        from selenium.webdriver.firefox.options import Options\n\n        options = Options()\n        options.add_argument(\"-headless\")\n\n        return Firefox(executable_path=\"geckodriver\", options=options)\n\n\nclass ChromeWrapper(SeleniumWrapper):\n\n    browser = \"chrome\"\n\n    def get_driver(self):\n        from selenium.webdriver import Chrome\n        from selenium.webdriver.chrome.options import Options\n\n        options = Options()\n        options.add_argument(\"--headless\")\n        options.add_argument(\"--no-sandbox\")\n\n        return Chrome(options=options)\n\n\nif pytest is not None:\n\n    @pytest.fixture(params=[\"firefox\", \"chrome\"])\n    def selenium_standalone(request, web_server_main):\n        server_hostname, server_port, server_log = web_server_main\n        if request.param == \"firefox\":\n            cls = FirefoxWrapper\n        elif request.param == \"chrome\":\n            cls = ChromeWrapper\n        selenium = cls(\n            build_dir=request.config.option.build_dir,\n            server_port=server_port,\n            server_hostname=server_hostname,\n            server_log=server_log,\n        )\n        try:\n            yield selenium\n        finally:\n            print(selenium.logs)\n            selenium.driver.quit()\n\n    @pytest.fixture(params=[\"firefox\", \"chrome\"], scope=\"module\")\n    def _selenium_cached(request, web_server_main):\n        # Cached selenium instance. This is a copy-paste of\n        # selenium_standalone to avoid fixture scope issues\n        server_hostname, server_port, server_log = web_server_main\n        if request.param == \"firefox\":\n            cls = FirefoxWrapper\n        elif request.param == \"chrome\":\n            cls = ChromeWrapper\n        selenium = cls(\n            build_dir=request.config.option.build_dir,\n            server_port=server_port,\n            server_hostname=server_hostname,\n            server_log=server_log,\n        )\n        try:\n            yield selenium\n        finally:\n            selenium.driver.quit()\n\n    @pytest.fixture\n    def selenium(_selenium_cached):\n        # selenium instance cached at the module level\n        try:\n            _selenium_cached.clean_logs()\n            yield _selenium_cached\n        finally:\n            print(_selenium_cached.logs)\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_main(request):\n    \"\"\"Web server that serves files in the build/ directory\"\"\"\n    with spawn_web_server(request.config.option.build_dir) as output:\n        yield output\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_secondary(request):\n    \"\"\"Secondary web server that serves files build/ directory\"\"\"\n    with spawn_web_server(request.config.option.build_dir) as output:\n        yield output\n\n\n@pytest.fixture(scope=\"session\")\ndef web_server_tst_data(request):\n    \"\"\"Web server that serves files in the src/tests/data/ directory\"\"\"\n    with spawn_web_server(TEST_PATH / \"data\") as output:\n        yield output\n\n\n@contextlib.contextmanager\ndef spawn_web_server(build_dir=None):\n\n    if build_dir is None:\n        build_dir = BUILD_PATH\n\n    tmp_dir = tempfile.mkdtemp()\n    log_path = pathlib.Path(tmp_dir) / \"http-server.log\"\n    q = multiprocessing.Queue()\n    p = multiprocessing.Process(target=run_web_server, args=(q, log_path, build_dir))\n\n    try:\n        p.start()\n        port = q.get()\n        hostname = \"127.0.0.1\"\n\n        print(\n            f\"Spawning webserver at http://{hostname}:{port} \"\n            f\"(see logs in {log_path})\"\n        )\n        yield hostname, port, log_path\n    finally:\n        q.put(\"TERMINATE\")\n        p.join()\n        shutil.rmtree(tmp_dir)\n\n\ndef run_web_server(q, log_filepath, build_dir):\n    \"\"\"Start the HTTP web server\n\n    Parameters\n    ----------\n    q : Queue\n      communication queue\n    log_path : pathlib.Path\n      path to the file where to store the logs\n    \"\"\"\n    import http.server\n    import socketserver\n\n    os.chdir(build_dir)\n\n    log_fh = log_filepath.open(\"w\", buffering=1)\n    sys.stdout = log_fh\n    sys.stderr = log_fh\n\n    test_prefix = \"/src/tests/\"\n\n    class Handler(http.server.SimpleHTTPRequestHandler):\n        def log_message(self, format_, *args):\n            print(\n                \"[%s] source: %s:%s - %s\"\n                % (self.log_date_time_string(), *self.client_address, format_ % args)\n            )\n\n        def end_headers(self):\n            # Enable Cross-Origin Resource Sharing (CORS)\n            self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n            super().end_headers()\n\n    with socketserver.TCPServer((\"\", 0), Handler) as httpd:\n        host, port = httpd.server_address\n        print(f\"Starting webserver at http://{host}:{port}\")\n        httpd.server_name = \"test-server\"\n        httpd.server_port = port\n        q.put(port)\n\n        def service_actions():\n            try:\n                if q.get(False) == \"TERMINATE\":\n                    print(\"Stopping server...\")\n                    sys.exit(0)\n            except queue.Empty:\n                pass\n\n        httpd.service_actions = service_actions\n        httpd.serve_forever()\n\n\nif (\n    __name__ == \"__main__\"\n    and multiprocessing.current_process().name == \"MainProcess\"\n    and not hasattr(sys, \"_pytest_session\")\n):\n    with spawn_web_server():\n        # run forever\n        while True:\n            time.sleep(1)\n", "path": "conftest.py"}]}