File Coverage

blib/lib/Test/Nginx/Socket.pm
Criterion Covered Total %
statement 324 1070 30.2
branch 112 574 19.5
condition 25 185 13.5
subroutine 34 53 64.1
pod 1 30 3.3
total 496 1912 25.9


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