File Coverage

blib/lib/Test/Nginx/Socket.pm
Criterion Covered Total %
statement 336 1328 25.3
branch 114 728 15.6
condition 26 243 10.7
subroutine 35 57 61.4
pod 1 33 3.0
total 512 2389 21.4


line stmt bran cond sub pod time code
1             package Test::Nginx::Socket;
2              
3 5     5   32119 use lib 'lib';
  5         1300  
  5         23  
4 5     5   607 use lib 'inc';
  5         20  
  5         22  
5              
6 5     5   452 use v5.10.1;
  5         17  
7 5     5   2494 use Test::Base -Base;
  5         17  
  5         30  
8 5     5   65  
  5     5   10  
  5     5   197  
  5     5   27  
  5         8  
  5         393  
  5         29  
  5         9  
  5         100  
  5         18  
  5         11  
  5         349  
9             our $VERSION = '0.32';
10              
11 5     5   3417 use POSIX qw( SIGQUIT SIGKILL SIGTERM SIGHUP );
  5         38919  
  5         34  
12 5     5   11820 use Encode;
  5         91411  
  5         654  
13             #use Data::Dumper;
14 5     5   46 use Time::HiRes qw(sleep time);
  5         11  
  5         52  
15 5     5   3357 use Test::LongString;
  5         192  
  5         31  
16 5     5   3756 use List::MoreUtils qw( any );
  5         37034  
  5         37  
17 5     5   7589 use List::Util qw( sum min );
  5         10  
  5         517  
18 5     5   3392 use IO::Select ();
  5         9911  
  5         175  
19 5     5   5148 use File::Temp qw( tempfile );
  5         140954  
  5         631  
20 5     5   50 use Digest::MD5 ();
  5         11  
  5         95  
21 5     5   3211 use Digest::SHA ();
  5         19897  
  5         205  
22 5     5   42 use POSIX ":sys_wait_h";
  5         11  
  5         71  
23              
24 5     5   5683 use Test::Nginx::Util;
  5         28  
  5         2330  
25 5     5   4919 use JSON::PP;
  5         91947  
  5         593  
26              
27             #use Smart::Comments::JSON '###';
28 5     5   50 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
  5         11  
  5         379  
29 5     5   31 use POSIX qw(EAGAIN);
  5         10  
  5         42  
30 5     5   497 use IO::Socket;
  5         10  
  5         54  
31              
32             #our ($PrevRequest, $PrevConfig);
33              
34             our @EXPORT = qw( env_to_nginx is_str plan run_tests run_test
35             repeat_each config_preamble worker_connections
36             master_process_enabled
37             no_long_string workers master_on master_off
38             log_level no_shuffle no_root_location use_hup
39             server_name
40             server_addr server_root html_dir server_port server_port_for_client
41             timeout no_nginx_manager check_accum_error_log
42             add_block_preprocessor bail_out add_test_cleanup_handler add_cleanup_handler
43             add_response_body_check
44             );
45              
46             our $CheckLeakCount = $ENV{TEST_NGINX_CHECK_LEAK_COUNT} // 100;
47             our $UseHttp2 = $Test::Nginx::Util::UseHttp2;
48             our $TotalConnectingTimeouts = 0;
49             our $PrevNginxPid;
50             our $UseValgrind = $ENV{TEST_NGINX_USE_VALGRIND};
51              
52             sub send_request ($$$$@);
53             sub send_http_req_by_curl ($$$);
54              
55             sub run_filter_helper($$$);
56             sub run_test_helper ($$);
57             sub test_stap ($$);
58              
59             sub error_event_handler ($);
60             sub read_event_handler ($);
61             sub write_event_handler ($);
62             sub transform_response_body ($$$);
63             sub check_response_body ($$$$$$);
64             sub fmt_str ($);
65             sub gen_ab_cmd_from_req ($$@);
66             sub gen_curl_cmd_from_req ($$);
67             sub get_linear_regression_slope ($);
68             sub value_contains ($$);
69              
70             $RunTestHelper = \&run_test_helper;
71             $CheckErrorLog = \&check_error_log;
72             $CheckShutdownErrorLog = \&check_shutdown_error_log;
73              
74             sub set_http_config_filter ($) {
75 0     0 0 0 $FilterHttpConfig = shift;
76             }
77              
78             our @ResponseBodyChecks;
79              
80             sub add_response_body_check ($) {
81 0     0 1 0 push @ResponseBodyChecks, shift;
82             }
83              
84             # This will parse a "request"" string. The expected format is:
85             # - One line for the HTTP verb (POST, GET, etc.) plus optional relative URL
86             # (default is /) plus optional HTTP version (default is HTTP/1.1).
87             # - More lines considered as the body of the request.
88             # Most people don't care about headers and this is enough.
89             #
90             # This function will return a reference to a hash with the parsed elements
91             # plus information on the parsing itself like "how many white spaces were
92             # skipped before the VERB" (skipped_before_method), "was the version provided"
93             # (http_ver_size = 0).
94             sub parse_request ($$) {
95 22     22 0 99 my ( $name, $rrequest ) = @_;
96 22         224 open my $in, '<', $rrequest;
97 22         74 my $first = <$in>;
98 22 50       51 if ( !$first ) {
99 0         0 bail_out("$name - Request line should be non-empty");
100             }
101             #$first =~ s/^\s+|\s+$//gs;
102 22         79 my ($before_meth, $meth, $after_meth);
103 22         0 my ($rel_url, $rel_url_size, $after_rel_url);
104 22         0 my ($http_ver, $http_ver_size, $after_http_ver);
105 22         0 my $end_line_size;
106 22 50       215 if ($first =~ /^(\s*)(\S+)( *)((\S+)( *))?((\S+)( *))?(\s*)$/) {
107 22 50       75 $before_meth = defined $1 ? length($1) : undef;
108 22         43 $meth = $2;
109 22 50       56 $after_meth = defined $3 ? length($3) : undef;
110 22         40 $rel_url = $5;
111 22 100       40 $rel_url_size = defined $5 ? length($5) : undef;
112 22 100       44 $after_rel_url = defined $6 ? length($6) : undef;
113 22         36 $http_ver = $8;
114 22 100       36 if (!defined $8) {
115 18         28 $http_ver_size = undef;
116             } else {
117 4 50       7 $http_ver_size = defined $8 ? length($8) : undef;
118             }
119 22 100       39 if (!defined $9) {
120 18         22 $after_http_ver = undef;
121             } else {
122 4 50       7 $after_http_ver = defined $9 ? length($9) : undef;
123             }
124 22 50       37 $end_line_size = defined $10 ? length($10) : undef;
125             } else {
126 0         0 bail_out("$name - Request line is not valid. Should be 'meth [url [version]]' but got \"$first\".");
127             }
128 22 100       42 if ( !defined $rel_url ) {
129 1         4 $rel_url = '/';
130 1         1 $rel_url_size = 0;
131 1         2 $after_rel_url = 0;
132             }
133 22 100       33 if ( !defined $http_ver ) {
134 18         23 $http_ver = 'HTTP/1.1';
135 18         23 $http_ver_size = 0;
136 18         46 $after_http_ver = 0;
137             }
138              
139             #my $url = "http://localhost:$ServerPortForClient" . $rel_url;
140              
141 22         26 my $content = do { local $/; <$in> };
  22         66  
  22         76  
142 22         24 my $content_size;
143 22 100       59 if ( !defined $content ) {
144 16         41 $content = "";
145 16         18 $content_size = 0;
146             } else {
147 6         12 $content_size = length($content);
148             }
149              
150             #warn Dumper($content);
151              
152 22         46 close $in;
153              
154             return {
155 22         332 method => $meth,
156             url => $rel_url,
157             content => $content,
158             http_ver => $http_ver,
159             skipped_before_method => $before_meth,
160             method_size => length($meth),
161             skipped_after_method => $after_meth,
162             url_size => $rel_url_size,
163             skipped_after_url => $after_rel_url,
164             http_ver_size => $http_ver_size,
165             skipped_after_http_ver => $after_http_ver + $end_line_size,
166             content_size => $content_size,
167             };
168             }
169              
170             # From a parsed request, builds the "moves" to apply to the original request
171             # to transform it (e.g. add missing version). Elements of the returned array
172             # are of 2 types:
173             # - d : number of characters to remove.
174             # - s_* : number of characters (s_s) to replace by value (s_v).
175             sub get_moves($) {
176 14     14 0 23 my ($parsed_req) = @_;
177             return ({d => $parsed_req->{skipped_before_method}},
178             {s_s => $parsed_req->{method_size},
179             s_v => $parsed_req->{method}},
180             {d => $parsed_req->{skipped_after_method}},
181             {s_s => $parsed_req->{url_size},
182             s_v => $parsed_req->{url}},
183             {d => $parsed_req->{skipped_after_url}},
184             {s_s => $parsed_req->{http_ver_size},
185             s_v => $parsed_req->{http_ver}},
186             {d => $parsed_req->{skipped_after_http_ver}},
187             {s_s => 0,
188             s_v => $parsed_req->{headers}},
189             {s_s => $parsed_req->{content_size},
190             s_v => $parsed_req->{content}}
191 14         204 );
192             }
193              
194             # Apply moves (see above) to an array of packets that correspond to a request.
195             # The use of this function is explained in the build_request_from_packets
196             # function.
197             sub apply_moves($$) {
198 20     20 0 71 my ($r_packet, $r_move) = @_;
199 20         30 my $current_packet = shift @$r_packet;
200 20         31 my $current_move = shift @$r_move;
201 20         23 my $in_packet_cursor = 0;
202 20         30 my @result = ();
203 20         43 while (defined $current_packet) {
204 165 100       259 if (!defined $current_move) {
    100          
205 20         36 push @result, $current_packet;
206 20         27 $current_packet = shift @$r_packet;
207 20         36 $in_packet_cursor = 0;
208             } elsif (defined $current_move->{d}) {
209             # Remove stuff from packet
210 61 50       96 if ($current_move->{d} > length($current_packet) - $in_packet_cursor) {
211             # Eat up what is left of packet.
212 0         0 $current_move->{d} -= length($current_packet) - $in_packet_cursor;
213 0 0       0 if ($in_packet_cursor > 0) {
214             # Something in packet from previous iteration.
215 0         0 push @result, $current_packet;
216             }
217 0         0 $current_packet = shift @$r_packet;
218 0         0 $in_packet_cursor = 0;
219             } else {
220             # Remove from current point in current packet
221 61         91 substr($current_packet, $in_packet_cursor, $current_move->{d}) = '';
222 61         118 $current_move = shift @$r_move;
223             }
224             } else {
225             # Substitute stuff
226 84 100       145 if ($current_move->{s_s} > length($current_packet) - $in_packet_cursor) {
227             # {s_s=>3, s_v=>GET} on ['GE', 'T /foo']
228 5         9 $current_move->{s_s} -= length($current_packet) - $in_packet_cursor;
229 5         16 substr($current_packet, $in_packet_cursor) = substr($current_move->{s_v}, 0, length($current_packet) - $in_packet_cursor);
230 5         10 push @result, $current_packet;
231 5         12 $current_move->{s_v} = substr($current_move->{s_v}, length($current_packet) - $in_packet_cursor);
232 5         8 $current_packet = shift @$r_packet;
233 5         10 $in_packet_cursor = 0;
234             } else {
235 79         141 substr($current_packet, $in_packet_cursor, $current_move->{s_s}) = $current_move->{s_v};
236 79         82 $in_packet_cursor += length($current_move->{s_v});
237 79         201 $current_move = shift @$r_move;
238             }
239             }
240             }
241 20         103 return \@result;
242             }
243             # Given a request as an array of packets, will parse it, append the appropriate
244             # headers and return another array of packets.
245             # The function implemented here can be high-level summarized as:
246             # 1 - Concatenate all packets to obtain a string representation of request.
247             # 2 - Parse the string representation
248             # 3 - Get the "moves" from the parsing
249             # 4 - Apply the "moves" to the packets.
250             sub build_request_from_packets($$$$$) {
251 14     14 0 33 my ( $name, $more_headers, $is_chunked, $conn_header, $request_packets ) = @_;
252             # Concatenate packets as a string
253 14         18 my $parsable_request = '';
254 14         19 my @packet_length;
255 14         22 for my $one_packet (@$request_packets) {
256 17         28 $parsable_request .= $one_packet;
257 17         46 push @packet_length, length($one_packet);
258             }
259             # Parse the string representation.
260 14         157 my $parsed_req = parse_request( $name, \$parsable_request );
261              
262             # Append headers
263 14         23 my $len_header = '';
264 14 100 33     93 if ( !$is_chunked
      66        
      66        
265             && defined $parsed_req->{content}
266             && $parsed_req->{content} ne ''
267             && $more_headers !~ /(?:^|\n)Content-Length:/ )
268             {
269 3         23 $parsed_req->{content} =~ s/^\s+|\s+$//gs;
270              
271             $len_header .=
272 3         9 "Content-Length: " . length( $parsed_req->{content} ) . "\r\n";
273             }
274              
275 14         21 $more_headers =~ s/(?
276              
277 14         15 my $headers = '';
278              
279 14 50       27 if ($more_headers !~ /(?:^|\n)Host:/msi) {
280 14         33 $headers .= "Host: $ServerName\r\n";
281             }
282              
283 14 50       29 if ($more_headers !~ /(?:^|\n)Connection/msi) {
284 14         33 $headers .= "Connection: $conn_header\r\n";
285             }
286              
287 14         21 $headers .= "$more_headers$len_header\r\n";
288              
289 14         19 $parsed_req->{method} .= ' ';
290 14         21 $parsed_req->{url} .= ' ';
291 14         21 $parsed_req->{http_ver} .= "\r\n";
292 14         28 $parsed_req->{headers} = $headers;
293              
294             # Get the moves from parsing
295 14         27 my @elements_moves = get_moves($parsed_req);
296             # Apply them to the packets.
297 14         32 return apply_moves($request_packets, \@elements_moves);
298             }
299              
300             sub parse_more_headers ($) {
301 14     14 0 25 my ($in) = @_;
302 14         30 my @headers = split /\n+/, $in;
303 14         17 my $is_chunked;
304 14         19 my $out = '';
305 14         23 for my $header (@headers) {
306 0 0       0 next if $header =~ /^\s*\#/;
307             #warn "HEADER: $header";
308 0         0 my ($key, $val) = split /:\s*/, $header, 2;
309 0 0       0 if (!defined $val) {
310 0         0 $val = '';
311             }
312 0 0 0     0 if (lc($key) eq 'transfer-encoding' and $val eq 'chunked') {
313 0         0 $is_chunked = 1;
314             }
315              
316             #warn "[$key, $val]\n";
317 0         0 $out .= "$key: $val\r\n";
318             }
319 14         35 return $out, $is_chunked;
320             }
321              
322             # Returns an array of array of hashes from the block. Each element of
323             # the first-level array is a request.
324             # Each request is an array of the "packets" to be sent. Each packet is a
325             # string to send, with an (optionnal) delay before sending it.
326             # This function parses (and therefore defines the syntax) of "request*"
327             # sections. See documentation for supported syntax.
328             sub get_req_from_block ($) {
329 9     9 0 36 my ($block) = @_;
330 9         35 my $name = $block->name;
331              
332 9         15 my @req_list = ();
333              
334 9 100       123 if (defined $block->raw_request) {
335              
336             # Should be deprecated.
337 2 100 66     6 if (ref $block->raw_request && ref $block->raw_request eq 'ARRAY') {
338              
339             # User already provided an array. So, he/she specified where the
340             # data should be split. This allows for backward compatibility but
341             # should use request with arrays as it provides the same functionnality.
342 1         3 my @rr_list = ();
343 1         2 for my $elt (@{ $block->raw_request }) {
  1         3  
344 3         9 push @rr_list, {value => $elt};
345             }
346 1         4 push @req_list, \@rr_list;
347              
348             } else {
349 1         4 push @req_list, [{value => $block->raw_request}];
350             }
351              
352             } else {
353 7         12 my $request;
354 7 50       48 if (defined $block->request_eval) {
355              
356 0         0 diag "$name - request_eval DEPRECATED. Use request eval instead.";
357 0         0 $request = eval $block->request_eval;
358 0 0       0 if ($@) {
359 0         0 warn $@;
360             }
361              
362             } else {
363 7         18 $request = $block->request;
364 7 100       18 if (defined $request) {
365 6         72 while ($request =~ s/^\s*\#[^\n]+\s+|^\s+//gs) {
366             # do nothing
367             }
368             }
369             #warn "my req: $request";
370             }
371              
372 7   50     58 my $more_headers = $block->more_headers || '';
373              
374 7 100       21 if ( $block->pipelined_requests ) {
375 1         5 my $reqs = $block->pipelined_requests;
376 1 50 33     9 if (!ref $reqs || ref $reqs ne 'ARRAY') {
377 0         0 bail_out(
378             "$name - invalid entries in --- pipelined_requests");
379             }
380 1         3 my $i = 0;
381 1         2 my $prq = "";
382 1         3 for my $request (@$reqs) {
383 2         18 $request = expand_env_in_text $request, $name, $Test::Nginx::Util::RandPorts;
384              
385 2         4 my $conn_type;
386 2 100       7 if ($i == @$reqs - 1) {
387 1         4 $conn_type = 'close';
388              
389             } else {
390 1         3 $conn_type = 'keep-alive';
391             }
392              
393 2         3 my ($hdr, $is_chunked);
394 2 50       5 if (ref $more_headers eq 'ARRAY') {
395             #warn "Found ", scalar @$more_headers, " entries in --- more_headers.";
396 0         0 $hdr = $more_headers->[$i];
397 0 0       0 if (!defined $hdr) {
398 0         0 bail_out("--- more_headers lacks data for the $i pipelined request");
399             }
400 0         0 ($hdr, $is_chunked) = parse_more_headers($hdr);
401             #warn "more headers: $hdr";
402              
403             } else {
404 2         7 ($hdr, $is_chunked) = parse_more_headers($more_headers);
405             }
406              
407 2         26 my $r_br = build_request_from_packets($name, $hdr,
408             $is_chunked, $conn_type,
409             [$request] );
410 2         8 $prq .= $$r_br[0];
411 2         179 $i++;
412             }
413 1         5 push @req_list, [{value =>$prq}];
414              
415             } else {
416 6         10 my ($is_chunked, $hdr);
417              
418             # request section.
419 6 100       23 if (!ref $request) {
    50          
420 2 50       6 if (ref $more_headers eq 'ARRAY') {
421             #warn "Found ", scalar @$more_headers, " entries in --- more_headers.";
422 0         0 $hdr = $more_headers->[0];
423 0 0       0 if (!defined $hdr) {
424 0         0 bail_out("--- more_headers lacks data for the request");
425             }
426 0         0 ($hdr, $is_chunked) = parse_more_headers($hdr);
427             #warn "more headers: $hdr";
428              
429             } else {
430 2         6 ($hdr, $is_chunked) = parse_more_headers($more_headers);
431             }
432              
433             # One request and it is a good old string.
434 2         9 my $r_br = build_request_from_packets($name, $hdr,
435             $is_chunked, 'close',
436             [$request] );
437 2         13 push @req_list, [{value => $$r_br[0]}];
438              
439             } elsif (ref $request eq 'ARRAY') {
440             # A bunch of requests...
441 4         5 my $i = 0;
442 4         10 for my $one_req (@$request) {
443              
444 10 50       22 if (ref $more_headers eq 'ARRAY') {
445             #warn "Found ", scalar @$more_headers, " entries in --- more_headers.";
446 0         0 $hdr = $more_headers->[$i];
447 0 0       0 if (!defined $hdr) {
448 0         0 bail_out("--- more_headers lacks data for the "
449             . "${i}th request");
450             }
451 0         0 ($hdr, $is_chunked) = parse_more_headers($hdr);
452             #warn "more headers: $hdr";
453              
454             } else {
455 10         20 ($hdr, $is_chunked) = parse_more_headers($more_headers);
456             }
457              
458 10 100       19 if (!ref $one_req) {
    50          
459             # This request is a good old string.
460 7         17 my $r_br = build_request_from_packets($name, $hdr,
461             $is_chunked, 'close',
462             [$one_req] );
463 7         22 push @req_list, [{value => $$r_br[0]}];
464              
465             } elsif (ref $one_req eq 'ARRAY') {
466             # Request expressed as a serie of packets
467 3         6 my @packet_array = ();
468 3         6 for my $one_packet (@$one_req) {
469 6 100       17 if (!ref $one_packet) {
    50          
470             # Packet is a string.
471 3         6 push @packet_array, $one_packet;
472             } elsif (ref $one_packet eq 'HASH'){
473             # Packet is a hash with a value...
474 3         8 push @packet_array, $one_packet->{value};
475             } else {
476 0         0 bail_out "$name - Invalid syntax. $one_packet should be a string or hash with value.";
477             }
478             }
479              
480 3         8 my $transformed_packet_array = build_request_from_packets($name, $hdr,
481             $is_chunked, 'close',
482             \@packet_array);
483 3         7 my @transformed_req = ();
484 3         4 my $idx = 0;
485 3         6 for my $one_transformed_packet (@$transformed_packet_array) {
486 6 100       48 if (!ref $$one_req[$idx]) {
487 3         9 push @transformed_req, {value => $one_transformed_packet};
488             } else {
489             # Is a HASH (checked above as $one_packet)
490 3         9 $$one_req[$idx]->{value} = $one_transformed_packet;
491 3         5 push @transformed_req, $$one_req[$idx];
492             }
493 6         11 $idx++;
494             }
495 3         9 push @req_list, \@transformed_req;
496              
497             } else {
498 0         0 bail_out "$name - Invalid syntax. $one_req should be a string or an array of packets.";
499             }
500              
501 10         48 $i++;
502             }
503              
504             } else {
505 0         0 bail_out(
506             "$name - invalid ---request : MUST be string or array of requests");
507             }
508             }
509              
510             }
511 9         125 return \@req_list;
512             }
513              
514             sub quote_sh_args ($) {
515 0     0 0 0 my ($args) = @_;
516 0         0 for my $arg (@$args) {
517 0 0       0 if ($arg =~ m{^[- "&%;,|?*.+=\w:/()]*$}) {
518 0 0       0 if ($arg =~ /[ "&%;,|?*()]/) {
519 0         0 $arg = "'$arg'";
520             }
521 0         0 next;
522             }
523 0         0 $arg =~ s/\\/\\\\/g;
524 0         0 $arg =~ s/'/\\'/g;
525 0         0 $arg =~ s/\n/\\n/g;
526 0         0 $arg =~ s/\r/\\r/g;
527 0         0 $arg =~ s/\t/\\t/g;
528 0         0 $arg = "\$'$arg'";
529             }
530 0         0 return "@$args";
531             }
532              
533             sub run_filter_helper($$$) {
534 20     20 0 23 my ($block, $filter, $content) = @_;
535              
536 20         26 my $name = $block->name;
537              
538 20 100 66     44 if (ref $filter && ref $filter eq 'CODE') {
    50          
539 4         12 $content = $filter->($content);
540              
541             } elsif (!ref $filter) {
542              
543 16         20 for ($filter) {
544 16 100       46 if ($_ eq 'md5_hex') {
    100          
    100          
    50          
    0          
    0          
    0          
545 3         16 $content = Digest::MD5::md5_hex($content);
546             } elsif ($_ eq 'sha1_hex') {
547 4         205 $content = Digest::SHA::sha1_hex($content);
548             } elsif ($_ eq 'uc') {
549 6         13 $content = uc($content);
550             } elsif ($_ eq 'lc') {
551 3         4 $content = lc($content);
552             } elsif ($_ eq 'ucfirst') {
553 0         0 $content = ucfirst($content);
554             } elsif ($_ eq 'lcfirst') {
555 0         0 $content = lcfirst($content);
556             } elsif ($_ eq 'length') {
557 0         0 $content = length($content);
558             } else {
559 0         0 bail_out("$name - unknown filter, \"$filter\", "
560             . "specified in the --- response_body_filters section");
561             }
562             }
563              
564             } else {
565 0         0 bail_out("$name - the --- response_body_filters section "
566             . "only supports subroutine reference values and string values");
567             }
568              
569 20         44 return $content;
570             }
571              
572             sub run_test_helper ($$) {
573 0     0 0 0 my ($block, $dry_run, $repeated_req_idx) = @_;
574              
575             #warn "repeated req idx: $repeated_req_idx";
576              
577 0         0 my $name = $block->name;
578              
579 0         0 my $r_req_list = get_req_from_block($block);
580              
581 0 0       0 if ( $#$r_req_list < 0 ) {
582 0         0 bail_out("$name - request empty");
583             }
584              
585 0 0       0 if (defined $block->curl) {
586 0         0 my $req = $r_req_list->[0];
587 0         0 my $cmd = gen_curl_cmd_from_req($block, $req);
588 0         0 warn "# ", quote_sh_args($cmd), "\n";
589             }
590              
591 0 0       0 if ($CheckLeak) {
592 0         0 $dry_run = "the \"check leak\" testing mode";
593             }
594              
595 0 0       0 if ($Benchmark) {
596 0         0 $dry_run = "the \"benchmark\" testing mode";
597             }
598              
599 0 0 0     0 if ($Benchmark && !defined $block->no_check_leak) {
600 0         0 warn "$name\n";
601              
602 0         0 my $req = $r_req_list->[0];
603 0         0 my ($nreqs, $concur);
604 0 0       0 if ($Benchmark =~ /^\s*(\d+)(?:\s+(\d+))?\s*$/) {
605 0         0 ($nreqs, $concur) = ($1, $2);
606             }
607              
608 0 0       0 if ($BenchmarkWarmup) {
609 0         0 my $cmd = gen_ab_cmd_from_req($block, $req, $BenchmarkWarmup, $concur);
610 0         0 warn "Warming up with $BenchmarkWarmup requests...\n";
611 0         0 system @$cmd;
612             }
613              
614 0         0 my $cmd = gen_ab_cmd_from_req($block, $req, $nreqs, $concur);
615 0         0 $cmd = quote_sh_args($cmd);
616              
617 0         0 warn "$cmd\n";
618 0         0 system "unbuffer $cmd > /dev/stderr";
619             }
620              
621 0 0 0     0 if ($CheckLeak && !defined $block->no_check_leak) {
622 0         0 warn "$name\n";
623              
624 0         0 my $req = $r_req_list->[0];
625 0         0 my $cmd = gen_ab_cmd_from_req($block, $req);
626              
627             # start a sub-process to run ab or weighttp
628 0         0 my $pid = fork();
629 0 0       0 if (!defined $pid) {
    0          
630 0         0 bail_out("$name - fork() failed: $!");
631              
632             } elsif ($pid == 0) {
633             # child process
634 0         0 exec @$cmd;
635              
636             } else {
637             # main process
638              
639 0         0 $Test::Nginx::Util::ChildPid = $pid;
640              
641 0         0 sleep(1);
642 0         0 my $ngx_pid = get_pid_from_pidfile($name);
643 0 0 0     0 if ($PrevNginxPid && $ngx_pid) {
644 0         0 my $i = 0;
645 0         0 while ($ngx_pid == $PrevNginxPid) {
646 0         0 sleep 0.01;
647 0         0 $ngx_pid = get_pid_from_pidfile($name);
648 0 0       0 if (++$i > 1000) {
649 0         0 bail_out("nginx cannot be started");
650             }
651             }
652             }
653 0         0 $PrevNginxPid = $ngx_pid;
654 0         0 my @rss_list;
655 0         0 for (my $i = 0; $i < $CheckLeakCount; $i++) {
656 0         0 sleep 0.02;
657 0         0 my $out = `ps -eo pid,rss|grep $ngx_pid`;
658 0 0 0     0 if ($? != 0 && !is_running($ngx_pid)) {
659 0 0       0 if (is_running($pid)) {
660 0         0 kill(SIGKILL, $pid);
661 0         0 waitpid($pid, 0);
662             }
663              
664 0         0 my $tb = Test::More->builder;
665 0         0 $tb->no_ending(1);
666              
667 0         0 Test::More::fail("$name - the nginx process $ngx_pid is gone");
668 0         0 last;
669             }
670              
671 0         0 my @lines = grep { $_->[0] eq $ngx_pid }
672 0         0 map { s/^\s+|\s+$//g; [ split /\s+/, $_ ] }
  0         0  
  0         0  
673             split /\n/, $out;
674              
675 0 0       0 if (@lines == 0) {
676 0         0 last;
677             }
678              
679 0 0       0 if (@lines > 1) {
680 0         0 warn "Bad ps output: \"$out\"\n";
681 0         0 next;
682             }
683              
684 0         0 my $ln = shift @lines;
685 0         0 push @rss_list, $ln->[1];
686             }
687              
688             #if ($Test::Nginx::Util::Verbose) {
689 0         0 warn "LeakTest: [@rss_list]\n";
690             #}
691              
692 0 0       0 if (@rss_list == 0) {
693 0         0 warn "LeakTest: k=N/A\n";
694              
695             } else {
696 0         0 my $k = get_linear_regression_slope(\@rss_list);
697 0         0 warn "LeakTest: k=$k\n";
698             #$k = get_linear_regression_slope([1 .. 100]);
699             #warn "K = $k (1 expected)\n";
700             #$k = get_linear_regression_slope([map { $_ * 2 } 1 .. 100]);
701             #warn "K = $k (2 expected)\n";
702             }
703              
704 0 0       0 if (is_running($pid)) {
705 0         0 kill(SIGKILL, $pid);
706 0         0 waitpid($pid, 0);
707             }
708             }
709             }
710              
711             #warn "request: $req\n";
712              
713 0         0 my $timeout = parse_time($block->timeout);
714 0 0       0 if ( !defined $timeout ) {
715 0         0 $timeout = timeout();
716             }
717              
718 0         0 my $res;
719 0         0 my $req_idx = 0;
720 0         0 my ($n, $need_array);
721              
722 0         0 for my $one_req (@$r_req_list) {
723 0         0 my ($raw_resp, $head_req);
724              
725 0 0       0 if ($dry_run) {
726 0         0 $raw_resp = "200 OK HTTP/1.0\r\nContent-Length: 0\r\n\r\n";
727              
728             } else {
729 0         0 ($raw_resp, $head_req) = send_request( $one_req, $block->raw_request_middle_delay,
730             $timeout, $block );
731             }
732              
733             #warn "raw resonse: [$raw_resp]\n";
734              
735 0 0       0 if ($block->pipelined_requests) {
736 0         0 $n = @{ $block->pipelined_requests };
  0         0  
737 0         0 $need_array = 1;
738              
739             } else {
740 0         0 $need_array = $#$r_req_list > 0;
741             }
742              
743             again:
744              
745 0 0       0 if ($Test::Nginx::Util::Verbose) {
746 0         0 warn "!!! resp: [$raw_resp]";
747             }
748              
749 0 0       0 if (!defined $raw_resp) {
750 0         0 $raw_resp = '';
751             }
752              
753 0         0 my ( $raw_headers, $left );
754              
755 0 0       0 if (!defined $block->ignore_response) {
756              
757 0 0       0 if ($Test::Nginx::Util::Verbose) {
758 0         0 warn "parse response\n";
759             }
760              
761 0 0       0 if (defined $block->http09) {
762 0         0 $res = HTTP::Response->new(200, "OK", [], $raw_resp);
763 0         0 $raw_headers = '';
764              
765             } else {
766 0         0 ( $res, $raw_headers, $left ) = parse_response( $name, $raw_resp, $head_req );
767             }
768             }
769              
770 0 0       0 if (!$n) {
771 0 0       0 if ($left) {
772 0         0 my $name = $block->name;
773 0         0 $left =~ s/([\0-\037\200-\377])/sprintf('\x{%02x}',ord $1)/eg;
  0         0  
774 0         0 warn "WARNING: $name - unexpected extra bytes after last chunk in ",
775             "response: \"$left\"\n";
776             }
777              
778             } else {
779 0         0 $raw_resp = $left;
780 0         0 $n--;
781             }
782              
783 0 0       0 if (!defined $block->ignore_response) {
784 0         0 check_error_code($block, $res, $dry_run, $req_idx, $need_array);
785 0         0 check_raw_response_headers($block, $raw_headers, $dry_run, $req_idx, $need_array);
786 0         0 check_response_headers($block, $res, $raw_headers, $dry_run, $req_idx, $need_array);
787 0         0 transform_response_body($block, $res, $req_idx);
788 0         0 check_response_body($block, $res, $dry_run, $req_idx, $repeated_req_idx, $need_array);
789             }
790              
791 0 0 0     0 if ($n || $req_idx < @$r_req_list - 1) {
792 0 0       0 if ($block->wait) {
793 0         0 sleep($block->wait);
794             }
795              
796 0         0 check_error_log($block, $res, $dry_run, $repeated_req_idx, $need_array);
797              
798 0 0       0 if (!defined $block->ignore_response) {
799 0         0 check_access_log($block, $dry_run, $repeated_req_idx);
800             }
801             }
802              
803 0         0 $req_idx++;
804              
805 0 0       0 if ($n) {
806 0         0 goto again;
807             }
808             }
809              
810 0 0       0 if ($block->wait) {
811 0         0 sleep($block->wait);
812             }
813              
814 0 0       0 if ($Test::Nginx::Util::Verbose) {
815 0         0 warn "Testing stap...\n";
816             }
817              
818 0         0 test_stap($block, $dry_run);
819              
820 0         0 check_error_log($block, $res, $dry_run, $repeated_req_idx, $need_array);
821              
822 0 0       0 if (!defined $block->ignore_response) {
823 0         0 check_access_log($block, $dry_run, $repeated_req_idx);
824             }
825             }
826              
827              
828             sub test_stap ($$) {
829 0     0 0 0 my ($block, $dry_run) = @_;
830 0 0       0 return if !$block->{stap};
831              
832 0         0 my $name = $block->name;
833              
834 0         0 my $reason;
835              
836 0 0       0 if ($dry_run) {
837 0         0 $reason = "the lack of directive $dry_run";
838             }
839              
840 0 0       0 if (!$UseStap) {
841 0         0 $dry_run = 1;
842 0   0     0 $reason ||= "env TEST_NGINX_USE_STAP is not set";
843             }
844              
845 0         0 my $fname = stap_out_fname();
846              
847 0 0 0     0 if ($fname && ($fname eq '/dev/stdout' || $fname eq '/dev/stderr')) {
      0        
848 0         0 $dry_run = 1;
849 0   0     0 $reason ||= "TEST_NGINX_TAP_OUT is set to $fname";
850             }
851              
852 0         0 my $stap_out = $block->stap_out;
853 0         0 my $stap_out_like = $block->stap_out_like;
854 0         0 my $stap_out_unlike = $block->stap_out_unlike;
855              
856             SKIP: {
857 0 0       0 skip "$name - stap_out - tests skipped due to $reason", 1 if $dry_run;
  0         0  
858              
859 0         0 my $fh = stap_out_fh();
860 0 0       0 if (!$fh) {
861 0         0 bail_out("no stap output file handle found");
862             }
863              
864 0         0 my $out = '';
865 0         0 for (1..2) {
866 0 0       0 if (sleep_time() < 0.2) {
867 0         0 sleep 0.2;
868              
869             } else {
870 0         0 sleep sleep_time();
871             }
872              
873 0         0 while (<$fh>) {
874 0         0 $out .= $_;
875             }
876              
877 0 0       0 if ($out) {
878 0         0 last;
879             }
880             }
881              
882 0 0       0 if ($Test::Nginx::Util::Verbose) {
883 0         0 warn "stap out: $out\n";
884             }
885              
886 0 0       0 if (defined $stap_out) {
887 0 0       0 if ($NoLongString) {
888 0         0 is($out, $block->stap_out, "$name - stap output expected");
889             } else {
890 0         0 is_string($out, $block->stap_out, "$name - stap output expected");
891             }
892             }
893              
894 0 0       0 if (defined $stap_out_like) {
895 0   0     0 like($out || '', qr/$stap_out_like/sm,
896             "$name - stap output should match the pattern");
897             }
898              
899 0 0       0 if (defined $stap_out_unlike) {
900 0   0     0 unlike($out || '', qr/$stap_out_unlike/sm,
901             "$name - stap output should not match the pattern");
902             }
903             }
904             }
905              
906              
907             # Helper function to retrieve a "check" (e.g. error_code) section. This also
908             # checks that tests with arrays of requests are arrays themselves.
909             sub get_indexed_value($$$$) {
910 16     16 0 35 my ($name, $value, $req_idx, $need_array) = @_;
911 16 100       23 if ($need_array) {
912 6 50 33     24 if (ref $value && ref $value eq 'ARRAY') {
913 6         14 return $$value[$req_idx];
914             }
915              
916 0         0 bail_out("$name - You asked for many requests, the expected results should be arrays as well.");
917              
918             } else {
919             # One element but still provided as an array.
920 10 100 66     20 if (ref $value && ref $value eq 'ARRAY') {
921 1 50       5 if ($req_idx != 0) {
922 0         0 bail_out("$name - SHOULD NOT HAPPEN: idx != 0 and don't need array.");
923             }
924              
925 1         3 return $$value[0];
926             }
927              
928 9         16 return $value;
929             }
930             }
931              
932             sub check_error_code ($$$$$) {
933 0     0 0 0 my ($block, $res, $dry_run, $req_idx, $need_array) = @_;
934              
935 0         0 my $name = $block->name;
936             SKIP: {
937 0 0       0 skip "$name - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
938              
939 0 0       0 if ( defined $block->error_code_like ) {
    0          
940              
941 0         0 my $val = get_indexed_value($name, $block->error_code_like, $req_idx, $need_array);
942 0   0     0 like( ($res && $res->code) || '',
943             qr/$val/sm,
944             "$name - status code ok" );
945              
946             } elsif ( defined $block->error_code ) {
947 0   0     0 is( ($res && $res->code) || '',
948             get_indexed_value($name, $block->error_code, $req_idx, $need_array),
949             "$name - status code ok" );
950              
951             } else {
952 0   0     0 is( ($res && $res->code) || '', 200, "$name - status code ok" );
953             }
954             }
955             }
956              
957             sub check_raw_response_headers($$$$$) {
958 0     0 0 0 my ($block, $raw_headers, $dry_run, $req_idx, $need_array) = @_;
959 0         0 my $name = $block->name;
960 0 0       0 if (defined $block->raw_response_headers_like) {
961             SKIP: {
962 0 0       0 skip "$name - raw_response_headers_like - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
963 0         0 my $expected = get_indexed_value($name,
964             $block->raw_response_headers_like,
965             $req_idx,
966             $need_array);
967 0         0 like $raw_headers, qr/$expected/s, "$name - raw resp headers like";
968             }
969             }
970              
971 0 0       0 if (defined $block->raw_response_headers_unlike) {
972             SKIP: {
973 0 0       0 skip "$name - raw_response_headers_unlike - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
974 0         0 my $expected = get_indexed_value($name,
975             $block->raw_response_headers_unlike,
976             $req_idx,
977             $need_array);
978 0         0 unlike $raw_headers, qr/$expected/s, "$name - raw resp headers unlike";
979             }
980             }
981             }
982              
983             sub check_response_headers($$$$$) {
984 0     0 0 0 my ($block, $res, $raw_headers, $dry_run, $req_idx, $need_array) = @_;
985 0         0 my $name = $block->name;
986 0 0       0 if ( defined $block->response_headers ) {
    0          
987 0         0 my $headers = parse_headers( get_indexed_value($name,
988             $block->response_headers,
989             $req_idx,
990             $need_array));
991 0         0 while ( my ( $key, $val ) = each %$headers ) {
992 0 0       0 if ( !defined $val ) {
993              
994             #warn "HIT";
995             SKIP: {
996 0 0       0 skip "$name - response_headers - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
997 0         0 unlike $raw_headers, qr/^\s*\Q$key\E\s*:/ms,
998             "$name - header $key not present in the raw headers";
999             }
1000 0         0 next;
1001             }
1002              
1003 0         0 $val =~ s/\$ServerPort\b/$ServerPort/g;
1004 0         0 $val =~ s/\$ServerPortForClient\b/$ServerPortForClient/g;
1005              
1006 0 0       0 my $actual_val = $res ? $res->header($key) : undef;
1007 0 0       0 if ( !defined $actual_val ) {
1008 0         0 $actual_val = '';
1009             }
1010              
1011             SKIP: {
1012 0 0       0 skip "$name - response_headers - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1013 0         0 is $actual_val, $val, "$name - header $key ok";
1014             }
1015             }
1016             }
1017             elsif ( defined $block->response_headers_like ) {
1018 0         0 my $headers = parse_headers( get_indexed_value($name,
1019             $block->response_headers_like,
1020             $req_idx,
1021             $need_array) );
1022 0         0 while ( my ( $key, $val ) = each %$headers ) {
1023 0         0 my $expected_val = $res->header($key);
1024 0 0       0 if ( !defined $expected_val ) {
1025 0         0 $expected_val = '';
1026             }
1027             SKIP: {
1028 0 0       0 skip "$name - response_headers_like - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1029 0         0 like $expected_val, qr/^$val$/, "$name - header $key like ok";
1030             }
1031             }
1032             }
1033             }
1034              
1035             sub value_contains ($$) {
1036 0     0 0 0 my ($val, $pat) = @_;
1037              
1038 0 0 0     0 if (!ref $val || ref $val eq 'Regexp') {
1039 0         0 return $val =~ /\Q$pat\E/;
1040             }
1041              
1042 0 0       0 if (ref $val eq 'ARRAY') {
1043 0         0 for my $v (@$val) {
1044 0 0       0 if (value_contains($v, $pat)) {
1045 0         0 return 1;
1046             }
1047             }
1048             }
1049              
1050 0         0 return undef;
1051             }
1052              
1053             sub check_access_log ($$$) {
1054 0     0 0 0 my ($block, $dry_run, $repeated_req_idx) = @_;
1055 0         0 my $name = $block->name;
1056 0         0 my $lines;
1057              
1058 0 0       0 if (defined $block->access_log) {
1059 0         0 my $pats = $block->access_log;
1060              
1061 0 0       0 if (!ref $pats) {
    0          
1062 0         0 chomp $pats;
1063 0         0 my @lines = split /\n+/, $pats;
1064 0         0 $pats = \@lines;
1065              
1066             } elsif (ref $pats eq 'Regexp') {
1067 0         0 $pats = [$pats];
1068              
1069             } else {
1070 0         0 my @clone = @$pats;
1071 0         0 $pats = \@clone;
1072             }
1073              
1074 0   0     0 $lines ||= access_log_data();
1075 0         0 for my $line (@$lines) {
1076 0         0 for my $pat (@$pats) {
1077 0 0       0 next if !defined $pat;
1078 0 0 0     0 if (ref $pat && $line =~ /$pat/ || $line =~ /\Q$pat\E/) {
      0        
1079             SKIP: {
1080 0 0       0 skip "$name - access_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1081 0         0 pass("$name - pattern \"$pat\" matches a line in access.log (req $repeated_req_idx)");
1082             }
1083 0         0 undef $pat;
1084             }
1085             }
1086             }
1087              
1088 0         0 for my $pat (@$pats) {
1089 0 0       0 if (defined $pat) {
1090             SKIP: {
1091 0 0       0 skip "$name - access_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1092 0         0 fail("$name - pattern \"$pat\" should match a line in access.log (req $repeated_req_idx)");
1093             #die join("", @$lines);
1094             }
1095             }
1096             }
1097             }
1098              
1099 0 0       0 if (defined $block->no_access_log) {
1100             #warn "HERE";
1101 0         0 my $pats = $block->no_access_log;
1102              
1103 0 0       0 if (!ref $pats) {
    0          
1104 0         0 chomp $pats;
1105 0         0 my @lines = split /\n+/, $pats;
1106 0         0 $pats = \@lines;
1107              
1108             } elsif (ref $pats eq 'Regexp') {
1109 0         0 $pats = [$pats];
1110              
1111             } else {
1112 0         0 my @clone = @$pats;
1113 0         0 $pats = \@clone;
1114             }
1115              
1116 0         0 my %found;
1117 0   0     0 $lines ||= access_log_data();
1118 0         0 my $i = 0;
1119 0         0 for my $line (@$lines) {
1120 0         0 for my $pat (@$pats) {
1121 0 0       0 next if !defined $pat;
1122             #warn "test $pat\n";
1123 0 0 0     0 if ((ref $pat && $line =~ /$pat/) || $line =~ /\Q$pat\E/) {
      0        
1124 0 0       0 if ($found{$pat}) {
1125 0         0 my $tb = Test::More->builder;
1126 0         0 $tb->no_ending(1);
1127              
1128             } else {
1129 0         0 $found{$pat} = 1;
1130             }
1131              
1132             SKIP: {
1133 0 0       0 skip "$name - no_access_log - tests skipped due to $dry_run ($line)", 1 if $dry_run;
  0         0  
1134 0         0 my $ln = fmt_str($line);
1135 0         0 my $p = fmt_str($pat);
1136 0         0 my @more_lines;
1137 0         0 for (my $j = $i + 1; $j < min($i + 10, @$lines - 1); $j++) {
1138 0         0 push @more_lines, $lines->[$j];
1139             }
1140              
1141 0         0 fail("$name - pattern \"$p\" should not match any line in access.log but matches line \"$ln\" (req $repeated_req_idx)\n"
1142             . join "", @more_lines);
1143             }
1144             }
1145             }
1146              
1147             } continue {
1148 0         0 $i++;
1149             }
1150              
1151 0         0 for my $pat (@$pats) {
1152 0 0       0 next if $found{$pat};
1153 0 0       0 if (defined $pat) {
1154             SKIP: {
1155 0 0       0 skip "$name - no_access_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1156 0         0 my $p = fmt_str($pat);
1157 0         0 pass("$name - pattern \"$p\" does not match a line in access.log (req $repeated_req_idx)");
1158             }
1159             }
1160             }
1161             }
1162             }
1163              
1164             sub check_error_log ($$$$) {
1165 0     0 0 0 my ($block, $res, $dry_run, $repeated_req_idx, $need_array) = @_;
1166 0         0 my $name = $block->name;
1167 0         0 my $lines;
1168              
1169 0         0 my $check_write_guard_message = 1;
1170 0         0 my $check_alert_message = 1;
1171 0         0 my $check_crit_message = 1;
1172 0         0 my $check_emerg_message = 1;
1173              
1174 0         0 my $grep_pat;
1175 0         0 my $grep_pats = $block->grep_error_log;
1176 0 0       0 if (defined $grep_pats) {
1177 0 0 0     0 if (ref $grep_pats && ref $grep_pats eq 'ARRAY') {
1178 0         0 $grep_pat = $grep_pats->[$repeated_req_idx];
1179              
1180             } else {
1181 0         0 $grep_pat = $grep_pats;
1182             }
1183              
1184             } else {
1185 0         0 my $grep_error_log_out = $block->grep_error_log_out;
1186 0 0       0 if (defined $grep_error_log_out) {
1187 0         0 bail_out("$name - No --- grep_error_log defined but --- grep_error_log_out is defined");
1188             }
1189             }
1190              
1191 0 0       0 if (defined $grep_pat) {
1192 0         0 my $expected = $block->grep_error_log_out;
1193 0 0       0 if (!defined $expected) {
1194 0         0 bail_out("$name - No --- grep_error_log_out defined but --- grep_error_log is defined");
1195             }
1196              
1197 0         0 $expected = expand_env_in_text $expected, $name, $Test::Nginx::Util::RandPorts;
1198              
1199             #warn "ref grep error log: ", ref $expected;
1200              
1201 0 0 0     0 if (ref $expected && ref $expected eq 'ARRAY') {
1202             #warn "grep error log out is an ARRAY";
1203 0         0 $expected = $expected->[$repeated_req_idx];
1204             }
1205              
1206             SKIP: {
1207 0 0       0 skip "$name - error_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1208              
1209 0   0     0 $lines ||= error_log_data();
1210              
1211 0         0 my $matched_lines = '';
1212 0         0 for my $line (@$lines) {
1213 0 0 0     0 if (ref $grep_pat && $line =~ /$grep_pat/ || $line =~ /\Q$grep_pat\E/) {
      0        
1214 0         0 my $matched = $&;
1215 0 0       0 if ($matched !~ /\n/) {
1216 0         0 $matched_lines .= $matched . "\n";
1217             }
1218             }
1219             }
1220              
1221 0 0       0 if (ref $expected eq 'Regexp') {
1222 0         0 like($matched_lines, $expected, "$name - grep_error_log_out (req $repeated_req_idx)");
1223              
1224             } else {
1225 0 0       0 if ($NoLongString) {
1226 0         0 is($matched_lines, $expected,
1227             "$name - grep_error_log_out (req $repeated_req_idx)" );
1228             } else {
1229 0         0 is_string($matched_lines, $expected,
1230             "$name - grep_error_log_out (req $repeated_req_idx)");
1231             }
1232             }
1233             }
1234             }
1235              
1236 0 0       0 if (defined $block->error_log) {
1237 0         0 my $pats = $block->error_log;
1238              
1239 0 0       0 if (value_contains($pats,
1240             "writing a global lua variable"))
1241             {
1242 0         0 undef $check_write_guard_message;
1243             }
1244              
1245 0 0       0 if (value_contains($pats, "[alert")) {
1246 0         0 undef $check_alert_message;
1247             }
1248              
1249 0 0       0 if (value_contains($pats, "[crit")) {
1250 0         0 undef $check_crit_message;
1251             }
1252              
1253 0 0       0 if (value_contains($pats, "[emerg")) {
1254 0         0 undef $check_emerg_message;
1255             }
1256              
1257 0 0       0 if (!ref $pats) {
    0          
1258 0         0 chomp $pats;
1259 0         0 my @lines = split /\n+/, $pats;
1260 0         0 $pats = \@lines;
1261              
1262             } elsif (ref $pats eq 'Regexp') {
1263 0         0 $pats = [$pats];
1264              
1265             } else {
1266 0         0 my @clone = @$pats;
1267 0         0 $pats = \@clone;
1268             }
1269              
1270 0   0     0 $lines ||= error_log_data();
1271             #warn "error log data: ", join "\n", @$lines;
1272 0         0 for my $line (@$lines) {
1273 0         0 for my $pat (@$pats) {
1274 0 0       0 next if !defined $pat;
1275 0 0 0     0 if (ref $pat && $line =~ /$pat/ || $line =~ /\Q$pat\E/) {
      0        
1276             SKIP: {
1277 0 0       0 skip "$name - error_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1278 0         0 pass("$name - pattern \"$pat\" matches a line in error.log (req $repeated_req_idx)");
1279             }
1280 0         0 undef $pat;
1281             }
1282             }
1283             }
1284              
1285 0         0 for my $pat (@$pats) {
1286 0 0       0 if (defined $pat) {
1287             SKIP: {
1288 0 0       0 skip "$name - error_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1289 0         0 fail("$name - pattern \"$pat\" should match a line in error.log (req $repeated_req_idx)");
1290             #die join("", @$lines);
1291             }
1292             }
1293             }
1294             }
1295              
1296 0 0       0 if (defined $block->no_error_log) {
1297             #warn "HERE";
1298 0         0 my $pats = $block->no_error_log;
1299              
1300 0 0       0 if (value_contains($pats,
1301             "writing a global lua variable"))
1302             {
1303 0         0 undef $check_write_guard_message;
1304             }
1305              
1306 0 0       0 if (value_contains($pats, "[alert")) {
1307 0         0 undef $check_alert_message;
1308             }
1309              
1310 0 0       0 if (value_contains($pats, "[crit")) {
1311 0         0 undef $check_crit_message;
1312             }
1313              
1314 0 0       0 if (value_contains($pats, "[emerg")) {
1315 0         0 undef $check_emerg_message;
1316             }
1317              
1318 0 0       0 if (!ref $pats) {
    0          
1319 0         0 chomp $pats;
1320 0         0 my @lines = split /\n+/, $pats;
1321 0         0 $pats = \@lines;
1322              
1323             } elsif (ref $pats eq 'Regexp') {
1324 0         0 $pats = [$pats];
1325              
1326             } else {
1327 0         0 my @clone = @$pats;
1328 0         0 $pats = \@clone;
1329             }
1330              
1331 0         0 my %found;
1332 0   0     0 $lines ||= error_log_data();
1333 0         0 my $i = 0;
1334 0         0 for my $line (@$lines) {
1335 0         0 for my $pat (@$pats) {
1336 0 0       0 next if !defined $pat;
1337             #warn "test $pat\n";
1338 0 0 0     0 if ((ref $pat && $line =~ /$pat/) || $line =~ /\Q$pat\E/) {
      0        
1339 0 0       0 if ($found{$pat}) {
1340 0         0 my $tb = Test::More->builder;
1341 0         0 $tb->no_ending(1);
1342              
1343             } else {
1344 0         0 $found{$pat} = 1;
1345             }
1346              
1347             SKIP: {
1348 0 0       0 skip "$name - no_error_log - tests skipped due to $dry_run ($line)", 1 if $dry_run;
  0         0  
1349 0         0 my $ln = fmt_str($line);
1350 0         0 my $p = fmt_str($pat);
1351 0         0 my @more_lines;
1352 0         0 for (my $j = $i + 1; $j < min($i + 10, @$lines - 1); $j++) {
1353 0         0 push @more_lines, $lines->[$j];
1354             }
1355              
1356 0         0 fail("$name - pattern \"$p\" should not match any line in error.log but matches line \"$ln\" (req $repeated_req_idx)\n"
1357             . join "", @more_lines);
1358             }
1359             }
1360             }
1361              
1362             } continue {
1363 0         0 $i++;
1364             }
1365              
1366 0         0 for my $pat (@$pats) {
1367 0 0       0 next if $found{$pat};
1368 0 0       0 if (defined $pat) {
1369             SKIP: {
1370 0 0       0 skip "$name - no_error_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1371 0         0 my $p = fmt_str($pat);
1372 0         0 pass("$name - pattern \"$p\" does not match a line in error.log (req $repeated_req_idx)");
1373             }
1374             }
1375             }
1376             }
1377              
1378 0 0 0     0 if ($check_write_guard_message && !$dry_run) {
1379 0   0     0 $lines ||= error_log_data();
1380 0         0 for my $line (@$lines) {
1381             #warn "test $pat\n";
1382 0 0       0 if ($line =~ /writing a global lua variable/) {
1383 0         0 my $ln = fmt_str($line);
1384 0         0 warn("WARNING: $name - $ln\n");
1385             }
1386             }
1387             }
1388              
1389 0 0 0     0 if ($check_alert_message && !$dry_run) {
1390 0   0     0 $lines ||= error_log_data();
1391 0         0 for my $line (@$lines) {
1392             #warn "test $pat\n";
1393 0 0       0 if ($line =~ /\[alert\]/) {
1394 0         0 my $ln = fmt_str($line);
1395 0         0 warn("WARNING: $name - $ln\n");
1396             }
1397             }
1398             }
1399              
1400 0 0 0     0 if ($check_crit_message && !$dry_run) {
1401 0   0     0 $lines ||= error_log_data();
1402 0         0 for my $line (@$lines) {
1403             #warn "test $pat\n";
1404 0 0       0 if ($line =~ /\[crit\]/) {
1405 0         0 my $ln = fmt_str($line);
1406 0         0 warn("WARNING: $name - $ln\n");
1407             }
1408             }
1409             }
1410              
1411 0 0 0     0 if ($check_emerg_message && !$dry_run) {
1412 0   0     0 $lines ||= error_log_data();
1413 0         0 for my $line (@$lines) {
1414             #warn "test $pat\n";
1415 0 0       0 if ($line =~ /\[emerg\]/) {
1416 0         0 my $ln = fmt_str($line);
1417 0         0 warn("WARNING: $name - $ln");
1418             }
1419             }
1420             }
1421              
1422 0         0 for my $line (@$lines) {
1423             #warn "test $pat\n";
1424 0 0       0 if ($line =~ /\bAssertion .*?failed\b/) {
1425 0         0 my $tb = Test::More->builder;
1426 0         0 $tb->no_ending(1);
1427              
1428 0         0 chomp $line;
1429 0         0 fail("$name - $line\n");
1430             }
1431             }
1432             }
1433              
1434             sub check_shutdown_error_log ($$) {
1435 0     0 0 0 my ($block, $dry_run) = @_;
1436 0         0 my $name = $block->name;
1437 0         0 my $lines;
1438              
1439 0         0 my $pats = $block->shutdown_error_log;
1440 0 0       0 if (defined $pats) {
1441 0 0       0 if (!ref $pats) {
    0          
1442 0         0 chomp $pats;
1443 0         0 my @lines = split /\n+/, $pats;
1444 0         0 $pats = \@lines;
1445              
1446             } elsif (ref $pats eq 'Regexp') {
1447 0         0 $pats = [$pats];
1448              
1449             } else {
1450 0         0 my @clone = @$pats;
1451 0         0 $pats = \@clone;
1452             }
1453              
1454 0   0     0 $lines ||= error_log_data();
1455             #warn "error log data: ", join "\n", @$lines;
1456 0         0 for my $line (@$lines) {
1457 0         0 for my $pat (@$pats) {
1458 0 0       0 next if !defined $pat;
1459              
1460 0 0 0     0 if (ref $pat && $line =~ /$pat/ || $line =~ /\Q$pat\E/) {
      0        
1461             SKIP: {
1462 0 0       0 skip "$name - shutdown_error_log - tests skipped due to dry_run", 1 if $dry_run;
  0         0  
1463 0         0 pass("$name - pattern \"$pat\" matches a line in error.log");
1464             }
1465 0         0 undef $pat;
1466             }
1467             }
1468             }
1469              
1470 0         0 for my $pat (@$pats) {
1471 0 0       0 if (defined $pat) {
1472             SKIP: {
1473 0 0       0 skip "$name - shutdown_error_log - tests skipped due to dry_run", 1 if $dry_run;
  0         0  
1474 0         0 fail("$name - pattern \"$pat\" should match a line in error.log");
1475             #die join("", @$lines);
1476             }
1477             }
1478             }
1479              
1480 0         0 for my $line (@$lines) {
1481             #warn "test $line\n";
1482 0 0       0 if ($line =~ /\bAssertion .*? failed\.$/) {
1483 0         0 my $tb = Test::More->builder;
1484 0         0 $tb->no_ending(1);
1485              
1486 0         0 chomp $line;
1487 0         0 fail("$name - $line");
1488             }
1489             }
1490             }
1491              
1492 0 0       0 if (defined $block->no_shutdown_error_log) {
1493             # warn "HERE";
1494 0         0 my $pats = $block->no_shutdown_error_log;
1495              
1496 0 0       0 if (!ref $pats) {
    0          
1497 0         0 chomp $pats;
1498 0         0 my @lines = split /\n+/, $pats;
1499 0         0 $pats = \@lines;
1500              
1501             } elsif (ref $pats eq 'Regexp') {
1502 0         0 $pats = [$pats];
1503              
1504             } else {
1505 0         0 my @clone = @$pats;
1506 0         0 $pats = \@clone;
1507             }
1508              
1509 0         0 my %found;
1510 0   0     0 $lines ||= error_log_data();
1511             # warn "error log data: ", join "\n", @$lines;
1512 0         0 for my $line (@$lines) {
1513 0         0 for my $pat (@$pats) {
1514 0 0       0 next if !defined $pat;
1515             #warn "test $pat\n";
1516 0 0 0     0 if ((ref $pat && $line =~ /$pat/) || $line =~ /\Q$pat\E/) {
      0        
1517 0 0       0 if ($found{$pat}) {
1518 0         0 my $tb = Test::More->builder;
1519 0         0 $tb->no_ending(1);
1520              
1521             } else {
1522 0         0 $found{$pat} = 1;
1523             }
1524              
1525             SKIP: {
1526 0 0       0 skip "$name - no_shutdown_error_log - tests skipped due to $dry_run ($line)", 1 if $dry_run;
  0         0  
1527 0         0 my $ln = fmt_str($line);
1528 0         0 my $p = fmt_str($pat);
1529 0         0 fail("$name - pattern \"$p\" should not match any line in error.log but matches line \"$ln\"");
1530             }
1531             }
1532             }
1533             }
1534              
1535 0         0 for my $pat (@$pats) {
1536 0 0       0 next if $found{$pat};
1537 0 0       0 if (defined $pat) {
1538             SKIP: {
1539 0 0       0 skip "$name - no_shutdown_error_log - tests skipped due to $dry_run", 1 if $dry_run;
  0         0  
1540 0         0 my $p = fmt_str($pat);
1541 0         0 pass("$name - pattern \"$p\" does not match a line in error.log");
1542             }
1543             }
1544             }
1545             }
1546             }
1547              
1548             sub fmt_str ($) {
1549 0     0 0 0 my $str = shift;
1550 0         0 chomp $str;
1551 0         0 $str =~ s/"/\\"/g;
1552 0         0 $str =~ s/\r/\\r/g;
1553 0         0 $str =~ s/\n/\\n/g;
1554 0         0 $str;
1555             }
1556              
1557             sub transform_response_body ($$$) {
1558 15     15 0 51 my ($block, $res, $req_idx) = @_;
1559              
1560 15 50       22 return unless defined $res;
1561              
1562 15         20 my $content = $res->content;
1563 15 50       128 return unless defined $content;
1564              
1565 15         12 my $is_2d_array = 0;
1566 15         38 my $name = $block->name;
1567 15         24 my $response_body_filters = $block->response_body_filters;
1568              
1569 15 50       26 if (defined $response_body_filters) {
1570              
1571 15 100       32 if (!ref $response_body_filters) {
    100          
1572 4         18 $response_body_filters =~ s/^\s+|\s+$//gs;
1573 4         40 $response_body_filters = [split /\s+/, $response_body_filters];
1574              
1575             } elsif (ref $response_body_filters ne 'ARRAY') {
1576 1         3 $response_body_filters = [$response_body_filters];
1577             }
1578              
1579 15 50       23 if (ref $response_body_filters eq 'ARRAY') {
1580              
1581 15 100       26 if (ref $response_body_filters->[0] eq 'ARRAY') {
1582 8         7 $is_2d_array = 1;
1583              
1584 8         12 for my $elem (@$response_body_filters) {
1585 22 50       30 if (ref $elem ne "ARRAY") {
1586 0         0 bail_out("$name - the --- response_body_filters two-dimensional array "
1587             . "only be like [[uc], [lc]] not [[uc], lc]");
1588             }
1589             }
1590             }
1591             }
1592              
1593 15         15 my $new = $content;
1594 15         13 my $filter = $response_body_filters;
1595              
1596 15 100       19 if ($is_2d_array) {
1597 8         9 $filter = $response_body_filters->[$req_idx];
1598              
1599 8 50       9 bail_out("$name - the ---response_body_filters two-dimensional array "
1600             . "unmatch the specified request($req_idx)") unless defined $filter;
1601             }
1602              
1603 15 50 33     32 if (ref $filter && ref $filter eq 'ARRAY') {
1604              
1605 15         20 for my $f (@$filter) {
1606 20         30 $new = run_filter_helper($block, $f, $new);
1607             }
1608              
1609             } else {
1610 0         0 $new = run_filter_helper($block, $filter, $new);
1611             }
1612              
1613 15         25 $res->content($new);
1614             }
1615              
1616             }
1617              
1618             sub check_response_body ($$$$$$) {
1619 16     16 0 311 my ($block, $res, $dry_run, $req_idx, $repeated_req_idx, $need_array) = @_;
1620 16         28 my $name = $block->name;
1621 16         117 my $write_resp_body_file = $block->write_resp_body_file;
1622 16 50 33     68 if (defined $write_resp_body_file && defined $res) {
1623 0   0     0 my $got_body = $res->content // '';
1624 0 0       0 open my $out, ">$write_resp_body_file"
1625             or bail_out "$name - failed to write to file '$write_resp_body_file': $!";
1626 0         0 print $out $got_body;
1627 0         0 close $out;
1628             }
1629 16 100 66     29 if ( defined $block->response_body
    50 33        
1630             || defined $block->response_body_eval )
1631             {
1632 9 50       20 my $content = $res ? $res->content : undef;
1633 9 50       85 if ( defined $content ) {
1634 9         14 $content =~ s/^TE: deflate,gzip;q=0\.3\r\n//gms;
1635 9         13 $content =~ s/^Connection: TE, close\r\n//gms;
1636             }
1637              
1638 9 50       30 if ( defined $block->response_body_json_sort ) {
1639 0         0 my $js = JSON::PP->new;
1640 0         0 $js->canonical(1);
1641 0         0 my $obj;
1642 0         0 my $rc = eval { $obj = $js->loose(1)->decode($content); 1; };
  0         0  
  0         0  
1643 0 0       0 if ($rc) {
1644 0         0 $content = $js->encode($obj) . "\n";
1645             }
1646             }
1647              
1648 9         11 my $expected;
1649 9 50       17 if ( $block->response_body_eval ) {
1650 0         0 diag "$name - response_body_eval is DEPRECATED. Use response_body eval instead.";
1651 0         0 $expected = eval get_indexed_value($name,
1652             $block->response_body_eval,
1653             $req_idx,
1654             $need_array);
1655 0 0       0 if ($@) {
1656 0         0 warn $@;
1657             }
1658             }
1659             else {
1660 9         12 $expected = get_indexed_value($name,
1661             $block->response_body,
1662             $req_idx,
1663             $need_array);
1664             }
1665              
1666 9 50       25 if ( $block->charset ) {
1667 0         0 Encode::from_to( $expected, 'UTF-8', $block->charset );
1668             }
1669              
1670 9 50 33     26 unless (!defined $expected || ref $expected) {
1671 9         17 $expected =~ s/\$ServerPort\b/$ServerPort/g;
1672 9         10 $expected =~ s/\$ServerPortForClient\b/$ServerPortForClient/g;
1673 9         26 $expected = expand_env_in_text $expected, $name, $Test::Nginx::Util::RandPorts;
1674             }
1675              
1676             #warn show_all_chars($content);
1677              
1678             #warn "no long string: $NoLongString";
1679             SKIP: {
1680 9 50       9 skip "$name - response_body - tests skipped due to $dry_run", 1 if $dry_run;
  9         11  
1681 9 50       11 if (ref $expected) {
1682 0         0 like $content, $expected, "$name - response_body - like (repeated req $repeated_req_idx, req $req_idx)";
1683              
1684             } else {
1685 9 50       14 if ($NoLongString) {
1686 0         0 is( $content, $expected,
1687             "$name - response_body - response is expected (repeated req $repeated_req_idx, req $req_idx)" );
1688             }
1689             else {
1690 9         39 is_string( $content, $expected,
1691             "$name - response_body - response is expected (repeated req $repeated_req_idx, req $req_idx)" );
1692             }
1693             }
1694             }
1695              
1696             } elsif (defined $block->response_body_like
1697             || defined $block->response_body_unlike)
1698             {
1699 7         12 my $patterns;
1700             my $type;
1701 7         0 my $cmp;
1702 7 50       10 if (defined $block->response_body_like) {
1703 7         9 $patterns = $block->response_body_like;
1704 7         9 $type = "like";
1705 7         11 $cmp = \&like;
1706              
1707             } else {
1708 0         0 $patterns = $block->response_body_unlike;
1709 0         0 $type = "unlike";
1710 0         0 $cmp = \&unlike;
1711             }
1712              
1713 7 50       39 my $content = $res ? $res->content : undef;
1714 7 50       55 if ( defined $content ) {
1715 7         14 $content =~ s/^TE: deflate,gzip;q=0\.3\r\n//gms;
1716 7         8 $content =~ s/^Connection: TE, close\r\n//gms;
1717             }
1718 7         15 my $expected_pat = get_indexed_value($name,
1719             $patterns,
1720             $req_idx,
1721             $need_array);
1722 7         10 $expected_pat =~ s/\$ServerPort\b/$ServerPort/g;
1723 7         7 $expected_pat =~ s/\$ServerPortForClient\b/$ServerPortForClient/g;
1724 7         21 my $summary = trim($content);
1725 7 50       11 if (!defined $summary) {
1726 0         0 $summary = "";
1727             }
1728              
1729             SKIP: {
1730 7 50       6 skip "$name - response_body_$type - tests skipped due to $dry_run", 1 if $dry_run;
  7         19  
1731 7         101 $cmp->( $content, qr/$expected_pat/s,
1732             "$name - response_body_$type - response is expected ($summary)"
1733             );
1734             }
1735             }
1736              
1737 16         584 for my $check (@ResponseBodyChecks) {
1738 0         0 $check->($block, $res->content, $req_idx, $repeated_req_idx, $dry_run);
1739             }
1740             }
1741              
1742             sub parse_response($$$) {
1743 16     16 0 129 my ( $name, $raw_resp, $head_req ) = @_;
1744              
1745 16         16 my $left;
1746              
1747 16         21 my $raw_headers = '';
1748 16 50       95 if ( $raw_resp =~ /(.*?\r\n)\r\n/s ) {
1749              
1750             #warn "\$1: $1";
1751 16         44 $raw_headers = $1;
1752             }
1753              
1754             #warn "raw headers: $raw_headers\n";
1755              
1756 16         75 my $res = HTTP::Response->parse($raw_resp);
1757              
1758 16         2983 my $code = $res->code;
1759              
1760 16         130 my $enc = $res->header('Transfer-Encoding');
1761 16         588 my $len = $res->header('Content-Length');
1762 16         454 my @trailers = $res->header('Trailer');
1763             # warn "trailers: @trailers";
1764              
1765 16 50 33     473 if ($code && $code !~ /^\d+$/) {
1766 0         0 undef $code;
1767 0         0 $res->code(undef);
1768             }
1769              
1770 16 50 33     84 if ($code && ($code == 304 || $code == 101)) {
      33        
1771 0         0 return $res, $raw_headers
1772             }
1773              
1774 16 50 33     101 if ( defined $enc && $enc eq 'chunked' ) {
    50 33        
      33        
1775              
1776             #warn "Found chunked!";
1777 0         0 my $raw = $res->content;
1778 0 0       0 if ( !defined $raw ) {
1779 0         0 $raw = '';
1780             }
1781              
1782 0         0 my $decoded = '';
1783 0         0 while (1) {
1784 0 0 0     0 if (@trailers == 0 && $raw =~ /\G 0 [\ \t]* \r\n \r\n /gcsx ) {
    0 0        
1785 0 0       0 if ( $raw =~ /\G (.+) /gcsx ) {
1786 0         0 $left = $1;
1787             }
1788              
1789 0         0 last;
1790              
1791             } elsif (@trailers > 0 && $raw =~ /\G 0 [\ \t]* \r\n /gcsx) {
1792             # skip HTTP Trailer
1793 0         0 for my $trailer (@trailers) {
1794 0 0       0 if ( $raw !~ /\G($trailer:\ [^\n]*\r\n)/gcs ) {
1795 0         0 my $tb = Test::More->builder;
1796 0         0 $tb->no_ending(1);
1797              
1798 0         0 fail(
1799             "$name - invalid trailer data received (expected $trailer)."
1800             );
1801 0         0 return;
1802             }
1803             }
1804              
1805 0 0       0 if ($raw !~ /\G\r\n/gcs ) {
1806 0         0 my $tb = Test::More->builder;
1807 0         0 $tb->no_ending(1);
1808              
1809 0         0 fail(
1810             "$name - invalid chunked data received (expected CRLF)."
1811             );
1812 0         0 return;
1813             }
1814              
1815 0 0       0 if ( $raw =~ /\G (.+) /gcsx ) {
1816 0         0 $left = $1;
1817             }
1818              
1819 0         0 last;
1820             }
1821              
1822 0 0       0 if ( $raw =~ m{ \G [\ \t]* ( [A-Fa-f0-9]+ ) [\ \t]* \r\n }gcsx ) {
    0          
1823 0         0 my $rest = hex($1);
1824              
1825             #warn "chunk size: $rest\n";
1826 0         0 my $bit_sz = 32765;
1827 0         0 while ( $rest > 0 ) {
1828 0 0       0 my $bit = $rest < $bit_sz ? $rest : $bit_sz;
1829              
1830             #warn "bit: $bit\n";
1831 0 0       0 if ( $raw =~ /\G(.{$bit})/gcs ) {
1832 0         0 $decoded .= $1;
1833              
1834             #warn "decoded: [$1]\n";
1835              
1836             } else {
1837 0         0 my $tb = Test::More->builder;
1838 0         0 $tb->no_ending(1);
1839              
1840 0         0 fail("$name - invalid chunked data received "
1841             ."(not enought octets for the data section)"
1842             );
1843 0         0 return;
1844             }
1845              
1846 0         0 $rest -= $bit;
1847             }
1848              
1849 0 0       0 if ( $raw !~ /\G\r\n/gcs ) {
1850 0         0 my $tb = Test::More->builder;
1851 0         0 $tb->no_ending(1);
1852              
1853 0         0 fail(
1854             "$name - invalid chunked data received (expected CRLF)."
1855             );
1856 0         0 return;
1857             }
1858              
1859             } elsif ( $raw =~ /\G.+/gcs ) {
1860 0         0 my $tb = Test::More->builder;
1861 0         0 $tb->no_ending(1);
1862              
1863 0         0 fail "$name - invalid chunked body received: $&";
1864 0         0 return;
1865              
1866             } else {
1867 0         0 my $tb = Test::More->builder;
1868 0         0 $tb->no_ending(1);
1869              
1870 0         0 fail "$name - no last chunk found - $raw";
1871 0         0 return;
1872             }
1873             }
1874              
1875             #warn "decoded: $decoded\n";
1876 0         0 $res->content($decoded);
1877              
1878             } elsif (defined $len && $len ne '' && $len >= 0) {
1879 16         33 my $raw = $res->content;
1880 16 50       179 if (length $raw < $len) {
    50          
1881 0 0       0 if (!$head_req) {
1882 0         0 warn "WARNING: $name - response body truncated: ",
1883             "$len expected, but got ", length $raw, "\n";
1884             }
1885              
1886             } elsif (length $raw > $len) {
1887 0         0 my $content = substr $raw, 0, $len;
1888 0         0 $left = substr $raw, $len;
1889 0         0 $res->content($content);
1890             #warn "parsed body: [", $res->content, "]\n";
1891             }
1892             }
1893              
1894 16         45 return ( $res, $raw_headers, $left );
1895             }
1896              
1897             sub send_http_req_by_curl ($$$) {
1898 0     0 0   my ($block, $req, $timeout) = @_;
1899              
1900 0           my $name = $block->name;
1901              
1902 0           my $cmd = gen_curl_cmd_from_req($block, $req);
1903              
1904 0 0         if ($Test::Nginx::Util::Verbose) {
1905 0           warn "running cmd @$cmd";
1906             }
1907              
1908 0 0         if (use_http2($block)) {
1909 0 0         my $total_tries = $TotalConnectingTimeouts ? 20 : 50;
1910 0           while ($total_tries-- > 0) {
1911 0 0         if (is_tcp_port_used($ServerPortForClient)) {
1912 0           last;
1913             }
1914              
1915 0           warn "$name - waiting for nginx to listen on port "
1916             . "$ServerPortForClient, Retry connecting after 1 sec\n";
1917 0           sleep 1;
1918             }
1919             }
1920              
1921 0 0         if (use_http3($block)) {
1922 0 0         my $total_tries = $TotalConnectingTimeouts ? 20 : 50;
1923 0           while ($total_tries-- > 0) {
1924 0 0         if (is_udp_port_used($ServerPortForClient)) {
1925 0           last;
1926             }
1927              
1928 0           warn "$name - waiting for nginx to listen on port "
1929             . "$ServerPortForClient, Retry connecting after 1 sec\n";
1930 0           sleep 1;
1931             }
1932             }
1933              
1934 0           my $ok = IPC::Run::run($cmd, \(my $in), \(my $out), \(my $err),
1935             IPC::Run::timeout($timeout));
1936              
1937             #my @cmd_copy = @$cmd;
1938             #warn "running cmd ", quote_sh_args(\@cmd_copy);
1939              
1940 0 0         if (!defined $ok) {
1941 0   0       fail "failed to run curl: $?: " . ($err // '');
1942 0           return;
1943             }
1944              
1945 0 0         if (!$out) {
1946 0 0         if ($err) {
1947 0           my $curl_err = $block->curl_error;
1948 0 0         if (defined $curl_err) {
1949 0 0 0       if (ref $curl_err && $err =~ /$curl_err/) {
    0          
1950 0           return;
1951              
1952             } elsif ($err =~ /\Q$curl_err\E/) {
1953 0           return;
1954             }
1955              
1956 0           fail "$name - command \"@$cmd\" generates stderr output: $err";
1957 0           return;
1958             }
1959              
1960 0           fail "$name - command \"@$cmd\" generates stderr output: $err";
1961 0           return;
1962             }
1963              
1964 0           fail "$name - curl command \"@$cmd\" generates no stdout output";
1965 0           return;
1966             }
1967              
1968 0 0         if ($err) {
1969 0           warn "WARNING: $name - command \"@$cmd\" generates stderr output: $err";
1970             }
1971              
1972 0           return $out;
1973             }
1974              
1975             sub send_request ($$$$@) {
1976 0     0 0   my ( $req, $middle_delay, $timeout, $block, $tries ) = @_;
1977              
1978 0           my $name = $block->name;
1979              
1980 0 0         my @req_bits = ref $req ? @$req : ($req);
1981              
1982 0           my $head_req = 0;
1983             {
1984 0           my $req = join '', map { $_->{value} } @req_bits;
  0            
  0            
1985             #warn "Request: $req\n";
1986 0 0         if ($req =~ /^\s*HEAD\s+/) {
1987             #warn "Found HEAD request!\n";
1988 0           $head_req = 1;
1989             }
1990             }
1991              
1992 0 0 0       if (use_http2($block) || use_http3($block)) {
1993 0           return send_http_req_by_curl($block, $req, $timeout), $head_req;
1994             }
1995              
1996 0           my $server_addr = $block->server_addr_for_client;
1997              
1998 0 0         if (!defined $server_addr) {
1999 0           $server_addr = $ServerAddr;
2000             }
2001              
2002 0           my $sock = IO::Socket::INET->new(
2003             PeerAddr => $server_addr,
2004             PeerPort => $ServerPortForClient,
2005             Proto => 'tcp',
2006             #ReuseAddr => 1,
2007             #ReusePort => 1,
2008             Blocking => 0,
2009             Timeout => $timeout,
2010             );
2011              
2012             #warn "connecting...\n";
2013 0 0         if (!defined $sock) {
2014 0   0       $tries ||= 1;
2015 0 0         my $total_tries = $TotalConnectingTimeouts ? 20 : 50;
2016 0 0         if ($tries <= $total_tries) {
2017 0           my $wait = (sleep_time() + sleep_time() * $tries) * $tries / 2;
2018 0 0         if ($wait >= 1) {
2019 0           $wait = 1;
2020             }
2021              
2022 0 0         if (defined $Test::Nginx::Util::ChildPid) {
2023 0           my $errcode = $!;
2024 0 0         if (waitpid($Test::Nginx::Util::ChildPid, WNOHANG) == -1) {
2025 0           warn "WARNING: Child process $Test::Nginx::Util::ChildPid is already gone.\n";
2026 0           warn `tail -n20 $Test::Nginx::Util::ErrLogFile`;
2027              
2028 0           my $tb = Test::More->builder;
2029 0           $tb->no_ending(1);
2030              
2031 0           fail("$name - Can't connect to $server_addr:$ServerPortForClient: $errcode (aborted)\n");
2032 0           return;
2033             }
2034             }
2035              
2036 0 0         if ($wait >= 0.6) {
2037 0           warn "$name - Can't connect to $server_addr:$ServerPortForClient: $!\n";
2038 0 0         if ($tries + 1 <= $total_tries) {
2039 0           warn "\tRetry connecting after $wait sec\n";
2040             }
2041             }
2042              
2043 0           sleep $wait;
2044              
2045             #warn "sending request";
2046 0           return send_request($req, $middle_delay, $timeout, $block, $tries + 1);
2047              
2048             }
2049              
2050 0           my $msg = "$name - Can't connect to $server_addr:$ServerPortForClient: $! (aborted)\n";
2051 0 0         if (++$TotalConnectingTimeouts < 3) {
2052 0           my $tb = Test::More->builder;
2053 0           $tb->no_ending(1);
2054 0           fail($msg);
2055              
2056             } else {
2057 0           bail_out($msg);
2058             }
2059              
2060 0           return;
2061             }
2062              
2063             #warn "connected";
2064              
2065             #my $flags = fcntl $sock, F_GETFL, 0
2066             #or die "Failed to get flags: $!\n";
2067              
2068             #fcntl $sock, F_SETFL, $flags | O_NONBLOCK
2069             #or die "Failed to set flags: $!\n";
2070              
2071             my $ctx = {
2072             resp => '',
2073             write_offset => 0,
2074             buf_size => 1024,
2075             req_bits => \@req_bits,
2076 0           write_buf => (shift @req_bits)->{"value"},
2077             middle_delay => $middle_delay,
2078             sock => $sock,
2079             name => $name,
2080             block => $block,
2081             };
2082              
2083 0           my $readable_hdls = IO::Select->new($sock);
2084 0           my $writable_hdls = IO::Select->new($sock);
2085 0           my $err_hdls = IO::Select->new($sock);
2086              
2087 0           while (1) {
2088 0 0 0       if ( $readable_hdls->count == 0
      0        
2089             && $writable_hdls->count == 0
2090             && $err_hdls->count == 0 )
2091             {
2092 0           last;
2093             }
2094              
2095             #warn "doing select...\n";
2096              
2097 0           my ($new_readable, $new_writable, $new_err) =
2098             IO::Select->select($readable_hdls, $writable_hdls, $err_hdls,
2099             $timeout);
2100              
2101 0 0 0       if (!defined $new_err
      0        
2102             && !defined $new_readable
2103             && !defined $new_writable)
2104             {
2105              
2106             # timed out
2107 0           timeout_event_handler($ctx);
2108 0           last;
2109             }
2110              
2111 0           for my $hdl (@$new_err) {
2112 0 0         next if !defined $hdl;
2113              
2114 0           error_event_handler($ctx);
2115              
2116 0 0         if ( $err_hdls->exists($hdl) ) {
2117 0           $err_hdls->remove($hdl);
2118             }
2119              
2120 0 0         if ( $readable_hdls->exists($hdl) ) {
2121 0           $readable_hdls->remove($hdl);
2122             }
2123              
2124 0 0         if ( $writable_hdls->exists($hdl) ) {
2125 0           $writable_hdls->remove($hdl);
2126             }
2127              
2128 0           for my $h (@$readable_hdls) {
2129 0 0         next if !defined $h;
2130 0 0         if ( $h eq $hdl ) {
2131 0           undef $h;
2132 0           last;
2133             }
2134             }
2135              
2136 0           for my $h (@$writable_hdls) {
2137 0 0         next if !defined $h;
2138 0 0         if ( $h eq $hdl ) {
2139 0           undef $h;
2140 0           last;
2141             }
2142             }
2143              
2144 0           close $hdl;
2145             }
2146              
2147 0           for my $hdl (@$new_readable) {
2148 0 0         next if !defined $hdl;
2149              
2150 0           my $res = read_event_handler($ctx);
2151 0 0         if ( !$res ) {
2152              
2153             # error occured
2154 0 0         if ( $err_hdls->exists($hdl) ) {
2155 0           $err_hdls->remove($hdl);
2156             }
2157              
2158 0 0         if ( $readable_hdls->exists($hdl) ) {
2159 0           $readable_hdls->remove($hdl);
2160             }
2161              
2162 0 0         if ( $writable_hdls->exists($hdl) ) {
2163 0           $writable_hdls->remove($hdl);
2164             }
2165              
2166 0           for my $h (@$writable_hdls) {
2167 0 0         next if !defined $h;
2168 0 0         if ( $h eq $hdl ) {
2169 0           undef $h;
2170 0           last;
2171             }
2172             }
2173              
2174 0           close $hdl;
2175             }
2176             }
2177              
2178 0           for my $hdl (@$new_writable) {
2179 0 0         next if !defined $hdl;
2180              
2181 0           my $res = write_event_handler($ctx);
2182 0 0         if ( !$res ) {
    0          
2183              
2184             # error occured
2185 0 0         if ( $err_hdls->exists($hdl) ) {
2186 0           $err_hdls->remove($hdl);
2187             }
2188              
2189 0 0         if ( $readable_hdls->exists($hdl) ) {
2190 0           $readable_hdls->remove($hdl);
2191             }
2192              
2193 0 0         if ( $writable_hdls->exists($hdl) ) {
2194 0           $writable_hdls->remove($hdl);
2195             }
2196              
2197 0           close $hdl;
2198              
2199             } elsif ( $res == 2 ) {
2200             # all data has been written
2201              
2202 0           my $shutdown = $block->shutdown;
2203 0 0         if (defined $shutdown) {
2204 0 0         if ($shutdown =~ /^$/s) {
2205 0           $shutdown = 1;
2206             }
2207              
2208             #warn "shutting down with $shutdown";
2209 0           shutdown($sock, $shutdown);
2210             }
2211              
2212 0 0         if ( $writable_hdls->exists($hdl) ) {
2213 0           $writable_hdls->remove($hdl);
2214             }
2215             }
2216             }
2217             }
2218              
2219 0           return ($ctx->{resp}, $head_req);
2220             }
2221              
2222             sub timeout_event_handler ($) {
2223 0     0 0   my $ctx = shift;
2224              
2225 0           close($ctx->{sock});
2226              
2227 0 0         if (!defined $ctx->{block}->abort) {
2228 0           my $tb = Test::More->builder;
2229 0           $tb->no_ending(1);
2230              
2231 0           fail("ERROR: client socket timed out - $ctx->{name}\n");
2232              
2233             } else {
2234 0           sleep 0.005;
2235             }
2236             }
2237              
2238             sub error_event_handler ($) {
2239 0     0 0   warn "exception occurs on the socket: $!\n";
2240             }
2241              
2242             sub write_event_handler ($) {
2243 0     0 0   my ($ctx) = @_;
2244              
2245 0           while (1) {
2246 0 0         return undef if !defined $ctx->{write_buf};
2247              
2248 0           my $rest = length( $ctx->{write_buf} ) - $ctx->{write_offset};
2249              
2250             #warn "offset: $write_offset, rest: $rest, length ", length($write_buf), "\n";
2251             #die;
2252              
2253 0 0         if ( $rest > 0 ) {
2254 0           my $bytes;
2255 0           eval {
2256             $bytes = syswrite(
2257             $ctx->{sock}, $ctx->{write_buf},
2258             $rest, $ctx->{write_offset}
2259 0           );
2260             };
2261              
2262 0 0         if ($@) {
2263 0           my $errmsg = "write failed: $@";
2264 0           warn "$errmsg\n";
2265 0           $ctx->{resp} = $errmsg;
2266 0           return undef;
2267             }
2268              
2269 0 0         if ( !defined $bytes ) {
2270 0 0         if ( $! == EAGAIN ) {
2271              
2272             #warn "write again...";
2273             #sleep 0.002;
2274 0           return 1;
2275             }
2276 0           my $errmsg = "write failed: $!";
2277 0           warn "$errmsg\n";
2278 0 0         if ( !$ctx->{resp} ) {
2279 0           $ctx->{resp} = "$errmsg";
2280             }
2281 0           return undef;
2282             }
2283              
2284             #warn "wrote $bytes bytes.\n";
2285 0           $ctx->{write_offset} += $bytes;
2286              
2287             } else {
2288             # $rest == 0
2289              
2290 0           my $next_send = shift @{ $ctx->{req_bits} };
  0            
2291              
2292 0 0         if (!defined $next_send) {
2293 0           return 2;
2294             }
2295              
2296 0           $ctx->{write_buf} = $next_send->{'value'};
2297 0           $ctx->{write_offset} = 0;
2298              
2299 0           my $wait_time;
2300              
2301 0 0         if (!defined $next_send->{'delay_before'}) {
2302 0 0         if (defined $ctx->{middle_delay}) {
2303 0           $wait_time = $ctx->{middle_delay};
2304             }
2305              
2306             } else {
2307 0           $wait_time = $next_send->{'delay_before'};
2308             }
2309              
2310 0 0         if ($wait_time) {
2311             #warn "sleeping..";
2312 0           sleep $wait_time;
2313             }
2314             }
2315             }
2316              
2317             # impossible to reach here...
2318 0           return undef;
2319             }
2320              
2321             sub read_event_handler ($) {
2322 0     0 0   my ($ctx) = @_;
2323 0           while (1) {
2324 0           my $read_buf;
2325 0           my $bytes = sysread( $ctx->{sock}, $read_buf, $ctx->{buf_size} );
2326              
2327 0 0         if ( !defined $bytes ) {
2328 0 0         if ( $! == EAGAIN ) {
2329              
2330             #warn "read again...";
2331             #sleep 0.002;
2332 0           return 1;
2333             }
2334 0           warn "WARNING: $ctx->{name} - HTTP response read failure: $!";
2335 0           return undef;
2336             }
2337              
2338 0 0         if ( $bytes == 0 ) {
2339 0           return undef; # connection closed
2340             }
2341              
2342 0           $ctx->{resp} .= $read_buf;
2343              
2344             #warn "read $bytes ($read_buf) bytes.\n";
2345             }
2346              
2347             # impossible to reach here...
2348 0           return undef;
2349             }
2350              
2351             sub gen_curl_cmd_from_req ($$) {
2352 0     0 0   my ($block, $req) = @_;
2353              
2354 0           my $name = $block->name;
2355              
2356 0           $req = join '', map { $_->{value} } @$req;
  0            
2357              
2358             #use JSON::XS;
2359             #warn "Req: ", JSON::XS->new->encode([$req]), "\n";
2360              
2361 0           my ($meth, $uri, $http_ver);
2362 0 0         if ($req =~ m{^\s*(\w+)\s+(\S+)\s+HTTP/(\S+)\r?\n}smig) {
    0          
2363 0           ($meth, $uri, $http_ver) = ($1, $2, $3);
2364              
2365             } elsif ($req =~ m{^\s*(\w+)\s+(.*\S)\s*\r?\n}smig) {
2366             # NB: there can be trailing spaces in the HTTP 0.9,
2367             # but it will be ignored by the server
2368 0           ($meth, $uri) = ($1, $2);
2369 0           $http_ver = '0.9';
2370              
2371             } else {
2372 0           bail_out "$name - cannot parse the status line in the request: $req";
2373             }
2374              
2375             # remove 'user-agent' and 'accept' request headers from curl
2376             # because test-nginx does not send these header by default
2377 0           my @args = ('curl', '-i', '-H', 'User-Agent:', '-H', 'Accept:',
2378             '-H', 'Host:');
2379              
2380 0           my $curl_protocol = $block->curl_protocol;
2381 0 0         if (!defined $curl_protocol) {
2382 0           $curl_protocol = "http";
2383             }
2384              
2385 0 0         if ($Test::Nginx::Util::Verbose) {
2386 0           push @args, "-vv";
2387              
2388             } else {
2389 0           push @args, '-sS';
2390             }
2391              
2392 0 0         if (use_http3($block)) {
    0          
2393 0           push @args, '--http3-only';
2394 0           push @args, '-k';
2395 0           $curl_protocol = "https";
2396              
2397             } elsif (use_http2($block)) {
2398 0           push @args, '--http2-prior-knowledge';
2399             }
2400              
2401 0 0         if ($meth eq 'HEAD') {
2402 0           push @args, '-I';
2403              
2404             } else {
2405 0 0         if ($meth ne 'GET') {
2406 0           push @args, "-X", $meth;
2407             }
2408             }
2409              
2410 0 0         if ($http_ver ne '1.1') {
2411             # HTTP 1.0 or HTTP 0.9
2412 0           push @args, '-0';
2413             }
2414              
2415 0           my @headers;
2416 0 0         if ($http_ver ge '1.0') {
2417 0 0         if ($req =~ m{\G(.*?)\r?\n\r?\n}gcs) {
2418 0           my $headers = $1;
2419             #warn "raw headers: $headers\n";
2420             @headers = grep {
2421 0           !/^Connection\s*:/i
  0            
2422             } split /\r\n/, $headers;
2423              
2424             } else {
2425 0           bail_out "cannot parse the header entries in the request: $req";
2426             }
2427             }
2428              
2429             #warn "headers: @headers ", scalar(@headers), "\n";
2430              
2431 0           my $found_content_type;
2432              
2433 0           for my $h (@headers) {
2434             #warn "h: $h\n";
2435 0 0         if ($h =~ /^\s*Content-Type\s*:/i) {
2436 0           $found_content_type = 1;
2437             }
2438              
2439 0           push @args, '-H', $h;
2440             }
2441              
2442 0 0         if ($req =~ m{\G(.+)}gcsm) {
2443             #warn "!! POST body data len: ", length($1);
2444 0 0         if (!$found_content_type) {
2445 0           push @args, "-H", 'Content-Type: ';
2446             }
2447 0           my $body = $1;
2448 0           my $filename = html_dir() . "/curl.data.bin";
2449 0           push @args, '--data-binary', '@' . $filename;
2450 0 0         open my $fh, ">", $filename or die "Could not open file. $!";
2451 0           print $fh $body;
2452 0           close $fh;
2453             }
2454              
2455 0           my $timeout = $block->timeout;
2456 0 0         if (!$timeout) {
2457 0           $timeout = timeout();
2458             }
2459              
2460 0           push @args, '--connect-timeout', $timeout;
2461              
2462             # http3 use udp, the connect-timeout does not take effect
2463             # so use the max-time instead.
2464 0           push @args, '--max-time', $timeout;
2465              
2466 0           my $link;
2467              
2468 0           my $server_addr = $block->server_addr_for_client;
2469              
2470 0 0         if (!defined $server_addr) {
2471 0           $server_addr = $ServerAddr;
2472             }
2473              
2474             {
2475 0           my $server = $server_addr;
  0            
2476 0           my $port = $ServerPortForClient;
2477 0           $link = "$curl_protocol://$server:$port$uri";
2478             }
2479              
2480 0           my $curl_options = $block->curl_options;
2481 0 0         if (defined $curl_options) {
2482 0           push @args, $curl_options;
2483             }
2484              
2485 0           push @args, $link;
2486              
2487 0           return \@args;
2488             }
2489              
2490             sub gen_ab_cmd_from_req ($$@) {
2491 0     0 0   my ($block, $req, $nreqs, $concur) = @_;
2492              
2493 0   0       $nreqs ||= 100000000;
2494 0   0       $concur ||= 2;
2495              
2496 0 0         if ($nreqs < $concur) {
2497 0           $concur = $nreqs;
2498             }
2499              
2500 0           my $name = $block->name;
2501              
2502 0           $req = join '', map { $_->{value} } @$req;
  0            
2503              
2504             #use JSON::XS;
2505             #warn "Req: ", JSON::XS->new->encode([$req]), "\n";
2506              
2507 0           my ($meth, $uri, $http_ver);
2508 0 0         if ($req =~ m{^\s*(\w+)\s+(\S+)\s+HTTP/(\S+)\r?\n}smig) {
    0          
2509 0           ($meth, $uri, $http_ver) = ($1, $2, $3);
2510              
2511             } elsif ($req =~ m{^\s*(\w+)\s+(.*\S)\s*\r?\n}smig) {
2512             # NB: there can be trailing spaces in the HTTP 0.9,
2513             # but it will be ignored by the server
2514 0           ($meth, $uri) = ($1, $2);
2515 0           $http_ver = '0.9';
2516              
2517             } else {
2518 0           bail_out "$name - cannot parse the status line in the request: $req";
2519             }
2520              
2521             #warn "HTTP version: $http_ver\n";
2522              
2523 0           my @opts = ("-c$concur", '-k', "-n$nreqs");
2524              
2525 0           my $prog;
2526 0 0 0       if ($http_ver eq '1.1' && $meth eq 'GET') {
2527 0           $prog = 'weighttp';
2528              
2529             } else {
2530             # HTTP 1.0 or HTTP 0.9
2531 0           $prog = 'ab';
2532 0           unshift @opts, '-r', '-d', '-S';
2533             }
2534              
2535 0           my @headers;
2536 0 0         if ($http_ver ge '1.0') {
2537 0 0         if ($req =~ m{\G(.*?)\r?\n\r?\n}gcs) {
2538 0           my $headers = $1;
2539             #warn "raw headers: $headers\n";
2540             @headers = grep {
2541 0   0       !/^Connection\s*:/i
  0            
2542             && !/^Host: \Q$ServerName\E$/i
2543             && !/^Content-Length\s*:/i
2544             } split /\r\n/, $headers;
2545              
2546             } else {
2547 0           bail_out "cannot parse the header entries in the request: $req";
2548             }
2549             }
2550              
2551             #warn "headers: @headers ", scalar(@headers), "\n";
2552              
2553 0           for my $h (@headers) {
2554             #warn "h: $h\n";
2555 0 0 0       if ($prog eq 'ab' && $h =~ /^\s*Content-Type\s*:\s*(.*\S)/i) {
2556 0           my $type = $1;
2557 0           push @opts, '-T', $type;
2558              
2559             } else {
2560 0           push @opts, '-H', $h;
2561             }
2562             }
2563              
2564 0           my $bodyfile;
2565              
2566 0 0 0       if ($req =~ m{\G.+}gcs || $meth eq 'POST' || $meth eq 'PUT') {
      0        
2567 0           my $body = $&;
2568              
2569 0 0         if (!defined $body) {
2570 0           $body = '';
2571             }
2572              
2573 0           my ($out, $bodyfile) = tempfile("bodyXXXXXXX", UNLINK => 1,
2574             SUFFIX => '.temp', TMPDIR => 1);
2575 0           print $out $body;
2576 0           close $out;
2577              
2578 0 0         if ($meth eq 'PUT') {
    0          
    0          
2579 0           push @opts, '-u', $bodyfile;
2580              
2581             } elsif ($meth eq 'POST') {
2582 0           push @opts, '-p', $bodyfile;
2583              
2584             } elsif ($meth eq 'GET') {
2585 0           warn "WARNING: method $meth not supported for ab when taking a request body\n";
2586              
2587             } else {
2588 0           warn "WARNING: method $meth not supported for ab when taking a request body\n";
2589 0           $meth = 'PUT';
2590 0           push @opts, '-p', $bodyfile;
2591             }
2592             }
2593              
2594 0 0         if ($meth eq 'HEAD') {
2595 0           unshift @opts, '-i';
2596             }
2597              
2598 0           my $link;
2599              
2600 0           my $server_addr = $block->server_addr_for_client;
2601              
2602 0 0         if (!defined $server_addr) {
2603 0           $server_addr = $ServerAddr;
2604             }
2605              
2606             {
2607 0           my $server = $server_addr;
  0            
2608 0           my $port = $ServerPortForClient;
2609 0           $link = "http://$server:$port$uri";
2610             }
2611              
2612 0           my @cmd = ($prog, @opts, $link);
2613              
2614 0 0         if ($Test::Nginx::Util::Verbose) {
2615 0           warn "command: @cmd\n";
2616             }
2617              
2618 0           return \@cmd;
2619             }
2620              
2621             sub get_linear_regression_slope ($) {
2622 0     0 0   my $list = shift;
2623              
2624 0           my $n = @$list;
2625 0           my $avg_x = ($n + 1) / 2;
2626 0           my $avg_y = sum(@$list) / $n;
2627              
2628 0           my $x = 0;
2629 0           my $avg_xy = sum(map { $x++; $x * $_ } @$list) / $n;
  0            
  0            
2630 0           my $avg_x2 = sum(map { $_ * $_ } 1 .. $n) / $n;
  0            
2631 0           my $denom = $avg_x2 - $avg_x * $avg_x;
2632 0 0         if ($denom == 0) {
2633 0           return 'Inf';
2634             }
2635 0           my $k = ($avg_xy - $avg_x * $avg_y) / $denom;
2636 0           return sprintf("%.01f", $k);
2637             }
2638              
2639             1;
2640             __END__