5 ##################################################
7 ##################################################
16 use HTTP
::Cookies
::Netscape
;
17 use File
::Temp qw
/ tempfile /;
18 use File
::Copy qw
/ cp /;
23 plan
( skip_all
=> "creating testsites requires root permission" );
25 our $omd_symlink_created = 0;
27 ##################################################
28 # HTML::Lint installed?
29 my $use_html_lint = 0;
35 ##################################################
36 # dont test over a proxy
37 delete $ENV{'HTTP_PROXY'};
38 delete $ENV{'HTTPS_PROXY'};
39 delete $ENV{'FTP_PROXY'};
40 delete $ENV{'http_proxy'};
41 delete $ENV{'https_proxy'};
42 delete $ENV{'ftp_proxy'};
45 ##################################################
49 returns path to omd binary
55 return $omd_bin if defined $omd_bin;
57 $omd_bin = $ENV{'OMD_BIN'} || '/usr/bin/omd';
61 if($omd_bin eq '/usr/bin/omd') {
62 BAIL_OUT
('Broken installation, got /usr/bin/omd but no /omd')
63 } elsif($omd_bin eq 'destdir/opt/omd/versions/default/bin/omd') {
64 symlink(getcwd
()."/destdir/omd", '/omd');
65 $omd_symlink_created = 1;
67 BAIL_OUT
('did not find a valid /omd, please make sure it exists')
72 my $target = readlink('/omd');
73 if($omd_bin eq '/usr/bin/omd') {
74 if($target ne "/opt/omd" && $target ne "opt/omd") {
75 BAIL_OUT
('symlink for /omd already exists but is wrong: should be: /opt/omd but got: '.$target);
78 elsif($omd_bin eq 'destdir/opt/omd/versions/default/bin/omd') {
79 if($target ne getcwd
()."/destdir/omd") {
80 BAIL_OUT
('symlink for /omd already exists but is wrong: should be: '.getcwd
().'/destdir/omd but got: '.$target);
84 BAIL_OUT
('cannot run tests, /omd has to be a symlink to '.getcwd
().'/destdir/omd (or /opt/omd for testing packages) in order to run tests for the source version');
88 -x
$omd_bin or BAIL_OUT
($omd_bin." is required for further tests: $!");
93 ##################################################
97 execute a test command
101 cmd => command line to execute
102 exit => expected exit code (set to undef to ignore exit code verification)
103 like => (list of) regular expressions which have to match stdout
104 errlike => (list of) regular expressions which have to match stderr, default: empty
105 sleep => time to wait after executing the command
111 my($rc, $stderr) = ( -1, '') ;
115 isnt
($test->{'cmd'}, undef, "running cmd: ".$test->{'cmd'}) or $return = 0;
117 my($prg,$arg) = split(/\s+/, $test->{'cmd'}, 2);
118 my $t = Test
::Cmd
->new(prog
=> $prg, workdir
=> '') or die($!);
121 local $SIG{ALRM
} = sub { die "timeout on cmd: ".$test->{'cmd'}."\n" };
122 $t->run(args
=> $arg, stdin
=> $test->{'stdin'});
128 $stderr = $t->stderr;
129 $stderr = TestUtils
::_clean_stderr
($stderr);
134 $test->{'exit'} = 0 unless exists $test->{'exit'};
135 if(defined $test->{'exit'} and $test->{'exit'} != -1) {
136 ok
($rc == $test->{'exit'}, "exit code: ".$rc." == ".$test->{'exit'}) or do { _diag_cmd
($test, $t); $return = 0 };
140 if(defined $test->{'like'}) {
141 for my $expr (ref $test->{'like'} eq 'ARRAY' ? @
{$test->{'like'}} : $test->{'like'} ) {
142 like
($t->stdout, $expr, "stdout like ".$expr) or do { diag
("\ncmd: '".$test->{'cmd'}."' failed\n"); $return = 0 };
147 $test->{'errlike'} = '/^\s*$/' unless exists $test->{'errlike'};
148 if(defined $test->{'errlike'}) {
149 for my $expr (ref $test->{'errlike'} eq 'ARRAY' ? @
{$test->{'errlike'}} : $test->{'errlike'} ) {
150 like
($stderr, $expr, "stderr like ".$expr) or do { diag
("\ncmd: '".$test->{'cmd'}."' failed"); $return = 0 };
154 # sleep after the command?
155 if(defined $test->{'sleep'}) {
156 ok
(sleep($test->{'sleep'}), "slept $test->{'sleep'} seconds") or do { $return = 0 };
160 $test->{'stdout'} = $t->stdout;
161 $test->{'stderr'} = $t->stderr;
162 $test->{'exit'} = $rc;
168 ##################################################
172 verify contents of a file
176 file => file to check
177 like => (list of) regular expressions which have to match
178 unlike => (list of) regular expressions which must not match stderr
188 if(defined $test->{'like'}) {
189 @like = ref $test->{'like'} eq 'ARRAY' ? @
{$test->{'like'}} : $test->{'like'};
192 if(defined $test->{'unlike'}) {
193 @unlike = ref $test->{'unlike'} eq 'ARRAY' ? @
{$test->{'unlike'}} : $test->{'unlike'};
196 ok
(-r
$test->{'file'}, $test->{'file'}." does exist");
199 skip
'file missing', (scalar @like + scalar @unlike) unless -r
$test->{'file'};
202 open my $fh, $test->{'file'} or die "Couldn't open file ".$test->{'file'}.": $!";
207 if(defined $test->{'like'}) {
208 for my $expr (@like) {
209 like
($content, $expr, "content like ".$expr) or $failed++;
214 if(defined $test->{'unlike'}) {
215 for my $expr (@unlike) {
216 unlike
($content, $expr, "output unlike ".$expr) or $failed++;
221 return 1 unless $failed;
226 ##################################################
228 =head2 create_test_site
230 creates a test site and returns the name
233 sub create_test_site
{
234 my $site = $_[0] || "testsite";
235 if(test_command
({ cmd
=> TestUtils
::get_omd_bin
()." create $site" })) {
242 ##################################################
244 =head2 remove_test_site
249 sub remove_test_site
{
251 test_command
({ cmd
=> TestUtils
::get_omd_bin
()." rm $site", stdin
=> "yes\n" });
256 ##################################################
264 url => url to request
265 auth => authentication (realm:user:pass)
266 code => expected response code
267 like => (list of) regular expressions which have to match content
268 unlike => (list of) regular expressions which must not match content
269 skip_html_lint => flag to disable the html lint checking
270 skip_link_check => (list of) regular expressions to skip the link checks for
271 waitfor => wait till regex occurs (max 60sec)
279 my $page = _request
($test);
281 # wait for something?
282 if(defined $test->{'waitfor'}) {
284 my $waitfor = $test->{'waitfor'};
286 while($now < $start + 60) {
287 if($page->{'content'} =~ m/$waitfor/mx) {
288 ok
(1, "content ".$waitfor." found after ".($now - $start)."seconds");
294 $page = _request
($test);
296 fail
("content did not occur within 60 seconds") unless $found;
301 $test->{'code'} = 200 unless exists $test->{'code'};
302 if(defined $test->{'code'}) {
303 is
($page->{'code'}, $test->{'code'}, "response code for ".$test->{'url'}." is: ".$test->{'code'}) or _diag_request
($test, $page);
307 if(defined $test->{'content_type'}) {
308 is
($page->{'content_type'}, $test->{'content_type'}, 'Content-Type is: '.$test->{'content_type'});
312 if(defined $test->{'like'}) {
313 for my $expr (ref $test->{'like'} eq 'ARRAY' ? @
{$test->{'like'}} : $test->{'like'} ) {
314 like
($page->{'content'}, $expr, "content like ".$expr);
318 # not matching output
319 if(defined $test->{'unlike'}) {
320 for my $expr (ref $test->{'unlike'} eq 'ARRAY' ? @
{$test->{'unlike'}} : $test->{'unlike'} ) {
321 unlike
($page->{'content'}, $expr, "content unlike ".$expr) or _diag_request
($test, $page);
327 if($page->{'content_type'} =~ 'text\/html') {
328 unless(defined $test->{'skip_html_lint'} && $test->{'skip_html_lint'} == 1) {
329 if($use_html_lint == 0) {
330 skip
"no HTML::Lint installed", 2;
332 if($page->{'content'} =~ /^\[.*\]$/mx) {
333 skip
"no lint check for json data", 2;
335 my $lint = new HTML
::Lint
;
336 isa_ok
( $lint, "HTML::Lint" );
338 $lint->parse($page->{'content'});
339 my @errors = $lint->errors;
340 @errors = _diag_lint_errors_and_remove_some_exceptions
($lint);
341 is
( scalar @errors, 0, "No errors found in HTML (".$test->{'url'}.")" );
342 $lint->clear_errors();
347 # check for missing images / css or js
348 if($page->{'content_type'} =~ 'text\/html'
349 and (!defined $test->{'skip_html_links'} or $test->{'skip_html_links'} == 0)
351 my $content = $page->{'content'};
352 $content =~ s/<\!\-\-.*?\-\->//gsmx;
353 my @matches = $content =~ m/(src|href)=['|"](.+?)['|"]/gi;
356 for my $match (@matches) {
359 next if $match =~ m/^http/;
360 next if $match =~ m/^mailto:/;
361 next if $match =~ m/^#/;
362 next if $match =~ m/^javascript:/;
363 next if $match =~ m/internal&srv=runtime/;
364 if(defined $test->{'skip_link_check'}) {
366 for my $expr (ref $test->{'skip_link_check'} eq 'ARRAY' ? @
{$test->{'skip_link_check'}} : $test->{'skip_link_check'} ) {
367 if($skip == 0 and $match =~ m/$expr/) {
373 $links_to_check->{$match} = 1;
376 for my $test_url (keys %{$links_to_check}) {
377 $test_url = _get_url
($test->{'url'}, $test_url);
378 our $already_checked;
379 $already_checked = {} unless defined $already_checked;
380 next if defined $already_checked->{$test_url};
381 #diag("checking link: ".$test_url);
382 my $req = _request
({url
=> $test_url, auth
=> $test->{'auth'}});
383 if($req->{'response'}->is_redirect) {
385 while(my $location = $req->{'response'}->{'_headers'}->{'location'}) {
386 if($location !~ m/^(http|\/)/gmx
) { $location = _relative_url
($location, $req->{'response'}->base()->as_string()); }
387 $req= _request
($location);
389 last if $redirects > 10;
392 if($req->{'code'} == 200) {
393 $already_checked->{$test_url} = 1;
396 diag
("got status ".$req->{'code'}." for url: '$test_url'");
398 my $tmp_test = { 'url' => $test_url };
399 _diag_request
($tmp_test, $req);
400 TestUtils
::bail_out_clean
("error in url '$test_url' linked from '".$test->{'url'}."'");
403 is
( $errors, 0, 'All stylesheets, images and javascript exist' );
408 ##################################################
418 return $config->{$key} if defined $config;
420 my $conf_file = "/omd/versions/default/share/omd/distro.info";
421 $config = read_config
($conf_file);
423 return $config->{$key};
427 ##################################################
431 return config from file
435 my $conf_file = shift;
438 open(my $fh, '<', $conf_file) or carp
("cannot open $conf_file: $!");
442 next if $line =~ m/^\s*(#|$)/;
443 $line =~ s/\s*#.*$//;
445 my($key,$value) = split/\s+\+=\s*/,$line,2;
449 ($key,$value) = split/\s+=\s*/,$line,2;
452 $value =~ s/\s+$// if defined $value;
454 $config->{$key} .= " ".$value;
456 $config->{$key} = $value;
465 ##################################################
474 my $timeout = shift || 120;
476 my $testfile = glob($file);
477 $file = $testfile if defined $testfile;
481 pass
("file: $file does already exist");
484 while($x < $timeout) {
486 pass
("file: $file appeared after $x seconds");
492 fail
("file: $file did not appear within $x seconds");
497 ##################################################
499 =head2 wait_for_content
501 waits for web page content until timeout
505 url => url to request
506 auth => authentication (realm:user:pass)
507 code => expected response code
508 like => (list of) regular expressions which have to match content
512 sub wait_for_content
{
514 my $timeout = shift || 120;
518 while ($x < $timeout) {
519 $req = _request
($test);
520 if($req->{'code'} == 200) {
521 #diag("code:$req->{code} url:$test->{url} auth:$test->{auth}");
523 for my $pattern (@
{$test->{'like'}}) {
524 if ($req->{'content'}!~/$pattern/) {
525 #diag("errors:$errors pattern:$pattern");
530 pass
(sprintf "content: [ %s ] appeared after $x seconds", join(',',@
{$test->{'like'}}));
534 diag
("Error searching for web content:\ncode:$req->{code}\nurl:$test->{url}\nauth:$test->{auth}\ncontent:$req->{content}");
539 fail
(sprintf "content: [ %s ] did not appear within $x seconds", join(',',@
{$test->{'like'}}));
544 ##################################################
546 =head2 prepare_obj_config
548 prepare test object config
551 sub prepare_obj_config
{
556 my $files = join(" ", (ref $src eq 'ARRAY' ? @
{$src} : $src));
557 for my $file (`find $files -type f`) {
560 if(-d
$dst) { $dstfile = $dst.'/'.basename
($file); }
561 cp
($file, $dstfile) or die("copy $file $dstfile failed: $!");
562 test_command
({ cmd
=> "/usr/bin/env sed -i $dstfile -e 's/###SITE###/".$site."/' -e 's|###ROOT###|/omd/sites/".$site."|'" }) if defined $site;
569 ##################################################
571 =head2 bail_out_clean
573 bail out from testing but some minor cleanup before
579 carp
("cleaning up before bailout");
581 my $omd_bin = get_omd_bin
();
582 for my $site (qw
/testsite testsite2 testsite3/) {
583 test_command
({ cmd
=> $omd_bin." rm $site", stdin
=> "yes\n", 'exit' => undef, errlike
=> undef });
590 ##################################################
592 =head2 _diag_lint_errors_and_remove_some_exceptions
594 removes some lint errors we want to ignore
597 sub _diag_lint_errors_and_remove_some_exceptions
{
600 LINT_ERROR
: for my $error ( $lint->errors ) {
601 my $err_str = $error->as_string;
602 for my $exclude_pattern (
603 "<IMG SRC=[^>]*>\ tag\ has\ no\ HEIGHT\ and\ WIDTH\ attributes",
604 "<IMG SRC=[^>]*>\ does\ not\ have\ ALT\ text\ defined",
605 "<input>\ is\ not\ a\ container\ \-\-\ <\/input>\ is\ not\ allowed",
606 "Unknown attribute \"start\" for tag <div>",
607 "Unknown attribute \"end\" for tag <div>",
609 "Unknown\ attribute\ \"placeholder\"\ for\ tag\ <input>",
611 next LINT_ERROR
if($err_str =~ m/$exclude_pattern/i);
613 diag
($error->as_string."\n");
614 push @return, $error;
620 ##################################################
628 url => url to request
629 auth => authentication (realm:user:pass)
639 if(!defined $cookie_jar) {
640 my($fh, $cookie_file) = tempfile
(undef, UNLINK
=> 1);
641 unlink ($cookie_file);
642 $cookie_jar = HTTP
::Cookies
::Netscape
->new(
643 file
=> $cookie_file,
648 my $ua = LWP
::UserAgent
->new;
651 $ua->cookie_jar($cookie_jar);
653 if(defined $data->{'auth'}) {
654 $data->{'url'} =~ m/(http|https):\/\
/(.*?)(\/|:\d
+)/;
657 if(defined $port and $port =~ m/^:(\d+)/) { $port = $1; } else { $port = 80; }
658 my($realm,$user,$pass) = split(/:/, $data->{'auth'}, 3);
659 $ua->credentials($netloc.":".$port, $realm, $user, $pass);
663 if(defined $data->{'post'}) {
664 $response = $ua->post($data->{'url'}, $data->{'post'});
666 $response = $ua->get($data->{'url'});
669 $return->{'response'} = $response;
670 $return->{'code'} = $response->code;
671 $return->{'content'} = $response->decoded_content;
672 $return->{'content_type'} = $response->header('Content-Type');
678 ##################################################
682 returns a absolute url
694 # split original url in host, path and file
695 if($url =~ m/^(http|https):\/\
/([^\/]*)(|\
/|:\d+)(.*?)$/) {
696 my $host = $1."://".$2.$3;
697 $host =~ s/\/$//; # remove last /
698 my $fullpath = $4 || '';
699 $fullpath =~ s/\?.*$//;
700 $fullpath =~ s/^\///;
701 my($path,$file) = ('', '');
702 if($fullpath =~ m/^(.+)\/(.*)$/) {
709 $path =~ s/^\///; # remove first /
711 if($link =~ m/^(http|https):\/\
//) {
714 elsif($link =~ m/^\//) { # absolute link
718 return $host."/".$link;
720 return $host."/".$path."/".$link;
724 TestUtils
::bail_out_clean
("unknown url scheme in _get_url: '".$url."'");
731 ##################################################
735 remove some know errors from stderr
739 my $text = shift || '';
740 $text =~ s/[\w\-]+: Could not reliably determine the server's fully qualified domain name, using .*? for ServerName//g;
741 $text =~ s/\[warn\] module \w+ is already loaded, skipping//g;
742 $text =~ s/Syntax OK//g;
743 $text =~ s/no crontab for \w+//g;
747 ##################################################
751 print diagnostic output for failed commands
757 my $stdout = $cmd->stdout || '';
758 my $stderr = $cmd->stderr || '';
759 diag
("\ncmd: '".$test->{'cmd'}."' failed\n");
760 diag
("stdout: ".$stdout."\n");
761 diag
("stderr: ".$stderr."\n");
763 # check logfiles on apache errors
764 if( $stdout =~ m/Starting dedicated Apache for site (\w+)[\.\ ]*ERROR/
765 or $stdout =~ m/500 Internal Server Error/) {
767 _tail
("apache logs:", "/omd/sites/$site/var/log/apache/error_log") if defined $site;
770 if( $stderr =~ m/User '(\w+)' still logged in or running processes/ ) {
772 diag
("ps: ".`ps -fu $site`) if $site;
777 ##################################################
781 print diagnostic output for failed requests
788 diag
(Dumper
($page->{'response'}));
790 $test->{'url'} =~ m/localhost\/(\w
+)\
//;
792 return unless defined $site;
794 # check logfiles on apache errors
795 _tail
("apache logs:", "/omd/sites/$site/var/log/apache/error_log");
797 _tail
("thruk logs:", "/omd/sites/$site/var/log/thruk.log") if $test->{'url'} =~ m
/\
/thruk\//;
802 ##################################################
812 return unless defined $file;
815 diag
(`tail -n100 $file`);
817 diag
("cannot read $file: $!");
823 ##################################################
825 =head2 _tail_apache_logs
827 print tail of all apache logs
830 sub _tail_apache_logs
{
831 _tail
("global apache logs:", glob('/var/log/apache*/error*log'));
832 _tail
("global apache logs:", glob('/var/log/httpd*/error*log'));
836 ##################################################
839 if(defined $omd_symlink_created and $omd_symlink_created == 1) {