File: test_emscripten.py

package info (click to toggle)
python-urllib3 2.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,340 kB
  • sloc: python: 26,167; makefile: 122; javascript: 92; sh: 11
file content (1291 lines) | stat: -rw-r--r-- 46,839 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
from __future__ import annotations

import sys
import typing

import pytest

from urllib3.fields import _TYPE_FIELD_VALUE_TUPLE

from ...port_helpers import find_unused_port

if sys.version_info < (3, 11):
    # pyodide only works on 3.11+
    pytest.skip(allow_module_level=True)

# only run these tests if pytest_pyodide is installed
# so we don't break non-emscripten pytest running
pytest_pyodide = pytest.importorskip("pytest_pyodide")

from pytest_pyodide import run_in_pyodide  # type: ignore[import-not-found] # noqa: E402

from .conftest import PyodideServerInfo, ServerRunnerInfo  # noqa: E402

# make our ssl certificates work in chrome
pyodide_config = pytest_pyodide.config.get_global_config()
pyodide_config.set_flags(
    "chrome", ["ignore-certificate-errors"] + pyodide_config.get_flags("chrome")
)


def test_index(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int, has_jspi: bool) -> None:  # type: ignore[no-untyped-def]
        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        assert urllib3.contrib.emscripten.fetch.has_jspi() == has_jspi
        conn = HTTPConnection(host, port)
        url = f"http://{host}:{port}/"
        conn.request("GET", url)
        response = conn.getresponse()
        # check methods of response
        assert isinstance(response, BaseHTTPResponse)
        assert response.url == url
        response.url = "http://woo"
        assert response.url == "http://woo"
        assert response.connection == conn
        assert response.retries is None
        data1 = response.data
        decoded1 = data1.decode("utf-8")
        data2 = response.data  # check that getting data twice works
        decoded2 = data2.decode("utf-8")
        assert decoded1 == decoded2 == "Dummy server!"

    pyodide_test(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        has_jspi,
    )


def test_pool_requests(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int, https_port: int, has_jspi: bool) -> None:  # type: ignore[no-untyped-def]
        # first with PoolManager
        import urllib3
        import urllib3.contrib.emscripten.fetch

        assert urllib3.contrib.emscripten.fetch.has_jspi() == has_jspi

        http = urllib3.PoolManager()
        resp = http.request("GET", f"http://{host}:{port}/")
        assert resp.data.decode("utf-8") == "Dummy server!"

        resp2 = http.request("GET", f"http://{host}:{port}/index")
        assert resp2.data.decode("utf-8") == "Dummy server!"

        # should all have come from one pool
        assert len(http.pools) == 1

        resp3 = http.request("GET", f"https://{host}:{https_port}/")
        assert resp3.data.decode("utf-8") == "Dummy server!"

        # one http pool + one https pool
        assert len(http.pools) == 2

        # now with ConnectionPool
        # because block == True, this will fail if the connection isn't
        # returned to the pool correctly after the first request
        pool = urllib3.HTTPConnectionPool(host, port, maxsize=1, block=True)
        resp3 = pool.urlopen("GET", "/index")
        assert resp3.data.decode("utf-8") == "Dummy server!"

        resp4 = pool.urlopen("GET", "/")
        assert resp4.data.decode("utf-8") == "Dummy server!"

        # now with manual release of connection
        # first - connection should be released once all
        # data is read
        pool2 = urllib3.HTTPConnectionPool(host, port, maxsize=1, block=True)

        resp5 = pool2.urlopen("GET", "/index", preload_content=False)
        assert pool2.pool is not None
        # at this point, the connection should not be in the pool
        assert pool2.pool.qsize() == 0
        assert resp5.data.decode("utf-8") == "Dummy server!"
        # now we've read all the data, connection should be back to the pool
        assert pool2.pool.qsize() == 1
        resp6 = pool2.urlopen("GET", "/index", preload_content=False)
        assert pool2.pool.qsize() == 0
        # force it back to the pool
        resp6.release_conn()
        assert pool2.pool.qsize() == 1
        read_str = resp6.read()
        # for consistency with urllib3, this still returns the correct data even though
        # we are in theory not using the connection any more
        assert read_str.decode("utf-8") == "Dummy server!"

    pyodide_test(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        testserver_http.https_port,
        has_jspi,
    )


# wrong protocol / protocol error etc. should raise an exception of http.client.HTTPException
def test_wrong_protocol(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import http.client

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        with pytest.raises(http.client.HTTPException):
            conn.request("GET", f"http://{host}:{port}/")

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


# wrong protocol / protocol error etc. should raise an exception of http.client.HTTPException
def test_bad_method(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import http.client

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        with pytest.raises(http.client.HTTPException):
            conn.request("TRACE", f"http://{host}:{port}/")

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


# no connection - should raise
def test_no_response(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import http.client

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        with pytest.raises(http.client.HTTPException):
            conn.request("GET", f"http://{host}:{port}/")
            _ = conn.getresponse()

    pyodide_test(selenium_coverage, testserver_http.http_host, find_unused_port())


def test_404(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPConnection(host, port)
        conn.request("GET", f"http://{host}:{port}/status?status=404 NOT FOUND")
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        assert response.status == 404

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


# setting timeout should show a warning to js console
# if we're on the ui thread, because XMLHttpRequest doesn't
# support timeout in async mode if globalThis == Window
@pytest.mark.without_jspi
def test_timeout_warning(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
) -> None:
    @run_in_pyodide()  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import js  # type: ignore[import-not-found]

        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPConnection

        old_log = js.console.warn
        log_msgs = []

        def capture_log(*args):  # type: ignore[no-untyped-def]
            log_msgs.append(str(args))
            old_log(*args)

        js.console.warn = capture_log

        conn = HTTPConnection(host, port, timeout=1.0)
        conn.request("GET", f"http://{host}:{port}/")
        conn.getresponse()
        js.console.warn = old_log
        # should have shown timeout warning exactly once by now
        assert len([x for x in log_msgs if x.find("Warning: Timeout") != -1]) == 1
        assert urllib3.contrib.emscripten.fetch._SHOWN_TIMEOUT_WARNING

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.webworkers
def test_timeout_in_worker_non_streaming(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    worker_code = f"""
        from urllib3.exceptions import TimeoutError
        from urllib3.connection import HTTPConnection
        from pyodide.ffi import JsException
        from http.client import HTTPException
        conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port},timeout=1.0)
        result=-1
        try:
            conn.request("GET","/slow",preload_content = True)
            _response = conn.getresponse()
            result=-3
        except TimeoutError as e:
            result=1 # we've got the correct exception
        except HTTPException as e:
            result=-3
        except BaseException as e:
            result=-2
            raise BaseException(str(result)+":"+str(type(e))+str(e.args) )
        except JsException as e:
            result=-4
        assert result == 1
"""
    run_from_server.run_webworker(worker_code)


@pytest.mark.webworkers
def test_timeout_in_worker_streaming(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    worker_code = f"""
        import urllib3.contrib.emscripten.fetch
        await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
        from urllib3.exceptions import TimeoutError
        from urllib3.connection import HTTPConnection
        conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port},timeout=1.0)
        result=-1
        try:
            conn.request("GET","/slow",preload_content=False)
            _response = conn.getresponse()
            result=-3
        except TimeoutError as e:
            result=1 # we've got the correct exception
        except BaseException as e:
            result=-2
        assert result == 1
"""
    run_from_server.run_webworker(worker_code)


def test_index_https(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3.connection import HTTPSConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPSConnection(host, port)
        conn.request("GET", f"https://{host}:{port}/")
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        data = response.data
        assert data.decode("utf-8") == "Dummy server!"

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


@pytest.mark.without_jspi
def test_non_streaming_no_fallback_warning(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import js

        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPSConnection
        from urllib3.response import BaseHTTPResponse

        log_msgs = []
        old_log = js.console.warn

        def capture_log(*args):  # type: ignore[no-untyped-def]
            log_msgs.append(str(args))
            old_log(*args)

        js.console.warn = capture_log
        conn = HTTPSConnection(host, port)
        conn.request("GET", f"https://{host}:{port}/", preload_content=True)
        response = conn.getresponse()
        js.console.warn = old_log
        assert isinstance(response, BaseHTTPResponse)
        data = response.data
        assert data.decode("utf-8") == "Dummy server!"
        # no console warnings because we didn't ask it to stream the response
        # check no log messages
        assert (
            len([x for x in log_msgs if x.find("Can't stream HTTP requests") != -1])
            == 0
        )
        assert not urllib3.contrib.emscripten.fetch._SHOWN_STREAMING_WARNING

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


@pytest.mark.without_jspi
def test_streaming_fallback_warning(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import js

        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPSConnection
        from urllib3.response import BaseHTTPResponse

        # monkeypatch is_cross_origin_isolated so that it warns about that
        # even if we're serving it so it is fine
        urllib3.contrib.emscripten.fetch.is_cross_origin_isolated = lambda: False

        log_msgs = []
        old_log = js.console.warn

        def capture_log(*args):  # type: ignore[no-untyped-def]
            log_msgs.append(str(args))
            old_log(*args)

        js.console.warn = capture_log

        conn = HTTPSConnection(host, port)
        conn.request("GET", f"https://{host}:{port}/", preload_content=False)
        response = conn.getresponse()
        js.console.warn = old_log
        assert isinstance(response, BaseHTTPResponse)
        data = response.data
        assert data.decode("utf-8") == "Dummy server!"
        # check that it has warned about falling back to non-streaming fetch exactly once
        assert (
            len([x for x in log_msgs if x.find("Can't stream HTTP requests") != -1])
            == 1
        )
        assert urllib3.contrib.emscripten.fetch._SHOWN_STREAMING_WARNING

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


def test_specific_method(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3 import HTTPSConnectionPool

        with HTTPSConnectionPool(host, port) as pool:
            path = "/specific_method?method=POST"
            response = pool.request("POST", path)
            assert response.status == 200

            response = pool.request("PUT", path)
            assert response.status == 400

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.https_port
    )


@pytest.mark.webworkers
def test_streaming_download(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    # test streaming download, which must be in a webworker
    # as you can't do it on main thread

    # this should return the 17mb big file, and
    # should not log any warning about falling back
    bigfile_url = (
        f"http://{testserver_http.http_host}:{testserver_http.http_port}/bigfile"
    )
    worker_code = f"""
            import urllib3.contrib.emscripten.fetch
            await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
            from urllib3.response import BaseHTTPResponse
            from urllib3.connection import HTTPConnection

            conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port})
            conn.request("GET", "{bigfile_url}",preload_content=False)
            response = conn.getresponse()
            assert isinstance(response, BaseHTTPResponse)
            assert urllib3.contrib.emscripten.fetch._SHOWN_STREAMING_WARNING==False
            assert(urllib3.contrib.emscripten.fetch.has_jspi() == {has_jspi})
            data=response.data.decode('utf-8')
            assert len(data) == 17825792
"""
    run_from_server.run_webworker(worker_code)


@pytest.mark.webworkers
def test_streaming_close(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    # test streaming download, which must be in a webworker
    # as you can't do it on main thread

    # this should return the 17mb big file, and
    # should not log any warning about falling back
    url = f"http://{testserver_http.http_host}:{testserver_http.http_port}/"
    worker_code = f"""
            import urllib3.contrib.emscripten.fetch
            await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
            from urllib3.response import BaseHTTPResponse
            from urllib3.connection import HTTPConnection
            from io import RawIOBase

            conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port})
            conn.request("GET", "{url}",preload_content=False)
            response = conn.getresponse()
            # check body is a RawIOBase stream and isn't seekable, writeable
            body_internal = response._response.body
            assert(isinstance(body_internal,RawIOBase))
            assert(body_internal.writable() is False)
            assert(body_internal.seekable() is False)
            assert(body_internal.readable() is True)
            assert(urllib3.contrib.emscripten.fetch.has_jspi() == {has_jspi})

            response.drain_conn()
            x=response.read()
            assert(not x)
            response.close()
            conn.close()
            # try and make destructor be covered
            # by killing everything
            del response
            del body_internal
            del conn
"""
    run_from_server.run_webworker(worker_code)


@pytest.mark.webworkers
def test_streaming_bad_url(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    # this should cause an error
    # because the protocol is bad
    bad_url = f"hsffsdft://{testserver_http.http_host}:{testserver_http.http_port}/"
    # this must be in a webworker
    # as you can't do it on main thread
    worker_code = f"""
            import pytest
            import http.client
            import urllib3.contrib.emscripten.fetch
            await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
            from urllib3.response import BaseHTTPResponse
            from urllib3.connection import HTTPConnection

            conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port})
            with pytest.raises(http.client.HTTPException):
                conn.request("GET", "{bad_url}",preload_content=False)
"""
    run_from_server.run_webworker(worker_code)


@pytest.mark.webworkers
def test_streaming_bad_method(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    # this should cause an error
    # because the protocol is bad
    bad_url = f"http://{testserver_http.http_host}:{testserver_http.http_port}/"
    # this must be in a webworker
    # as you can't do it on main thread
    worker_code = f"""
            import pytest
            import http.client

            import urllib3.contrib.emscripten.fetch
            await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
            from urllib3.response import BaseHTTPResponse
            from urllib3.connection import HTTPConnection

            conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port})
            with pytest.raises(http.client.HTTPException):
                # TRACE method should throw SecurityError in Javascript
                conn.request("TRACE", "{bad_url}",preload_content=False)
"""
    run_from_server.run_webworker(worker_code)


@pytest.mark.webworkers
@pytest.mark.without_jspi
def test_streaming_notready_warning(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
) -> None:
    # test streaming download but don't wait for
    # worker to be ready - should fallback to non-streaming
    # and log a warning
    file_url = f"http://{testserver_http.http_host}:{testserver_http.http_port}/"
    worker_code = f"""
        import js
        import urllib3.contrib.emscripten.fetch
        from urllib3.response import BaseHTTPResponse
        from urllib3.connection import HTTPConnection

        urllib3.contrib.emscripten.fetch.streaming_ready = lambda :False
        log_msgs=[]
        old_log=js.console.warn
        def capture_log(*args):
            log_msgs.append(str(args))
            old_log(*args)
        js.console.warn=capture_log

        conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port})
        conn.request("GET", "{file_url}",preload_content=False)
        js.console.warn=old_log
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        data=response.data.decode('utf-8')
        #assert len([x for x in log_msgs if x.find("Can't stream HTTP requests")!=-1])==1
        #assert urllib3.contrib.emscripten.fetch._SHOWN_STREAMING_WARNING==True
        """
    run_from_server.run_webworker(worker_code)


def test_post_receive_json(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import json

        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        json_data = {
            "Bears": "like",
            "to": {"eat": "buns", "with": ["marmalade", "and custard"]},
        }
        conn = HTTPConnection(host, port)
        conn.request(
            "POST",
            f"http://{host}:{port}/echo_json",
            body=json.dumps(json_data).encode("utf-8"),
            headers={"Content-type": "application/json"},
        )
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        data = response.json()
        assert data == json_data

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


def test_upload(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3 import HTTPConnectionPool

        data = "I'm in ur multipart form-data, hazing a cheezburgr"
        fields: dict[str, _TYPE_FIELD_VALUE_TUPLE] = {
            "upload_param": "filefield",
            "upload_filename": "lolcat.txt",
            "filefield": ("lolcat.txt", data),
        }
        fields["upload_size"] = str(len(data))
        with HTTPConnectionPool(host, port) as pool:
            r = pool.request("POST", "/upload", fields=fields)
            assert r.status == 200

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.without_jspi
@pytest.mark.in_webbrowser
def test_streaming_not_ready_in_browser(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    # streaming ready should always be false
    # if we're in the main browser thread
    selenium_coverage.run_async(
        """
        import urllib3.contrib.emscripten.fetch
        result=await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
        assert(result is False)
        assert(urllib3.contrib.emscripten.fetch.streaming_ready() is None )
        """
    )


def test_requests_with_micropip(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
) -> None:
    @run_in_pyodide(packages=["micropip"])  # type: ignore[misc]
    async def test_fn(
        selenium_coverage: typing.Any, http_host: str, http_port: int, https_port: int
    ) -> None:
        import micropip  # type: ignore[import-not-found]

        await micropip.install("requests")
        import requests

        r = requests.get(f"http://{http_host}:{http_port}/")
        assert r.status_code == 200
        assert r.text == "Dummy server!"
        json_data = {"woo": "yay"}
        # try posting some json with requests on https
        r = requests.post(f"https://{http_host}:{https_port}/echo_json", json=json_data)
        assert r.json() == json_data

    test_fn(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        testserver_http.https_port,
    )


def test_open_close(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from http.client import ResponseNotReady

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        # initially connection should be closed
        assert conn.is_closed is True
        # connection should have no response
        with pytest.raises(ResponseNotReady):
            response = conn.getresponse()
        # now make the response
        conn.request("GET", f"http://{host}:{port}/")
        # we never connect to proxy (or if we do, browser handles it)
        assert conn.has_connected_to_proxy is False
        # now connection should be open
        assert conn.is_closed is False
        # and should have a response
        response = conn.getresponse()
        assert response is not None
        conn.close()
        # now it is closed
        assert conn.is_closed is True
        # closed connection shouldn't have any response
        with pytest.raises(ResponseNotReady):
            conn.getresponse()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


# check that various ways that the worker may be broken
# throw exceptions nicely, by deliberately breaking things
# this is for coverage
@pytest.mark.webworkers
@pytest.mark.without_jspi
def test_break_worker_streaming(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
) -> None:
    worker_code = f"""
        import pytest
        import urllib3.contrib.emscripten.fetch
        import js
        import http.client

        await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()
        from urllib3.exceptions import TimeoutError
        from urllib3.connection import HTTPConnection
        conn = HTTPConnection("{testserver_http.http_host}", {testserver_http.http_port},timeout=1.0)
        # make the fetch worker return a bad response by:
        # 1) Clearing the int buffer
        #    in the receive stream
        with pytest.raises(http.client.HTTPException):
            conn.request("GET","/",preload_content=False)
            response = conn.getresponse()
            body_internal = response._response.body
            assert(body_internal.int_buffer!=None)
            body_internal.int_buffer=None
            data=response.read()
        # 2) Monkeypatch postMessage so that it just sets an
        #    exception status
        old_pm= body_internal.worker.postMessage
        with pytest.raises(http.client.HTTPException):
            conn.request("GET","/",preload_content=False)
            response = conn.getresponse()
            # make posted messages set an exception
            body_internal = response._response.body
            def set_exception(*args):
                body_internal.worker.postMessage = old_pm
                body_internal.int_buffer[1]=4
                body_internal.byte_buffer[0]=ord("W")
                body_internal.byte_buffer[1]=ord("O")
                body_internal.byte_buffer[2]=ord("O")
                body_internal.byte_buffer[3]=ord("!")
                body_internal.byte_buffer[4]=0
                js.Atomics.store(body_internal.int_buffer, 0, -4)
                js.Atomics.notify(body_internal.int_buffer,0)
            body_internal.worker.postMessage = set_exception
            data=response.read()
        # monkeypatch so it returns an unknown value for the magic number on initial fetch call
        with pytest.raises(http.client.HTTPException):
            # make posted messages set an exception
            worker=urllib3.contrib.emscripten.fetch._fetcher.js_worker
            def set_exception(self,*args):
                array=js.Int32Array.new(args[0].buffer)
                array[0]=-1234
            worker.postMessage=set_exception.__get__(worker,worker.__class__)
            conn.request("GET","/",preload_content=False)
            response = conn.getresponse()
            data=response.read()
        urllib3.contrib.emscripten.fetch._fetcher.js_worker.postMessage=old_pm
        # 3) Stopping the worker receiving any messages which should cause a timeout error
        #    in the receive stream
        with pytest.raises(TimeoutError):
            conn.request("GET","/",preload_content=False)
            response = conn.getresponse()
            # make posted messages not be send
            body_internal = response._response.body
            def ignore_message(*args):
                pass
            old_pm= body_internal.worker.postMessage
            body_internal.worker.postMessage = ignore_message
            data=response.read()
        body_internal.worker.postMessage = old_pm
"""
    run_from_server.run_webworker(worker_code)


def test_response_init_length(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import pytest

        import urllib3.exceptions
        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPConnection(host, port)
        conn.request("GET", f"http://{host}:{port}/")
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        # head shouldn't have length
        length = response._init_length("HEAD")
        assert length == 0
        # multiple inconsistent lengths - should raise invalid header
        with pytest.raises(urllib3.exceptions.InvalidHeader):
            response.headers["Content-Length"] = "4,5,6"
            length = response._init_length("GET")
        # non-numeric length - should return None
        response.headers["Content-Length"] = "anna"
        length = response._init_length("GET")
        assert length is None
        # numeric length - should return it
        response.headers["Content-Length"] = "54"
        length = response._init_length("GET")
        assert length == 54
        # negative length - should return None
        response.headers["Content-Length"] = "-12"
        length = response._init_length("GET")
        assert length is None
        # none -> None
        del response.headers["Content-Length"]
        length = response._init_length("GET")
        assert length is None

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


def test_response_close_connection(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPConnection(host, port)
        conn.request("GET", f"http://{host}:{port}/")
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        response.close()
        assert conn.is_closed

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


def test_read_chunked(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        conn.request("GET", f"http://{host}:{port}/mediumfile", preload_content=False)
        response = conn.getresponse()
        count = 0
        for x in response.read_chunked(512):
            count += 1
            if count < 10:
                assert len(x) == 512

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


def test_retries(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import pytest

        import urllib3

        pool = urllib3.HTTPConnectionPool(
            host,
            port,
            maxsize=1,
            block=True,
            retries=urllib3.util.Retry(connect=5, read=5, redirect=5),
        )

        # monkeypatch connection class to count calls
        old_request = urllib3.connection.HTTPConnection.request
        count = 0

        def count_calls(self, *args, **argv):  # type: ignore[no-untyped-def]
            nonlocal count
            count += 1
            return old_request(self, *args, **argv)

        urllib3.connection.HTTPConnection.request = count_calls  # type: ignore[method-assign]
        with pytest.raises(urllib3.exceptions.MaxRetryError):
            pool.urlopen("GET", "/")
        # this should fail, but should have tried 6 times total
        assert count == 6

    pyodide_test(selenium_coverage, testserver_http.http_host, find_unused_port())


def test_redirects(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
        from urllib3 import request

        redirect_url = f"http://{host}:{port}/redirect"
        response = request("GET", redirect_url)
        assert response.status == 200

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.with_jspi
def test_disabled_redirects(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    """
    Test that urllib3 can control redirects in Node.js.
    """

    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage: typing.Any, host: str, port: int) -> None:
        import pytest

        from urllib3 import PoolManager, request
        from urllib3.contrib.emscripten.fetch import _is_node_js
        from urllib3.exceptions import MaxRetryError

        if not _is_node_js():
            pytest.skip("urllib3 does not control redirects in browsers.")

        redirect_url = f"http://{host}:{port}/redirect"

        with PoolManager(retries=0) as http:
            with pytest.raises(MaxRetryError):
                http.request("GET", redirect_url)

            response = http.request("GET", redirect_url, redirect=False)
            assert response.status == 303

        with PoolManager(retries=False) as http:
            response = http.request("GET", redirect_url)
            assert response.status == 303

        with pytest.raises(MaxRetryError):
            request("GET", redirect_url, retries=0)

        response = request("GET", redirect_url, redirect=False)
        assert response.status == 303

        response = request("GET", redirect_url, retries=0, redirect=False)
        assert response.status == 303

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


def test_insecure_requests_warning(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int, https_port: int) -> None:  # type: ignore[no-untyped-def]
        import warnings

        import urllib3
        import urllib3.exceptions

        http = urllib3.PoolManager()

        with warnings.catch_warnings(record=True) as w:
            http.request("GET", f"https://{host}:{https_port}")
        assert len(w) == 0

    pyodide_test(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        testserver_http.https_port,
    )


@pytest.mark.webworkers
def test_has_jspi_worker(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
    has_jspi: bool,
) -> None:
    worker_code = f"""
    import urllib3.contrib.emscripten.fetch
    assert(urllib3.contrib.emscripten.fetch.has_jspi() == {has_jspi})
    """

    run_from_server.run_webworker(worker_code)


def test_has_jspi(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo, has_jspi: bool
) -> None:
    @run_in_pyodide
    def pyodide_test(selenium, has_jspi):  # type: ignore[no-untyped-def]
        import urllib3.contrib.emscripten.fetch

        assert urllib3.contrib.emscripten.fetch.has_jspi() == has_jspi

    pyodide_test(selenium_coverage, has_jspi)


@pytest.mark.with_jspi
def test_timeout_jspi(
    selenium_coverage: typing.Any,
    testserver_http: PyodideServerInfo,
    run_from_server: ServerRunnerInfo,
) -> None:
    @run_in_pyodide
    def pyodide_test(selenium, host, port):  # type: ignore[no-untyped-def]
        import pytest

        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPConnection
        from urllib3.exceptions import TimeoutError

        conn = HTTPConnection(host, port, timeout=0.1)
        assert urllib3.contrib.emscripten.fetch.has_jspi() is True
        with pytest.raises(TimeoutError):
            conn.request("GET", "/slow")
            conn.getresponse()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.with_jspi
def test_streaming_jspi(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    bigfile_url = (
        f"http://{testserver_http.http_host}:{testserver_http.http_port}/dripfeed"
    )

    @run_in_pyodide
    def pyodide_test(selenium, host, port, bigfile_url):  # type: ignore[no-untyped-def]
        import time

        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPConnection(host, port)
        start_time = time.time()
        conn.request("GET", bigfile_url, preload_content=False)
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        # first data should be received before the timeout
        # on the server
        first_data = response.read(32768)
        assert time.time() - start_time < 2
        all_data = first_data + response.read()
        # make sure that the timeout on server side really happened
        # by checking that it took greater than the timeout
        assert time.time() - start_time > 2
        assert len(all_data.decode("utf-8")) == 17825792

    pyodide_test(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        bigfile_url,
    )


# another streaming test - uses chunked read
# and streaming to check that it works okay
# (see https://github.com/urllib3/urllib3/issues/3555 )
@pytest.mark.with_jspi
def test_streaming2_jspi(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    bigfile_url = (
        f"http://{testserver_http.http_host}:{testserver_http.http_port}/dripfeed"
    )

    @run_in_pyodide
    def pyodide_test(selenium, host, port, bigfile_url):  # type: ignore[no-untyped-def]
        from urllib3.connection import HTTPConnection
        from urllib3.response import BaseHTTPResponse

        conn = HTTPConnection(host, port)
        conn.request("GET", bigfile_url, preload_content=False)
        response = conn.getresponse()
        assert isinstance(response, BaseHTTPResponse)
        # get first data
        all_data = response.read(32768)
        # now get the rest in chunks
        # to make sure that streaming works
        # correctly even if the low level read doesn't
        # always return a full buffer (which it doesn't)
        while not response._response.body.closed:  # type: ignore[attr-defined]
            all_data += response.read(32768)
        assert len(all_data.decode("utf-8")) == 17825792

    pyodide_test(
        selenium_coverage,
        testserver_http.http_host,
        testserver_http.http_port,
        bigfile_url,
    )


@pytest.mark.node_without_jspi
def test_non_jspi_fail_in_node(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    if selenium_coverage.browser != "node":
        pytest.skip("node only test")

    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import http.client

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        url = f"http://{host}:{port}/"
        # check streaming and non-streaming requests both fail
        with pytest.raises(http.client.HTTPException):
            conn.request("GET", url)
            conn.getresponse()
        with pytest.raises(http.client.HTTPException):
            conn.request("GET", url, preload_content=False)
            conn.getresponse()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.with_jspi
def test_jspi_fetch_error(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import http.client

        import pytest

        from urllib3.connection import HTTPConnection

        conn = HTTPConnection(host, port)
        url = f"sdfsdfsffhttp://{host}:{port}/"
        with pytest.raises(http.client.HTTPException):
            conn.request("GET", url)
            conn.getresponse()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.with_jspi
def test_jspi_readstream_errors(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        import io
        from http.client import HTTPException

        import pytest

        import urllib3.contrib.emscripten.fetch
        from urllib3.connection import HTTPConnection
        from urllib3.exceptions import TimeoutError

        conn = HTTPConnection(host, port)
        url = f"http://{host}:{port}/"
        conn.request("GET", url, preload_content=False)
        response = conn.getresponse()
        assert isinstance(response._response.body, io.RawIOBase)  # type: ignore[attr-defined]
        old_run_sync = urllib3.contrib.emscripten.fetch._run_sync_with_timeout
        with pytest.raises(TimeoutError):

            def raise_timeout(*args, **argv):  # type: ignore[no-untyped-def]
                raise urllib3.contrib.emscripten.fetch._TimeoutError()

            urllib3.contrib.emscripten.fetch._run_sync_with_timeout = raise_timeout
            response.read()
        urllib3.contrib.emscripten.fetch._run_sync_with_timeout = old_run_sync
        conn.request("GET", url, preload_content=False)
        response = conn.getresponse()
        with pytest.raises(HTTPException):

            def raise_error(*args, **argv):  # type: ignore[no-untyped-def]
                raise urllib3.contrib.emscripten.fetch._RequestError()

            urllib3.contrib.emscripten.fetch._run_sync_with_timeout = raise_error
            response.read()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )


@pytest.mark.with_jspi
def test_has_jspi_exception(
    selenium_coverage: typing.Any, testserver_http: PyodideServerInfo
) -> None:
    @run_in_pyodide  # type: ignore[misc]
    def pyodide_test(selenium_coverage, host: str, port: int) -> None:  # type: ignore[no-untyped-def]
        from unittest.mock import patch

        import pyodide.ffi  # type: ignore[import-not-found]

        if hasattr(pyodide.ffi, "can_run_sync"):

            @patch("pyodide.ffi.can_run_sync")
            def should_return_false(func):  # type: ignore[no-untyped-def]
                func.return_value = (20, False)
                func.side_effect = ImportError()
                from urllib3.contrib.emscripten.fetch import has_jspi

                assert has_jspi() is False

        else:
            from unittest.mock import patch

            @patch("pyodide_js._module")
            def should_return_false(func):  # type: ignore[no-untyped-def]
                from urllib3.contrib.emscripten.fetch import has_jspi

                assert has_jspi() is False

        should_return_false()

    pyodide_test(
        selenium_coverage, testserver_http.http_host, testserver_http.http_port
    )