xref: /unit/test/test_ruby_application.py (revision 2618:3ba1a53cba07)
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