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