11304St.nateldemoura@f5.comimport grp 21791Szelenkov@nginx.comimport os 31304St.nateldemoura@f5.comimport pwd 41477Szelenkov@nginx.comimport re 5899Szelenkov@nginx.comimport time 61477Szelenkov@nginx.com 71635Szelenkov@nginx.comimport pytest 81019Szelenkov@nginx.comfrom unit.applications.lang.python import TestApplicationPython 9484Szelenkov@nginx.com 101017Szelenkov@nginx.com 111019Szelenkov@nginx.comclass TestPythonApplication(TestApplicationPython): 121467Szelenkov@nginx.com prerequisites = {'modules': {'python': 'all'}} 13484Szelenkov@nginx.com 14552Szelenkov@nginx.com def test_python_application_variables(self): 15552Szelenkov@nginx.com self.load('variables') 16484Szelenkov@nginx.com 17484Szelenkov@nginx.com body = 'Test body string.' 18484Szelenkov@nginx.com 191605Smax.romanov@nginx.com resp = self.http( 201605Smax.romanov@nginx.com b"""POST / HTTP/1.1 211605Smax.romanov@nginx.comHost: localhost 221605Smax.romanov@nginx.comContent-Length: %d 231605Smax.romanov@nginx.comCustom-Header: blah 241605Smax.romanov@nginx.comCustom-hEader: Blah 251605Smax.romanov@nginx.comContent-Type: text/html 261605Smax.romanov@nginx.comConnection: close 271605Smax.romanov@nginx.comcustom-header: BLAH 281605Smax.romanov@nginx.com 291848Szelenkov@nginx.com%s""" 301848Szelenkov@nginx.com % (len(body), body.encode()), 311605Smax.romanov@nginx.com raw=True, 321017Szelenkov@nginx.com ) 33484Szelenkov@nginx.com 341596Szelenkov@nginx.com assert resp['status'] == 200, 'status' 35505Szelenkov@nginx.com headers = resp['headers'] 36674Szelenkov@nginx.com header_server = headers.pop('Server') 371596Szelenkov@nginx.com assert re.search(r'Unit/[\d\.]+', header_server), 'server header' 381596Szelenkov@nginx.com assert ( 391596Szelenkov@nginx.com headers.pop('Server-Software') == header_server 401596Szelenkov@nginx.com ), 'server software header' 41599Szelenkov@nginx.com 42599Szelenkov@nginx.com date = headers.pop('Date') 431596Szelenkov@nginx.com assert date[-4:] == ' GMT', 'date header timezone' 441596Szelenkov@nginx.com assert ( 451596Szelenkov@nginx.com abs(self.date_to_sec_epoch(date) - self.sec_epoch()) < 5 461596Szelenkov@nginx.com ), 'date header' 47599Szelenkov@nginx.com 481596Szelenkov@nginx.com assert headers == { 491596Szelenkov@nginx.com 'Connection': 'close', 501596Szelenkov@nginx.com 'Content-Length': str(len(body)), 511596Szelenkov@nginx.com 'Content-Type': 'text/html', 521596Szelenkov@nginx.com 'Request-Method': 'POST', 531596Szelenkov@nginx.com 'Request-Uri': '/', 541596Szelenkov@nginx.com 'Http-Host': 'localhost', 551596Szelenkov@nginx.com 'Server-Protocol': 'HTTP/1.1', 561605Smax.romanov@nginx.com 'Custom-Header': 'blah, Blah, BLAH', 571596Szelenkov@nginx.com 'Wsgi-Version': '(1, 0)', 581596Szelenkov@nginx.com 'Wsgi-Url-Scheme': 'http', 591596Szelenkov@nginx.com 'Wsgi-Multithread': 'False', 601596Szelenkov@nginx.com 'Wsgi-Multiprocess': 'True', 611596Szelenkov@nginx.com 'Wsgi-Run-Once': 'False', 621596Szelenkov@nginx.com }, 'headers' 631596Szelenkov@nginx.com assert resp['body'] == body, 'body' 64484Szelenkov@nginx.com 65497Szelenkov@nginx.com def test_python_application_query_string(self): 66552Szelenkov@nginx.com self.load('query_string') 67497Szelenkov@nginx.com 68505Szelenkov@nginx.com resp = self.get(url='/?var1=val1&var2=val2') 69497Szelenkov@nginx.com 701596Szelenkov@nginx.com assert ( 711596Szelenkov@nginx.com resp['headers']['Query-String'] == 'var1=val1&var2=val2' 721596Szelenkov@nginx.com ), 'Query-String header' 73497Szelenkov@nginx.com 741171Svbart@nginx.com def test_python_application_query_string_space(self): 751171Svbart@nginx.com self.load('query_string') 761171Svbart@nginx.com 771171Svbart@nginx.com resp = self.get(url='/ ?var1=val1&var2=val2') 781596Szelenkov@nginx.com assert ( 791596Szelenkov@nginx.com resp['headers']['Query-String'] == 'var1=val1&var2=val2' 801596Szelenkov@nginx.com ), 'Query-String space' 811171Svbart@nginx.com 821171Svbart@nginx.com resp = self.get(url='/ %20?var1=val1&var2=val2') 831596Szelenkov@nginx.com assert ( 841596Szelenkov@nginx.com resp['headers']['Query-String'] == 'var1=val1&var2=val2' 851596Szelenkov@nginx.com ), 'Query-String space 2' 861171Svbart@nginx.com 871171Svbart@nginx.com resp = self.get(url='/ %20 ?var1=val1&var2=val2') 881596Szelenkov@nginx.com assert ( 891596Szelenkov@nginx.com resp['headers']['Query-String'] == 'var1=val1&var2=val2' 901596Szelenkov@nginx.com ), 'Query-String space 3' 911171Svbart@nginx.com 921171Svbart@nginx.com resp = self.get(url='/blah %20 blah? var1= val1 & var2=val2') 931596Szelenkov@nginx.com assert ( 941596Szelenkov@nginx.com resp['headers']['Query-String'] == ' var1= val1 & var2=val2' 951596Szelenkov@nginx.com ), 'Query-String space 4' 961171Svbart@nginx.com 97894Szelenkov@nginx.com def test_python_application_query_string_empty(self): 98894Szelenkov@nginx.com self.load('query_string') 99894Szelenkov@nginx.com 100894Szelenkov@nginx.com resp = self.get(url='/?') 101894Szelenkov@nginx.com 1021596Szelenkov@nginx.com assert resp['status'] == 200, 'query string empty status' 1031596Szelenkov@nginx.com assert resp['headers']['Query-String'] == '', 'query string empty' 104894Szelenkov@nginx.com 105894Szelenkov@nginx.com def test_python_application_query_string_absent(self): 106894Szelenkov@nginx.com self.load('query_string') 107894Szelenkov@nginx.com 108894Szelenkov@nginx.com resp = self.get() 109894Szelenkov@nginx.com 1101596Szelenkov@nginx.com assert resp['status'] == 200, 'query string absent status' 1111596Szelenkov@nginx.com assert resp['headers']['Query-String'] == '', 'query string absent' 112894Szelenkov@nginx.com 1131596Szelenkov@nginx.com @pytest.mark.skip('not yet') 114495Szelenkov@nginx.com def test_python_application_server_port(self): 115552Szelenkov@nginx.com self.load('server_port') 116495Szelenkov@nginx.com 1171596Szelenkov@nginx.com assert ( 1181596Szelenkov@nginx.com self.get()['headers']['Server-Port'] == '7080' 1191596Szelenkov@nginx.com ), 'Server-Port header' 120484Szelenkov@nginx.com 1211596Szelenkov@nginx.com @pytest.mark.skip('not yet') 1221250Szelenkov@nginx.com def test_python_application_working_directory_invalid(self): 1231250Szelenkov@nginx.com self.load('empty') 1241250Szelenkov@nginx.com 1251596Szelenkov@nginx.com assert 'success' in self.conf( 1261596Szelenkov@nginx.com '"/blah"', 'applications/empty/working_directory' 1271596Szelenkov@nginx.com ), 'configure invalid working_directory' 1281250Szelenkov@nginx.com 1291596Szelenkov@nginx.com assert self.get()['status'] == 500, 'status' 1301250Szelenkov@nginx.com 131496Szelenkov@nginx.com def test_python_application_204_transfer_encoding(self): 132552Szelenkov@nginx.com self.load('204_no_content') 133496Szelenkov@nginx.com 1341596Szelenkov@nginx.com assert ( 1351596Szelenkov@nginx.com 'Transfer-Encoding' not in self.get()['headers'] 1361596Szelenkov@nginx.com ), '204 header transfer encoding' 137484Szelenkov@nginx.com 138602Szelenkov@nginx.com def test_python_application_ctx_iter_atexit(self): 139602Szelenkov@nginx.com self.load('ctx_iter_atexit') 140602Szelenkov@nginx.com 1411017Szelenkov@nginx.com resp = self.post( 1421017Szelenkov@nginx.com headers={ 1431017Szelenkov@nginx.com 'Host': 'localhost', 1441017Szelenkov@nginx.com 'Connection': 'close', 1451017Szelenkov@nginx.com 'Content-Type': 'text/html', 1461017Szelenkov@nginx.com }, 1471017Szelenkov@nginx.com body='0123456789', 1481017Szelenkov@nginx.com ) 149602Szelenkov@nginx.com 1501596Szelenkov@nginx.com assert resp['status'] == 200, 'ctx iter status' 1511596Szelenkov@nginx.com assert resp['body'] == '0123456789', 'ctx iter body' 152602Szelenkov@nginx.com 1531775Szelenkov@nginx.com assert 'success' in self.conf({"listeners": {}, "applications": {}}) 154602Szelenkov@nginx.com 1551596Szelenkov@nginx.com assert ( 1561596Szelenkov@nginx.com self.wait_for_record(r'RuntimeError') is not None 1571596Szelenkov@nginx.com ), 'ctx iter atexit' 158602Szelenkov@nginx.com 159603Szelenkov@nginx.com def test_python_keepalive_body(self): 160603Szelenkov@nginx.com self.load('mirror') 161603Szelenkov@nginx.com 1621596Szelenkov@nginx.com assert self.get()['status'] == 200, 'init' 1631029Szelenkov@nginx.com 1641453Szelenkov@nginx.com body = '0123456789' * 500 1651017Szelenkov@nginx.com (resp, sock) = self.post( 1661017Szelenkov@nginx.com headers={ 1671017Szelenkov@nginx.com 'Host': 'localhost', 1681017Szelenkov@nginx.com 'Connection': 'keep-alive', 1691017Szelenkov@nginx.com 'Content-Type': 'text/html', 1701017Szelenkov@nginx.com }, 1711017Szelenkov@nginx.com start=True, 1721453Szelenkov@nginx.com body=body, 1731029Szelenkov@nginx.com read_timeout=1, 1741017Szelenkov@nginx.com ) 175603Szelenkov@nginx.com 1761596Szelenkov@nginx.com assert resp['body'] == body, 'keep-alive 1' 177603Szelenkov@nginx.com 1781453Szelenkov@nginx.com body = '0123456789' 1791017Szelenkov@nginx.com resp = self.post( 1801017Szelenkov@nginx.com headers={ 1811017Szelenkov@nginx.com 'Host': 'localhost', 1821017Szelenkov@nginx.com 'Connection': 'close', 1831017Szelenkov@nginx.com 'Content-Type': 'text/html', 1841017Szelenkov@nginx.com }, 1851017Szelenkov@nginx.com sock=sock, 1861453Szelenkov@nginx.com body=body, 1871017Szelenkov@nginx.com ) 188603Szelenkov@nginx.com 1891596Szelenkov@nginx.com assert resp['body'] == body, 'keep-alive 2' 190603Szelenkov@nginx.com 191684Szelenkov@nginx.com def test_python_keepalive_reconfigure(self): 192684Szelenkov@nginx.com self.load('mirror') 193684Szelenkov@nginx.com 1941596Szelenkov@nginx.com assert self.get()['status'] == 200, 'init' 1951029Szelenkov@nginx.com 196684Szelenkov@nginx.com body = '0123456789' 197684Szelenkov@nginx.com conns = 3 198684Szelenkov@nginx.com socks = [] 199684Szelenkov@nginx.com 200684Szelenkov@nginx.com for i in range(conns): 2011017Szelenkov@nginx.com (resp, sock) = self.post( 2021017Szelenkov@nginx.com headers={ 2031017Szelenkov@nginx.com 'Host': 'localhost', 2041017Szelenkov@nginx.com 'Connection': 'keep-alive', 2051017Szelenkov@nginx.com 'Content-Type': 'text/html', 2061017Szelenkov@nginx.com }, 2071017Szelenkov@nginx.com start=True, 2081017Szelenkov@nginx.com body=body, 2091029Szelenkov@nginx.com read_timeout=1, 2101017Szelenkov@nginx.com ) 211684Szelenkov@nginx.com 2121596Szelenkov@nginx.com assert resp['body'] == body, 'keep-alive open' 2131695Smax.romanov@nginx.com 2141695Smax.romanov@nginx.com self.load('mirror', processes=i + 1) 215684Szelenkov@nginx.com 216684Szelenkov@nginx.com socks.append(sock) 217684Szelenkov@nginx.com 218684Szelenkov@nginx.com for i in range(conns): 2191017Szelenkov@nginx.com (resp, sock) = self.post( 2201017Szelenkov@nginx.com headers={ 2211017Szelenkov@nginx.com 'Host': 'localhost', 2221017Szelenkov@nginx.com 'Connection': 'keep-alive', 2231017Szelenkov@nginx.com 'Content-Type': 'text/html', 2241017Szelenkov@nginx.com }, 2251017Szelenkov@nginx.com start=True, 2261017Szelenkov@nginx.com sock=socks[i], 2271017Szelenkov@nginx.com body=body, 2281029Szelenkov@nginx.com read_timeout=1, 2291017Szelenkov@nginx.com ) 230684Szelenkov@nginx.com 2311596Szelenkov@nginx.com assert resp['body'] == body, 'keep-alive request' 2321695Smax.romanov@nginx.com 2331695Smax.romanov@nginx.com self.load('mirror', processes=i + 1) 234684Szelenkov@nginx.com 235684Szelenkov@nginx.com for i in range(conns): 2361017Szelenkov@nginx.com resp = self.post( 2371017Szelenkov@nginx.com headers={ 2381017Szelenkov@nginx.com 'Host': 'localhost', 2391017Szelenkov@nginx.com 'Connection': 'close', 2401017Szelenkov@nginx.com 'Content-Type': 'text/html', 2411017Szelenkov@nginx.com }, 2421017Szelenkov@nginx.com sock=socks[i], 2431017Szelenkov@nginx.com body=body, 2441017Szelenkov@nginx.com ) 245684Szelenkov@nginx.com 2461596Szelenkov@nginx.com assert resp['body'] == body, 'keep-alive close' 2471695Smax.romanov@nginx.com 2481695Smax.romanov@nginx.com self.load('mirror', processes=i + 1) 249684Szelenkov@nginx.com 250750Szelenkov@nginx.com def test_python_keepalive_reconfigure_2(self): 251750Szelenkov@nginx.com self.load('mirror') 252750Szelenkov@nginx.com 2531596Szelenkov@nginx.com assert self.get()['status'] == 200, 'init' 2541029Szelenkov@nginx.com 255750Szelenkov@nginx.com body = '0123456789' 256750Szelenkov@nginx.com 2571017Szelenkov@nginx.com (resp, sock) = self.post( 2581017Szelenkov@nginx.com headers={ 2591017Szelenkov@nginx.com 'Host': 'localhost', 2601017Szelenkov@nginx.com 'Connection': 'keep-alive', 2611017Szelenkov@nginx.com 'Content-Type': 'text/html', 2621017Szelenkov@nginx.com }, 2631017Szelenkov@nginx.com start=True, 2641017Szelenkov@nginx.com body=body, 2651029Szelenkov@nginx.com read_timeout=1, 2661017Szelenkov@nginx.com ) 267750Szelenkov@nginx.com 2681596Szelenkov@nginx.com assert resp['body'] == body, 'reconfigure 2 keep-alive 1' 269750Szelenkov@nginx.com 270750Szelenkov@nginx.com self.load('empty') 271750Szelenkov@nginx.com 2721596Szelenkov@nginx.com assert self.get()['status'] == 200, 'init' 2731029Szelenkov@nginx.com 2741017Szelenkov@nginx.com (resp, sock) = self.post( 2751017Szelenkov@nginx.com headers={ 2761017Szelenkov@nginx.com 'Host': 'localhost', 2771017Szelenkov@nginx.com 'Connection': 'close', 2781017Szelenkov@nginx.com 'Content-Type': 'text/html', 2791017Szelenkov@nginx.com }, 2801017Szelenkov@nginx.com start=True, 2811017Szelenkov@nginx.com sock=sock, 2821017Szelenkov@nginx.com body=body, 2831017Szelenkov@nginx.com ) 284750Szelenkov@nginx.com 2851596Szelenkov@nginx.com assert resp['status'] == 200, 'reconfigure 2 keep-alive 2' 2861596Szelenkov@nginx.com assert resp['body'] == '', 'reconfigure 2 keep-alive 2 body' 287750Szelenkov@nginx.com 2881596Szelenkov@nginx.com assert 'success' in self.conf( 2891596Szelenkov@nginx.com {"listeners": {}, "applications": {}} 2901596Szelenkov@nginx.com ), 'reconfigure 2 clear configuration' 291750Szelenkov@nginx.com 292750Szelenkov@nginx.com resp = self.get(sock=sock) 293750Szelenkov@nginx.com 2941596Szelenkov@nginx.com assert resp == {}, 'reconfigure 2 keep-alive 3' 295750Szelenkov@nginx.com 296603Szelenkov@nginx.com def test_python_atexit(self): 297603Szelenkov@nginx.com self.load('atexit') 298603Szelenkov@nginx.com 299603Szelenkov@nginx.com self.get() 300603Szelenkov@nginx.com 3011775Szelenkov@nginx.com assert 'success' in self.conf({"listeners": {}, "applications": {}}) 302603Szelenkov@nginx.com 3031596Szelenkov@nginx.com assert self.wait_for_record(r'At exit called\.') is not None, 'atexit' 304603Szelenkov@nginx.com 3051454Szelenkov@nginx.com def test_python_process_switch(self): 3061695Smax.romanov@nginx.com self.load('delayed', processes=2) 3071454Szelenkov@nginx.com 3081596Szelenkov@nginx.com self.get( 3091596Szelenkov@nginx.com headers={ 3101596Szelenkov@nginx.com 'Host': 'localhost', 3111596Szelenkov@nginx.com 'Content-Length': '0', 3121596Szelenkov@nginx.com 'X-Delay': '5', 3131596Szelenkov@nginx.com 'Connection': 'close', 3141596Szelenkov@nginx.com }, 3151596Szelenkov@nginx.com no_recv=True, 3161596Szelenkov@nginx.com ) 3171454Szelenkov@nginx.com 3181454Szelenkov@nginx.com headers_delay_1 = { 3191454Szelenkov@nginx.com 'Connection': 'close', 3201454Szelenkov@nginx.com 'Host': 'localhost', 3211454Szelenkov@nginx.com 'Content-Length': '0', 3221454Szelenkov@nginx.com 'X-Delay': '1', 3231454Szelenkov@nginx.com } 3241454Szelenkov@nginx.com 3251454Szelenkov@nginx.com self.get(headers=headers_delay_1, no_recv=True) 3261454Szelenkov@nginx.com 3271454Szelenkov@nginx.com time.sleep(0.5) 3281454Szelenkov@nginx.com 3291454Szelenkov@nginx.com for _ in range(10): 3301454Szelenkov@nginx.com self.get(headers=headers_delay_1, no_recv=True) 3311454Szelenkov@nginx.com 3321454Szelenkov@nginx.com self.get(headers=headers_delay_1) 3331454Szelenkov@nginx.com 3341596Szelenkov@nginx.com @pytest.mark.skip('not yet') 335603Szelenkov@nginx.com def test_python_application_start_response_exit(self): 336603Szelenkov@nginx.com self.load('start_response_exit') 337603Szelenkov@nginx.com 3381596Szelenkov@nginx.com assert self.get()['status'] == 500, 'start response exit' 339603Szelenkov@nginx.com 340603Szelenkov@nginx.com def test_python_application_input_iter(self): 341603Szelenkov@nginx.com self.load('input_iter') 342603Szelenkov@nginx.com 3431400Smax.romanov@nginx.com body = '''0123456789 3441400Smax.romanov@nginx.comnext line 3451400Smax.romanov@nginx.com 3461400Smax.romanov@nginx.comlast line''' 3471400Smax.romanov@nginx.com 3481400Smax.romanov@nginx.com resp = self.post(body=body) 3491596Szelenkov@nginx.com assert resp['body'] == body, 'input iter' 3501596Szelenkov@nginx.com assert resp['headers']['X-Lines-Count'] == '4', 'input iter lines' 3511400Smax.romanov@nginx.com 3521400Smax.romanov@nginx.com def test_python_application_input_readline(self): 3531400Smax.romanov@nginx.com self.load('input_readline') 3541400Smax.romanov@nginx.com 3551400Smax.romanov@nginx.com body = '''0123456789 3561400Smax.romanov@nginx.comnext line 3571400Smax.romanov@nginx.com 3581400Smax.romanov@nginx.comlast line''' 3591400Smax.romanov@nginx.com 3601400Smax.romanov@nginx.com resp = self.post(body=body) 3611596Szelenkov@nginx.com assert resp['body'] == body, 'input readline' 3621596Szelenkov@nginx.com assert resp['headers']['X-Lines-Count'] == '4', 'input readline lines' 3631400Smax.romanov@nginx.com 3641400Smax.romanov@nginx.com def test_python_application_input_readline_size(self): 3651400Smax.romanov@nginx.com self.load('input_readline_size') 3661400Smax.romanov@nginx.com 3671400Smax.romanov@nginx.com body = '''0123456789 3681400Smax.romanov@nginx.comnext line 3691400Smax.romanov@nginx.com 3701400Smax.romanov@nginx.comlast line''' 371603Szelenkov@nginx.com 3721596Szelenkov@nginx.com assert self.post(body=body)['body'] == body, 'input readline size' 3731596Szelenkov@nginx.com assert ( 3741596Szelenkov@nginx.com self.post(body='0123')['body'] == '0123' 3751596Szelenkov@nginx.com ), 'input readline size less' 3761400Smax.romanov@nginx.com 3771400Smax.romanov@nginx.com def test_python_application_input_readlines(self): 3781400Smax.romanov@nginx.com self.load('input_readlines') 3791400Smax.romanov@nginx.com 3801400Smax.romanov@nginx.com body = '''0123456789 3811400Smax.romanov@nginx.comnext line 3821400Smax.romanov@nginx.com 3831400Smax.romanov@nginx.comlast line''' 3841400Smax.romanov@nginx.com 3851400Smax.romanov@nginx.com resp = self.post(body=body) 3861596Szelenkov@nginx.com assert resp['body'] == body, 'input readlines' 3871596Szelenkov@nginx.com assert resp['headers']['X-Lines-Count'] == '4', 'input readlines lines' 3881400Smax.romanov@nginx.com 3891400Smax.romanov@nginx.com def test_python_application_input_readlines_huge(self): 3901400Smax.romanov@nginx.com self.load('input_readlines') 3911400Smax.romanov@nginx.com 3921400Smax.romanov@nginx.com body = ( 3931400Smax.romanov@nginx.com '''0123456789 abcdefghi 3941400Smax.romanov@nginx.comnext line: 0123456789 abcdefghi 3951400Smax.romanov@nginx.com 3961400Smax.romanov@nginx.comlast line: 987654321 3971400Smax.romanov@nginx.com''' 3981400Smax.romanov@nginx.com * 512 3991400Smax.romanov@nginx.com ) 4001400Smax.romanov@nginx.com 4011596Szelenkov@nginx.com assert ( 4021596Szelenkov@nginx.com self.post(body=body, read_buffer_size=16384)['body'] == body 4031596Szelenkov@nginx.com ), 'input readlines huge' 404603Szelenkov@nginx.com 405603Szelenkov@nginx.com def test_python_application_input_read_length(self): 406603Szelenkov@nginx.com self.load('input_read_length') 407603Szelenkov@nginx.com 408603Szelenkov@nginx.com body = '0123456789' 409603Szelenkov@nginx.com 4101017Szelenkov@nginx.com resp = self.post( 4111017Szelenkov@nginx.com headers={ 4121017Szelenkov@nginx.com 'Host': 'localhost', 4131017Szelenkov@nginx.com 'Input-Length': '5', 4141017Szelenkov@nginx.com 'Connection': 'close', 4151017Szelenkov@nginx.com }, 4161017Szelenkov@nginx.com body=body, 4171017Szelenkov@nginx.com ) 418603Szelenkov@nginx.com 4191596Szelenkov@nginx.com assert resp['body'] == body[:5], 'input read length lt body' 420603Szelenkov@nginx.com 4211017Szelenkov@nginx.com resp = self.post( 4221017Szelenkov@nginx.com headers={ 4231017Szelenkov@nginx.com 'Host': 'localhost', 4241017Szelenkov@nginx.com 'Input-Length': '15', 4251017Szelenkov@nginx.com 'Connection': 'close', 4261017Szelenkov@nginx.com }, 4271017Szelenkov@nginx.com body=body, 4281017Szelenkov@nginx.com ) 429603Szelenkov@nginx.com 4301596Szelenkov@nginx.com assert resp['body'] == body, 'input read length gt body' 431603Szelenkov@nginx.com 4321017Szelenkov@nginx.com resp = self.post( 4331017Szelenkov@nginx.com headers={ 4341017Szelenkov@nginx.com 'Host': 'localhost', 4351017Szelenkov@nginx.com 'Input-Length': '0', 4361017Szelenkov@nginx.com 'Connection': 'close', 4371017Szelenkov@nginx.com }, 4381017Szelenkov@nginx.com body=body, 4391017Szelenkov@nginx.com ) 440603Szelenkov@nginx.com 4411596Szelenkov@nginx.com assert resp['body'] == '', 'input read length zero' 442603Szelenkov@nginx.com 4431017Szelenkov@nginx.com resp = self.post( 4441017Szelenkov@nginx.com headers={ 4451017Szelenkov@nginx.com 'Host': 'localhost', 4461017Szelenkov@nginx.com 'Input-Length': '-1', 4471017Szelenkov@nginx.com 'Connection': 'close', 4481017Szelenkov@nginx.com }, 4491017Szelenkov@nginx.com body=body, 4501017Szelenkov@nginx.com ) 451603Szelenkov@nginx.com 4521596Szelenkov@nginx.com assert resp['body'] == body, 'input read length negative' 453603Szelenkov@nginx.com 4541596Szelenkov@nginx.com @pytest.mark.skip('not yet') 455603Szelenkov@nginx.com def test_python_application_errors_write(self): 456603Szelenkov@nginx.com self.load('errors_write') 457603Szelenkov@nginx.com 458603Szelenkov@nginx.com self.get() 459603Szelenkov@nginx.com 4601596Szelenkov@nginx.com assert ( 4611596Szelenkov@nginx.com self.wait_for_record(r'\[error\].+Error in application\.') 4621596Szelenkov@nginx.com is not None 4631596Szelenkov@nginx.com ), 'errors write' 464603Szelenkov@nginx.com 465603Szelenkov@nginx.com def test_python_application_body_array(self): 466603Szelenkov@nginx.com self.load('body_array') 467603Szelenkov@nginx.com 4681596Szelenkov@nginx.com assert self.get()['body'] == '0123456789', 'body array' 469603Szelenkov@nginx.com 470603Szelenkov@nginx.com def test_python_application_body_io(self): 471603Szelenkov@nginx.com self.load('body_io') 472603Szelenkov@nginx.com 4731596Szelenkov@nginx.com assert self.get()['body'] == '0123456789', 'body io' 474603Szelenkov@nginx.com 475603Szelenkov@nginx.com def test_python_application_body_io_file(self): 476603Szelenkov@nginx.com self.load('body_io_file') 477603Szelenkov@nginx.com 4781596Szelenkov@nginx.com assert self.get()['body'] == 'body\n', 'body io file' 479603Szelenkov@nginx.com 4801596Szelenkov@nginx.com @pytest.mark.skip('not yet') 4811736Szelenkov@nginx.com def test_python_application_syntax_error(self, skip_alert): 4821596Szelenkov@nginx.com skip_alert(r'Python failed to import module "wsgi"') 483603Szelenkov@nginx.com self.load('syntax_error') 484603Szelenkov@nginx.com 4851596Szelenkov@nginx.com assert self.get()['status'] == 500, 'syntax error' 486603Szelenkov@nginx.com 4871736Szelenkov@nginx.com def test_python_application_loading_error(self, skip_alert): 4881596Szelenkov@nginx.com skip_alert(r'Python failed to import module "blah"') 4891568Szelenkov@nginx.com 4901695Smax.romanov@nginx.com self.load('empty', module="blah") 4911568Szelenkov@nginx.com 4921596Szelenkov@nginx.com assert self.get()['status'] == 503, 'loading error' 4931568Szelenkov@nginx.com 494603Szelenkov@nginx.com def test_python_application_close(self): 495603Szelenkov@nginx.com self.load('close') 496603Szelenkov@nginx.com 497603Szelenkov@nginx.com self.get() 498603Szelenkov@nginx.com 4991596Szelenkov@nginx.com assert self.wait_for_record(r'Close called\.') is not None, 'close' 500603Szelenkov@nginx.com 501603Szelenkov@nginx.com def test_python_application_close_error(self): 502603Szelenkov@nginx.com self.load('close_error') 503603Szelenkov@nginx.com 504603Szelenkov@nginx.com self.get() 505603Szelenkov@nginx.com 5061596Szelenkov@nginx.com assert ( 5071596Szelenkov@nginx.com self.wait_for_record(r'Close called\.') is not None 5081596Szelenkov@nginx.com ), 'close error' 509603Szelenkov@nginx.com 510617Szelenkov@nginx.com def test_python_application_not_iterable(self): 511617Szelenkov@nginx.com self.load('not_iterable') 512617Szelenkov@nginx.com 513665Szelenkov@nginx.com self.get() 514617Szelenkov@nginx.com 5151596Szelenkov@nginx.com assert ( 5161028Szelenkov@nginx.com self.wait_for_record( 5171017Szelenkov@nginx.com r'\[error\].+the application returned not an iterable object' 5181596Szelenkov@nginx.com ) 5191596Szelenkov@nginx.com is not None 5201596Szelenkov@nginx.com ), 'not iterable' 521617Szelenkov@nginx.com 522664Szelenkov@nginx.com def test_python_application_write(self): 523664Szelenkov@nginx.com self.load('write') 524664Szelenkov@nginx.com 5251596Szelenkov@nginx.com assert self.get()['body'] == '0123456789', 'write' 526664Szelenkov@nginx.com 5271261Szelenkov@nginx.com def test_python_application_threading(self): 5281261Szelenkov@nginx.com """wait_for_record() timeouts after 5s while every thread works at 5291261Szelenkov@nginx.com least 3s. So without releasing GIL test should fail. 5301261Szelenkov@nginx.com """ 5311261Szelenkov@nginx.com 5321261Szelenkov@nginx.com self.load('threading') 5331261Szelenkov@nginx.com 5341261Szelenkov@nginx.com for _ in range(10): 5351261Szelenkov@nginx.com self.get(no_recv=True) 5361261Szelenkov@nginx.com 5371596Szelenkov@nginx.com assert ( 5381804Szelenkov@nginx.com self.wait_for_record(r'\(5\) Thread: 100', wait=50) is not None 5391596Szelenkov@nginx.com ), 'last thread finished' 5401017Szelenkov@nginx.com 5411283Szelenkov@nginx.com def test_python_application_iter_exception(self): 5421283Szelenkov@nginx.com self.load('iter_exception') 5431283Szelenkov@nginx.com 5441283Szelenkov@nginx.com # Default request doesn't lead to the exception. 5451283Szelenkov@nginx.com 5461283Szelenkov@nginx.com resp = self.get( 5471283Szelenkov@nginx.com headers={ 5481283Szelenkov@nginx.com 'Host': 'localhost', 5491283Szelenkov@nginx.com 'X-Skip': '9', 5501283Szelenkov@nginx.com 'X-Chunked': '1', 5511283Szelenkov@nginx.com 'Connection': 'close', 5521283Szelenkov@nginx.com } 5531283Szelenkov@nginx.com ) 5541596Szelenkov@nginx.com assert resp['status'] == 200, 'status' 5551596Szelenkov@nginx.com assert resp['body'] == 'XXXXXXX', 'body' 5561283Szelenkov@nginx.com 5571283Szelenkov@nginx.com # Exception before start_response(). 5581283Szelenkov@nginx.com 5591596Szelenkov@nginx.com assert self.get()['status'] == 503, 'error' 5601283Szelenkov@nginx.com 5611596Szelenkov@nginx.com assert self.wait_for_record(r'Traceback') is not None, 'traceback' 5621596Szelenkov@nginx.com assert ( 5631596Szelenkov@nginx.com self.wait_for_record(r'raise Exception\(\'first exception\'\)') 5641596Szelenkov@nginx.com is not None 5651596Szelenkov@nginx.com ), 'first exception raise' 5661596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 1, 'traceback count 1' 5671283Szelenkov@nginx.com 5681283Szelenkov@nginx.com # Exception after start_response(), before first write(). 5691283Szelenkov@nginx.com 5701596Szelenkov@nginx.com assert ( 5711283Szelenkov@nginx.com self.get( 5721283Szelenkov@nginx.com headers={ 5731283Szelenkov@nginx.com 'Host': 'localhost', 5741283Szelenkov@nginx.com 'X-Skip': '1', 5751283Szelenkov@nginx.com 'Connection': 'close', 5761283Szelenkov@nginx.com } 5771596Szelenkov@nginx.com )['status'] 5781596Szelenkov@nginx.com == 503 5791596Szelenkov@nginx.com ), 'error 2' 5801283Szelenkov@nginx.com 5811596Szelenkov@nginx.com assert ( 5821596Szelenkov@nginx.com self.wait_for_record(r'raise Exception\(\'second exception\'\)') 5831596Szelenkov@nginx.com is not None 5841596Szelenkov@nginx.com ), 'exception raise second' 5851596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 2, 'traceback count 2' 5861283Szelenkov@nginx.com 5871283Szelenkov@nginx.com # Exception after first write(), before first __next__(). 5881283Szelenkov@nginx.com 5891283Szelenkov@nginx.com _, sock = self.get( 5901283Szelenkov@nginx.com headers={ 5911283Szelenkov@nginx.com 'Host': 'localhost', 5921283Szelenkov@nginx.com 'X-Skip': '2', 5931283Szelenkov@nginx.com 'Connection': 'keep-alive', 5941283Szelenkov@nginx.com }, 5951283Szelenkov@nginx.com start=True, 5961283Szelenkov@nginx.com ) 5971283Szelenkov@nginx.com 5981596Szelenkov@nginx.com assert ( 5991596Szelenkov@nginx.com self.wait_for_record(r'raise Exception\(\'third exception\'\)') 6001596Szelenkov@nginx.com is not None 6011596Szelenkov@nginx.com ), 'exception raise third' 6021596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 3, 'traceback count 3' 6031283Szelenkov@nginx.com 6041596Szelenkov@nginx.com assert self.get(sock=sock) == {}, 'closed connection' 6051283Szelenkov@nginx.com 6061283Szelenkov@nginx.com # Exception after first write(), before first __next__(), 6071283Szelenkov@nginx.com # chunked (incomplete body). 6081283Szelenkov@nginx.com 6091283Szelenkov@nginx.com resp = self.get( 6101283Szelenkov@nginx.com headers={ 6111283Szelenkov@nginx.com 'Host': 'localhost', 6121283Szelenkov@nginx.com 'X-Skip': '2', 6131283Szelenkov@nginx.com 'X-Chunked': '1', 6141283Szelenkov@nginx.com 'Connection': 'close', 6151295St.nateldemoura@f5.com }, 6161596Szelenkov@nginx.com raw_resp=True, 6171283Szelenkov@nginx.com ) 6181295St.nateldemoura@f5.com if resp: 6191596Szelenkov@nginx.com assert resp[-5:] != '0\r\n\r\n', 'incomplete body' 6201596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 4, 'traceback count 4' 6211283Szelenkov@nginx.com 6221283Szelenkov@nginx.com # Exception in __next__(). 6231283Szelenkov@nginx.com 6241283Szelenkov@nginx.com _, sock = self.get( 6251283Szelenkov@nginx.com headers={ 6261283Szelenkov@nginx.com 'Host': 'localhost', 6271283Szelenkov@nginx.com 'X-Skip': '3', 6281283Szelenkov@nginx.com 'Connection': 'keep-alive', 6291283Szelenkov@nginx.com }, 6301283Szelenkov@nginx.com start=True, 6311283Szelenkov@nginx.com ) 6321283Szelenkov@nginx.com 6331596Szelenkov@nginx.com assert ( 6341596Szelenkov@nginx.com self.wait_for_record(r'raise Exception\(\'next exception\'\)') 6351596Szelenkov@nginx.com is not None 6361596Szelenkov@nginx.com ), 'exception raise next' 6371596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 5, 'traceback count 5' 6381283Szelenkov@nginx.com 6391596Szelenkov@nginx.com assert self.get(sock=sock) == {}, 'closed connection 2' 6401283Szelenkov@nginx.com 6411283Szelenkov@nginx.com # Exception in __next__(), chunked (incomplete body). 6421283Szelenkov@nginx.com 6431283Szelenkov@nginx.com resp = self.get( 6441283Szelenkov@nginx.com headers={ 6451283Szelenkov@nginx.com 'Host': 'localhost', 6461283Szelenkov@nginx.com 'X-Skip': '3', 6471283Szelenkov@nginx.com 'X-Chunked': '1', 6481283Szelenkov@nginx.com 'Connection': 'close', 6491295St.nateldemoura@f5.com }, 6501596Szelenkov@nginx.com raw_resp=True, 6511283Szelenkov@nginx.com ) 6521295St.nateldemoura@f5.com if resp: 6531596Szelenkov@nginx.com assert resp[-5:] != '0\r\n\r\n', 'incomplete body 2' 6541596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 6, 'traceback count 6' 6551283Szelenkov@nginx.com 6561283Szelenkov@nginx.com # Exception before start_response() and in close(). 6571283Szelenkov@nginx.com 6581596Szelenkov@nginx.com assert ( 6591283Szelenkov@nginx.com self.get( 6601283Szelenkov@nginx.com headers={ 6611283Szelenkov@nginx.com 'Host': 'localhost', 6621283Szelenkov@nginx.com 'X-Not-Skip-Close': '1', 6631283Szelenkov@nginx.com 'Connection': 'close', 6641283Szelenkov@nginx.com } 6651596Szelenkov@nginx.com )['status'] 6661596Szelenkov@nginx.com == 503 6671596Szelenkov@nginx.com ), 'error' 6681283Szelenkov@nginx.com 6691596Szelenkov@nginx.com assert ( 6701596Szelenkov@nginx.com self.wait_for_record(r'raise Exception\(\'close exception\'\)') 6711596Szelenkov@nginx.com is not None 6721596Szelenkov@nginx.com ), 'exception raise close' 6731596Szelenkov@nginx.com assert len(self.findall(r'Traceback')) == 8, 'traceback count 8' 6741283Szelenkov@nginx.com 6751596Szelenkov@nginx.com def test_python_user_group(self, is_su): 6761596Szelenkov@nginx.com if not is_su: 6771596Szelenkov@nginx.com pytest.skip('requires root') 6781304St.nateldemoura@f5.com 6791304St.nateldemoura@f5.com nobody_uid = pwd.getpwnam('nobody').pw_uid 6801304St.nateldemoura@f5.com 6811304St.nateldemoura@f5.com group = 'nobody' 6821304St.nateldemoura@f5.com 6831304St.nateldemoura@f5.com try: 6841304St.nateldemoura@f5.com group_id = grp.getgrnam(group).gr_gid 6851706Smax.romanov@nginx.com except KeyError: 6861304St.nateldemoura@f5.com group = 'nogroup' 6871304St.nateldemoura@f5.com group_id = grp.getgrnam(group).gr_gid 6881304St.nateldemoura@f5.com 6891304St.nateldemoura@f5.com self.load('user_group') 6901304St.nateldemoura@f5.com 6911304St.nateldemoura@f5.com obj = self.getjson()['body'] 6921596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'nobody uid' 6931596Szelenkov@nginx.com assert obj['GID'] == group_id, 'nobody gid' 6941304St.nateldemoura@f5.com 6951304St.nateldemoura@f5.com self.load('user_group', user='nobody') 6961304St.nateldemoura@f5.com 6971304St.nateldemoura@f5.com obj = self.getjson()['body'] 6981596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'nobody uid user=nobody' 6991596Szelenkov@nginx.com assert obj['GID'] == group_id, 'nobody gid user=nobody' 7001304St.nateldemoura@f5.com 7011304St.nateldemoura@f5.com self.load('user_group', user='nobody', group=group) 7021304St.nateldemoura@f5.com 7031304St.nateldemoura@f5.com obj = self.getjson()['body'] 7041596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, ( 7051596Szelenkov@nginx.com 'nobody uid user=nobody group=%s' % group 7061304St.nateldemoura@f5.com ) 7071304St.nateldemoura@f5.com 708*2073Szelenkov@nginx.com assert obj['GID'] == group_id, 'nobody gid user=nobody group=%s' % group 7091304St.nateldemoura@f5.com 7101304St.nateldemoura@f5.com self.load('user_group', group=group) 7111304St.nateldemoura@f5.com 7121304St.nateldemoura@f5.com obj = self.getjson()['body'] 7131596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'nobody uid group=%s' % group 7141304St.nateldemoura@f5.com 7151596Szelenkov@nginx.com assert obj['GID'] == group_id, 'nobody gid group=%s' % group 7161304St.nateldemoura@f5.com 7171304St.nateldemoura@f5.com self.load('user_group', user='root') 7181304St.nateldemoura@f5.com 7191304St.nateldemoura@f5.com obj = self.getjson()['body'] 7201596Szelenkov@nginx.com assert obj['UID'] == 0, 'root uid user=root' 7211596Szelenkov@nginx.com assert obj['GID'] == 0, 'root gid user=root' 7221304St.nateldemoura@f5.com 7231304St.nateldemoura@f5.com group = 'root' 7241304St.nateldemoura@f5.com 7251304St.nateldemoura@f5.com try: 7261304St.nateldemoura@f5.com grp.getgrnam(group) 7271304St.nateldemoura@f5.com group = True 7281706Smax.romanov@nginx.com except KeyError: 7291304St.nateldemoura@f5.com group = False 7301304St.nateldemoura@f5.com 7311304St.nateldemoura@f5.com if group: 7321304St.nateldemoura@f5.com self.load('user_group', user='root', group='root') 7331304St.nateldemoura@f5.com 7341304St.nateldemoura@f5.com obj = self.getjson()['body'] 7351596Szelenkov@nginx.com assert obj['UID'] == 0, 'root uid user=root group=root' 7361596Szelenkov@nginx.com assert obj['GID'] == 0, 'root gid user=root group=root' 7371304St.nateldemoura@f5.com 7381304St.nateldemoura@f5.com self.load('user_group', group='root') 7391304St.nateldemoura@f5.com 7401304St.nateldemoura@f5.com obj = self.getjson()['body'] 7411596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'root uid group=root' 7421596Szelenkov@nginx.com assert obj['GID'] == 0, 'root gid group=root' 7431604Smax.romanov@nginx.com 7441736Szelenkov@nginx.com def test_python_application_callable(self, skip_alert): 7451604Smax.romanov@nginx.com skip_alert(r'Python failed to get "blah" from module') 7461604Smax.romanov@nginx.com self.load('callable') 7471604Smax.romanov@nginx.com 7481604Smax.romanov@nginx.com assert self.get()['status'] == 204, 'default application response' 7491604Smax.romanov@nginx.com 7501695Smax.romanov@nginx.com self.load('callable', callable="app") 7511604Smax.romanov@nginx.com 7521604Smax.romanov@nginx.com assert self.get()['status'] == 200, 'callable response' 7531604Smax.romanov@nginx.com 7541695Smax.romanov@nginx.com self.load('callable', callable="blah") 7551604Smax.romanov@nginx.com 7561604Smax.romanov@nginx.com assert self.get()['status'] not in [200, 204], 'callable response inv' 7571604Smax.romanov@nginx.com 7581791Szelenkov@nginx.com def test_python_application_path(self): 7591791Szelenkov@nginx.com self.load('path') 7601791Szelenkov@nginx.com 7611791Szelenkov@nginx.com def set_path(path): 7621791Szelenkov@nginx.com assert 'success' in self.conf(path, 'applications/path/path') 7631791Szelenkov@nginx.com 7641791Szelenkov@nginx.com def get_path(): 7651791Szelenkov@nginx.com return self.get()['body'].split(os.pathsep) 7661791Szelenkov@nginx.com 7671791Szelenkov@nginx.com default_path = self.conf_get('/config/applications/path/path') 7681791Szelenkov@nginx.com assert 'success' in self.conf( 7691791Szelenkov@nginx.com {"PYTHONPATH": default_path}, 7701791Szelenkov@nginx.com '/config/applications/path/environment', 7711791Szelenkov@nginx.com ) 7721791Szelenkov@nginx.com 7731791Szelenkov@nginx.com self.conf_delete('/config/applications/path/path') 7741791Szelenkov@nginx.com sys_path = get_path() 7751791Szelenkov@nginx.com 7761791Szelenkov@nginx.com set_path('"/blah"') 7771791Szelenkov@nginx.com assert ['/blah', *sys_path] == get_path(), 'check path' 7781791Szelenkov@nginx.com 7791791Szelenkov@nginx.com set_path('"/new"') 7801791Szelenkov@nginx.com assert ['/new', *sys_path] == get_path(), 'check path update' 7811791Szelenkov@nginx.com 7821791Szelenkov@nginx.com set_path('["/blah1", "/blah2"]') 7831848Szelenkov@nginx.com assert [ 7841848Szelenkov@nginx.com '/blah1', 7851848Szelenkov@nginx.com '/blah2', 7861848Szelenkov@nginx.com *sys_path, 7871848Szelenkov@nginx.com ] == get_path(), 'check path array' 7881791Szelenkov@nginx.com 7891791Szelenkov@nginx.com def test_python_application_path_invalid(self): 7901791Szelenkov@nginx.com self.load('path') 7911791Szelenkov@nginx.com 7921791Szelenkov@nginx.com def check_path(path): 7931791Szelenkov@nginx.com assert 'error' in self.conf(path, 'applications/path/path') 7941791Szelenkov@nginx.com 7951791Szelenkov@nginx.com check_path('{}') 7961791Szelenkov@nginx.com check_path('["/blah", []]') 7971791Szelenkov@nginx.com 7981683Smax.romanov@nginx.com def test_python_application_threads(self): 7991695Smax.romanov@nginx.com self.load('threads', threads=4) 8001683Smax.romanov@nginx.com 8011683Smax.romanov@nginx.com socks = [] 8021683Smax.romanov@nginx.com 8031683Smax.romanov@nginx.com for i in range(4): 8041683Smax.romanov@nginx.com (_, sock) = self.get( 8051683Smax.romanov@nginx.com headers={ 8061683Smax.romanov@nginx.com 'Host': 'localhost', 8071683Smax.romanov@nginx.com 'X-Delay': '2', 8081683Smax.romanov@nginx.com 'Connection': 'close', 8091683Smax.romanov@nginx.com }, 8101683Smax.romanov@nginx.com no_recv=True, 8111683Smax.romanov@nginx.com start=True, 8121683Smax.romanov@nginx.com ) 8131683Smax.romanov@nginx.com 8141683Smax.romanov@nginx.com socks.append(sock) 8151683Smax.romanov@nginx.com 8161683Smax.romanov@nginx.com threads = set() 8171683Smax.romanov@nginx.com 8181683Smax.romanov@nginx.com for sock in socks: 8191683Smax.romanov@nginx.com resp = self.recvall(sock).decode('utf-8') 8201683Smax.romanov@nginx.com 8211683Smax.romanov@nginx.com self.log_in(resp) 8221683Smax.romanov@nginx.com 8231683Smax.romanov@nginx.com resp = self._resp_to_dict(resp) 8241683Smax.romanov@nginx.com 8251683Smax.romanov@nginx.com assert resp['status'] == 200, 'status' 8261683Smax.romanov@nginx.com 8271683Smax.romanov@nginx.com threads.add(resp['headers']['X-Thread']) 8281683Smax.romanov@nginx.com 8291683Smax.romanov@nginx.com assert resp['headers']['Wsgi-Multithread'] == 'True', 'multithread' 8301683Smax.romanov@nginx.com 8311683Smax.romanov@nginx.com sock.close() 8321683Smax.romanov@nginx.com 8331683Smax.romanov@nginx.com assert len(socks) == len(threads), 'threads differs' 834