11626Smax.romanov@nginx.comimport re
21626Smax.romanov@nginx.comimport time
31626Smax.romanov@nginx.comfrom distutils.version import LooseVersion
41626Smax.romanov@nginx.com
5*1635Szelenkov@nginx.comimport pytest
6*1635Szelenkov@nginx.com
7*1635Szelenkov@nginx.comfrom conftest import skip_alert
81626Smax.romanov@nginx.comfrom unit.applications.lang.python import TestApplicationPython
91626Smax.romanov@nginx.com
101626Smax.romanov@nginx.com
111626Smax.romanov@nginx.comclass TestASGIApplication(TestApplicationPython):
121626Smax.romanov@nginx.com    prerequisites = {'modules': {'python':
131626Smax.romanov@nginx.com                            lambda v: LooseVersion(v) >= LooseVersion('3.5')}}
141626Smax.romanov@nginx.com    load_module = 'asgi'
151626Smax.romanov@nginx.com
161626Smax.romanov@nginx.com    def findall(self, pattern):
171626Smax.romanov@nginx.com        with open(self.temp_dir + '/unit.log', 'r', errors='ignore') as f:
181626Smax.romanov@nginx.com            return re.findall(pattern, f.read())
191626Smax.romanov@nginx.com
20*1635Szelenkov@nginx.com    def test_asgi_application_variables(self):
211626Smax.romanov@nginx.com        self.load('variables')
221626Smax.romanov@nginx.com
231626Smax.romanov@nginx.com        body = 'Test body string.'
241626Smax.romanov@nginx.com
251626Smax.romanov@nginx.com        resp = self.http(
261626Smax.romanov@nginx.com            b"""POST / HTTP/1.1
271626Smax.romanov@nginx.comHost: localhost
281626Smax.romanov@nginx.comContent-Length: %d
291626Smax.romanov@nginx.comCustom-Header: blah
301626Smax.romanov@nginx.comCustom-hEader: Blah
311626Smax.romanov@nginx.comContent-Type: text/html
321626Smax.romanov@nginx.comConnection: close
331626Smax.romanov@nginx.comcustom-header: BLAH
341626Smax.romanov@nginx.com
351626Smax.romanov@nginx.com%s""" % (len(body), body.encode()),
361626Smax.romanov@nginx.com            raw=True,
371626Smax.romanov@nginx.com        )
381626Smax.romanov@nginx.com
391626Smax.romanov@nginx.com        assert resp['status'] == 200, 'status'
401626Smax.romanov@nginx.com        headers = resp['headers']
411626Smax.romanov@nginx.com        header_server = headers.pop('Server')
421626Smax.romanov@nginx.com        assert re.search(r'Unit/[\d\.]+', header_server), 'server header'
431626Smax.romanov@nginx.com
441626Smax.romanov@nginx.com        date = headers.pop('Date')
451626Smax.romanov@nginx.com        assert date[-4:] == ' GMT', 'date header timezone'
461626Smax.romanov@nginx.com        assert (
471626Smax.romanov@nginx.com            abs(self.date_to_sec_epoch(date) - self.sec_epoch()) < 5
481626Smax.romanov@nginx.com        ), 'date header'
491626Smax.romanov@nginx.com
501626Smax.romanov@nginx.com        assert headers == {
511626Smax.romanov@nginx.com            'Connection': 'close',
521626Smax.romanov@nginx.com            'content-length': str(len(body)),
531626Smax.romanov@nginx.com            'content-type': 'text/html',
541626Smax.romanov@nginx.com            'request-method': 'POST',
551626Smax.romanov@nginx.com            'request-uri': '/',
561626Smax.romanov@nginx.com            'http-host': 'localhost',
571626Smax.romanov@nginx.com            'http-version': '1.1',
581626Smax.romanov@nginx.com            'custom-header': 'blah, Blah, BLAH',
591626Smax.romanov@nginx.com            'asgi-version': '3.0',
601626Smax.romanov@nginx.com            'asgi-spec-version': '2.1',
611626Smax.romanov@nginx.com            'scheme': 'http',
621626Smax.romanov@nginx.com        }, 'headers'
631626Smax.romanov@nginx.com        assert resp['body'] == body, 'body'
641626Smax.romanov@nginx.com
65*1635Szelenkov@nginx.com    def test_asgi_application_query_string(self):
661626Smax.romanov@nginx.com        self.load('query_string')
671626Smax.romanov@nginx.com
681626Smax.romanov@nginx.com        resp = self.get(url='/?var1=val1&var2=val2')
691626Smax.romanov@nginx.com
701626Smax.romanov@nginx.com        assert (
711626Smax.romanov@nginx.com            resp['headers']['query-string'] == 'var1=val1&var2=val2'
721626Smax.romanov@nginx.com        ), 'query-string header'
731626Smax.romanov@nginx.com
74*1635Szelenkov@nginx.com    def test_asgi_application_query_string_space(self):
751626Smax.romanov@nginx.com        self.load('query_string')
761626Smax.romanov@nginx.com
771626Smax.romanov@nginx.com        resp = self.get(url='/ ?var1=val1&var2=val2')
781626Smax.romanov@nginx.com        assert (
791626Smax.romanov@nginx.com            resp['headers']['query-string'] == 'var1=val1&var2=val2'
801626Smax.romanov@nginx.com        ), 'query-string space'
811626Smax.romanov@nginx.com
821626Smax.romanov@nginx.com        resp = self.get(url='/ %20?var1=val1&var2=val2')
831626Smax.romanov@nginx.com        assert (
841626Smax.romanov@nginx.com            resp['headers']['query-string'] == 'var1=val1&var2=val2'
851626Smax.romanov@nginx.com        ), 'query-string space 2'
861626Smax.romanov@nginx.com
871626Smax.romanov@nginx.com        resp = self.get(url='/ %20 ?var1=val1&var2=val2')
881626Smax.romanov@nginx.com        assert (
891626Smax.romanov@nginx.com            resp['headers']['query-string'] == 'var1=val1&var2=val2'
901626Smax.romanov@nginx.com        ), 'query-string space 3'
911626Smax.romanov@nginx.com
921626Smax.romanov@nginx.com        resp = self.get(url='/blah %20 blah? var1= val1 & var2=val2')
931626Smax.romanov@nginx.com        assert (
941626Smax.romanov@nginx.com            resp['headers']['query-string'] == ' var1= val1 & var2=val2'
951626Smax.romanov@nginx.com        ), 'query-string space 4'
961626Smax.romanov@nginx.com
97*1635Szelenkov@nginx.com    def test_asgi_application_query_string_empty(self):
981626Smax.romanov@nginx.com        self.load('query_string')
991626Smax.romanov@nginx.com
1001626Smax.romanov@nginx.com        resp = self.get(url='/?')
1011626Smax.romanov@nginx.com
1021626Smax.romanov@nginx.com        assert resp['status'] == 200, 'query string empty status'
1031626Smax.romanov@nginx.com        assert resp['headers']['query-string'] == '', 'query string empty'
1041626Smax.romanov@nginx.com
105*1635Szelenkov@nginx.com    def test_asgi_application_query_string_absent(self):
1061626Smax.romanov@nginx.com        self.load('query_string')
1071626Smax.romanov@nginx.com
1081626Smax.romanov@nginx.com        resp = self.get()
1091626Smax.romanov@nginx.com
1101626Smax.romanov@nginx.com        assert resp['status'] == 200, 'query string absent status'
1111626Smax.romanov@nginx.com        assert resp['headers']['query-string'] == '', 'query string absent'
1121626Smax.romanov@nginx.com
1131626Smax.romanov@nginx.com    @pytest.mark.skip('not yet')
114*1635Szelenkov@nginx.com    def test_asgi_application_server_port(self):
1151626Smax.romanov@nginx.com        self.load('server_port')
1161626Smax.romanov@nginx.com
1171626Smax.romanov@nginx.com        assert (
1181626Smax.romanov@nginx.com            self.get()['headers']['Server-Port'] == '7080'
1191626Smax.romanov@nginx.com        ), 'Server-Port header'
1201626Smax.romanov@nginx.com
1211626Smax.romanov@nginx.com    @pytest.mark.skip('not yet')
122*1635Szelenkov@nginx.com    def test_asgi_application_working_directory_invalid(self):
1231626Smax.romanov@nginx.com        self.load('empty')
1241626Smax.romanov@nginx.com
1251626Smax.romanov@nginx.com        assert 'success' in self.conf(
1261626Smax.romanov@nginx.com            '"/blah"', 'applications/empty/working_directory'
1271626Smax.romanov@nginx.com        ), 'configure invalid working_directory'
1281626Smax.romanov@nginx.com
1291626Smax.romanov@nginx.com        assert self.get()['status'] == 500, 'status'
1301626Smax.romanov@nginx.com
131*1635Szelenkov@nginx.com    def test_asgi_application_204_transfer_encoding(self):
1321626Smax.romanov@nginx.com        self.load('204_no_content')
1331626Smax.romanov@nginx.com
1341626Smax.romanov@nginx.com        assert (
1351626Smax.romanov@nginx.com            'Transfer-Encoding' not in self.get()['headers']
1361626Smax.romanov@nginx.com        ), '204 header transfer encoding'
1371626Smax.romanov@nginx.com
138*1635Szelenkov@nginx.com    def test_asgi_application_shm_ack_handle(self):
1391626Smax.romanov@nginx.com        self.load('mirror')
1401626Smax.romanov@nginx.com
1411626Smax.romanov@nginx.com        # Minimum possible limit
1421626Smax.romanov@nginx.com        shm_limit = 10 * 1024 * 1024
1431626Smax.romanov@nginx.com
1441626Smax.romanov@nginx.com        assert (
1451626Smax.romanov@nginx.com            'success' in self.conf('{"shm": ' + str(shm_limit) + '}',
1461626Smax.romanov@nginx.com                                 'applications/mirror/limits')
1471626Smax.romanov@nginx.com        )
1481626Smax.romanov@nginx.com
1491626Smax.romanov@nginx.com        # Should exceed shm_limit
1501626Smax.romanov@nginx.com        max_body_size = 12 * 1024 * 1024
1511626Smax.romanov@nginx.com
1521626Smax.romanov@nginx.com        assert (
1531626Smax.romanov@nginx.com            'success' in self.conf('{"http":{"max_body_size": '
1541626Smax.romanov@nginx.com                                  + str(max_body_size) + ' }}',
1551626Smax.romanov@nginx.com                                 'settings')
1561626Smax.romanov@nginx.com        )
1571626Smax.romanov@nginx.com
1581626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
1591626Smax.romanov@nginx.com
1601626Smax.romanov@nginx.com        body = '0123456789AB' * 1024 * 1024  # 12 Mb
1611626Smax.romanov@nginx.com        resp = self.post(
1621626Smax.romanov@nginx.com            headers={
1631626Smax.romanov@nginx.com                'Host': 'localhost',
1641626Smax.romanov@nginx.com                'Connection': 'close',
1651626Smax.romanov@nginx.com                'Content-Type': 'text/html',
1661626Smax.romanov@nginx.com            },
1671626Smax.romanov@nginx.com            body=body,
1681626Smax.romanov@nginx.com            read_buffer_size=1024 * 1024,
1691626Smax.romanov@nginx.com        )
1701626Smax.romanov@nginx.com
1711626Smax.romanov@nginx.com        assert resp['body'] == body, 'keep-alive 1'
1721626Smax.romanov@nginx.com
1731626Smax.romanov@nginx.com    def test_asgi_keepalive_body(self):
1741626Smax.romanov@nginx.com        self.load('mirror')
1751626Smax.romanov@nginx.com
1761626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
1771626Smax.romanov@nginx.com
1781626Smax.romanov@nginx.com        body = '0123456789' * 500
1791626Smax.romanov@nginx.com        (resp, sock) = self.post(
1801626Smax.romanov@nginx.com            headers={
1811626Smax.romanov@nginx.com                'Host': 'localhost',
1821626Smax.romanov@nginx.com                'Connection': 'keep-alive',
1831626Smax.romanov@nginx.com                'Content-Type': 'text/html',
1841626Smax.romanov@nginx.com            },
1851626Smax.romanov@nginx.com            start=True,
1861626Smax.romanov@nginx.com            body=body,
1871626Smax.romanov@nginx.com            read_timeout=1,
1881626Smax.romanov@nginx.com        )
1891626Smax.romanov@nginx.com
1901626Smax.romanov@nginx.com        assert resp['body'] == body, 'keep-alive 1'
1911626Smax.romanov@nginx.com
1921626Smax.romanov@nginx.com        body = '0123456789'
1931626Smax.romanov@nginx.com        resp = self.post(
1941626Smax.romanov@nginx.com            headers={
1951626Smax.romanov@nginx.com                'Host': 'localhost',
1961626Smax.romanov@nginx.com                'Connection': 'close',
1971626Smax.romanov@nginx.com                'Content-Type': 'text/html',
1981626Smax.romanov@nginx.com            },
1991626Smax.romanov@nginx.com            sock=sock,
2001626Smax.romanov@nginx.com            body=body,
2011626Smax.romanov@nginx.com        )
2021626Smax.romanov@nginx.com
2031626Smax.romanov@nginx.com        assert resp['body'] == body, 'keep-alive 2'
2041626Smax.romanov@nginx.com
2051626Smax.romanov@nginx.com    def test_asgi_keepalive_reconfigure(self):
2061626Smax.romanov@nginx.com        skip_alert(
2071626Smax.romanov@nginx.com            r'pthread_mutex.+failed',
2081626Smax.romanov@nginx.com            r'failed to apply',
2091626Smax.romanov@nginx.com            r'process \d+ exited on signal',
2101626Smax.romanov@nginx.com        )
2111626Smax.romanov@nginx.com        self.load('mirror')
2121626Smax.romanov@nginx.com
2131626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
2141626Smax.romanov@nginx.com
2151626Smax.romanov@nginx.com        body = '0123456789'
2161626Smax.romanov@nginx.com        conns = 3
2171626Smax.romanov@nginx.com        socks = []
2181626Smax.romanov@nginx.com
2191626Smax.romanov@nginx.com        for i in range(conns):
2201626Smax.romanov@nginx.com            (resp, sock) = self.post(
2211626Smax.romanov@nginx.com                headers={
2221626Smax.romanov@nginx.com                    'Host': 'localhost',
2231626Smax.romanov@nginx.com                    'Connection': 'keep-alive',
2241626Smax.romanov@nginx.com                    'Content-Type': 'text/html',
2251626Smax.romanov@nginx.com                },
2261626Smax.romanov@nginx.com                start=True,
2271626Smax.romanov@nginx.com                body=body,
2281626Smax.romanov@nginx.com                read_timeout=1,
2291626Smax.romanov@nginx.com            )
2301626Smax.romanov@nginx.com
2311626Smax.romanov@nginx.com            assert resp['body'] == body, 'keep-alive open'
2321626Smax.romanov@nginx.com            assert 'success' in self.conf(
2331626Smax.romanov@nginx.com                str(i + 1), 'applications/mirror/processes'
2341626Smax.romanov@nginx.com            ), 'reconfigure'
2351626Smax.romanov@nginx.com
2361626Smax.romanov@nginx.com            socks.append(sock)
2371626Smax.romanov@nginx.com
2381626Smax.romanov@nginx.com        for i in range(conns):
2391626Smax.romanov@nginx.com            (resp, sock) = self.post(
2401626Smax.romanov@nginx.com                headers={
2411626Smax.romanov@nginx.com                    'Host': 'localhost',
2421626Smax.romanov@nginx.com                    'Connection': 'keep-alive',
2431626Smax.romanov@nginx.com                    'Content-Type': 'text/html',
2441626Smax.romanov@nginx.com                },
2451626Smax.romanov@nginx.com                start=True,
2461626Smax.romanov@nginx.com                sock=socks[i],
2471626Smax.romanov@nginx.com                body=body,
2481626Smax.romanov@nginx.com                read_timeout=1,
2491626Smax.romanov@nginx.com            )
2501626Smax.romanov@nginx.com
2511626Smax.romanov@nginx.com            assert resp['body'] == body, 'keep-alive request'
2521626Smax.romanov@nginx.com            assert 'success' in self.conf(
2531626Smax.romanov@nginx.com                str(i + 1), 'applications/mirror/processes'
2541626Smax.romanov@nginx.com            ), 'reconfigure 2'
2551626Smax.romanov@nginx.com
2561626Smax.romanov@nginx.com        for i in range(conns):
2571626Smax.romanov@nginx.com            resp = self.post(
2581626Smax.romanov@nginx.com                headers={
2591626Smax.romanov@nginx.com                    'Host': 'localhost',
2601626Smax.romanov@nginx.com                    'Connection': 'close',
2611626Smax.romanov@nginx.com                    'Content-Type': 'text/html',
2621626Smax.romanov@nginx.com                },
2631626Smax.romanov@nginx.com                sock=socks[i],
2641626Smax.romanov@nginx.com                body=body,
2651626Smax.romanov@nginx.com            )
2661626Smax.romanov@nginx.com
2671626Smax.romanov@nginx.com            assert resp['body'] == body, 'keep-alive close'
2681626Smax.romanov@nginx.com            assert 'success' in self.conf(
2691626Smax.romanov@nginx.com                str(i + 1), 'applications/mirror/processes'
2701626Smax.romanov@nginx.com            ), 'reconfigure 3'
2711626Smax.romanov@nginx.com
2721626Smax.romanov@nginx.com    def test_asgi_keepalive_reconfigure_2(self):
2731626Smax.romanov@nginx.com        self.load('mirror')
2741626Smax.romanov@nginx.com
2751626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
2761626Smax.romanov@nginx.com
2771626Smax.romanov@nginx.com        body = '0123456789'
2781626Smax.romanov@nginx.com
2791626Smax.romanov@nginx.com        (resp, sock) = self.post(
2801626Smax.romanov@nginx.com            headers={
2811626Smax.romanov@nginx.com                'Host': 'localhost',
2821626Smax.romanov@nginx.com                'Connection': 'keep-alive',
2831626Smax.romanov@nginx.com                'Content-Type': 'text/html',
2841626Smax.romanov@nginx.com            },
2851626Smax.romanov@nginx.com            start=True,
2861626Smax.romanov@nginx.com            body=body,
2871626Smax.romanov@nginx.com            read_timeout=1,
2881626Smax.romanov@nginx.com        )
2891626Smax.romanov@nginx.com
2901626Smax.romanov@nginx.com        assert resp['body'] == body, 'reconfigure 2 keep-alive 1'
2911626Smax.romanov@nginx.com
2921626Smax.romanov@nginx.com        self.load('empty')
2931626Smax.romanov@nginx.com
2941626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
2951626Smax.romanov@nginx.com
2961626Smax.romanov@nginx.com        (resp, sock) = self.post(
2971626Smax.romanov@nginx.com            headers={
2981626Smax.romanov@nginx.com                'Host': 'localhost',
2991626Smax.romanov@nginx.com                'Connection': 'close',
3001626Smax.romanov@nginx.com                'Content-Type': 'text/html',
3011626Smax.romanov@nginx.com            },
3021626Smax.romanov@nginx.com            start=True,
3031626Smax.romanov@nginx.com            sock=sock,
3041626Smax.romanov@nginx.com            body=body,
3051626Smax.romanov@nginx.com        )
3061626Smax.romanov@nginx.com
3071626Smax.romanov@nginx.com        assert resp['status'] == 200, 'reconfigure 2 keep-alive 2'
3081626Smax.romanov@nginx.com        assert resp['body'] == '', 'reconfigure 2 keep-alive 2 body'
3091626Smax.romanov@nginx.com
3101626Smax.romanov@nginx.com        assert 'success' in self.conf(
3111626Smax.romanov@nginx.com            {"listeners": {}, "applications": {}}
3121626Smax.romanov@nginx.com        ), 'reconfigure 2 clear configuration'
3131626Smax.romanov@nginx.com
3141626Smax.romanov@nginx.com        resp = self.get(sock=sock)
3151626Smax.romanov@nginx.com
3161626Smax.romanov@nginx.com        assert resp == {}, 'reconfigure 2 keep-alive 3'
3171626Smax.romanov@nginx.com
3181626Smax.romanov@nginx.com    def test_asgi_keepalive_reconfigure_3(self):
3191626Smax.romanov@nginx.com        self.load('empty')
3201626Smax.romanov@nginx.com
3211626Smax.romanov@nginx.com        assert self.get()['status'] == 200, 'init'
3221626Smax.romanov@nginx.com
3231626Smax.romanov@nginx.com        (_, sock) = self.http(
3241626Smax.romanov@nginx.com            b"""GET / HTTP/1.1
3251626Smax.romanov@nginx.com""",
3261626Smax.romanov@nginx.com            start=True,
3271626Smax.romanov@nginx.com            raw=True,
3281626Smax.romanov@nginx.com            no_recv=True,
3291626Smax.romanov@nginx.com        )
3301626Smax.romanov@nginx.com
3311626Smax.romanov@nginx.com        assert self.get()['status'] == 200
3321626Smax.romanov@nginx.com
3331626Smax.romanov@nginx.com        assert 'success' in self.conf(
3341626Smax.romanov@nginx.com            {"listeners": {}, "applications": {}}
3351626Smax.romanov@nginx.com        ), 'reconfigure 3 clear configuration'
3361626Smax.romanov@nginx.com
3371626Smax.romanov@nginx.com        resp = self.http(
3381626Smax.romanov@nginx.com            b"""Host: localhost
3391626Smax.romanov@nginx.comConnection: close
3401626Smax.romanov@nginx.com
3411626Smax.romanov@nginx.com""",
3421626Smax.romanov@nginx.com            sock=sock,
3431626Smax.romanov@nginx.com            raw=True,
3441626Smax.romanov@nginx.com        )
3451626Smax.romanov@nginx.com
3461626Smax.romanov@nginx.com        assert resp['status'] == 200, 'reconfigure 3'
3471626Smax.romanov@nginx.com
3481626Smax.romanov@nginx.com    def test_asgi_process_switch(self):
3491626Smax.romanov@nginx.com        self.load('delayed')
3501626Smax.romanov@nginx.com
3511626Smax.romanov@nginx.com        assert 'success' in self.conf(
3521626Smax.romanov@nginx.com            '2', 'applications/delayed/processes'
3531626Smax.romanov@nginx.com        ), 'configure 2 processes'
3541626Smax.romanov@nginx.com
3551626Smax.romanov@nginx.com        self.get(
3561626Smax.romanov@nginx.com            headers={
3571626Smax.romanov@nginx.com                'Host': 'localhost',
3581626Smax.romanov@nginx.com                'Content-Length': '0',
3591626Smax.romanov@nginx.com                'X-Delay': '5',
3601626Smax.romanov@nginx.com                'Connection': 'close',
3611626Smax.romanov@nginx.com            },
3621626Smax.romanov@nginx.com            no_recv=True,
3631626Smax.romanov@nginx.com        )
3641626Smax.romanov@nginx.com
3651626Smax.romanov@nginx.com        headers_delay_1 = {
3661626Smax.romanov@nginx.com            'Connection': 'close',
3671626Smax.romanov@nginx.com            'Host': 'localhost',
3681626Smax.romanov@nginx.com            'Content-Length': '0',
3691626Smax.romanov@nginx.com            'X-Delay': '1',
3701626Smax.romanov@nginx.com        }
3711626Smax.romanov@nginx.com
3721626Smax.romanov@nginx.com        self.get(headers=headers_delay_1, no_recv=True)
3731626Smax.romanov@nginx.com
3741626Smax.romanov@nginx.com        time.sleep(0.5)
3751626Smax.romanov@nginx.com
3761626Smax.romanov@nginx.com        for _ in range(10):
3771626Smax.romanov@nginx.com            self.get(headers=headers_delay_1, no_recv=True)
3781626Smax.romanov@nginx.com
3791626Smax.romanov@nginx.com        self.get(headers=headers_delay_1)
3801626Smax.romanov@nginx.com
381*1635Szelenkov@nginx.com    def test_asgi_application_loading_error(self):
3821626Smax.romanov@nginx.com        skip_alert(r'Python failed to import module "blah"')
3831626Smax.romanov@nginx.com
3841626Smax.romanov@nginx.com        self.load('empty')
3851626Smax.romanov@nginx.com
3861626Smax.romanov@nginx.com        assert 'success' in self.conf('"blah"', 'applications/empty/module')
3871626Smax.romanov@nginx.com
3881626Smax.romanov@nginx.com        assert self.get()['status'] == 503, 'loading error'
3891626Smax.romanov@nginx.com
390*1635Szelenkov@nginx.com    def test_asgi_application_threading(self):
3911626Smax.romanov@nginx.com        """wait_for_record() timeouts after 5s while every thread works at
3921626Smax.romanov@nginx.com        least 3s.  So without releasing GIL test should fail.
3931626Smax.romanov@nginx.com        """
3941626Smax.romanov@nginx.com
3951626Smax.romanov@nginx.com        self.load('threading')
3961626Smax.romanov@nginx.com
3971626Smax.romanov@nginx.com        for _ in range(10):
3981626Smax.romanov@nginx.com            self.get(no_recv=True)
3991626Smax.romanov@nginx.com
4001626Smax.romanov@nginx.com        assert (
4011626Smax.romanov@nginx.com            self.wait_for_record(r'\(5\) Thread: 100') is not None
4021626Smax.romanov@nginx.com        ), 'last thread finished'
403