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