Annotation of embedaddon/lighttpd/tests/LightyTest.pm, revision 1.1.1.2

1.1       misho       1: package LightyTest;
1.1.1.2 ! misho       2: 
1.1       misho       3: use strict;
                      4: use IO::Socket;
                      5: use Test::More;
                      6: use Socket;
                      7: use Cwd 'abs_path';
                      8: use POSIX qw(:sys_wait_h dup2);
                      9: use Errno qw(EADDRINUSE);
                     10: 
1.1.1.2 ! misho      11: sub find_program {
        !            12:        my @DEFAULT_PATHS = ('/usr/bin/', '/usr/local/bin/');
        !            13:        my ($envname, $program) = @_;
        !            14:        my $location;
        !            15: 
        !            16:        if (defined $ENV{$envname}) {
        !            17:                $location = $ENV{$envname};
        !            18:        } else {
        !            19:                $location = `which "$program" 2>/dev/null`;
        !            20:                chomp $location;
        !            21:                if (! -x $location) {
        !            22:                        for my $path (@DEFAULT_PATHS) {
        !            23:                                $location = $path . $program;
        !            24:                                last if -x $location;
        !            25:                        }
        !            26:                }
        !            27:        }
        !            28: 
        !            29:        if (-x $location) {
        !            30:                $ENV{$envname} = $location;
        !            31:                return 1;
        !            32:        } else {
        !            33:                delete $ENV{$envname};
        !            34:                return 0;
        !            35:        }
        !            36: }
        !            37: 
        !            38: BEGIN {
        !            39:        our $HAVE_PHP = find_program('PHP', 'php-cgi');
        !            40:        our $HAVE_PERL = find_program('PERL', 'perl');
        !            41:        if (!$HAVE_PERL) {
        !            42:                die "Couldn't find path to perl, but it obviously seems to be running";
        !            43:        }
        !            44: }
        !            45: 
1.1       misho      46: sub mtime {
                     47:        my $file = shift;
                     48:        my @stat = stat $file;
                     49:        return @stat ? $stat[9] : 0;
                     50: }
1.1.1.2 ! misho      51: 
1.1       misho      52: sub new {
                     53:        my $class = shift;
                     54:        my $self = {};
                     55:        my $lpath;
                     56: 
                     57:        $self->{CONFIGFILE} = 'lighttpd.conf';
                     58: 
                     59:        $lpath = (defined $ENV{'top_builddir'} ? $ENV{'top_builddir'} : '..');
                     60:        $self->{BASEDIR} = abs_path($lpath);
                     61: 
                     62:        $lpath = (defined $ENV{'top_builddir'} ? $ENV{'top_builddir'}."/tests/" : '.');
                     63:        $self->{TESTDIR} = abs_path($lpath);
                     64: 
                     65:        $lpath = (defined $ENV{'srcdir'} ? $ENV{'srcdir'} : '.');
                     66:        $self->{SRCDIR} = abs_path($lpath);
                     67: 
                     68: 
                     69:        if (mtime($self->{BASEDIR}.'/src/lighttpd') > mtime($self->{BASEDIR}.'/build/lighttpd')) {
                     70:                $self->{BINDIR} = $self->{BASEDIR}.'/src';
                     71:                if (mtime($self->{BASEDIR}.'/src/.libs')) {
                     72:                        $self->{MODULES_PATH} = $self->{BASEDIR}.'/src/.libs';
                     73:                } else {
                     74:                        $self->{MODULES_PATH} = $self->{BASEDIR}.'/src';
                     75:                }
                     76:        } else {
                     77:                $self->{BINDIR} = $self->{BASEDIR}.'/build';
                     78:                $self->{MODULES_PATH} = $self->{BASEDIR}.'/build';
                     79:        }
                     80:        $self->{LIGHTTPD_PATH} = $self->{BINDIR}.'/lighttpd';
                     81:        $self->{PORT} = 2048;
                     82: 
                     83:        my ($name, $aliases, $addrtype, $net) = gethostbyaddr(inet_aton("127.0.0.1"), AF_INET);
                     84: 
                     85:        $self->{HOSTNAME} = $name;
                     86: 
                     87:        bless($self, $class);
                     88: 
                     89:        return $self;
                     90: }
                     91: 
                     92: sub listening_on {
                     93:        my $self = shift;
                     94:        my $port = shift;
                     95: 
1.1.1.2 ! misho      96:        my $remote = IO::Socket::INET->new(
        !            97:                Proto    => "tcp",
        !            98:                PeerAddr => "127.0.0.1",
        !            99:                PeerPort => $port) or return 0;
1.1       misho     100: 
                    101:        close $remote;
                    102: 
                    103:        return 1;
                    104: }
                    105: 
                    106: sub stop_proc {
                    107:        my $self = shift;
                    108: 
                    109:        my $pid = $self->{LIGHTTPD_PID};
                    110:        if (defined $pid && $pid != -1) {
                    111:                kill('TERM', $pid) or return -1;
                    112:                return -1 if ($pid != waitpid($pid, 0));
                    113:        } else {
                    114:                diag("\nProcess not started, nothing to stop");
                    115:                return -1;
                    116:        }
                    117: 
                    118:        return 0;
                    119: }
                    120: 
                    121: sub wait_for_port_with_proc {
                    122:        my $self = shift;
                    123:        my $port = shift;
                    124:        my $child = shift;
1.1.1.2 ! misho     125:        my $timeout = 10*10; # 10 secs (valgrind might take a while), select waits 0.1 s
1.1       misho     126: 
                    127:        while (0 == $self->listening_on($port)) {
                    128:                select(undef, undef, undef, 0.1);
                    129:                $timeout--;
                    130: 
                    131:                # the process is gone, we failed
                    132:                if (0 != waitpid($child, WNOHANG)) {
                    133:                        return -1;
                    134:                }
                    135:                if (0 >= $timeout) {
                    136:                        diag("\nTimeout while trying to connect; killing child");
                    137:                        kill('TERM', $child);
                    138:                        return -1;
                    139:                }
                    140:        }
                    141: 
                    142:        return 0;
                    143: }
                    144: 
                    145: sub start_proc {
                    146:        my $self = shift;
                    147:        # kill old proc if necessary
                    148:        #$self->stop_proc;
                    149: 
                    150:        if ($self->listening_on($self->{PORT})) {
                    151:                diag("\nPort ".$self->{PORT}." already in use");
                    152:                return -1;
                    153:        }
                    154: 
                    155:        # pre-process configfile if necessary
                    156:        #
                    157: 
                    158:        $ENV{'SRCDIR'} = $self->{BASEDIR}.'/tests';
                    159:        $ENV{'PORT'} = $self->{PORT};
                    160: 
                    161:        my @cmdline = ($self->{LIGHTTPD_PATH}, "-D", "-f", $self->{SRCDIR}."/".$self->{CONFIGFILE}, "-m", $self->{MODULES_PATH});
                    162:        if (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'strace') {
1.1.1.2 ! misho     163:                @cmdline = (qw(strace -tt -s 4096 -o strace -f -v), @cmdline);
1.1       misho     164:        } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'truss') {
                    165:                @cmdline = (qw(truss -a -l -w all -v all -o strace), @cmdline);
                    166:        } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'gdb') {
                    167:                @cmdline = ('gdb', '--batch', '--ex', 'run', '--ex', 'bt full', '--args', @cmdline);
                    168:        } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'valgrind') {
1.1.1.2 ! misho     169:                @cmdline = (qw(valgrind --tool=memcheck --track-origins=yes --show-reachable=yes --leak-check=yes --log-file=valgrind.%p), @cmdline);
1.1       misho     170:        }
                    171:        # diag("\nstarting lighttpd at :".$self->{PORT}.", cmdline: ".@cmdline );
                    172:        my $child = fork();
                    173:        if (not defined $child) {
                    174:                diag("\nFork failed");
                    175:                return -1;
                    176:        }
                    177:        if ($child == 0) {
                    178:                exec @cmdline or die($?);
                    179:        }
                    180: 
                    181:        if (0 != $self->wait_for_port_with_proc($self->{PORT}, $child)) {
                    182:                diag(sprintf('\nThe process %i is not up', $child));
                    183:                return -1;
                    184:        }
                    185: 
                    186:        $self->{LIGHTTPD_PID} = $child;
                    187: 
                    188:        0;
                    189: }
                    190: 
                    191: sub handle_http {
                    192:        my $self = shift;
                    193:        my $t = shift;
                    194:        my $EOL = "\015\012";
                    195:        my $BLANK = $EOL x 2;
                    196:        my $host = "127.0.0.1";
                    197: 
                    198:        my @request = $t->{REQUEST};
                    199:        my @response = $t->{RESPONSE};
                    200:        my $slow = defined $t->{SLOWREQUEST};
                    201:        my $is_debug = $ENV{"TRACE_HTTP"};
                    202: 
1.1.1.2 ! misho     203:        my $remote =
        !           204:                IO::Socket::INET->new(
        !           205:                        Proto    => "tcp",
        !           206:                        PeerAddr => $host,
        !           207:                        PeerPort => $self->{PORT});
1.1       misho     208: 
                    209:        if (not defined $remote) {
                    210:                diag("\nconnect failed: $!");
1.1.1.2 ! misho     211:                return -1;
1.1       misho     212:        }
                    213: 
                    214:        $remote->autoflush(1);
                    215: 
                    216:        if (!$slow) {
                    217:                diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
                    218:                foreach(@request) {
                    219:                        # pipeline requests
                    220:                        s/\r//g;
                    221:                        s/\n/$EOL/g;
                    222: 
                    223:                        print $remote $_.$BLANK;
                    224:                        diag("\n<< ".$_) if $is_debug;
                    225:                }
                    226:        } else {
                    227:                diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
                    228:                foreach(@request) {
                    229:                        # pipeline requests
                    230:                        chomp;
                    231:                        s/\r//g;
                    232:                        s/\n/$EOL/g;
                    233: 
                    234:                        print $remote $_;
                    235:                        diag("<< ".$_."\n") if $is_debug;
                    236:                        select(undef, undef, undef, 0.1);
                    237:                        print $remote "\015";
                    238:                        select(undef, undef, undef, 0.1);
                    239:                        print $remote "\012";
                    240:                        select(undef, undef, undef, 0.1);
                    241:                        print $remote "\015";
                    242:                        select(undef, undef, undef, 0.1);
                    243:                        print $remote "\012";
                    244:                        select(undef, undef, undef, 0.1);
                    245:                }
1.1.1.2 ! misho     246: 
1.1       misho     247:        }
                    248:        diag("\n... done") if $is_debug;
                    249: 
                    250:        my $lines = "";
                    251: 
                    252:        diag("\nreceiving response") if $is_debug;
                    253:        # read everything
                    254:        while(<$remote>) {
                    255:                $lines .= $_;
                    256:                diag(">> ".$_) if $is_debug;
                    257:        }
                    258:        diag("\n... done") if $is_debug;
1.1.1.2 ! misho     259: 
1.1       misho     260:        close $remote;
                    261: 
                    262:        my $full_response = $lines;
                    263: 
                    264:        my $href;
                    265:        foreach $href ( @{ $t->{RESPONSE} }) {
                    266:                # first line is always response header
                    267:                my %resp_hdr;
                    268:                my $resp_body;
                    269:                my $resp_line;
                    270:                my $conditions = $_;
                    271: 
                    272:                for (my $ln = 0; defined $lines; $ln++) {
                    273:                        (my $line, $lines) = split($EOL, $lines, 2);
                    274: 
                    275:                        # header finished
                    276:                        last if(!defined $line or length($line) == 0);
                    277: 
                    278:                        if ($ln == 0) {
                    279:                                # response header
                    280:                                $resp_line = $line;
                    281:                        } else {
                    282:                                # response vars
                    283: 
                    284:                                if ($line =~ /^([^:]+):\s*(.+)$/) {
                    285:                                        (my $h = $1) =~ tr/[A-Z]/[a-z]/;
                    286: 
                    287:                                        if (defined $resp_hdr{$h}) {
1.1.1.2 ! misho     288: #                                              diag(sprintf("\nheader '%s' is duplicated: '%s' and '%s'\n",
        !           289: #                                                           $h, $resp_hdr{$h}, $2));
1.1       misho     290:                                                $resp_hdr{$h} .= ', '.$2;
                    291:                                        } else {
                    292:                                                $resp_hdr{$h} = $2;
                    293:                                        }
                    294:                                } else {
                    295:                                        diag(sprintf("\nunexpected line '%s'", $line));
                    296:                                        return -1;
                    297:                                }
                    298:                        }
                    299:                }
                    300: 
                    301:                if (not defined($resp_line)) {
                    302:                        diag(sprintf("\nempty response"));
                    303:                        return -1;
                    304:                }
                    305: 
                    306:                $t->{etag} = $resp_hdr{'etag'};
                    307:                $t->{date} = $resp_hdr{'date'};
                    308: 
                    309:                # check length
                    310:                if (defined $resp_hdr{"content-length"}) {
                    311:                        $resp_body = substr($lines, 0, $resp_hdr{"content-length"});
                    312:                        if (length($lines) < $resp_hdr{"content-length"}) {
                    313:                                $lines = "";
                    314:                        } else {
                    315:                                $lines = substr($lines, $resp_hdr{"content-length"});
                    316:                        }
                    317:                        undef $lines if (length($lines) == 0);
                    318:                } else {
                    319:                        $resp_body = $lines;
                    320:                        undef $lines;
                    321:                }
                    322: 
                    323:                # check conditions
                    324:                if ($resp_line =~ /^(HTTP\/1\.[01]) ([0-9]{3}) .+$/) {
                    325:                        if ($href->{'HTTP-Protocol'} ne $1) {
                    326:                                diag(sprintf("\nproto failed: expected '%s', got '%s'", $href->{'HTTP-Protocol'}, $1));
                    327:                                return -1;
                    328:                        }
                    329:                        if ($href->{'HTTP-Status'} ne $2) {
                    330:                                diag(sprintf("\nstatus failed: expected '%s', got '%s'", $href->{'HTTP-Status'}, $2));
                    331:                                return -1;
                    332:                        }
                    333:                } else {
                    334:                        diag(sprintf("\nunexpected resp_line '%s'", $resp_line));
                    335:                        return -1;
                    336:                }
                    337: 
                    338:                if (defined $href->{'HTTP-Content'}) {
                    339:                        $resp_body = "" unless defined $resp_body;
                    340:                        if ($href->{'HTTP-Content'} ne $resp_body) {
                    341:                                diag(sprintf("\nbody failed: expected '%s', got '%s'", $href->{'HTTP-Content'}, $resp_body));
                    342:                                return -1;
                    343:                        }
                    344:                }
1.1.1.2 ! misho     345: 
1.1       misho     346:                if (defined $href->{'-HTTP-Content'}) {
                    347:                        if (defined $resp_body && $resp_body ne '') {
                    348:                                diag(sprintf("\nbody failed: expected empty body, got '%s'", $resp_body));
                    349:                                return -1;
                    350:                        }
                    351:                }
                    352: 
                    353:                foreach (keys %{ $href }) {
                    354:                        next if $_ eq 'HTTP-Protocol';
                    355:                        next if $_ eq 'HTTP-Status';
                    356:                        next if $_ eq 'HTTP-Content';
                    357:                        next if $_ eq '-HTTP-Content';
                    358: 
                    359:                        (my $k = $_) =~ tr/[A-Z]/[a-z]/;
                    360: 
                    361:                        my $verify_value = 1;
                    362:                        my $key_inverted = 0;
                    363: 
                    364:                        if (substr($k, 0, 1) eq '+') {
                    365:                                $k = substr($k, 1);
                    366:                                $verify_value = 0;
                    367:                        } elsif (substr($k, 0, 1) eq '-') {
                    368:                                ## the key should NOT exist
                    369:                                $k = substr($k, 1);
                    370:                                $key_inverted = 1;
                    371:                                $verify_value = 0; ## skip the value check
1.1.1.2 ! misho     372:                        }
1.1       misho     373: 
                    374:                        if ($key_inverted) {
                    375:                                if (defined $resp_hdr{$k}) {
                    376:                                        diag(sprintf("\nheader '%s' MUST not be set", $k));
                    377:                                        return -1;
                    378:                                }
                    379:                        } else {
                    380:                                if (not defined $resp_hdr{$k}) {
                    381:                                        diag(sprintf("\nrequired header '%s' is missing", $k));
                    382:                                        return -1;
                    383:                                }
                    384:                        }
                    385: 
                    386:                        if ($verify_value) {
                    387:                                if ($href->{$_} =~ /^\/(.+)\/$/) {
                    388:                                        if ($resp_hdr{$k} !~ /$1/) {
1.1.1.2 ! misho     389:                                                diag(sprintf(
        !           390:                                                        "\nresponse-header failed: expected '%s', got '%s', regex: %s",
        !           391:                                                        $href->{$_}, $resp_hdr{$k}, $1));
1.1       misho     392:                                                return -1;
                    393:                                        }
                    394:                                } elsif ($href->{$_} ne $resp_hdr{$k}) {
1.1.1.2 ! misho     395:                                        diag(sprintf(
        !           396:                                                "\nresponse-header failed: expected '%s', got '%s'",
        !           397:                                                $href->{$_}, $resp_hdr{$k}));
1.1       misho     398:                                        return -1;
                    399:                                }
                    400:                        }
                    401:                }
                    402:        }
                    403: 
                    404:        # we should have sucked up everything
                    405:        if (defined $lines) {
                    406:                diag(sprintf("\nunexpected lines '%s'", $lines));
                    407:                return -1;
                    408:        }
                    409: 
                    410:        return 0;
                    411: }
                    412: 
                    413: sub spawnfcgi {
                    414:        my ($self, $binary, $port) = @_;
                    415:        my $child = fork();
                    416:        if (not defined $child) {
                    417:                diag("\nCouldn't fork");
                    418:                return -1;
                    419:        }
                    420:        if ($child == 0) {
                    421:                my $iaddr   = inet_aton('localhost') || die "no host: localhost";
                    422:                my $proto   = getprotobyname('tcp');
                    423:                socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
                    424:                setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
                    425:                bind(SOCK, sockaddr_in($port, $iaddr)) || die "bind: $!";
                    426:                listen(SOCK, 1024) || die "listen: $!";
                    427:                dup2(fileno(SOCK), 0) || die "dup2: $!";
                    428:                exec { $binary } ($binary) or die($?);
                    429:        } else {
                    430:                if (0 != $self->wait_for_port_with_proc($port, $child)) {
                    431:                        diag(sprintf("\nThe process %i is not up (port %i, %s)", $child, $port, $binary));
                    432:                        return -1;
                    433:                }
                    434:                return $child;
                    435:        }
                    436: }
                    437: 
                    438: sub endspawnfcgi {
                    439:        my ($self, $pid) = @_;
                    440:        return -1 if (-1 == $pid);
                    441:        kill(2, $pid);
                    442:        waitpid($pid, 0);
                    443:        return 0;
                    444: }
                    445: 
                    446: 1;

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>