xref: /unit/test/test_php_application.py (revision 2230:83b2d20d8f5c)
1import getpass
2import os
3import re
4import shutil
5import signal
6import time
7from pathlib import Path
8
9import pytest
10from unit.applications.lang.php import TestApplicationPHP
11from unit.option import option
12
13
14class TestPHPApplication(TestApplicationPHP):
15    prerequisites = {'modules': {'php': 'all'}}
16
17    def before_disable_functions(self):
18        body = self.get()['body']
19
20        assert re.search(r'time: \d+', body), 'disable_functions before time'
21        assert re.search(r'exec: \/\w+', body), 'disable_functions before exec'
22
23    def check_opcache(self):
24        resp = self.get()
25        assert resp['status'] == 200, 'status'
26
27        headers = resp['headers']
28        if 'X-OPcache' in headers and headers['X-OPcache'] == '-1':
29            pytest.skip('opcache is not supported')
30
31        return resp
32
33    def set_opcache(self, app, val):
34        assert 'success' in self.conf(
35            {"admin": {"opcache.enable": val, "opcache.enable_cli": val}},
36            'applications/' + app + '/options',
37        )
38
39        r = self.check_opcache()
40        assert r['headers']['X-OPcache'] == val, 'opcache value'
41
42    def set_preload(self, preload):
43        with open(option.temp_dir + '/php.ini', 'w') as f:
44            f.write(
45                """opcache.preload = %(test_dir)s/php/opcache/preload\
46/%(preload)s
47opcache.preload_user = %(user)s
48"""
49                % {
50                    'test_dir': option.test_dir,
51                    'preload': preload,
52                    'user': option.user or getpass.getuser(),
53                }
54            )
55
56        assert 'success' in self.conf(
57            {"file": option.temp_dir + "/php.ini"},
58            'applications/opcache/options',
59        )
60
61    def test_php_application_variables(self):
62        self.load('variables')
63
64        body = 'Test body string.'
65
66        resp = self.post(
67            headers={
68                'Host': 'localhost',
69                'Content-Type': 'text/html',
70                'Custom-Header': 'blah',
71                'Connection': 'close',
72            },
73            body=body,
74            url='/index.php/blah?var=val',
75        )
76
77        assert resp['status'] == 200, 'status'
78        headers = resp['headers']
79        header_server = headers.pop('Server')
80        assert re.search(r'Unit/[\d\.]+', header_server), 'server header'
81        assert (
82            headers.pop('Server-Software') == header_server
83        ), 'server software header'
84
85        date = headers.pop('Date')
86        assert date[-4:] == ' GMT', 'date header timezone'
87        assert (
88            abs(self.date_to_sec_epoch(date) - self.sec_epoch()) < 5
89        ), 'date header'
90
91        if 'X-Powered-By' in headers:
92            headers.pop('X-Powered-By')
93
94        headers.pop('Content-type')
95        assert headers == {
96            'Connection': 'close',
97            'Content-Length': str(len(body)),
98            'Request-Method': 'POST',
99            'Path-Info': '/blah',
100            'Request-Uri': '/index.php/blah?var=val',
101            'Http-Host': 'localhost',
102            'Server-Protocol': 'HTTP/1.1',
103            'Custom-Header': 'blah',
104        }, 'headers'
105        assert resp['body'] == body, 'body'
106
107    def test_php_application_query_string(self):
108        self.load('query_string')
109
110        resp = self.get(url='/?var1=val1&var2=val2')
111
112        assert (
113            resp['headers']['Query-String'] == 'var1=val1&var2=val2'
114        ), 'query string'
115
116    def test_php_application_query_string_empty(self):
117        self.load('query_string')
118
119        resp = self.get(url='/?')
120
121        assert resp['status'] == 200, 'query string empty status'
122        assert resp['headers']['Query-String'] == '', 'query string empty'
123
124    def test_php_application_fastcgi_finish_request(self, unit_pid):
125        self.load('fastcgi_finish_request')
126
127        assert 'success' in self.conf(
128            {"admin": {"auto_globals_jit": "1"}},
129            'applications/fastcgi_finish_request/options',
130        )
131
132        assert self.get()['body'] == '0123'
133
134        os.kill(unit_pid, signal.SIGUSR1)
135
136        errs = self.findall(r'Error in fastcgi_finish_request')
137
138        assert len(errs) == 0, 'no error'
139
140    def test_php_application_fastcgi_finish_request_2(self, unit_pid):
141        self.load('fastcgi_finish_request')
142
143        assert 'success' in self.conf(
144            {"admin": {"auto_globals_jit": "1"}},
145            'applications/fastcgi_finish_request/options',
146        )
147
148        resp = self.get(url='/?skip')
149        assert resp['status'] == 200
150        assert resp['body'] == ''
151
152        os.kill(unit_pid, signal.SIGUSR1)
153
154        errs = self.findall(r'Error in fastcgi_finish_request')
155
156        assert len(errs) == 0, 'no error'
157
158    def test_php_application_query_string_absent(self):
159        self.load('query_string')
160
161        resp = self.get()
162
163        assert resp['status'] == 200, 'query string absent status'
164        assert resp['headers']['Query-String'] == '', 'query string absent'
165
166    def test_php_application_phpinfo(self):
167        self.load('phpinfo')
168
169        resp = self.get()
170
171        assert resp['status'] == 200, 'status'
172        assert resp['body'] != '', 'body not empty'
173
174    def test_php_application_header_status(self):
175        self.load('header')
176
177        assert (
178            self.get(
179                headers={
180                    'Host': 'localhost',
181                    'Connection': 'close',
182                    'X-Header': 'HTTP/1.1 404 Not Found',
183                }
184            )['status']
185            == 404
186        ), 'status'
187
188        assert (
189            self.get(
190                headers={
191                    'Host': 'localhost',
192                    'Connection': 'close',
193                    'X-Header': 'http/1.1 404 Not Found',
194                }
195            )['status']
196            == 404
197        ), 'status case insensitive'
198
199        assert (
200            self.get(
201                headers={
202                    'Host': 'localhost',
203                    'Connection': 'close',
204                    'X-Header': 'HTTP/ 404 Not Found',
205                }
206            )['status']
207            == 404
208        ), 'status version empty'
209
210    def test_php_application_404(self):
211        self.load('404')
212
213        resp = self.get()
214
215        assert resp['status'] == 404, '404 status'
216        assert re.search(
217            r'<title>404 Not Found</title>', resp['body']
218        ), '404 body'
219
220    def test_php_application_keepalive_body(self):
221        self.load('mirror')
222
223        assert self.get()['status'] == 200, 'init'
224
225        body = '0123456789' * 500
226        (resp, sock) = self.post(
227            headers={
228                'Host': 'localhost',
229                'Connection': 'keep-alive',
230            },
231            start=True,
232            body=body,
233            read_timeout=1,
234        )
235
236        assert resp['body'] == body, 'keep-alive 1'
237
238        body = '0123456789'
239        resp = self.post(sock=sock, body=body)
240
241        assert resp['body'] == body, 'keep-alive 2'
242
243    def test_php_application_conditional(self):
244        self.load('conditional')
245
246        assert re.search(r'True', self.get()['body']), 'conditional true'
247        assert re.search(r'False', self.post()['body']), 'conditional false'
248
249    def test_php_application_get_variables(self):
250        self.load('get_variables')
251
252        resp = self.get(url='/?var1=val1&var2=&var3')
253        assert resp['headers']['X-Var-1'] == 'val1', 'GET variables'
254        assert resp['headers']['X-Var-2'] == '', 'GET variables 2'
255        assert resp['headers']['X-Var-3'] == '', 'GET variables 3'
256        assert resp['headers']['X-Var-4'] == 'not set', 'GET variables 4'
257
258    def test_php_application_post_variables(self):
259        self.load('post_variables')
260
261        resp = self.post(
262            headers={
263                'Content-Type': 'application/x-www-form-urlencoded',
264                'Host': 'localhost',
265                'Connection': 'close',
266            },
267            body='var1=val1&var2=',
268        )
269        assert resp['headers']['X-Var-1'] == 'val1', 'POST variables'
270        assert resp['headers']['X-Var-2'] == '', 'POST variables 2'
271        assert resp['headers']['X-Var-3'] == 'not set', 'POST variables 3'
272
273    def test_php_application_cookies(self):
274        self.load('cookies')
275
276        resp = self.get(
277            headers={
278                'Cookie': 'var=val; var2=val2',
279                'Host': 'localhost',
280                'Connection': 'close',
281            }
282        )
283
284        assert resp['headers']['X-Cookie-1'] == 'val', 'cookie'
285        assert resp['headers']['X-Cookie-2'] == 'val2', 'cookie'
286
287    def test_php_application_ini_precision(self):
288        self.load('ini_precision')
289
290        assert self.get()['headers']['X-Precision'] != '4', 'ini value default'
291
292        assert 'success' in self.conf(
293            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
294        )
295
296        assert (
297            self.get()['headers']['X-File']
298            == option.test_dir + '/php/ini_precision/ini/php.ini'
299        ), 'ini file'
300        assert self.get()['headers']['X-Precision'] == '4', 'ini value'
301
302    @pytest.mark.skip('not yet')
303    def test_php_application_ini_admin_user(self):
304        self.load('ini_precision')
305
306        assert 'error' in self.conf(
307            {"user": {"precision": "4"}, "admin": {"precision": "5"}},
308            'applications/ini_precision/options',
309        ), 'ini admin user'
310
311    def test_php_application_ini_admin(self):
312        self.load('ini_precision')
313
314        assert 'success' in self.conf(
315            {"file": "ini/php.ini", "admin": {"precision": "5"}},
316            'applications/ini_precision/options',
317        )
318
319        assert (
320            self.get()['headers']['X-File']
321            == option.test_dir + '/php/ini_precision/ini/php.ini'
322        ), 'ini file'
323        assert self.get()['headers']['X-Precision'] == '5', 'ini value admin'
324
325    def test_php_application_ini_user(self):
326        self.load('ini_precision')
327
328        assert 'success' in self.conf(
329            {"file": "ini/php.ini", "user": {"precision": "5"}},
330            'applications/ini_precision/options',
331        )
332
333        assert (
334            self.get()['headers']['X-File']
335            == option.test_dir + '/php/ini_precision/ini/php.ini'
336        ), 'ini file'
337        assert self.get()['headers']['X-Precision'] == '5', 'ini value user'
338
339    def test_php_application_ini_user_2(self):
340        self.load('ini_precision')
341
342        assert 'success' in self.conf(
343            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
344        )
345
346        assert self.get()['headers']['X-Precision'] == '4', 'ini user file'
347
348        assert 'success' in self.conf(
349            {"precision": "5"}, 'applications/ini_precision/options/user'
350        )
351
352        assert self.get()['headers']['X-Precision'] == '5', 'ini value user'
353
354    def test_php_application_ini_set_admin(self):
355        self.load('ini_precision')
356
357        assert 'success' in self.conf(
358            {"admin": {"precision": "5"}}, 'applications/ini_precision/options'
359        )
360
361        assert (
362            self.get(url='/?precision=6')['headers']['X-Precision'] == '5'
363        ), 'ini set admin'
364
365    def test_php_application_ini_set_user(self):
366        self.load('ini_precision')
367
368        assert 'success' in self.conf(
369            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
370        )
371
372        assert (
373            self.get(url='/?precision=6')['headers']['X-Precision'] == '6'
374        ), 'ini set user'
375
376    def test_php_application_ini_repeat(self):
377        self.load('ini_precision')
378
379        assert 'success' in self.conf(
380            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
381        )
382
383        assert self.get()['headers']['X-Precision'] == '5', 'ini value'
384
385        assert self.get()['headers']['X-Precision'] == '5', 'ini value repeat'
386
387    def test_php_application_disable_functions_exec(self):
388        self.load('time_exec')
389
390        self.before_disable_functions()
391
392        assert 'success' in self.conf(
393            {"admin": {"disable_functions": "exec"}},
394            'applications/time_exec/options',
395        )
396
397        body = self.get()['body']
398
399        assert re.search(r'time: \d+', body), 'disable_functions time'
400        assert not re.search(r'exec: \/\w+', body), 'disable_functions exec'
401
402    def test_php_application_disable_functions_comma(self):
403        self.load('time_exec')
404
405        self.before_disable_functions()
406
407        assert 'success' in self.conf(
408            {"admin": {"disable_functions": "exec,time"}},
409            'applications/time_exec/options',
410        )
411
412        body = self.get()['body']
413
414        assert not re.search(r'time: \d+', body), 'disable_functions comma time'
415        assert not re.search(
416            r'exec: \/\w+', body
417        ), 'disable_functions comma exec'
418
419    def test_php_application_auth(self):
420        self.load('auth')
421
422        resp = self.get()
423        assert resp['status'] == 200, 'status'
424        assert resp['headers']['X-Digest'] == 'not set', 'digest'
425        assert resp['headers']['X-User'] == 'not set', 'user'
426        assert resp['headers']['X-Password'] == 'not set', 'password'
427
428        resp = self.get(
429            headers={
430                'Host': 'localhost',
431                'Authorization': 'Basic dXNlcjpwYXNzd29yZA==',
432                'Connection': 'close',
433            }
434        )
435        assert resp['status'] == 200, 'basic status'
436        assert resp['headers']['X-Digest'] == 'not set', 'basic digest'
437        assert resp['headers']['X-User'] == 'user', 'basic user'
438        assert resp['headers']['X-Password'] == 'password', 'basic password'
439
440        resp = self.get(
441            headers={
442                'Host': 'localhost',
443                'Authorization': 'Digest username="blah", realm="", uri="/"',
444                'Connection': 'close',
445            }
446        )
447        assert resp['status'] == 200, 'digest status'
448        assert (
449            resp['headers']['X-Digest'] == 'username="blah", realm="", uri="/"'
450        ), 'digest digest'
451        assert resp['headers']['X-User'] == 'not set', 'digest user'
452        assert resp['headers']['X-Password'] == 'not set', 'digest password'
453
454    def test_php_application_auth_invalid(self):
455        self.load('auth')
456
457        def check_auth(auth):
458            resp = self.get(
459                headers={
460                    'Host': 'localhost',
461                    'Authorization': auth,
462                    'Connection': 'close',
463                }
464            )
465
466            assert resp['status'] == 200, 'status'
467            assert resp['headers']['X-Digest'] == 'not set', 'Digest'
468            assert resp['headers']['X-User'] == 'not set', 'User'
469            assert resp['headers']['X-Password'] == 'not set', 'Password'
470
471        check_auth('Basic dXN%cjpwYXNzd29yZA==')
472        check_auth('Basic XNlcjpwYXNzd29yZA==')
473        check_auth('Basic DdXNlcjpwYXNzd29yZA==')
474        check_auth('Basic blah')
475        check_auth('Basic')
476        check_auth('Digest')
477        check_auth('blah')
478
479    def test_php_application_disable_functions_space(self):
480        self.load('time_exec')
481
482        self.before_disable_functions()
483
484        assert 'success' in self.conf(
485            {"admin": {"disable_functions": "exec time"}},
486            'applications/time_exec/options',
487        )
488
489        body = self.get()['body']
490
491        assert not re.search(r'time: \d+', body), 'disable_functions space time'
492        assert not re.search(
493            r'exec: \/\w+', body
494        ), 'disable_functions space exec'
495
496    def test_php_application_disable_functions_user(self):
497        self.load('time_exec')
498
499        self.before_disable_functions()
500
501        assert 'success' in self.conf(
502            {"user": {"disable_functions": "exec"}},
503            'applications/time_exec/options',
504        )
505
506        body = self.get()['body']
507
508        assert re.search(r'time: \d+', body), 'disable_functions user time'
509        assert not re.search(
510            r'exec: \/\w+', body
511        ), 'disable_functions user exec'
512
513    def test_php_application_disable_functions_nonexistent(self):
514        self.load('time_exec')
515
516        self.before_disable_functions()
517
518        assert 'success' in self.conf(
519            {"admin": {"disable_functions": "blah"}},
520            'applications/time_exec/options',
521        )
522
523        body = self.get()['body']
524
525        assert re.search(
526            r'time: \d+', body
527        ), 'disable_functions nonexistent time'
528        assert re.search(
529            r'exec: \/\w+', body
530        ), 'disable_functions nonexistent exec'
531
532    def test_php_application_disable_classes(self):
533        self.load('date_time')
534
535        assert re.search(
536            r'012345', self.get()['body']
537        ), 'disable_classes before'
538
539        assert 'success' in self.conf(
540            {"admin": {"disable_classes": "DateTime"}},
541            'applications/date_time/options',
542        )
543
544        assert not re.search(
545            r'012345', self.get()['body']
546        ), 'disable_classes before'
547
548    def test_php_application_disable_classes_user(self):
549        self.load('date_time')
550
551        assert re.search(
552            r'012345', self.get()['body']
553        ), 'disable_classes before'
554
555        assert 'success' in self.conf(
556            {"user": {"disable_classes": "DateTime"}},
557            'applications/date_time/options',
558        )
559
560        assert not re.search(
561            r'012345', self.get()['body']
562        ), 'disable_classes before'
563
564    def test_php_application_error_log(self):
565        self.load('error_log')
566
567        assert self.get()['status'] == 200, 'status'
568
569        time.sleep(1)
570
571        assert self.get()['status'] == 200, 'status 2'
572
573        pattern = r'\d{4}\/\d\d\/\d\d\s\d\d:.+\[notice\].+Error in application'
574
575        assert self.wait_for_record(pattern) is not None, 'errors print'
576
577        errs = self.findall(pattern)
578
579        assert len(errs) == 2, 'error_log count'
580
581        date = errs[0].split('[')[0]
582        date2 = errs[1].split('[')[0]
583        assert date != date2, 'date diff'
584
585    def test_php_application_script(self):
586        assert 'success' in self.conf(
587            {
588                "listeners": {"*:7080": {"pass": "applications/script"}},
589                "applications": {
590                    "script": {
591                        "type": self.get_application_type(),
592                        "processes": {"spare": 0},
593                        "root": option.test_dir + "/php/script",
594                        "script": "phpinfo.php",
595                    }
596                },
597            }
598        ), 'configure script'
599
600        resp = self.get()
601
602        assert resp['status'] == 200, 'status'
603        assert resp['body'] != '', 'body not empty'
604
605    def test_php_application_index_default(self):
606        assert 'success' in self.conf(
607            {
608                "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
609                "applications": {
610                    "phpinfo": {
611                        "type": self.get_application_type(),
612                        "processes": {"spare": 0},
613                        "root": option.test_dir + "/php/phpinfo",
614                    }
615                },
616            }
617        ), 'configure index default'
618
619        resp = self.get()
620
621        assert resp['status'] == 200, 'status'
622        assert resp['body'] != '', 'body not empty'
623
624    def test_php_application_trailing_slash(self, temp_dir):
625        new_root = temp_dir + "/php-root"
626        os.makedirs(new_root + '/path')
627
628        Path(new_root + '/path/index.php').write_text('<?php echo "OK\n"; ?>')
629
630        addr = temp_dir + '/sock'
631
632        assert 'success' in self.conf(
633            {
634                "listeners": {
635                    "*:7080": {"pass": "applications/php-path"},
636                    "unix:" + addr: {"pass": "applications/php-path"},
637                },
638                "applications": {
639                    "php-path": {
640                        "type": self.get_application_type(),
641                        "processes": {"spare": 0},
642                        "root": new_root,
643                    }
644                },
645            }
646        ), 'configure trailing slash'
647
648        assert self.get(url='/path/')['status'] == 200, 'uri with trailing /'
649
650        resp = self.get(url='/path?q=a')
651        assert resp['status'] == 301, 'uri without trailing /'
652        assert (
653            resp['headers']['Location'] == 'http://localhost:7080/path/?q=a'
654        ), 'Location with query string'
655
656        resp = self.get(
657            sock_type='unix',
658            addr=addr,
659            url='/path',
660            headers={'Host': 'foo', 'Connection': 'close'},
661        )
662        assert resp['status'] == 301, 'uri without trailing /'
663        assert (
664            resp['headers']['Location'] == 'http://foo/path/'
665        ), 'Location with custom Host over UDS'
666
667    def test_php_application_extension_check(self, temp_dir):
668        self.load('phpinfo')
669
670        assert self.get(url='/index.wrong')['status'] != 200, 'status'
671
672        new_root = temp_dir + "/php"
673        os.mkdir(new_root)
674        shutil.copy(option.test_dir + '/php/phpinfo/index.wrong', new_root)
675
676        assert 'success' in self.conf(
677            {
678                "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
679                "applications": {
680                    "phpinfo": {
681                        "type": self.get_application_type(),
682                        "processes": {"spare": 0},
683                        "root": new_root,
684                        "working_directory": new_root,
685                    }
686                },
687            }
688        ), 'configure new root'
689
690        resp = self.get()
691        assert str(resp['status']) + resp['body'] != '200', 'status new root'
692
693    def run_php_application_cwd_root_tests(self):
694        assert 'success' in self.conf_delete(
695            'applications/cwd/working_directory'
696        )
697
698        script_cwd = option.test_dir + '/php/cwd'
699
700        resp = self.get()
701        assert resp['status'] == 200, 'status ok'
702        assert resp['body'] == script_cwd, 'default cwd'
703
704        assert 'success' in self.conf(
705            '"' + option.test_dir + '"',
706            'applications/cwd/working_directory',
707        )
708
709        resp = self.get()
710        assert resp['status'] == 200, 'status ok'
711        assert resp['body'] == script_cwd, 'wdir cwd'
712
713        resp = self.get(url='/?chdir=/')
714        assert resp['status'] == 200, 'status ok'
715        assert resp['body'] == '/', 'cwd after chdir'
716
717        # cwd must be restored
718
719        resp = self.get()
720        assert resp['status'] == 200, 'status ok'
721        assert resp['body'] == script_cwd, 'cwd restored'
722
723        resp = self.get(url='/subdir/')
724        assert resp['body'] == script_cwd + '/subdir', 'cwd subdir'
725
726    def test_php_application_cwd_root(self):
727        self.load('cwd')
728        self.run_php_application_cwd_root_tests()
729
730    def test_php_application_cwd_opcache_disabled(self):
731        self.load('cwd')
732        self.set_opcache('cwd', '0')
733        self.run_php_application_cwd_root_tests()
734
735    def test_php_application_cwd_opcache_enabled(self):
736        self.load('cwd')
737        self.set_opcache('cwd', '1')
738        self.run_php_application_cwd_root_tests()
739
740    def run_php_application_cwd_script_tests(self):
741        self.load('cwd')
742
743        script_cwd = option.test_dir + '/php/cwd'
744
745        assert 'success' in self.conf_delete(
746            'applications/cwd/working_directory'
747        )
748
749        assert 'success' in self.conf('"index.php"', 'applications/cwd/script')
750
751        assert self.get()['body'] == script_cwd, 'default cwd'
752
753        assert self.get(url='/?chdir=/')['body'] == '/', 'cwd after chdir'
754
755        # cwd must be restored
756        assert self.get()['body'] == script_cwd, 'cwd restored'
757
758    def test_php_application_cwd_script(self):
759        self.load('cwd')
760        self.run_php_application_cwd_script_tests()
761
762    def test_php_application_cwd_script_opcache_disabled(self):
763        self.load('cwd')
764        self.set_opcache('cwd', '0')
765        self.run_php_application_cwd_script_tests()
766
767    def test_php_application_cwd_script_opcache_enabled(self):
768        self.load('cwd')
769        self.set_opcache('cwd', '1')
770        self.run_php_application_cwd_script_tests()
771
772    def test_php_application_path_relative(self):
773        self.load('open')
774
775        assert self.get()['body'] == 'test', 'relative path'
776
777        assert (
778            self.get(url='/?chdir=/')['body'] != 'test'
779        ), 'relative path w/ chdir'
780
781        assert self.get()['body'] == 'test', 'relative path 2'
782
783    def test_php_application_shared_opcache(self):
784        self.load('opcache', limits={'requests': 1})
785
786        r = self.check_opcache()
787        pid = r['headers']['X-Pid']
788        assert r['headers']['X-Cached'] == '0', 'not cached'
789
790        r = self.get()
791
792        assert r['headers']['X-Pid'] != pid, 'new instance'
793        assert r['headers']['X-Cached'] == '1', 'cached'
794
795    def test_php_application_opcache_preload_chdir(self, temp_dir):
796        self.load('opcache')
797
798        self.check_opcache()
799
800        self.set_preload('chdir.php')
801
802        assert self.get()['headers']['X-Cached'] == '0', 'not cached'
803        assert self.get()['headers']['X-Cached'] == '1', 'cached'
804
805    def test_php_application_opcache_preload_ffr(self, temp_dir):
806        self.load('opcache')
807
808        self.check_opcache()
809
810        self.set_preload('fastcgi_finish_request.php')
811
812        assert self.get()['headers']['X-Cached'] == '0', 'not cached'
813        assert self.get()['headers']['X-Cached'] == '1', 'cached'
814