1import re 2import subprocess 3 4import pytest 5 6from unit.applications.lang.ruby import ApplicationRuby 7 8prerequisites = {'modules': {'ruby': 'all'}} 9 10client = ApplicationRuby() 11 12 13def test_ruby_application(date_to_sec_epoch, sec_epoch): 14 client.load('variables') 15 16 body = 'Test body string.' 17 18 resp = client.post( 19 headers={ 20 'Host': 'localhost', 21 'Content-Type': 'text/html', 22 'Custom-Header': 'blah', 23 'Connection': 'close', 24 }, 25 body=body, 26 ) 27 28 assert resp['status'] == 200, 'status' 29 headers = resp['headers'] 30 header_server = headers.pop('Server') 31 assert re.search(r'Unit/[\d\.]+', header_server), 'server header' 32 assert ( 33 headers.pop('Server-Software') == header_server 34 ), 'server software header' 35 36 date = headers.pop('Date') 37 assert date[-4:] == ' GMT', 'date header timezone' 38 assert abs(date_to_sec_epoch(date) - sec_epoch) < 5, 'date header' 39 40 assert headers == { 41 'Connection': 'close', 42 'Content-Length': str(len(body)), 43 'Content-Type': 'text/html', 44 'Request-Method': 'POST', 45 'Request-Uri': '/', 46 'Http-Host': 'localhost', 47 'Script-Name': '', 48 'Server-Protocol': 'HTTP/1.1', 49 'Custom-Header': 'blah', 50 'Rack-Version': '13', 51 'Rack-Url-Scheme': 'http', 52 'Rack-Multithread': 'false', 53 'Rack-Multiprocess': 'true', 54 'Rack-Run-Once': 'false', 55 'Rack-Hijack-Q': 'false', 56 'Rack-Hijack': '', 57 'Rack-Hijack-IO': '', 58 }, 'headers' 59 assert resp['body'] == body, 'body' 60 61 62def test_ruby_application_query_string(): 63 client.load('query_string') 64 65 resp = client.get(url='/?var1=val1&var2=val2') 66 67 assert ( 68 resp['headers']['Query-String'] == 'var1=val1&var2=val2' 69 ), 'Query-String header' 70 71 72def test_ruby_application_query_string_empty(): 73 client.load('query_string') 74 75 resp = client.get(url='/?') 76 77 assert resp['status'] == 200, 'query string empty status' 78 assert resp['headers']['Query-String'] == '', 'query string empty' 79 80 81def test_ruby_application_query_string_absent(): 82 client.load('query_string') 83 84 resp = client.get() 85 86 assert resp['status'] == 200, 'query string absent status' 87 assert resp['headers']['Query-String'] == '', 'query string absent' 88 89 90@pytest.mark.skip('not yet') 91def test_ruby_application_server_port(): 92 client.load('server_port') 93 94 assert ( 95 client.get()['headers']['Server-Port'] == '8080' 96 ), 'Server-Port header' 97 98 99def test_ruby_application_status_int(): 100 client.load('status_int') 101 102 assert client.get()['status'] == 200, 'status int' 103 104 105def test_ruby_application_input_read_empty(): 106 client.load('input_read_empty') 107 108 assert client.get()['body'] == '', 'read empty' 109 110 111def test_ruby_application_input_read_parts(): 112 client.load('input_read_parts') 113 114 assert ( 115 client.post(body='0123456789')['body'] == '012345678' 116 ), 'input read parts' 117 118 119def test_ruby_application_input_read_buffer(): 120 client.load('input_read_buffer') 121 122 assert ( 123 client.post(body='0123456789')['body'] == '0123456789' 124 ), 'input read buffer' 125 126 127def test_ruby_application_input_read_buffer_not_empty(): 128 client.load('input_read_buffer_not_empty') 129 130 assert ( 131 client.post(body='0123456789')['body'] == '0123456789' 132 ), 'input read buffer not empty' 133 134 135def test_ruby_application_input_gets(): 136 client.load('input_gets') 137 138 body = '0123456789' 139 140 assert client.post(body=body)['body'] == body, 'input gets' 141 142 143def test_ruby_application_input_gets_2(): 144 client.load('input_gets') 145 146 assert ( 147 client.post(body='01234\n56789\n')['body'] == '01234\n' 148 ), 'input gets 2' 149 150 151def test_ruby_application_input_gets_all(): 152 client.load('input_gets_all') 153 154 body = '\n01234\n56789\n\n' 155 156 assert client.post(body=body)['body'] == body, 'input gets all' 157 158 159def test_ruby_application_input_each(): 160 client.load('input_each') 161 162 body = '\n01234\n56789\n\n' 163 164 assert client.post(body=body)['body'] == body, 'input each' 165 166 167@pytest.mark.skip('not yet') 168def test_ruby_application_syntax_error(skip_alert): 169 skip_alert( 170 r'Failed to parse rack script', 171 r'syntax error', 172 r'new_from_string', 173 r'parse_file', 174 ) 175 client.load('syntax_error') 176 177 assert client.get()['status'] == 500, 'syntax error' 178 179 180def test_ruby_application_errors_puts(wait_for_record): 181 client.load('errors_puts') 182 183 assert client.get()['status'] == 200 184 185 assert ( 186 wait_for_record(r'\[error\].+Error in application') is not None 187 ), 'errors puts' 188 189 190def test_ruby_application_errors_puts_int(wait_for_record): 191 client.load('errors_puts_int') 192 193 assert client.get()['status'] == 200 194 195 assert ( 196 wait_for_record(r'\[error\].+1234567890') is not None 197 ), 'errors puts int' 198 199 200def test_ruby_application_errors_write(wait_for_record): 201 client.load('errors_write') 202 203 assert client.get()['status'] == 200 204 assert ( 205 wait_for_record(r'\[error\].+Error in application') is not None 206 ), 'errors write' 207 208 209def test_ruby_application_errors_write_to_s_custom(): 210 client.load('errors_write_to_s_custom') 211 212 assert client.get()['status'] == 200, 'errors write to_s custom' 213 214 215def test_ruby_application_errors_write_int(wait_for_record): 216 client.load('errors_write_int') 217 218 assert client.get()['status'] == 200 219 assert ( 220 wait_for_record(r'\[error\].+1234567890') is not None 221 ), 'errors write int' 222 223 224def test_ruby_application_at_exit(wait_for_record): 225 client.load('at_exit') 226 227 assert client.get()['status'] == 200 228 229 assert 'success' in client.conf({"listeners": {}, "applications": {}}) 230 231 assert ( 232 wait_for_record(r'\[error\].+At exit called\.') is not None 233 ), 'at exit' 234 235 236def test_ruby_application_encoding(): 237 client.load('encoding') 238 239 try: 240 locales = ( 241 subprocess.check_output( 242 ['locale', '-a'], 243 stderr=subprocess.STDOUT, 244 ) 245 .decode() 246 .split('\n') 247 ) 248 249 except (FileNotFoundError, subprocess.CalledProcessError): 250 pytest.skip('require locale') 251 252 def get_locale(pattern): 253 return next( 254 (l for l in locales if re.match(pattern, l.upper()) is not None), 255 None, 256 ) 257 258 utf8 = get_locale(r'.*UTF[-_]?8') 259 iso88591 = get_locale(r'.*ISO[-_]?8859[-_]?1') 260 261 def check_locale(enc): 262 assert 'success' in client.conf( 263 {"LC_CTYPE": enc, "LC_ALL": ""}, 264 '/config/applications/encoding/environment', 265 ) 266 267 resp = client.get() 268 assert resp['status'] == 200, 'status' 269 270 enc_default = re.sub(r'[-_]', '', resp['headers']['X-Enc']).upper() 271 assert enc_default == re.sub(r'[-_]', '', enc.split('.')[-1]).upper() 272 273 if utf8: 274 check_locale(utf8) 275 276 if iso88591: 277 check_locale(iso88591) 278 279 if not utf8 and not iso88591: 280 pytest.skip('no available locales') 281 282 283def test_ruby_application_header_custom(): 284 client.load('header_custom') 285 286 resp = client.post(body="\ntc=one,two\ntc=three,four,\n\n") 287 288 assert resp['headers']['Custom-Header'] == [ 289 '', 290 'tc=one,two', 291 'tc=three,four,', 292 '', 293 '', 294 ], 'header custom' 295 296 297@pytest.mark.skip('not yet') 298def test_ruby_application_header_custom_non_printable(): 299 client.load('header_custom') 300 301 assert ( 302 client.post(body='\b')['status'] == 500 303 ), 'header custom non printable' 304 305 306def test_ruby_application_header_status(): 307 client.load('header_status') 308 309 assert client.get()['status'] == 200, 'header status' 310 311 312def test_ruby_application_header_array(): 313 client.load('header_array') 314 315 assert client.get()['headers']['x-array'] == 'name=value; ; value; av' 316 317 318def test_ruby_application_header_array_nil(): 319 client.load('header_array_nil') 320 321 assert client.get()['status'] == 503 322 323 324def test_ruby_application_header_array_empty(): 325 client.load('header_array_empty') 326 327 headers = client.get()['headers'] 328 assert 'x-array' in headers 329 assert headers['x-array'] == '' 330 331 332@pytest.mark.skip('not yet') 333def test_ruby_application_header_rack(): 334 client.load('header_rack') 335 336 assert client.get()['status'] == 500, 'header rack' 337 338 339@pytest.mark.skip('not yet') 340def test_ruby_application_session(): 341 client.load('session') 342 343 assert client.get()['status'] == 200 344 345 346@pytest.mark.skip('not yet') 347def test_ruby_application_multipart(): 348 client.load('multipart') 349 350 assert client.get()['status'] == 200 351 352 353def test_ruby_application_body_empty(): 354 client.load('body_empty') 355 356 assert client.get()['body'] == '', 'body empty' 357 358 359def test_ruby_application_body_array(): 360 client.load('body_array') 361 362 assert client.get()['body'] == '0123456789', 'body array' 363 364 365def test_ruby_application_body_large(): 366 client.load('mirror') 367 368 body = '0123456789' * 1000 369 370 assert client.post(body=body)['body'] == body, 'body large' 371 372 373@pytest.mark.skip('not yet') 374def test_ruby_application_body_each_error(wait_for_record): 375 client.load('body_each_error') 376 377 assert client.get()['status'] == 500, 'body each error status' 378 379 assert ( 380 wait_for_record(r'\[error\].+Failed to run ruby script') is not None 381 ), 'body each error' 382 383 384def test_ruby_application_body_file(): 385 client.load('body_file') 386 387 assert client.get()['body'] == 'body\n', 'body file' 388 389 390def test_ruby_keepalive_body(): 391 client.load('mirror') 392 393 assert client.get()['status'] == 200, 'init' 394 395 body = '0123456789' * 500 396 (resp, sock) = client.post( 397 headers={ 398 'Host': 'localhost', 399 'Connection': 'keep-alive', 400 }, 401 start=True, 402 body=body, 403 read_timeout=1, 404 ) 405 406 assert resp['body'] == body, 'keep-alive 1' 407 408 body = '0123456789' 409 resp = client.post(sock=sock, body=body) 410 411 assert resp['body'] == body, 'keep-alive 2' 412 413 414def test_ruby_application_constants(): 415 client.load('constants') 416 417 resp = client.get() 418 419 assert resp['status'] == 200, 'status' 420 421 headers = resp['headers'] 422 assert len(headers['X-Copyright']) > 0, 'RUBY_COPYRIGHT' 423 assert len(headers['X-Description']) > 0, 'RUBY_DESCRIPTION' 424 assert len(headers['X-Engine']) > 0, 'RUBY_ENGINE' 425 assert len(headers['X-Engine-Version']) > 0, 'RUBY_ENGINE_VERSION' 426 assert len(headers['X-Patchlevel']) > 0, 'RUBY_PATCHLEVEL' 427 assert len(headers['X-Platform']) > 0, 'RUBY_PLATFORM' 428 assert len(headers['X-Release-Date']) > 0, 'RUBY_RELEASE_DATE' 429 assert len(headers['X-Revision']) > 0, 'RUBY_REVISION' 430 assert len(headers['X-Version']) > 0, 'RUBY_VERSION' 431 432 433def test_ruby_application_threads(): 434 client.load('threads') 435 436 assert 'success' in client.conf( 437 '4', 'applications/threads/threads' 438 ), 'configure 4 threads' 439 440 socks = [] 441 442 for _ in range(4): 443 sock = client.get( 444 headers={ 445 'Host': 'localhost', 446 'X-Delay': '2', 447 'Connection': 'close', 448 }, 449 no_recv=True, 450 ) 451 452 socks.append(sock) 453 454 threads = set() 455 456 for sock in socks: 457 resp = client.recvall(sock).decode('utf-8') 458 459 client.log_in(resp) 460 461 resp = client._resp_to_dict(resp) 462 463 assert resp['status'] == 200, 'status' 464 465 threads.add(resp['headers']['X-Thread']) 466 467 assert resp['headers']['Rack-Multithread'] == 'true', 'multithread' 468 469 sock.close() 470 471 assert len(socks) == len(threads), 'threads differs' 472