diff options
author | Jelmer Vernooij <jelmer@samba.org> | 2008-09-18 19:51:03 +0200 |
---|---|---|
committer | Jelmer Vernooij <jelmer@samba.org> | 2008-09-18 19:51:03 +0200 |
commit | 182a0e349082fd43ec410cd6ac512376748fa27d (patch) | |
tree | 77780338f77b52287ca635d7aaacc46f846522a0 /selftest | |
parent | cb16488cb1bc29657a024a74c21b33445cde87b0 (diff) | |
download | samba-182a0e349082fd43ec410cd6ac512376748fa27d.tar.gz samba-182a0e349082fd43ec410cd6ac512376748fa27d.tar.bz2 samba-182a0e349082fd43ec410cd6ac512376748fa27d.zip |
Move selftest code to top-level.
Diffstat (limited to 'selftest')
-rw-r--r-- | selftest/README | 119 | ||||
-rw-r--r-- | selftest/SocketWrapper.pm | 66 | ||||
-rw-r--r-- | selftest/Subunit.pm | 97 | ||||
-rw-r--r-- | selftest/TODO | 2 | ||||
-rw-r--r-- | selftest/output/buildfarm.pm | 120 | ||||
-rw-r--r-- | selftest/output/html.pm | 354 | ||||
-rw-r--r-- | selftest/output/plain.pm | 195 | ||||
-rw-r--r-- | selftest/output/testresults.css | 129 | ||||
-rwxr-xr-x | selftest/selftest.pl | 874 | ||||
-rw-r--r-- | selftest/target/Kvm.pm | 167 | ||||
-rw-r--r-- | selftest/target/Samba3.pm | 434 | ||||
-rw-r--r-- | selftest/target/Samba4.pm | 957 | ||||
-rw-r--r-- | selftest/target/Windows.pm | 40 | ||||
-rwxr-xr-x | selftest/test_samba4.pl | 20 | ||||
-rwxr-xr-x | selftest/test_subunit.pl | 7 | ||||
-rwxr-xr-x | selftest/test_w2k3.sh | 48 |
16 files changed, 3629 insertions, 0 deletions
diff --git a/selftest/README b/selftest/README new file mode 100644 index 0000000000..f8be20a569 --- /dev/null +++ b/selftest/README @@ -0,0 +1,119 @@ +# vim: ft=rst + +This directory contains test scripts that are useful for running a +bunch of tests all at once. + +Available testsuites +==================== +The available testsuites are obtained from a script, usually +selftest/samba{3,4}_tests.sh. This script should for each testsuite output +the name of the test, the command to run and the environment that should be +provided. Use the included "plantest" function to generate the required output. + +Testsuite behaviour +================================ + +Exit code +------------ +The testsuites should exit with a non-zero exit code if at least one +test failed. Skipped tests should not influence the exit code. + +Output format +------------- +Testsuites can simply use the exit code to indicate whether all of their +tests have succeeded or one or more have failed. It is also possible to +provide more granular information using the Subunit protocol. + +This protocol works by writing simple messages to standard output. Any +messages that can not be interpreted by this protocol are considered comments +for the last announced test. + +Accepted commands are: + +test +~~~~~~~~~~~~ +test: <NAME> + +Announce that a new test with the specified name is starting + +success +~~~~~~~~~~~~~~~ +success: <NAME> + +Announce that the test with the specified name is done and ran successfully. + +failure +~~~~~~~~~~~~~~~ +failure: <NAME> +failure: <NAME> [ REASON ] + +Announce that the test with the specified name failed. Optionally, it is +possible to specify a reason it failed. + +skip +~~~~~~~~~~~~ +skip: <NAME> +skip: <NAME> [ REASON ] + +Announce that the test with the specified name was skipped. Optionally a +reason can be specified. + +knownfail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +knownfail: <NAME> +knownfail: <NAME> [ REASON ] + +Announce that the test with the specified name was run and failed as expected. +Alternatively it is also possible to simply return "failure:" here but +specify in the samba4-knownfailures file that it is failing. + +Environments +============ +Tests often need to run against a server with particular things set up, +a "environment". This environment is provided by the test "target": Samba 3, +Samba 4 or Windows. + +The following environments are currently available: + + - none: No server set up, no variables set. + - dc: Domain controller set up. The following environment variables will + be set: + + * USERNAME: Administrator user name + * PASSWORD: Administrator password + * DOMAIN: Domain name + * REALM: Realm name + * SERVER: DC host name + * SERVER_IP: DC IPv4 address + * NETBIOSNAME: DC NetBIOS name + * NETIOSALIAS: DC NetBIOS alias + + - member: Domain controller and member server that is joined to it set up. The + following environment variables will be set: + + * USERNAME: Domain administrator user name + * PASSWORD: Domain administrator password + * DOMAIN: Domain name + * REALM: Realm name + * SERVER: Name of the member server + + +Running tests +============= + +To run all the tests use:: + + make test + +To run a quick subset (aiming for about 1 minute of testing) run:: + + make quicktest + +To run a specific test, use this syntax:: + + make test TESTS=testname + +for example:: + + make test TESTS=samba4.BASE-DELETE + diff --git a/selftest/SocketWrapper.pm b/selftest/SocketWrapper.pm new file mode 100644 index 0000000000..e63605b8df --- /dev/null +++ b/selftest/SocketWrapper.pm @@ -0,0 +1,66 @@ +#!/usr/bin/perl +# Bootstrap Samba and run a number of tests against it. +# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +package SocketWrapper; + +use Exporter; +@ISA = qw(Exporter); +@EXPORT_OK = qw(setup_dir setup_pcap set_default_iface); + +use strict; +use FindBin qw($RealBin); + +sub setup_dir($$) +{ + my ($dir, $pcap) = @_; + my $pcap_dir = undef; + + if (defined($dir)) { + if ( -d $dir ) { + unlink <$dir/*>; + } else { + mkdir($dir, 0777); + } + + if ($pcap) { + $pcap_dir = $dir."/pcap"; + + if ( -d $pcap_dir ) { + unlink <$pcap_dir/*>; + } else { + mkdir($pcap_dir, 0777); + } + } + } + + if (defined($pcap_dir)) { + $ENV{SOCKET_WRAPPER_PCAP_DIR} = $pcap_dir; + } else { + delete $ENV{SOCKET_WRAPPER_PCAP_DIR}; + } + + if (defined($dir)) { + $ENV{SOCKET_WRAPPER_DIR} = $dir; + } else { + delete $ENV{SOCKET_WRAPPER_DIR}; + } + + return $dir; +} + +sub setup_pcap($) +{ + my ($pcap_file) = @_; + + $ENV{SOCKET_WRAPPER_PCAP_FILE} = $pcap_file; +} + +sub set_default_iface($) +{ + my ($i) = @_; + $ENV{SOCKET_WRAPPER_DEFAULT_IFACE} = $i; +} + +1; diff --git a/selftest/Subunit.pm b/selftest/Subunit.pm new file mode 100644 index 0000000000..05e51da541 --- /dev/null +++ b/selftest/Subunit.pm @@ -0,0 +1,97 @@ +package Subunit; + +require Exporter; +@ISA = qw(Exporter); +@EXPORT_OK = qw(parse_results); + +use strict; + +sub parse_results($$$$$) +{ + my ($msg_ops, $statistics, $fh, $expecting_failure, $open_tests) = @_; + my $unexpected_ok = 0; + my $expected_fail = 0; + my $unexpected_fail = 0; + my $unexpected_err = 0; + my $orig_open_len = $#$open_tests; + + while(<$fh>) { + if (/^test: (.+)\n/) { + $msg_ops->control_msg($_); + $msg_ops->start_test($open_tests, $1); + push (@$open_tests, $1); + } elsif (/^(success|successful|failure|skip|knownfail|error): (.*?)( \[)?([ \t]*)\n/) { + $msg_ops->control_msg($_); + my $reason = undef; + if ($3) { + $reason = ""; + # reason may be specified in next lines + my $terminated = 0; + while(<$fh>) { + $msg_ops->control_msg($_); + if ($_ eq "]\n") { $terminated = 1; last; } else { $reason .= $_; } + } + + unless ($terminated) { + $statistics->{TESTS_ERROR}++; + $msg_ops->end_test($open_tests, $2, $1, 1, "reason interrupted"); + return 1; + } + } + my $result = $1; + if ($1 eq "success" or $1 eq "successful") { + pop(@$open_tests); #FIXME: Check that popped value == $2 + if ($expecting_failure->(join(".", @$open_tests) . ".$2")) { + $statistics->{TESTS_UNEXPECTED_OK}++; + $msg_ops->end_test($open_tests, $2, $1, 1, $reason); + $unexpected_ok++; + } else { + $statistics->{TESTS_EXPECTED_OK}++; + $msg_ops->end_test($open_tests, $2, $1, 0, $reason); + } + } elsif ($1 eq "failure") { + pop(@$open_tests); #FIXME: Check that popped value == $2 + if ($expecting_failure->(join(".", @$open_tests) . ".$2")) { + $statistics->{TESTS_EXPECTED_FAIL}++; + $msg_ops->end_test($open_tests, $2, $1, 0, $reason); + $expected_fail++; + } else { + $statistics->{TESTS_UNEXPECTED_FAIL}++; + $msg_ops->end_test($open_tests, $2, $1, 1, $reason); + $unexpected_fail++; + } + } elsif ($1 eq "knownfail") { + pop(@$open_tests); #FIXME: Check that popped value == $2 + $statistics->{TESTS_EXPECTED_FAIL}++; + $msg_ops->end_test($open_tests, $2, $1, 0, $reason); + } elsif ($1 eq "skip") { + $statistics->{TESTS_SKIP}++; + pop(@$open_tests); #FIXME: Check that popped value == $2 + $msg_ops->end_test($open_tests, $2, $1, 0, $reason); + } elsif ($1 eq "error") { + $statistics->{TESTS_ERROR}++; + pop(@$open_tests); #FIXME: Check that popped value == $2 + $msg_ops->end_test($open_tests, $2, $1, 1, $reason); + $unexpected_err++; + } + } else { + $msg_ops->output_msg($_); + } + } + + while ($#$open_tests > $orig_open_len) { + $msg_ops->end_test($open_tests, pop(@$open_tests), "error", 1, + "was started but never finished!"); + $statistics->{TESTS_ERROR}++; + $unexpected_err++; + } + + return 1 if $unexpected_err > 0; + return 1 if $unexpected_fail > 0; + return 1 if $unexpected_ok > 0 and $expected_fail > 0; + return 0 if $unexpected_ok > 0 and $expected_fail == 0; + return 0 if $expected_fail > 0; + return 1; +} + +1; diff --git a/selftest/TODO b/selftest/TODO new file mode 100644 index 0000000000..67776ffc76 --- /dev/null +++ b/selftest/TODO @@ -0,0 +1,2 @@ +- warn about unexpected successes +- better way to detect that smbd has finished initialization diff --git a/selftest/output/buildfarm.pm b/selftest/output/buildfarm.pm new file mode 100644 index 0000000000..cee6c1e63a --- /dev/null +++ b/selftest/output/buildfarm.pm @@ -0,0 +1,120 @@ +#!/usr/bin/perl + +package output::buildfarm; + +use Exporter; +@ISA = qw(Exporter); + +use FindBin qw($RealBin); +use lib "$RealBin/.."; + +use Subunit qw(parse_results); + +use strict; + +sub new($$$) { + my ($class) = @_; + my $self = { + test_output => {}, + start_time => time() + }; + bless($self, $class); +} + +sub start_testsuite($$) +{ + my ($self, $name) = @_; + my $out = ""; + + $self->{NAME} = $name; + $self->{START_TIME} = time(); + + my $duration = $self->{START_TIME} - $self->{start_time}; + $out .= "--==--==--==--==--==--==--==--==--==--==--\n"; + $out .= "Running test $name (level 0 stdout)\n"; + $out .= "--==--==--==--==--==--==--==--==--==--==--\n"; + $out .= scalar(localtime())."\n"; + $out .= "SELFTEST RUNTIME: " . $duration . "s\n"; + $out .= "NAME: $name\n"; + + $self->{test_output}->{$name} = ""; + + print $out; +} + +sub output_msg($$) +{ + my ($self, $output) = @_; + + $self->{test_output}->{$self->{NAME}} .= $output; +} + +sub control_msg($$) +{ + my ($self, $output) = @_; + + $self->{test_output}->{$self->{NAME}} .= $output; +} + +sub end_testsuite($$$$$$) +{ + my ($self, $name, $result, $unexpected, $reason) = @_; + my $out = ""; + + $out .= "TEST RUNTIME: " . (time() - $self->{START_TIME}) . "s\n"; + + if (not $unexpected) { + $out .= "ALL OK\n"; + } else { + $out .= "ERROR: $reason\n"; + $out .= $self->{test_output}->{$name}; + } + + $out .= "==========================================\n"; + if (not $unexpected) { + $out .= "TEST PASSED: $name\n"; + } else { + $out .= "TEST FAILED: $name (status $reason)\n"; + } + $out .= "==========================================\n"; + + print $out; +} + +sub start_test($$$) +{ + my ($self, $parents, $testname) = @_; + + if ($#$parents == -1) { + $self->start_testsuite($testname); + } +} + +sub end_test($$$$$) +{ + my ($self, $parents, $testname, $result, $unexpected, $reason) = @_; + + if ($unexpected) { + $self->{test_output}->{$self->{NAME}} .= "UNEXPECTED($result): $testname\n"; + } + + if ($#$parents == -1) { + $self->end_testsuite($testname, $result, $unexpected, $reason); + } +} + +sub summary($) +{ + my ($self) = @_; + + print "DURATION: " . (time() - $self->{start_time}) . " seconds\n"; +} + +sub skip_testsuite($$$$) +{ + my ($self, $name, $reason) = @_; + + print "SKIPPED: $name\n"; +} + +1; diff --git a/selftest/output/html.pm b/selftest/output/html.pm new file mode 100644 index 0000000000..1049527129 --- /dev/null +++ b/selftest/output/html.pm @@ -0,0 +1,354 @@ +#!/usr/bin/perl + +package output::html; +use Exporter; +@ISA = qw(Exporter); + +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/.."; + +use Subunit qw(parse_results); + +sub new($$$) { + my ($class, $dirname, $statistics) = @_; + my $self = { + dirname => $dirname, + active_test => undef, + local_statistics => {}, + statistics => $statistics, + msg => "", + error_summary => { + skip => [], + expected_success => [], + unexpected_success => [], + expected_failure => [], + unexpected_failure => [], + skip_testsuites => [], + error => [] + } + }; + + link("$RealBin/output/testresults.css", "$dirname/testresults.css"); + + open(INDEX, ">$dirname/index.html"); + + bless($self, $class); + + $self->print_html_header("Samba Testsuite Run", *INDEX); + + print INDEX " <center>"; + print INDEX " <table>\n"; + print INDEX " <tr>\n"; + print INDEX " <td class=\"tableHead\">Test</td>\n"; + print INDEX " <td class=\"tableHead\">Result</td>\n"; + print INDEX " </tr>\n"; + + return $self; +} + +sub print_html_header($$$) +{ + my ($self, $title, $fh) = @_; + + print $fh "<html lang=\"en\">\n"; + print $fh "<head>\n"; + print $fh " <title>$title</title>\n"; + print $fh " <link rel=\"stylesheet\" type=\"text/css\" href=\"testresults.css\"/>\n"; + print $fh "</head>\n"; + print $fh "<body>\n"; + print $fh "<table width=\"100%\" border=\"0\" cellspacing=\"0\">\n"; + print $fh " <tr><td class=\"title\">$title</td></tr>\n"; + print $fh " <tr><td>\n"; +} + +sub print_html_footer($$) +{ + my ($self, $fh) = @_; + + print $fh "</td></tr>\n"; + print $fh "</table>\n"; + print $fh "</body>\n"; + print $fh "</html>\n"; +} + +sub output_msg($$); + +sub start_testsuite($$) +{ + my ($self, $name) = @_; + + $self->{local_statistics} = { + success => 0, + skip => 0, + error => 0, + failure => 0 + }; + + $self->{NAME} = $name; + $self->{HTMLFILE} = "$name.html"; + $self->{HTMLFILE} =~ s/[:\t\n \/]/_/g; + + open(TEST, ">$self->{dirname}/$self->{HTMLFILE}") or die("Unable to open $self->{HTMLFILE} for writing"); + + $self->print_html_header("Test Results for $name", *TEST); + + print TEST "<h2>Tests</h2>\n"; + + print TEST " <table>\n"; +} + +sub control_msg($$) +{ + my ($self, $output) = @_; + + $self->{msg} .= "<span class=\"control\">$output<br/></span>\n"; +} + +sub output_msg($$) +{ + my ($self, $output) = @_; + + unless (defined($self->{active_test})) { + print TEST "$output<br/>"; + } else { + $self->{msg} .= "$output<br/>"; + } +} + +sub end_testsuite($$$$) +{ + my ($self, $name, $result, $unexpected, $reason) = @_; + + print TEST "</table>\n"; + + print TEST "<div class=\"duration\">Duration: " . (time() - $self->{START_TIME}) . "s</div>\n"; + + $self->print_html_footer(*TEST); + + close(TEST); + + print INDEX "<tr>\n"; + print INDEX " <td class=\"testSuite\"><a href=\"$self->{HTMLFILE}\">$name</a></td>\n"; + my $st = $self->{local_statistics}; + + if (not $unexpected) { + if ($result eq "failure") { + print INDEX " <td class=\"resultExpectedFailure\">"; + } else { + print INDEX " <td class=\"resultOk\">"; + } + } else { + print INDEX " <td class=\"resultFailure\">"; + } + + my $l = 0; + if ($st->{success} > 0) { + print INDEX "$st->{success} ok"; + $l++; + } + if ($st->{skip} > 0) { + print INDEX ", " if ($l); + print INDEX "$st->{skip} skipped"; + $l++; + } + if ($st->{failure} > 0) { + print INDEX ", " if ($l); + print INDEX "$st->{failure} failures"; + $l++; + } + if ($st->{error} > 0) { + print INDEX ", " if ($l); + print INDEX "$st->{error} errors"; + $l++; + } + + if ($l == 0) { + if (not $unexpected) { + print INDEX "OK"; + } else { + print INDEX "FAIL"; + } + } + + print INDEX "</td>"; + + print INDEX "</tr>\n"; +} + +sub start_test($$) +{ + my ($self, $parents, $testname) = @_; + + if ($#$parents == -1) { + $self->{START_TIME} = time(); + $self->start_testsuite($testname); + return; + } + + $self->{active_test} = $testname; + $self->{msg} = ""; +} + +sub end_test($$$$$$) +{ + my ($self, $parents, $testname, $result, $unexpected, $reason) = @_; + + if ($#$parents == -1) { + $self->end_testsuite($testname, $result, $unexpected, $reason); + return; + } + + print TEST "<tr>"; + + $self->{local_statistics}->{$result}++; + + my $track_class; + + if ($result eq "skip") { + print TEST "<td class=\"outputSkipped\">\n"; + $track_class = "skip"; + } elsif ($unexpected) { + print TEST "<td class=\"outputFailure\">\n"; + if ($result eq "error") { + $track_class = "error"; + } else { + $track_class = "unexpected_$result"; + } + } else { + if ($result eq "failure") { + print TEST "<td class=\"outputExpectedFailure\">\n"; + } else { + print TEST "<td class=\"outputOk\">\n"; + } + $track_class = "expected_$result"; + } + + push(@{$self->{error_summary}->{$track_class}}, , + [$self->{HTMLFILE}, $testname, $self->{NAME}, + $reason]); + + print TEST "<a name=\"$testname\"><h3>$testname</h3></a>\n"; + + print TEST $self->{msg}; + + if (defined($reason)) { + print TEST "<div class=\"reason\">$reason</div>\n"; + } + + print TEST "</td></tr>\n"; + + $self->{active_test} = undef; +} + +sub summary($) +{ + my ($self) = @_; + + my $st = $self->{statistics}; + print INDEX "<tr>\n"; + print INDEX " <td class=\"testSuiteTotal\">Total</td>\n"; + + if ($st->{TESTS_UNEXPECTED_OK} == 0 and + $st->{TESTS_UNEXPECTED_FAIL} == 0 and + $st->{TESTS_ERROR} == 0) { + print INDEX " <td class=\"resultOk\">"; + } else { + print INDEX " <td class=\"resultFailure\">"; + } + print INDEX ($st->{TESTS_EXPECTED_OK} + $st->{TESTS_UNEXPECTED_OK}) . " ok"; + if ($st->{TESTS_UNEXPECTED_OK} > 0) { + print INDEX " ($st->{TESTS_UNEXPECTED_OK} unexpected)"; + } + if ($st->{TESTS_SKIP} > 0) { + print INDEX ", $st->{TESTS_SKIP} skipped"; + } + if (($st->{TESTS_UNEXPECTED_FAIL} + $st->{TESTS_EXPECTED_FAIL}) > 0) { + print INDEX ", " . ($st->{TESTS_UNEXPECTED_FAIL} + $st->{TESTS_EXPECTED_FAIL}) . " failures"; + if ($st->{TESTS_UNEXPECTED_FAIL} > 0) { + print INDEX " ($st->{TESTS_EXPECTED_FAIL} expected)"; + } + } + if ($st->{TESTS_ERROR} > 0) { + print INDEX ", $st->{TESTS_ERROR} errors"; + } + + print INDEX "</td>"; + + print INDEX "</tr>\n"; + + print INDEX "</table>\n"; + print INDEX "<a href=\"summary.html\">Summary</a>\n"; + print INDEX "</center>\n"; + $self->print_html_footer(*INDEX); + close(INDEX); + + my $summ = $self->{error_summary}; + open(SUMMARY, ">$self->{dirname}/summary.html"); + $self->print_html_header("Summary", *SUMMARY); + sub print_table($$) { + my ($title, $list) = @_; + return if ($#$list == -1); + print SUMMARY "<h3>$title</h3>\n"; + print SUMMARY "<table>\n"; + print SUMMARY "<tr>\n"; + print SUMMARY " <td class=\"tableHead\">Testsuite</td>\n"; + print SUMMARY " <td class=\"tableHead\">Test</td>\n"; + print SUMMARY " <td class=\"tableHead\">Reason</td>\n"; + print SUMMARY "</tr>\n"; + + foreach (@$list) { + print SUMMARY "<tr>\n"; + print SUMMARY " <td><a href=\"" . $$_[0] . "\">$$_[2]</a></td>\n"; + print SUMMARY " <td><a href=\"" . $$_[0] . "#$$_[1]\">$$_[1]</a></td>\n"; + if (defined($$_[3])) { + print SUMMARY " <td>$$_[3]</td>\n"; + } else { + print SUMMARY " <td></td>\n"; + } + print SUMMARY "</tr>\n"; + } + + print SUMMARY "</table>"; + } + print_table("Errors", $summ->{error}); + print_table("Unexpected successes", $summ->{unexpected_success}); + print_table("Unexpected failures", $summ->{unexpected_failure}); + print_table("Skipped tests", $summ->{skip}); + print_table("Expected failures", $summ->{expected_failure}); + + print SUMMARY "<h3>Skipped testsuites</h3>\n"; + print SUMMARY "<table>\n"; + print SUMMARY "<tr>\n"; + print SUMMARY " <td class=\"tableHead\">Testsuite</td>\n"; + print SUMMARY " <td class=\"tableHead\">Reason</td>\n"; + print SUMMARY "</tr>\n"; + + foreach (@{$summ->{skip_testsuites}}) { + print SUMMARY "<tr>\n"; + print SUMMARY " <td>$$_[0]</td>\n"; + if (defined($$_[1])) { + print SUMMARY " <td>$$_[1]</td>\n"; + } else { + print SUMMARY " <td></td>\n"; + } + print SUMMARY "</tr>\n"; + } + + print SUMMARY "</table>"; + + $self->print_html_footer(*SUMMARY); + close(SUMMARY); +} + +sub skip_testsuite($$$$) +{ + my ($self, $name, $reason) = @_; + + push (@{$self->{error_summary}->{skip_testsuites}}, + [$name, $reason]); +} + +1; diff --git a/selftest/output/plain.pm b/selftest/output/plain.pm new file mode 100644 index 0000000000..4bec4e0fdc --- /dev/null +++ b/selftest/output/plain.pm @@ -0,0 +1,195 @@ +#!/usr/bin/perl + +package output::plain; +use Exporter; +@ISA = qw(Exporter); + +use FindBin qw($RealBin); +use lib "$RealBin/.."; + +use strict; + +sub new($$$$$$$) { + my ($class, $summaryfile, $verbose, $immediate, $statistics, $totaltests) = @_; + my $self = { + verbose => $verbose, + immediate => $immediate, + statistics => $statistics, + start_time => time(), + test_output => {}, + suitesfailed => [], + suites_ok => 0, + skips => {}, + summaryfile => $summaryfile, + index => 0, + totalsuites => $totaltests, + }; + bless($self, $class); +} + +sub output_msg($$); + +sub start_testsuite($$) +{ + my ($self, $name) = @_; + + $self->{index}++; + $self->{NAME} = $name; + $self->{START_TIME} = time(); + + my $duration = $self->{START_TIME} - $self->{start_time}; + + $self->{test_output}->{$name} = "" unless($self->{verbose}); + + my $out = ""; + $out .= "[$self->{index}/$self->{totalsuites} in ".$duration."s"; + $out .= sprintf(", %d errors", ($#{$self->{suitesfailed}}+1)) if ($#{$self->{suitesfailed}} > -1); + $out .= "] $name"; + if ($self->{immediate}) { + print "$out\n"; + } else { + require Term::ReadKey; + my ($wchar, $hchar, $wpixels, $hpixels) = Term::ReadKey::GetTerminalSize(); + foreach (1..$wchar) { $out.= " "; } + print "\r".substr($out, 0, $wchar); + } +} + +sub output_msg($$) +{ + my ($self, $output) = @_; + + if ($self->{verbose}) { + require FileHandle; + print $output; + STDOUT->flush(); + } else { + $self->{test_output}->{$self->{NAME}} .= $output; + } +} + +sub control_msg($$) +{ + my ($self, $output) = @_; + + $self->output_msg($output); +} + +sub end_testsuite($$$$$) +{ + my ($self, $name, $result, $unexpected, $reason) = @_; + my $out = ""; + + if ($unexpected) { + if ($result eq "success" and not defined($reason)) { + $reason = "Expected negative exit code, got positive exit code"; + } + $self->output_msg("ERROR: $reason\n"); + push (@{$self->{suitesfailed}}, $name); + } else { + $self->{suites_ok}++; + } + + if ($unexpected and $self->{immediate} and not $self->{verbose}) { + $out .= $self->{test_output}->{$name}; + } + + + print $out; +} + +sub start_test($$$) +{ + my ($self, $parents, $testname) = @_; + + if ($#$parents == -1) { + $self->start_testsuite($testname); + } +} + +sub end_test($$$$$) +{ + my ($self, $parents, $testname, $result, $unexpected, $reason) = @_; + + if ($#$parents == -1) { + $self->end_testsuite($testname, $result, $unexpected, $reason); + return; + } + + my $append = ""; + + unless ($unexpected) { + $self->{test_output}->{$self->{NAME}} = ""; + return; + } + + my $fullname = join(".", @$parents).".$testname"; + + $append = "UNEXPECTED($result): $testname ($fullname)\n"; + + $self->{test_output}->{$self->{NAME}} .= $append; + + if ($self->{immediate} and not $self->{verbose}) { + print $self->{test_output}->{$self->{NAME}}; + $self->{test_output}->{$self->{NAME}} = ""; + } +} + +sub summary($) +{ + my ($self) = @_; + + open(SUMMARY, ">$self->{summaryfile}"); + + if ($#{$self->{suitesfailed}} > -1) { + print SUMMARY "= Failed tests =\n"; + + foreach (@{$self->{suitesfailed}}) { + print SUMMARY "== $_ ==\n"; + print SUMMARY $self->{test_output}->{$_}."\n\n"; + } + + print SUMMARY "\n"; + } + + if (not $self->{immediate} and not $self->{verbose}) { + foreach (@{$self->{suitesfailed}}) { + print "===============================================================================\n"; + print "FAIL: $_\n"; + print $self->{test_output}->{$_}; + print "\n"; + } + } + + print SUMMARY "= Skipped tests =\n"; + foreach my $reason (keys %{$self->{skips}}) { + print SUMMARY "$reason\n"; + foreach my $name (@{$self->{skips}->{$reason}}) { + print SUMMARY "\t$name\n"; + } + print SUMMARY "\n"; + } + close(SUMMARY); + + print "\nA summary with detailed informations can be found in:\n $self->{summaryfile}\n"; + + if ($#{$self->{suitesfailed}} == -1) { + my $ok = $self->{statistics}->{TESTS_EXPECTED_OK} + + $self->{statistics}->{TESTS_EXPECTED_FAIL}; + print "\nALL OK ($ok tests in $self->{suites_ok} testsuites)\n"; + } else { + print "\nFAILED ($self->{statistics}->{TESTS_UNEXPECTED_FAIL} failures and $self->{statistics}->{TESTS_ERROR} errors in ". ($#{$self->{suitesfailed}}+1) ." testsuites)\n"; + } + +} + +sub skip_testsuite($$) +{ + my ($self, $name, $reason) = @_; + + push (@{$self->{skips}->{$reason}}, $name); + + $self->{totalsuites}--; +} + +1; diff --git a/selftest/output/testresults.css b/selftest/output/testresults.css new file mode 100644 index 0000000000..66d1d6b2ad --- /dev/null +++ b/selftest/output/testresults.css @@ -0,0 +1,129 @@ +/* Stylesheet for Samba test results. + * + * Partially based on the CSS file from lcov. + */ + +/* All views: main title format */ +td.title +{ + text-align: center; + padding-bottom: 10px; + font-family: sans-serif; + font-size: 20pt; + font-style: italic; + font-weight: bold; +} + +/* Index table headers */ +td.tableHead +{ + text-align: center; + color: #FFFFFF; + background-color: #6688D4; + font-family: sans-serif; + font-size: 120%; + font-weight: bold; +} + +/* Testsuite names */ +td.testSuite +{ + text-align: left; + padding-left: 10px; + padding-right: 20px; + color: #284FA8; + background-color: #DAE7FE; + font-family: monospace; +} + +/* Successful */ +td.resultOk +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #A7FC9D; + font-weight: bold; +} + +/* Failure */ +td.resultFailure +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FF0000; + font-weight: bold; +} + +/* Expected failure */ +td.resultExpectedFailure +{ + text-align: right; + padding-left: 10px; + padding-right: 10px; + background-color: #FFA500; + font-weight: bold; +} + +/* Skipped */ +td.resultSkipped +{ + text-align: center; + padding-left: 10px; + padding-right: 10px; + background-color: #FFEA20; + font-weight: bold; +} + +td.duration +{ + text-align: right; +} + +td.durationSkipped +{ + text-align: right; +} + +td.outputSkipped +{ + background-color: #FFEA20; +} + +td.outputOk +{ + background-color: #A7FC9D; +} + +td.outputFailure +{ + background-color: #FF0000; +} + +td.outputExpectedFailure +{ + background-color: #FFA500; +} + +div.reason +{ + text-align: center; + font-weight: bold; +} + +span.control +{ + display: none; +} + +div.duration +{ + text-align: right; + font-weight: bold; +} + +div.command +{ + background-color: gray; +} diff --git a/selftest/selftest.pl b/selftest/selftest.pl new file mode 100755 index 0000000000..84b2f52058 --- /dev/null +++ b/selftest/selftest.pl @@ -0,0 +1,874 @@ +#!/usr/bin/perl +# Bootstrap Samba and run a number of tests against it. +# Copyright (C) 2005-2008 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +=pod + +=head1 NAME + +selftest - Samba test runner + +=head1 SYNOPSIS + +selftest --help + +selftest [--srcdir=DIR] [--builddir=DIR] [--target=samba4|samba3|win|kvm] [--socket-wrapper] [--quick] [--exclude=FILE] [--include=FILE] [--one] [--prefix=prefix] [--immediate] [--testlist=FILE] [TESTS] + +=head1 DESCRIPTION + +A simple test runner. TESTS is a regular expression with tests to run. + +=head1 OPTIONS + +=over 4 + +=item I<--help> + +Show list of available options. + +=item I<--srcdir=DIR> + +Source directory. + +=item I<--builddir=DIR> + +Build directory. + +=item I<--prefix=DIR> + +Change directory to run tests in. Default is 'st'. + +=item I<--immediate> + +Show errors as soon as they happen rather than at the end of the test run. + +=item I<--target samba4|samba3|win|kvm> + +Specify test target against which to run. Default is 'samba4'. + +=item I<--quick> + +Run only a limited number of tests. Intended to run in about 30 seconds on +moderately recent systems. + +=item I<--socket-wrapper> + +Use socket wrapper library for communication with server. Only works +when the server is running locally. + +Will prevent TCP and UDP ports being opened on the local host but +(transparently) redirects these calls to use unix domain sockets. + +=item I<--expected-failures> + +Specify a file containing a list of tests that are expected to fail. Failures for +these tests will be counted as successes, successes will be counted as failures. + +The format for the file is, one entry per line: + +TESTSUITE-NAME.TEST-NAME + +The reason for a test can also be specified, by adding a hash sign (#) and the reason +after the test name. + +=item I<--exclude> + +Specify a file containing a list of tests that should be skipped. Possible +candidates are tests that segfault the server, flip or don't end. The format of this file is the same as +for the --expected-failures flag. + +=item I<--include> + +Specify a file containing a list of tests that should be run. Same format +as the --exclude flag. + +Not includes specified means all tests will be run. + +=item I<--one> + +Abort as soon as one test fails. + +=item I<--testlist> + +Load a list of tests from the specified location. + +=back + +=head1 ENVIRONMENT + +=over 4 + +=item I<SMBD_VALGRIND> + +=item I<TORTURE_MAXTIME> + +=item I<VALGRIND> + +=item I<TLS_ENABLED> + +=item I<srcdir> + +=back + +=head1 LICENSE + +selftest is licensed under the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>. + +=head1 AUTHOR + +Jelmer Vernooij + +=cut + +use strict; + +use FindBin qw($RealBin $Script); +use File::Spec; +use Getopt::Long; +use POSIX; +use Cwd qw(abs_path); +use lib "$RealBin"; +use Subunit qw(parse_results); +use SocketWrapper; + +my $opt_help = 0; +my $opt_target = "samba4"; +my $opt_quick = 0; +my $opt_socket_wrapper = 0; +my $opt_socket_wrapper_pcap = undef; +my $opt_socket_wrapper_keep_pcap = undef; +my $opt_one = 0; +my $opt_immediate = 0; +my $opt_expected_failures = undef; +my @opt_exclude = (); +my @opt_include = (); +my $opt_verbose = 0; +my $opt_image = undef; +my $opt_testenv = 0; +my $ldap = undef; +my $opt_analyse_cmd = undef; +my $opt_resetup_env = undef; +my $opt_bindir = undef; +my $opt_no_lazy_setup = undef; +my $opt_format = "plain"; +my @testlists = (); + +my $srcdir = "."; +my $builddir = "."; +my $prefix = "./st"; + +my @expected_failures = (); +my @includes = (); +my @excludes = (); + +my $statistics = { + SUITES_FAIL => 0, + + TESTS_UNEXPECTED_OK => 0, + TESTS_EXPECTED_OK => 0, + TESTS_UNEXPECTED_FAIL => 0, + TESTS_EXPECTED_FAIL => 0, + TESTS_ERROR => 0, + TESTS_SKIP => 0, +}; + +sub find_in_list($$) +{ + my ($list, $fullname) = @_; + + foreach (@$list) { + if ($fullname =~ /$$_[0]/) { + return ($$_[1]) if ($$_[1]); + return "NO REASON SPECIFIED"; + } + } + + return undef; +} + +sub expecting_failure($) +{ + my ($name) = @_; + return find_in_list(\@expected_failures, $name); +} + +sub skip($) +{ + my ($name) = @_; + + return find_in_list(\@excludes, $name); +} + +sub getlog_env($); + +sub setup_pcap($) +{ + my ($name) = @_; + + return unless ($opt_socket_wrapper_pcap); + return unless defined($ENV{SOCKET_WRAPPER_PCAP_DIR}); + + my $fname = $name; + $fname =~ s%[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\-]%_%g; + + my $pcap_file = "$ENV{SOCKET_WRAPPER_PCAP_DIR}/$fname.pcap"; + + SocketWrapper::setup_pcap($pcap_file); + + return $pcap_file; +} + +sub cleanup_pcap($$$) +{ + my ($pcap_file, $expected_ret, $ret) = @_; + + return unless ($opt_socket_wrapper_pcap); + return if ($opt_socket_wrapper_keep_pcap); + return unless ($expected_ret == $ret); + return unless defined($pcap_file); + + unlink($pcap_file); +} + +sub run_testsuite($$$$$$) +{ + my ($envname, $name, $cmd, $i, $totalsuites, $msg_ops) = @_; + my $pcap_file = setup_pcap($name); + + $msg_ops->start_test([], $name); + + unless (open(RESULT, "$cmd 2>&1|")) { + $statistics->{TESTS_ERROR}++; + $msg_ops->end_test([], $name, "error", 1, "Unable to run $cmd: $!"); + $statistics->{SUITES_FAIL}++; + return 0; + } + + my $expected_ret = parse_results( + $msg_ops, $statistics, *RESULT, \&expecting_failure, [$name]); + + my $envlog = getlog_env($envname); + $msg_ops->output_msg("ENVLOG: $envlog\n") if ($envlog ne ""); + + $msg_ops->output_msg("CMD: $cmd\n"); + + my $ret = close(RESULT); + $ret = 0 unless $ret == 1; + + my $exitcode = $? >> 8; + + if ($ret == 1) { + $msg_ops->end_test([], $name, "success", $expected_ret != $ret, undef); + } else { + $msg_ops->end_test([], $name, "failure", $expected_ret != $ret, "Exit code was $exitcode"); + } + + cleanup_pcap($pcap_file, $expected_ret, $ret); + + if (not $opt_socket_wrapper_keep_pcap and defined($pcap_file)) { + $msg_ops->output_msg("PCAP FILE: $pcap_file\n"); + } + + if ($ret != $expected_ret) { + $statistics->{SUITES_FAIL}++; + exit(1) if ($opt_one); + } + + return ($ret == $expected_ret); +} + +sub ShowHelp() +{ + print "Samba test runner +Copyright (C) Jelmer Vernooij <jelmer\@samba.org> + +Usage: $Script [OPTIONS] PREFIX + +Generic options: + --help this help page + --target=samba[34]|win|kvm Samba version to target + --testlist=FILE file to read available tests from + +Paths: + --prefix=DIR prefix to run tests in [st] + --srcdir=DIR source directory [.] + --builddir=DIR output directory [.] + +Target Specific: + --socket-wrapper-pcap save traffic to pcap directories + --socket-wrapper-keep-pcap keep all pcap files, not just those for tests that + failed + --socket-wrapper enable socket wrapper + --expected-failures=FILE specify list of tests that is guaranteed to fail + +Samba4 Specific: + --ldap=openldap|fedora-ds back smbd onto specified ldap server + +Samba3 Specific: + --bindir=PATH path to binaries + +Kvm Specific: + --image=PATH path to KVM image + +Behaviour: + --quick run quick overall test + --one abort when the first test fails + --immediate print test output for failed tests during run + --verbose be verbose + --analyse-cmd CMD command to run after each test +"; + exit(0); +} + +my $result = GetOptions ( + 'help|h|?' => \$opt_help, + 'target=s' => \$opt_target, + 'prefix=s' => \$prefix, + 'socket-wrapper' => \$opt_socket_wrapper, + 'socket-wrapper-pcap' => \$opt_socket_wrapper_pcap, + 'socket-wrapper-keep-pcap' => \$opt_socket_wrapper_keep_pcap, + 'quick' => \$opt_quick, + 'one' => \$opt_one, + 'immediate' => \$opt_immediate, + 'expected-failures=s' => \$opt_expected_failures, + 'exclude=s' => \@opt_exclude, + 'include=s' => \@opt_include, + 'srcdir=s' => \$srcdir, + 'builddir=s' => \$builddir, + 'verbose' => \$opt_verbose, + 'testenv' => \$opt_testenv, + 'ldap:s' => \$ldap, + 'analyse-cmd=s' => \$opt_analyse_cmd, + 'no-lazy-setup' => \$opt_no_lazy_setup, + 'resetup-environment' => \$opt_resetup_env, + 'bindir:s' => \$opt_bindir, + 'format=s' => \$opt_format, + 'image=s' => \$opt_image, + 'testlist=s' => \@testlists + ); + +exit(1) if (not $result); + +ShowHelp() if ($opt_help); + +my $tests = shift; + +# quick hack to disable rpc validation when using valgrind - its way too slow +unless (defined($ENV{VALGRIND})) { + $ENV{VALIDATE} = "validate"; + $ENV{MALLOC_CHECK_} = 2; +} + +my $old_pwd = "$RealBin/.."; + +# Backwards compatibility: +if (defined($ENV{TEST_LDAP}) and $ENV{TEST_LDAP} eq "yes") { + if (defined($ENV{FEDORA_DS_ROOT})) { + $ldap = "fedora-ds"; + } else { + $ldap = "openldap"; + } +} + +my $torture_maxtime = ($ENV{TORTURE_MAXTIME} or 1200); +if ($ldap) { + # LDAP is slow + $torture_maxtime *= 2; +} + +$prefix =~ s+//+/+; +$prefix =~ s+/./+/+; +$prefix =~ s+/$++; + +die("using an empty prefix isn't allowed") unless $prefix ne ""; + +#Ensure we have the test prefix around +mkdir($prefix, 0777) unless -d $prefix; + +my $prefix_abs = abs_path($prefix); +my $srcdir_abs = abs_path($srcdir); + +die("using an empty absolute prefix isn't allowed") unless $prefix_abs ne ""; +die("using '/' as absolute prefix isn't allowed") unless $prefix_abs ne "/"; + +$ENV{PREFIX} = $prefix; +$ENV{KRB5CCNAME} = "$prefix/krb5ticket"; +$ENV{PREFIX_ABS} = $prefix_abs; +$ENV{SRCDIR} = $srcdir; +$ENV{SRCDIR_ABS} = $srcdir_abs; + +if (defined($ENV{RUN_FROM_BUILD_FARM}) and + ($ENV{RUN_FROM_BUILD_FARM} eq "yes")) { + $opt_format = "buildfarm"; +} + +my $tls_enabled = not $opt_quick; +$ENV{TLS_ENABLED} = ($tls_enabled?"yes":"no"); +$ENV{LDB_MODULES_PATH} = "$old_pwd/source4/bin/modules/ldb"; +$ENV{LD_SAMBA_MODULE_PATH} = "$old_pwd/source4/bin/modules"; +sub prefix_pathvar($$) +{ + my ($name, $newpath) = @_; + if (defined($ENV{$name})) { + $ENV{$name} = "$newpath:$ENV{$name}"; + } else { + $ENV{$name} = $newpath; + } +} +prefix_pathvar("PKG_CONFIG_PATH", "$old_pwd/source4/bin/pkgconfig"); +# Required for smbscript: +prefix_pathvar("PATH", "$old_pwd/source4/bin"); +prefix_pathvar("PYTHONPATH", "$old_pwd/source4/bin/python"); + +if ($opt_socket_wrapper_keep_pcap) { + # Socket wrapper keep pcap implies socket wrapper pcap + $opt_socket_wrapper_pcap = 1; +} + +if ($opt_socket_wrapper_pcap) { + # Socket wrapper pcap implies socket wrapper + $opt_socket_wrapper = 1; +} + +my $socket_wrapper_dir; +if ($opt_socket_wrapper) { + $socket_wrapper_dir = SocketWrapper::setup_dir("$prefix/w", $opt_socket_wrapper_pcap); + print "SOCKET_WRAPPER_DIR=$socket_wrapper_dir\n"; +} else { + warn("Not using socket wrapper, but also not running as root. Will not be able to listen on proper ports") unless $< == 0; +} + +my $target; +my $testenv_default = "none"; + +if ($opt_target eq "samba4") { + $testenv_default = "member"; + require target::Samba4; + $target = new Samba4($opt_bindir or "$srcdir/bin", $ldap, "$srcdir/setup"); +} elsif ($opt_target eq "samba3") { + if ($opt_socket_wrapper and `$opt_bindir/smbd -b | grep SOCKET_WRAPPER` eq "") { + die("You must include --enable-socket-wrapper when compiling Samba in order to execute 'make test'. Exiting...."); + } + $testenv_default = "dc"; + require target::Samba3; + $target = new Samba3($opt_bindir); +} elsif ($opt_target eq "win") { + die("Windows tests will not run with socket wrapper enabled.") + if ($opt_socket_wrapper); + $testenv_default = "dc"; + require target::Windows; + $target = new Windows(); +} elsif ($opt_target eq "kvm") { + die("Kvm tests will not run with socket wrapper enabled.") + if ($opt_socket_wrapper); + require target::Kvm; + die("No image specified") unless ($opt_image); + $target = new Kvm($opt_image, undef); +} + +# +# Start a Virtual Distributed Ethernet Switch +# Returns the pid of the switch. +# +sub start_vde_switch($) +{ + my ($path) = @_; + + system("vde_switch --pidfile $path/vde.pid --sock $path/vde.sock --daemon"); + + open(PID, "$path/vde.pid"); + <PID> =~ /([0-9]+)/; + my $pid = $1; + close(PID); + + return $pid; +} + +# Stop a Virtual Distributed Ethernet Switch +sub stop_vde_switch($) +{ + my ($pid) = @_; + kill 9, $pid; +} + +sub read_test_regexes($) +{ + my ($name) = @_; + my @ret = (); + open(LF, "<$name") or die("unable to read $name: $!"); + while (<LF>) { + chomp; + next if (/^#/); + if (/^(.*?)([ \t]+)\#([\t ]*)(.*?)$/) { + push (@ret, [$1, $4]); + } else { + s/^(.*?)([ \t]+)\#([\t ]*)(.*?)$//; + push (@ret, [$_, undef]); + } + } + close(LF); + return @ret; +} + +if (defined($opt_expected_failures)) { + @expected_failures = read_test_regexes($opt_expected_failures); +} + +foreach (@opt_exclude) { + push (@excludes, read_test_regexes($_)); +} + +if ($opt_quick) { + push (@includes, read_test_regexes("samba4-quick")); +} + +foreach (@opt_include) { + push (@includes, read_test_regexes($_)); +} + +my $interfaces = join(',', ("127.0.0.6/8", + "127.0.0.7/8", + "127.0.0.8/8", + "127.0.0.9/8", + "127.0.0.10/8", + "127.0.0.11/8")); + +my $conffile = "$prefix_abs/client/client.conf"; + +sub write_clientconf($$) +{ + my ($conffile, $vars) = @_; + + mkdir("$prefix/client", 0777) unless -d "$prefix/client"; + + if ( -d "$prefix/client/private" ) { + unlink <$prefix/client/private/*>; + } else { + mkdir("$prefix/client/private", 0777); + } + + open(CF, ">$conffile"); + print CF "[global]\n"; + if (defined($ENV{VALGRIND})) { + print CF "\ticonv:native = true\n"; + } else { + print CF "\ticonv:native = false\n"; + } + print CF "\tnetbios name = client\n"; + if (defined($vars->{DOMAIN})) { + print CF "\tworkgroup = $vars->{DOMAIN}\n"; + } + if (defined($vars->{REALM})) { + print CF "\trealm = $vars->{REALM}\n"; + } + if (defined($vars->{NCALRPCDIR})) { + print CF "\tncalrpc dir = $vars->{NCALRPCDIR}\n"; + } + if (defined($vars->{PIDDIR})) { + print CF "\tpid directory = $vars->{PIDDIR}\n"; + } + if (defined($vars->{WINBINDD_SOCKET_DIR})) { + print CF "\twinbindd socket directory = $vars->{WINBINDD_SOCKET_DIR}\n"; + } + if ($opt_socket_wrapper) { + print CF "\tinterfaces = $interfaces\n"; + } + print CF " + private dir = $prefix_abs/client/private + js include = $srcdir_abs/scripting/libjs + name resolve order = bcast + panic action = $srcdir_abs/script/gdb_backtrace \%PID\% \%PROG\% + max xmit = 32K + notify:inotify = false + ldb:nosync = true + system:anonymous = true + torture:basedir = $prefix_abs/client +#We don't want to pass our self-tests if the PAC code is wrong + gensec:require_pac = true + modules dir = $ENV{LD_SAMBA_MODULE_PATH} +"; + close(CF); +} + +my @torture_options = (); +push (@torture_options, "--configfile=$conffile"); +# ensure any one smbtorture call doesn't run too long +push (@torture_options, "--maximum-runtime=$torture_maxtime"); +push (@torture_options, "--target=$opt_target"); +push (@torture_options, "--basedir=$prefix_abs"); +push (@torture_options, "--option=torture:progress=no") unless ($opt_verbose); +push (@torture_options, "--format=subunit"); +push (@torture_options, "--option=torture:quick=yes") if ($opt_quick); + +$ENV{TORTURE_OPTIONS} = join(' ', @torture_options); +print "OPTIONS $ENV{TORTURE_OPTIONS}\n"; + +my @todo = (); + +my $testsdir = "$srcdir/selftest"; +$ENV{SMB_CONF_PATH} = "$conffile"; +$ENV{CONFIGURATION} = "--configfile=$conffile"; + +my %required_envs = (); + +sub read_testlist($) +{ + my ($filename) = @_; + + my @ret = (); + open(IN, $filename) or die("Unable to open $filename: $!"); + + while (<IN>) { + if ($_ eq "-- TEST --\n") { + my $name = <IN>; + $name =~ s/\n//g; + my $env = <IN>; + $env =~ s/\n//g; + my $cmdline = <IN>; + $cmdline =~ s/\n//g; + if (not defined($tests) or $name =~ /$tests/) { + $required_envs{$env} = 1; + push (@ret, [$name, $env, $cmdline]); + } + } else { + print; + } + } + close(IN) or die("Error creating recipe"); + return @ret; +} + +if ($#testlists == -1) { + die("No testlists specified"); +} + +my @available = (); +foreach my $fn (@testlists) { + foreach (read_testlist($fn)) { + my $name = $$_[0]; + next if (@includes and not find_in_list(\@includes, $name)); + push (@available, $_); + } +} + +my $msg_ops; +if ($opt_format eq "buildfarm") { + require output::buildfarm; + $msg_ops = new output::buildfarm($statistics); +} elsif ($opt_format eq "plain") { + require output::plain; + $msg_ops = new output::plain("$prefix/summary", $opt_verbose, $opt_immediate, $statistics, $#available+1); +} elsif ($opt_format eq "html") { + require output::html; + mkdir("test-results", 0777); + $msg_ops = new output::html("test-results", $statistics); +} else { + die("Invalid output format '$opt_format'"); +} + + +foreach (@available) { + my $name = $$_[0]; + my $skipreason = skip($name); + if ($skipreason) { + $msg_ops->skip_testsuite($name, $skipreason); + } else { + push(@todo, $_); + } +} + +if ($#todo == -1) { + print STDERR "No tests to run\n"; + exit(1); + } + +my $suitestotal = $#todo + 1; +my $i = 0; +$| = 1; + +my %running_envs = (); + +my @exported_envvars = ( + # domain stuff + "DOMAIN", + "REALM", + + # domain controller stuff + "DC_SERVER", + "DC_SERVER_IP", + "DC_NETBIOSNAME", + "DC_NETBIOSALIAS", + + # server stuff + "SERVER", + "SERVER_IP", + "NETBIOSNAME", + "NETBIOSALIAS", + + # user stuff + "USERNAME", + "PASSWORD", + "DC_USERNAME", + "DC_PASSWORD", + + # misc stuff + "KRB5_CONFIG", + "WINBINDD_SOCKET_DIR", + "WINBINDD_PRIV_PIPE_DIR" +); + +$SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub { + my $signame = shift; + teardown_env($_) foreach(keys %running_envs); + die("Received signal $signame"); +}; + +sub setup_env($) +{ + my ($envname) = @_; + + my $testenv_vars; + if ($envname eq "none") { + $testenv_vars = {}; + } elsif (defined($running_envs{$envname})) { + $testenv_vars = $running_envs{$envname}; + if (not $target->check_env($testenv_vars)) { + $testenv_vars = undef; + } + } else { + $testenv_vars = $target->setup_env($envname, $prefix); + } + + return undef unless defined($testenv_vars); + + $running_envs{$envname} = $testenv_vars; + + SocketWrapper::set_default_iface(6); + write_clientconf($conffile, $testenv_vars); + + foreach (@exported_envvars) { + if (defined($testenv_vars->{$_})) { + $ENV{$_} = $testenv_vars->{$_}; + } else { + delete $ENV{$_}; + } + } + + return $testenv_vars; +} + +sub exported_envvars_str($) +{ + my ($testenv_vars) = @_; + my $out = ""; + + foreach (@exported_envvars) { + next unless defined($testenv_vars->{$_}); + $out .= $_."=".$testenv_vars->{$_}."\n"; + } + + return $out; +} + +sub getlog_env($) +{ + my ($envname) = @_; + return "" if ($envname eq "none"); + return $target->getlog_env($running_envs{$envname}); +} + +sub check_env($) +{ + my ($envname) = @_; + return 1 if ($envname eq "none"); + return $target->check_env($running_envs{$envname}); +} + +sub teardown_env($) +{ + my ($envname) = @_; + return if ($envname eq "none"); + $target->teardown_env($running_envs{$envname}); + delete $running_envs{$envname}; +} + +if ($opt_no_lazy_setup) { + setup_env($_) foreach (keys %required_envs); +} + +if ($opt_testenv) { + my $testenv_name = $ENV{SELFTEST_TESTENV}; + $testenv_name = $testenv_default unless defined($testenv_name); + + my $testenv_vars = setup_env($testenv_name); + + $ENV{PIDDIR} = $testenv_vars->{PIDDIR}; + + my $envvarstr = exported_envvars_str($testenv_vars); + + my $term = ($ENV{TERM} or "xterm"); + system("$term -e 'echo -e \" +Welcome to the Samba4 Test environment '$testenv_name' + +This matches the client environment used in make test +smbd is pid `cat \$PIDDIR/smbd.pid` + +Some useful environment variables: +TORTURE_OPTIONS=\$TORTURE_OPTIONS +CONFIGURATION=\$CONFIGURATION + +$envvarstr +\" && LD_LIBRARY_PATH=$ENV{LD_LIBRARY_PATH} bash'"); + teardown_env($testenv_name); +} else { + foreach (@todo) { + $i++; + my $cmd = $$_[2]; + $cmd =~ s/([\(\)])/\\$1/g; + my $name = $$_[0]; + my $envname = $$_[1]; + + my $envvars = setup_env($envname); + if (not defined($envvars)) { + $msg_ops->skip_testsuite($name, "unable to set up environment $envname"); + next; + } + + run_testsuite($envname, $name, $cmd, $i, $suitestotal, + $msg_ops); + + if (defined($opt_analyse_cmd)) { + system("$opt_analyse_cmd \"$name\""); + } + + teardown_env($envname) if ($opt_resetup_env); + } +} + +print "\n"; + +teardown_env($_) foreach (keys %running_envs); + +$target->stop(); + +$msg_ops->summary(); + +my $failed = 0; + +# if there were any valgrind failures, show them +foreach (<$prefix/valgrind.log*>) { + next unless (-s $_); + system("grep DWARF2.CFI.reader $_ > /dev/null"); + if ($? >> 8 == 0) { + print "VALGRIND FAILURE\n"; + $failed++; + system("cat $_"); + } +} + +if ($opt_format eq "buildfarm") { + print "TEST STATUS: $statistics->{SUITES_FAIL}\n"; +} + +exit $statistics->{SUITES_FAIL}; diff --git a/selftest/target/Kvm.pm b/selftest/target/Kvm.pm new file mode 100644 index 0000000000..3b17a2909c --- /dev/null +++ b/selftest/target/Kvm.pm @@ -0,0 +1,167 @@ +#!/usr/bin/perl +# Start a KVM machine and run a number of tests against it. +# Copyright (C) 2005-2008 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +package Kvm; + +use strict; +use Cwd qw(abs_path); +use FindBin qw($RealBin); +use POSIX; + +sub new($$$$) { + my ($classname, $dc_image, $vdesocket) = @_; + my $self = { + dc_image => $dc_image, + vdesocket => $vdesocket, + }; + bless $self; + return $self; +} + +sub write_kvm_ifup($$$) +{ + my ($self, $path, $ip_prefix) = @_; + open(SCRIPT, ">$path/kvm-ifup"); + + print SCRIPT <<__EOF__; +#!/bin/sh + +PREFIX=$ip_prefix + +/sbin/ifconfig \$1 \$PREFIX.1 up + +cat <<EOF>$path/udhcpd.conf +interface \$1 +start \$PREFIX.20 +end \$PREFIX.20 +max_leases 1 +lease_file $path/udhcpd.leases +pidfile $path/udhcpd.pid +EOF + +touch $path/udhcpd.leases + +/usr/sbin/udhcpd $path/udhcpd.conf + +exit 0 +__EOF__ + close(SCRIPT); + chmod(0755, "$path/kvm-ifup"); + + return ("$path/kvm-ifup", "$path/udhcpd.pid", "$ip_prefix.20"); +} + +sub teardown_env($$) +{ + my ($self, $envvars) = @_; + + print "Killing kvm instance $envvars->{KVM_PID}\n"; + + kill 9, $envvars->{KVM_PID}; + + if (defined($envvars->{DHCPD_PID})) { + print "Killing dhcpd instance $envvars->{DHCPD_PID}\n"; + kill 9, $envvars->{DHCPD_PID}; + } + + return 0; +} + +sub getlog_env($$) +{ + my ($self, $envvars) = @_; + + return ""; +} + +sub check_env($$) +{ + my ($self, $envvars) = @_; + + # FIXME: Check whether $self->{pid} is still running + + return 1; +} + +sub read_pidfile($) +{ + my ($path) = @_; + + open(PID, $path); + <PID> =~ /([0-9]+)/; + my ($pid) = $1; + close(PID); + return $pid; +} + +sub start($$$) +{ + my ($self, $path, $image) = @_; + + my $pidfile = "$path/kvm.pid"; + + my $opts = (defined($ENV{KVM_OPTIONS})?$ENV{KVM_OPTIONS}:"-nographic"); + + if (defined($ENV{KVM_SNAPSHOT})) { + $opts .= " -loadvm $ENV{KVM_SNAPSHOT}"; + } + + my $netopts; + my $dhcp_pid; + my $ip_address; + + if ($self->{vdesocket}) { + $netopts = "vde,socket=$self->{vdesocket}"; + } else { + my $ifup_script, $dhcpd_pidfile; + ($ifup_script, $dhcpd_pidfile, $ip_address) = $self->write_kvm_ifup($path, "192.168.9"); + $netopts = "tap,script=$ifup_script"; + $dhcp_pid = read_pidfile($dhcpd_pidfile); + } + + system("kvm -name \"Samba 4 Test Subject\" $opts -monitor unix:$path/kvm.monitor,server,nowait -daemonize -pidfile $pidfile -snapshot $image -net nic -net $netopts"); + + return (read_pidfile($pidfile), $dhcp_pid, $ip_address); +} + +sub setup_env($$$) +{ + my ($self, $envname, $path) = @_; + + if ($envname eq "dc") { + ($self->{dc_pid}, $self->{dc_dhcpd_pid}, $self->{dc_ip}) = $self->start($path, $self->{dc_image}); + + sub choose_var($$) { + my ($name, $default) = @_; + return defined($ENV{"KVM_DC_$name"})?$ENV{"KVM_DC_$name"}:$default; + } + + if ($envname eq "dc") { + return { + KVM_PID => $self->{dc_pid}, + DHCPD_PID => $self->{dc_dhcpd_pid}, + USERNAME => choose_var("USERNAME", "Administrator"), + PASSWORD => choose_var("PASSWORD", "penguin"), + DOMAIN => choose_var("DOMAIN", "SAMBA"), + REALM => choose_var("REALM", "SAMBA"), + SERVER => choose_var("SERVER", "DC"), + SERVER_IP => $self->{dc_ip}, + NETBIOSNAME => choose_var("NETBIOSNAME", "DC"), + NETBIOSALIAS => choose_var("NETBIOSALIAS", "DC"), + }; + } else { + return undef; + } + } else { + return undef; + } +} + +sub stop($) +{ + my ($self) = @_; +} + +1; diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm new file mode 100644 index 0000000000..b0c4eb22bd --- /dev/null +++ b/selftest/target/Samba3.pm @@ -0,0 +1,434 @@ +#!/usr/bin/perl +# Bootstrap Samba and run a number of tests against it. +# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +package Samba3; + +use strict; +use Cwd qw(abs_path); +use FindBin qw($RealBin); +use POSIX; + +sub binpath($$) +{ + my ($self, $binary) = @_; + + if (defined($self->{bindir})) { + my $path = "$self->{bindir}/$binary"; + -f $path or die("File $path doesn't exist"); + return $path; + } + + return $binary; +} + +sub new($$) { + my ($classname, $bindir) = @_; + my $self = { bindir => $bindir }; + bless $self; + return $self; +} + +sub teardown_env($$) +{ + my ($self, $envvars) = @_; + + my $smbdpid = read_pid($envvars, "smbd"); + my $nmbdpid = read_pid($envvars, "nmbd"); +# my $winbinddpid = read_pid($envvars, "winbindd"); + + $self->stop_sig_term($smbdpid); + $self->stop_sig_term($nmbdpid); +# $self->stop_sig_term($winbinddpid); + $self->stop_sig_kill($smbdpid); + $self->stop_sig_kill($nmbdpid); +# $self->stop_sig_kill($winbinddpid); + + return 0; +} + +sub getlog_env_app($$$) +{ + my ($self, $envvars, $name) = @_; + + my $title = "$name LOG of: $envvars->{NETBIOSNAME}\n"; + my $out = $title; + + open(LOG, "<".$envvars->{$name."_TEST_LOG"}); + + seek(LOG, $envvars->{$name."_TEST_LOG_POS"}, SEEK_SET); + while (<LOG>) { + $out .= $_; + } + $envvars->{$name."_TEST_LOG_POS"} = tell(LOG); + close(LOG); + + return "" if $out eq $title; + + return $out; +} + +sub getlog_env($$) +{ + my ($self, $envvars) = @_; + my $ret = ""; + + $ret .= $self->getlog_env_app($envvars, "SMBD"); + $ret .= $self->getlog_env_app($envvars, "NMBD"); +# $ret .= $self->getlog_env_app($envvars, "WINBINDD"); + + return $ret; +} + +sub check_env($$) +{ + my ($self, $envvars) = @_; + + # TODO ... + return 1; +} + +sub setup_env($$$) +{ + my ($self, $envname, $path) = @_; + + if ($envname eq "dc") { + return $self->setup_dc("$path/dc"); + } else { + return undef; + } +} + +sub setup_dc($$) +{ + my ($self, $path) = @_; + + my $vars = $self->provision($path, "dc"); + + $self->check_or_start($vars, + ($ENV{NMBD_MAXTIME} or 2700), + ($ENV{WINBINDD_MAXTIME} or 2700), + ($ENV{SMBD_MAXTIME} or 2700)); + + $self->wait_for_start($vars); + + return $vars; +} + +sub stop($) +{ + my ($self) = @_; +} + +sub stop_sig_term($$) { + my ($self, $pid) = @_; + kill("USR1", $pid) or kill("ALRM", $pid) or warn("Unable to kill $pid: $!"); +} + +sub stop_sig_kill($$) { + my ($self, $pid) = @_; + kill("ALRM", $pid) or warn("Unable to kill $pid: $!"); +} + +sub write_pid($$$) +{ + my ($env_vars, $app, $pid) = @_; + + open(PID, ">$env_vars->{PIDDIR}/timelimit.$app.pid"); + print PID $pid; + close(PID); +} + +sub read_pid($$) +{ + my ($env_vars, $app) = @_; + + open(PID, "<$env_vars->{PIDDIR}/timelimit.$app.pid"); + my $pid = <PID>; + close(PID); + return $pid; +} + +sub check_or_start($$$$) { + my ($self, $env_vars, $nmbd_maxtime, $winbindd_maxtime, $smbd_maxtime) = @_; + + unlink($env_vars->{NMBD_TEST_LOG}); + print "STARTING NMBD..."; + my $pid = fork(); + if ($pid == 0) { + open STDOUT, ">$env_vars->{NMBD_TEST_LOG}"; + open STDERR, '>&STDOUT'; + + $ENV{WINBINDD_SOCKET_DIR} = $env_vars->{WINBINDD_SOCKET_DIR}; + + my @optargs = ("-d0"); + if (defined($ENV{NMBD_OPTIONS})) { + @optargs = split(/ /, $ENV{NMBD_OPTIONS}); + } + + $ENV{MAKE_TEST_BINARY} = $self->binpath("nmbd"); + + my @preargs = ($self->binpath("timelimit"), $nmbd_maxtime); + if(defined($ENV{NMBD_VALGRIND})) { + @preargs = split(/ /, $ENV{NMBD_VALGRIND}); + } + + exec(@preargs, $self->binpath("nmbd"), "-F", "-S", "--no-process-group", "-s", $env_vars->{SERVERCONFFILE}, @optargs) or die("Unable to start nmbd: $!"); + } + write_pid($env_vars, "nmbd", $pid); + print "DONE\n"; + +# disable winbindd until the build-farm faked_users work with it +# unlink($env_vars->{WINBINDD_TEST_LOG}); +# print "STARTING WINBINDD..."; +# $pid = fork(); +# if ($pid == 0) { +# open STDOUT, ">$env_vars->{WINBINDD_TEST_LOG}"; +# open STDERR, '>&STDOUT'; +# +# $ENV{WINBINDD_SOCKET_DIR} = $env_vars->{WINBINDD_SOCKET_DIR}; +# +# my @optargs = ("-d0"); +# if (defined($ENV{WINBINDD_OPTIONS})) { +# @optargs = split(/ /, $ENV{WINBINDD_OPTIONS}); +# } +# +# $ENV{MAKE_TEST_BINARY} = $self->binpath("winbindd"); +# exec($self->binpath("timelimit"), $winbindd_maxtime, $ENV{WINBINDD_VALGRIND}, $self->binpath("winbindd"), "-F", "-S", "--no-process-group", "-s", $env_vars->{SERVERCONFFILE}, @optargs) or die("Unable to start winbindd: $!"); +# } +# write_pid($env_vars, "winbindd", $pid); +# print "DONE\n"; + + unlink($env_vars->{SMBD_TEST_LOG}); + print "STARTING SMBD..."; + $pid = fork(); + if ($pid == 0) { + open STDOUT, ">$env_vars->{SMBD_TEST_LOG}"; + open STDERR, '>&STDOUT'; + + $ENV{WINBINDD_SOCKET_DIR} = $env_vars->{WINBINDD_SOCKET_DIR}; + + $ENV{MAKE_TEST_BINARY} = $self->binpath("smbd"); + my @optargs = ("-d0"); + if (defined($ENV{SMBD_OPTIONS})) { + @optargs = split(/ /, $ENV{SMBD_OPTIONS}); + } + my @preargs = ($self->binpath("timelimit"), $smbd_maxtime); + if(defined($ENV{SMBD_VALGRIND})) { + @preargs = split(/ /,$ENV{SMBD_VALGRIND}); + } + exec(@preargs, $self->binpath("smbd"), "-F", "-S", "--no-process-group", "-s", $env_vars->{SERVERCONFFILE}, @optargs) or die("Unable to start smbd: $!"); + } + write_pid($env_vars, "smbd", $pid); + print "DONE\n"; + + return 0; +} + +sub create_clientconf($$$) +{ + my ($self, $prefix, $domain) = @_; + + my $lockdir = "$prefix/locks"; + my $logdir = "$prefix/logs"; + my $piddir = "$prefix/pid"; + my $privatedir = "$prefix/private"; + my $scriptdir = "$RealBin/.."; + my $conffile = "$prefix/smb.conf"; + + my $torture_interfaces='127.0.0.6/8,127.0.0.7/8,127.0.0.8/8,127.0.0.9/8,127.0.0.10/8,127.0.0.11/8'; + open(CONF, ">$conffile"); + print CONF " +[global] + workgroup = $domain + + private dir = $privatedir + pid directory = $piddir + lock directory = $lockdir + log file = $logdir/log.\%m + log level = 0 + + name resolve order = bcast + + netbios name = TORTURE_6 + interfaces = $torture_interfaces + panic action = $scriptdir/gdb_backtrace \%d %\$(MAKE_TEST_BINARY) + + passdb backend = tdbsam + "; + close(CONF); +} + +sub provision($$$) +{ + my ($self, $prefix, $role) = @_; + + ## + ## setup the various environment variables we need + ## + + my %ret = (); + my $server = "LOCALHOST2"; + my $server_ip = "127.0.0.2"; + my $domain = "SAMBA-TEST"; + + my $username = `PATH=/usr/ucb:$ENV{PATH} whoami`; + chomp $username; + my $password = "test"; + + my $srcdir="$RealBin/.."; + my $scriptdir="$srcdir/selftest"; + my $prefix_abs = abs_path($prefix); + + my @dirs = (); + + my $shrdir="$prefix_abs/share"; + push(@dirs,$shrdir); + + my $libdir="$prefix_abs/lib"; + push(@dirs,$libdir); + + my $piddir="$prefix_abs/pid"; + push(@dirs,$piddir); + + my $privatedir="$prefix_abs/private"; + push(@dirs,$privatedir); + + my $lockdir="$prefix_abs/lockdir"; + push(@dirs,$lockdir); + + my $logdir="$prefix_abs/logs"; + push(@dirs,$logdir); + + # this gets autocreated by winbindd + my $wbsockdir="$prefix_abs/winbindd"; + my $wbsockprivdir="$lockdir/winbindd_privileged"; + + ## + ## create the test directory layout + ## + mkdir($prefix_abs, 0777); + print "CREATE TEST ENVIRONMENT IN '$prefix'..."; + system("rm -rf $prefix_abs/*"); + mkdir($_, 0777) foreach(@dirs); + + my $conffile="$libdir/server.conf"; + + open(CONF, ">$conffile") or die("Unable to open $conffile"); + print CONF " +[global] + workgroup = $domain + + private dir = $privatedir + pid directory = $piddir + lock directory = $lockdir + log file = $logdir/log.\%m + log level = 0 + + name resolve order = bcast + + netbios name = $server + interfaces = $server_ip/8 + bind interfaces only = yes + panic action = $scriptdir/gdb_backtrace %d %\$(MAKE_TEST_BINARY) + + passdb backend = tdbsam + + ; Necessary to add the build farm hacks + add user script = /bin/false + add machine script = /bin/false + + kernel oplocks = no + kernel change notify = no + + syslog = no + printing = bsd + printcap name = /dev/null + +"; + + if ($role eq "dc") { + print CONF "\tdomain logons = yes\n"; + print CONF "\tdomain master = yes\n"; + } + +print CONF " + + winbindd:socket dir = $wbsockdir + +[tmp] + path = $shrdir + read only = no + smbd:sharedelay = 100000 + map hidden = yes + map system = yes + create mask = 755 +[hideunread] + copy = tmp + hide unreadable = yes +[hideunwrite] + copy = tmp + hide unwriteable files = yes +[print1] + copy = tmp + printable = yes + printing = test +[print2] + copy = print1 +[print3] + copy = print1 +[print4] + copy = print1 + "; + close(CONF); + + ## + ## create a test account + ## + + open(PWD, "|".$self->binpath("smbpasswd")." -c $conffile -L -s -a $username"); + print PWD "$password\n$password\n"; + close(PWD) or die("Unable to set password for test account"); + + print "DONE\n"; + + $ret{SERVER_IP} = $server_ip; + $ret{NMBD_TEST_LOG} = "$prefix/nmbd_test.log"; + $ret{WINBINDD_TEST_LOG} = "$prefix/winbindd_test.log"; + $ret{SMBD_TEST_LOG} = "$prefix/smbd_test.log"; + $ret{SERVERCONFFILE} = $conffile; + $ret{CONFIGURATION} ="-s $conffile"; + $ret{SERVER} = $server; + $ret{USERNAME} = $username; + $ret{DOMAIN} = $domain; + $ret{NETBIOSNAME} = $server; + $ret{PASSWORD} = $password; + $ret{PIDDIR} = $piddir; + $ret{WINBINDD_SOCKET_DIR} = $wbsockdir; + $ret{WINBINDD_PRIV_PIPE_DIR} = $wbsockprivdir; + return \%ret; +} + +sub wait_for_start($$) +{ + my ($self, $envvars) = @_; + + # give time for nbt server to register its names + print "delaying for nbt name registration\n"; + sleep(10); + # This will return quickly when things are up, but be slow if we need to wait for (eg) SSL init + system($self->binpath("nmblookup") ." $envvars->{CONFIGURATION} -U $envvars->{SERVER_IP} __SAMBA__"); + system($self->binpath("nmblookup") ." $envvars->{CONFIGURATION} __SAMBA__"); + system($self->binpath("nmblookup") ." $envvars->{CONFIGURATION} -U 127.255.255.255 __SAMBA__"); + system($self->binpath("nmblookup") ." $envvars->{CONFIGURATION} -U $envvars->{SERVER_IP} $envvars->{SERVER}"); + system($self->binpath("nmblookup") ." $envvars->{CONFIGURATION} $envvars->{SERVER}"); + # make sure smbd is also up set + print "wait for smbd\n"; + system($self->binpath("smbclient") ." $envvars->{CONFIGURATION} -L $envvars->{SERVER_IP} -U% -p 139 | head -2"); + system($self->binpath("smbclient") ." $envvars->{CONFIGURATION} -L $envvars->{SERVER_IP} -U% -p 139 | head -2"); + + print $self->getlog_env($envvars); +} + +1; diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm new file mode 100644 index 0000000000..8835f69c6c --- /dev/null +++ b/selftest/target/Samba4.pm @@ -0,0 +1,957 @@ +#!/usr/bin/perl +# Bootstrap Samba and run a number of tests against it. +# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +package Samba4; + +use strict; +use Cwd qw(abs_path); +use FindBin qw($RealBin); +use POSIX; + +sub new($$$$) { + my ($classname, $bindir, $ldap, $setupdir) = @_; + my $self = { + vars => {}, + ldap => $ldap, + bindir => $bindir, + setupdir => $setupdir + }; + bless $self; + return $self; +} + +sub openldap_start($$$) { + my ($slapd_conf, $uri, $logs) = @_; + my $oldpath = $ENV{PATH}; + my $olroot = ""; + my $olpath = ""; + if (defined $ENV{OPENLDAP_ROOT}) { + $olroot = "$ENV{OPENLDAP_ROOT}"; + $olpath = "$olroot/libexec:$olroot/sbin:"; + } + $ENV{PATH} = "$olpath/usr/local/sbin:/usr/sbin:/sbin:$ENV{PATH}"; + system("slapd -d63 -f $slapd_conf -h $uri > $logs 2>&1 &"); + $ENV{PATH} = $oldpath; +} + +sub slapd_start($$) +{ + my $count = 0; + my ($self, $env_vars) = @_; + + my $uri = $env_vars->{LDAP_URI}; + + # running slapd in the background means it stays in the same process group, so it can be + # killed by timelimit + if ($self->{ldap} eq "fedora-ds") { + system("$ENV{FEDORA_DS_ROOT}/sbin/ns-slapd -D $env_vars->{FEDORA_DS_DIR} -d0 -i $env_vars->{FEDORA_DS_PIDFILE}> $env_vars->{LDAPDIR}/logs 2>&1 &"); + } elsif ($self->{ldap} eq "openldap") { + openldap_start($env_vars->{SLAPD_CONF}, $uri, "$env_vars->{LDAPDIR}/logs"); + } + while (system("$self->{bindir}/ldbsearch -H $uri -s base -b \"\" supportedLDAPVersion > /dev/null") != 0) { + $count++; + if ($count > 40) { + $self->slapd_stop($env_vars); + return 0; + } + sleep(1); + } + return 1; +} + +sub slapd_stop($$) +{ + my ($self, $envvars) = @_; + if ($self->{ldap} eq "fedora-ds") { + system("$envvars->{LDAPDIR}/slapd-samba4/stop-slapd"); + } elsif ($self->{ldap} eq "openldap") { + open(IN, "<$envvars->{OPENLDAP_PIDFILE}") or + die("unable to open slapd pid file: $envvars->{OPENLDAP_PIDFILE}"); + kill 9, <IN>; + close(IN); + } + return 1; +} + +sub check_or_start($$$) +{ + my ($self, $env_vars, $max_time) = @_; + return 0 if ( -p $env_vars->{SMBD_TEST_FIFO}); + + unlink($env_vars->{SMBD_TEST_FIFO}); + POSIX::mkfifo($env_vars->{SMBD_TEST_FIFO}, 0700); + unlink($env_vars->{SMBD_TEST_LOG}); + + print "STARTING SMBD... "; + my $pid = fork(); + if ($pid == 0) { + open STDIN, $env_vars->{SMBD_TEST_FIFO}; + open STDOUT, ">$env_vars->{SMBD_TEST_LOG}"; + open STDERR, '>&STDOUT'; + + SocketWrapper::set_default_iface($env_vars->{SOCKET_WRAPPER_DEFAULT_IFACE}); + + my $valgrind = ""; + if (defined($ENV{SMBD_VALGRIND})) { + $valgrind = $ENV{SMBD_VALGRIND}; + } + + $ENV{KRB5_CONFIG} = $env_vars->{KRB5_CONFIG}; + + $ENV{NSS_WRAPPER_PASSWD} = $env_vars->{NSS_WRAPPER_PASSWD}; + $ENV{NSS_WRAPPER_GROUP} = $env_vars->{NSS_WRAPPER_GROUP}; + + # Start slapd before smbd, but with the fifo on stdin + if (defined($self->{ldap})) { + $self->slapd_start($env_vars) or + die("couldn't start slapd (2nd time)"); + } + + my $optarg = ""; + if (defined($max_time)) { + $optarg = "--maximum-runtime=$max_time "; + } + if (defined($ENV{SMBD_OPTIONS})) { + $optarg.= " $ENV{SMBD_OPTIONS}"; + } + my $ret = system("$valgrind $self->{bindir}/smbd $optarg $env_vars->{CONFIGURATION} -M single -i --leak-report-full"); + if ($? == -1) { + print "Unable to start smbd: $ret: $!\n"; + exit 1; + } + unlink($env_vars->{SMBD_TEST_FIFO}); + my $exit = $? >> 8; + if ( $ret == 0 ) { + print "smbd exits with status $exit\n"; + } elsif ( $ret & 127 ) { + print "smbd got signal ".($ret & 127)." and exits with $exit!\n"; + } else { + $ret = $? >> 8; + print "smbd failed with status $exit!\n"; + } + exit $exit; + } + print "DONE\n"; + + open(DATA, ">$env_vars->{SMBD_TEST_FIFO}"); + + return $pid; +} + +sub wait_for_start($$) +{ + my ($self, $testenv_vars) = @_; + # give time for nbt server to register its names + print "delaying for nbt name registration\n"; + sleep 2; + + # This will return quickly when things are up, but be slow if we + # need to wait for (eg) SSL init + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{SERVER}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{SERVER}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{NETBIOSNAME}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{NETBIOSNAME}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{NETBIOSALIAS}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{NETBIOSALIAS}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{SERVER}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{SERVER}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{NETBIOSNAME}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{NETBIOSNAME}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} $testenv_vars->{NETBIOSALIAS}"); + system("bin/nmblookup $testenv_vars->{CONFIGURATION} -U $testenv_vars->{SERVER_IP} $testenv_vars->{NETBIOSALIAS}"); + + print $self->getlog_env($testenv_vars); +} + +sub write_ldb_file($$$) +{ + my ($self, $file, $ldif) = @_; + + open(LDIF, "|$self->{bindir}/ldbadd -H $file >/dev/null"); + print LDIF $ldif; + return close(LDIF); +} + +sub add_wins_config($$) +{ + my ($self, $privatedir) = @_; + + return $self->write_ldb_file("$privatedir/wins_config.ldb", " +dn: name=TORTURE_6,CN=PARTNERS +objectClass: wreplPartner +name: TORTURE_6 +address: 127.0.0.6 +pullInterval: 0 +pushChangeCount: 0 +type: 0x3 +"); +} + +sub mk_fedora_ds($$$) +{ + my ($self, $ldapdir, $configuration) = @_; + + my $fedora_ds_inf = "$ldapdir/fedorads.inf"; + my $fedora_ds_extra_ldif = "$ldapdir/fedorads-partitions.ldif"; + + #Make the subdirectory be as fedora DS would expect + my $fedora_ds_dir = "$ldapdir/slapd-samba4"; + + my $pidfile = "$fedora_ds_dir/logs/slapd-samba4.pid"; + +my $dir = getcwd(); +chdir "$ENV{FEDORA_DS_ROOT}/bin" || die; + if (system("perl $ENV{FEDORA_DS_ROOT}/sbin/setup-ds.pl --silent --file=$fedora_ds_inf >&2") != 0) { + chdir $dir; + die("perl $ENV{FEDORA_DS_ROOT}/sbin/setup-ds.pl --silent --file=$fedora_ds_inf FAILED: $?"); + } + chdir $dir || die; + + return ($fedora_ds_dir, $pidfile); +} + +sub mk_openldap($$$) +{ + my ($self, $ldapdir, $configuration) = @_; + + my $slapd_conf = "$ldapdir/slapd.conf"; + my $pidfile = "$ldapdir/slapd.pid"; + my $modconf = "$ldapdir/modules.conf"; + + my $oldpath = $ENV{PATH}; + my $olpath = ""; + my $olroot = ""; + if (defined $ENV{OPENLDAP_ROOT}) { + $olroot = "$ENV{OPENLDAP_ROOT}"; + $olpath = "$olroot/libexec:$olroot/sbin:"; + } + $ENV{PATH} = "$olpath/usr/local/sbin:/usr/sbin:/sbin:$ENV{PATH}"; + + unlink($modconf); + open(CONF, ">$modconf"); close(CONF); + + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules + print CONF " +modulepath $olroot/libexec/openldap +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules + print CONF " +modulepath $olroot/libexec/openldap +moduleload back_hdb +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules + print CONF " +moduleload back_hdb +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules + print CONF " +modulepath /usr/lib/ldap +moduleload back_hdb +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules (Fedora layout) + print CONF " +modulepath /usr/lib/openldap +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + + if (system("slaptest -u -f $slapd_conf >&2") != 0) { + open(CONF, ">$modconf"); + # enable slapd modules (Fedora x86_64 layout) + print CONF " +modulepath /usr/lib64/openldap +moduleload syncprov +moduleload memberof +moduleload refint +"; + close(CONF); + } + + system("slaptest -u -f $slapd_conf") == 0 or die("slaptest still fails after adding modules"); + + + $ENV{PATH} = $oldpath; + + return ($slapd_conf, $pidfile); +} + +sub mk_keyblobs($$) +{ + my ($self, $tlsdir) = @_; + + #TLS and PKINIT crypto blobs + my $dhfile = "$tlsdir/dhparms.pem"; + my $cafile = "$tlsdir/ca.pem"; + my $certfile = "$tlsdir/cert.pem"; + my $reqkdc = "$tlsdir/req-kdc.der"; + my $kdccertfile = "$tlsdir/kdc.pem"; + my $keyfile = "$tlsdir/key.pem"; + my $adminkeyfile = "$tlsdir/adminkey.pem"; + my $reqadmin = "$tlsdir/req-admin.der"; + my $admincertfile = "$tlsdir/admincert.pem"; + + mkdir($tlsdir, 0777); + + #This is specified here to avoid draining entropy on every run + open(DHFILE, ">$dhfile"); + print DHFILE <<EOF; +-----BEGIN DH PARAMETERS----- +MGYCYQC/eWD2xkb7uELmqLi+ygPMKyVcpHUo2yCluwnbPutEueuxrG/Cys8j8wLO +svCN/jYNyR2NszOmg7ZWcOC/4z/4pWDVPUZr8qrkhj5MRKJc52MncfaDglvEdJrv +YX70obsCAQI= +-----END DH PARAMETERS----- +EOF + close(DHFILE); + + #Likewise, we pregenerate the key material. This allows the + #other certificates to be pre-generated + open(KEYFILE, ">$keyfile"); + print KEYFILE <<EOF; +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDKg6pAwCHUMA1DfHDmWhZfd+F0C+9Jxcqvpw9ii9En3E1uflpc +ol3+S9/6I/uaTmJHZre+DF3dTzb/UOZo0Zem8N+IzzkgoGkFafjXuT3BL5UPY2/H +6H+pPqVIRLOmrWImai359YyoKhFyo37Y6HPeU8QcZ+u2rS9geapIWfeuowIDAQAB +AoGAAqDLzFRR/BF1kpsiUfL4WFvTarCe9duhwj7ORc6fs785qAXuwUYAJ0Uvzmy6 +HqoGv3t3RfmeHDmjcpPHsbOKnsOQn2MgmthidQlPBMWtQMff5zdoYNUFiPS0XQBq +szNW4PRjaA9KkLQVTwnzdXGkBSkn/nGxkaVu7OR3vJOBoo0CQQDO4upypesnbe6p +9/xqfZ2uim8IwV1fLlFClV7WlCaER8tsQF4lEi0XSzRdXGUD/dilpY88Nb+xok/X +8Z8OvgAXAkEA+pcLsx1gN7kxnARxv54jdzQjC31uesJgMKQXjJ0h75aUZwTNHmZQ +vPxi6u62YiObrN5oivkixwFNncT9MxTxVQJBAMaWUm2SjlLe10UX4Zdm1MEB6OsC +kVoX37CGKO7YbtBzCfTzJGt5Mwc1DSLA2cYnGJqIfSFShptALlwedot0HikCQAJu +jNKEKnbf+TdGY8Q0SKvTebOW2Aeg80YFkaTvsXCdyXrmdQcifw4WdO9KucJiDhSz +Y9hVapz7ykEJtFtWjLECQQDIlfc63I5ZpXfg4/nN4IJXUW6AmPVOYIA5215itgki +cSlMYli1H9MEXH0pQMGv5Qyd0OYIx2DDg96mZ+aFvqSG +-----END RSA PRIVATE KEY----- +EOF + close(KEYFILE); + + open(ADMINKEYFILE, ">$adminkeyfile"); + + print ADMINKEYFILE <<EOF; +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQD0+OL7TQBj0RejbIH1+g5GeRaWaM9xF43uE5y7jUHEsi5owhZF +5iIoHZeeL6cpDF5y1BZRs0JlA1VqMry1jjKlzFYVEMMFxB6esnXhl0Jpip1JkUMM +XLOP1m/0dqayuHBWozj9f/cdyCJr0wJIX1Z8Pr+EjYRGPn/MF0xdl3JRlwIDAQAB +AoGAP8mjCP628Ebc2eACQzOWjgEvwYCPK4qPmYOf1zJkArzG2t5XAGJ5WGrENRuB +cm3XFh1lpmaADl982UdW3gul4gXUy6w4XjKK4vVfhyHj0kZ/LgaXUK9BAGhroJ2L +osIOUsaC6jdx9EwSRctwdlF3wWJ8NK0g28AkvIk+FlolW4ECQQD7w5ouCDnf58CN +u4nARx4xv5XJXekBvOomkCQAmuOsdOb6b9wn3mm2E3au9fueITjb3soMR31AF6O4 +eAY126rXAkEA+RgHzybzZEP8jCuznMqoN2fq/Vrs6+W3M8/G9mzGEMgLLpaf2Jiz +I9tLZ0+OFk9tkRaoCHPfUOCrVWJZ7Y53QQJBAMhoA6rw0WDyUcyApD5yXg6rusf4 +ASpo/tqDkqUIpoL464Qe1tjFqtBM3gSXuhs9xsz+o0bzATirmJ+WqxrkKTECQHt2 +OLCpKqwAspU7N+w32kaUADoRLisCEdrhWklbwpQgwsIVsCaoEOpt0CLloJRYTANE +yoZeAErTALjyZYZEPcECQQDlUi0N8DFxQ/lOwWyR3Hailft+mPqoPCa8QHlQZnlG ++cfgNl57YHMTZFwgUVFRdJNpjH/WdZ5QxDcIVli0q+Ko +-----END RSA PRIVATE KEY----- +EOF + + #generated with + # hxtool issue-certificate --self-signed --issue-ca \ + # --ca-private-key="FILE:$KEYFILE" \ + # --subject="CN=CA,DC=samba,DC=example,DC=com" \ + # --certificate="FILE:$CAFILE" --lifetime="25 years" + + open(CAFILE, ">$cafile"); + print CAFILE <<EOF; +-----BEGIN CERTIFICATE----- +MIICcTCCAdqgAwIBAgIUaBPmjnPVqyFqR5foICmLmikJTzgwCwYJKoZIhvcNAQEFMFIxEzAR +BgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxlMRUwEwYKCZImiZPy +LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDgwMzAxMTIyMzEyWhgPMjAzMzAyMjQx +MjIzMTJaMFIxEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl +MRUwEwYKCZImiZPyLGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDKg6pAwCHUMA1DfHDmWhZfd+F0C+9Jxcqvpw9ii9En3E1uflpcol3+S9/6 +I/uaTmJHZre+DF3dTzb/UOZo0Zem8N+IzzkgoGkFafjXuT3BL5UPY2/H6H+pPqVIRLOmrWIm +ai359YyoKhFyo37Y6HPeU8QcZ+u2rS9geapIWfeuowIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AaYwHQYDVR0OBBYEFMLZufegDKLZs0VOyFXYK1L6M8oyMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQEFBQADgYEAAZJbCAAkaqgFJ0xgNovn8Ydd0KswQPjicwiODPgw9ZPoD2HiOUVO +yYDRg/dhFF9y656OpcHk4N7qZ2sl3RlHkzDu+dseETW+CnKvQIoXNyeARRJSsSlwrwcoD4JR +HTLk2sGigsWwrJ2N99sG/cqSJLJ1MFwLrs6koweBnYU0f/g= +-----END CERTIFICATE----- +EOF + + #generated with GNUTLS internally in Samba. + + open(CERTFILE, ">$certfile"); + print CERTFILE <<EOF; +-----BEGIN CERTIFICATE----- +MIICYTCCAcygAwIBAgIE5M7SRDALBgkqhkiG9w0BAQUwZTEdMBsGA1UEChMUU2Ft +YmEgQWRtaW5pc3RyYXRpb24xNDAyBgNVBAsTK1NhbWJhIC0gdGVtcG9yYXJ5IGF1 +dG9nZW5lcmF0ZWQgY2VydGlmaWNhdGUxDjAMBgNVBAMTBVNhbWJhMB4XDTA2MDgw +NDA0MzY1MloXDTA4MDcwNDA0MzY1MlowZTEdMBsGA1UEChMUU2FtYmEgQWRtaW5p +c3RyYXRpb24xNDAyBgNVBAsTK1NhbWJhIC0gdGVtcG9yYXJ5IGF1dG9nZW5lcmF0 +ZWQgY2VydGlmaWNhdGUxDjAMBgNVBAMTBVNhbWJhMIGcMAsGCSqGSIb3DQEBAQOB +jAAwgYgCgYDKg6pAwCHUMA1DfHDmWhZfd+F0C+9Jxcqvpw9ii9En3E1uflpcol3+ +S9/6I/uaTmJHZre+DF3dTzb/UOZo0Zem8N+IzzkgoGkFafjXuT3BL5UPY2/H6H+p +PqVIRLOmrWImai359YyoKhFyo37Y6HPeU8QcZ+u2rS9geapIWfeuowIDAQABoyUw +IzAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAsGCSqGSIb3DQEB +BQOBgQAmkN6XxvDnoMkGcWLCTwzxGfNNSVcYr7TtL2aJh285Xw9zaxcm/SAZBFyG +LYOChvh6hPU7joMdDwGfbiLrBnMag+BtGlmPLWwp/Kt1wNmrRhduyTQFhN3PP6fz +nBr9vVny2FewB2gHmelaPS//tXdxivSXKz3NFqqXLDJjq7P8wA== +-----END CERTIFICATE----- +EOF + close(CERTFILE); + + #KDC certificate + # hxtool request-create \ + # --subject="CN=krbtgt,CN=users,DC=samba,DC=example,DC=com" \ + # --key="FILE:$KEYFILE" $KDCREQ + + # hxtool issue-certificate --ca-certificate=FILE:$CAFILE,$KEYFILE \ + # --type="pkinit-kdc" \ + # --pk-init-principal="krbtgt/SAMBA.EXAMPLE.COM@SAMBA.EXAMPLE.COM" \ + # --req="PKCS10:$KDCREQ" --certificate="FILE:$KDCCERTFILE" \ + # --lifetime="25 years" + + open(KDCCERTFILE, ">$kdccertfile"); + print KDCCERTFILE <<EOF; +-----BEGIN CERTIFICATE----- +MIIDDDCCAnWgAwIBAgIUI2Tzj+JnMzMcdeabcNo30rovzFAwCwYJKoZIhvcNAQEFMFIxEzAR +BgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxlMRUwEwYKCZImiZPy +LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDgwMzAxMTMxOTIzWhgPMjAzMzAyMjQx +MzE5MjNaMGYxEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl +MRUwEwYKCZImiZPyLGQBGQwFc2FtYmExDjAMBgNVBAMMBXVzZXJzMQ8wDQYDVQQDDAZrcmJ0 +Z3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMqDqkDAIdQwDUN8cOZaFl934XQL70nF +yq+nD2KL0SfcTW5+WlyiXf5L3/oj+5pOYkdmt74MXd1PNv9Q5mjRl6bw34jPOSCgaQVp+Ne5 +PcEvlQ9jb8fof6k+pUhEs6atYiZqLfn1jKgqEXKjftjoc95TxBxn67atL2B5qkhZ966jAgMB +AAGjgcgwgcUwDgYDVR0PAQH/BAQDAgWgMBIGA1UdJQQLMAkGBysGAQUCAwUwVAYDVR0RBE0w +S6BJBgYrBgEFAgKgPzA9oBMbEVNBTUJBLkVYQU1QTEUuQ09NoSYwJKADAgEBoR0wGxsGa3Ji +dGd0GxFTQU1CQS5FWEFNUExFLkNPTTAfBgNVHSMEGDAWgBTC2bn3oAyi2bNFTshV2CtS+jPK +MjAdBgNVHQ4EFgQUwtm596AMotmzRU7IVdgrUvozyjIwCQYDVR0TBAIwADANBgkqhkiG9w0B +AQUFAAOBgQBmrVD5MCmZjfHp1nEnHqTIh8r7lSmVtDx4s9MMjxm9oNrzbKXynvdhwQYFVarc +ge4yRRDXtSebErOl71zVJI9CVeQQpwcH+tA85oGA7oeFtO/S7ls581RUU6tGgyxV4veD+lJv +KPH5LevUtgD+q9H4LU4Sq5N3iFwBaeryB0g2wg== +-----END CERTIFICATE----- +EOF + + # hxtool request-create \ + # --subject="CN=Administrator,CN=users,DC=samba,DC=example,DC=com" \ + # --key="FILE:$ADMINKEYFILE" $ADMINREQFILE + + # hxtool issue-certificate --ca-certificate=FILE:$CAFILE,$KEYFILE \ + # --type="pkinit-client" \ + # --pk-init-principal="administrator@SAMBA.EXAMPLE.COM" \ + # --req="PKCS10:$ADMINREQFILE" --certificate="FILE:$ADMINCERTFILE" \ + # --lifetime="25 years" + + open(ADMINCERTFILE, ">$admincertfile"); + print ADMINCERTFILE <<EOF; +-----BEGIN CERTIFICATE----- +MIIDHTCCAoagAwIBAgIUC0W5dW/N9kE+NgD0mKK34YgyqQ0wCwYJKoZIhvcNAQEFMFIxEzAR +BgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxlMRUwEwYKCZImiZPy +LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDgwMzAxMTMyMzAwWhgPMjAzMzAyMjQx +MzIzMDBaMG0xEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl +MRUwEwYKCZImiZPyLGQBGQwFc2FtYmExDjAMBgNVBAMMBXVzZXJzMRYwFAYDVQQDDA1BZG1p +bmlzdHJhdG9yMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0+OL7TQBj0RejbIH1+g5G +eRaWaM9xF43uE5y7jUHEsi5owhZF5iIoHZeeL6cpDF5y1BZRs0JlA1VqMry1jjKlzFYVEMMF +xB6esnXhl0Jpip1JkUMMXLOP1m/0dqayuHBWozj9f/cdyCJr0wJIX1Z8Pr+EjYRGPn/MF0xd +l3JRlwIDAQABo4HSMIHPMA4GA1UdDwEB/wQEAwIFoDAoBgNVHSUEITAfBgcrBgEFAgMEBggr +BgEFBQcDAgYKKwYBBAGCNxQCAjBIBgNVHREEQTA/oD0GBisGAQUCAqAzMDGgExsRU0FNQkEu +RVhBTVBMRS5DT02hGjAYoAMCAQGhETAPGw1hZG1pbmlzdHJhdG9yMB8GA1UdIwQYMBaAFMLZ +ufegDKLZs0VOyFXYK1L6M8oyMB0GA1UdDgQWBBQg81bLyfCA88C2B/BDjXlGuaFaxjAJBgNV +HRMEAjAAMA0GCSqGSIb3DQEBBQUAA4GBAHsqSqul0hZCXn4t8Kfp3v/JLMiUMJihR1XOgzoa +ufLOQ1HNzFUHKuo1JEQ1+i5gHT/arLu/ZBF4BfQol7vW27gKIEt0fkRV8EvoPxXvSokHq0Ku +HCuPOhYNEP3wYiwB3g93NMCinWVlz0mh5aijEU7y/XrjlZxBKFFrTE+BJi1o +-----END CERTIFICATE----- +EOF + close(ADMINCERTFILE); +} + +sub provision($$$$$$) +{ + my ($self, $prefix, $server_role, $netbiosname, $netbiosalias, $swiface, $password) = @_; + + my $smbd_loglevel = 1; + my $username = "administrator"; + my $domain = "SAMBADOMAIN"; + my $realm = "SAMBA.EXAMPLE.COM"; + my $dnsname = "samba.example.com"; + my $basedn = "dc=samba,dc=example,dc=com"; + my $unix_name = ($ENV{USER} or $ENV{LOGNAME} or `whoami`); + chomp $unix_name; + my $unix_uid = $>; + my $unix_gids_str = $); + my @unix_gids = split(" ", $unix_gids_str); + my $srcdir="$RealBin/.."; + -d $prefix or mkdir($prefix, 0777) or die("Unable to create $prefix"); + my $prefix_abs = abs_path($prefix); + my $tmpdir = "$prefix_abs/tmp"; + my $etcdir = "$prefix_abs/etc"; + my $piddir = "$prefix_abs/pid"; + my $conffile = "$etcdir/smb.conf"; + my $krb5_config = "$etcdir/krb5.conf"; + my $privatedir = "$prefix_abs/private"; + my $ncalrpcdir = "$prefix_abs/ncalrpc"; + my $lockdir = "$prefix_abs/lockdir"; + my $winbindd_socket_dir = "$prefix_abs/winbindd_socket"; + my $winbindd_privileged_socket_dir = "$prefix_abs/winbindd_privileged_socket"; + my $ntp_signd_socket_dir = "$prefix_abs/ntp_signd_socket"; + my $nsswrap_passwd = "$etcdir/passwd"; + my $nsswrap_group = "$etcdir/group"; + + my $configuration = "--configfile=$conffile"; + my $ldapdir = "$privatedir/ldap"; + + my $tlsdir = "$privatedir/tls"; + + my $ifaceipv4 = "127.0.0.$swiface"; + my $interfaces = "$ifaceipv4/8"; + + (system("rm -rf $prefix/*") == 0) or die("Unable to clean up"); + mkdir($_, 0777) foreach ($privatedir, $etcdir, $piddir, $ncalrpcdir, $lockdir, + $tmpdir, "$tmpdir/test1", "$tmpdir/test2"); + + + my $localbasedn = $basedn; + $localbasedn = "CN=$netbiosname" if $server_role eq "member server"; + + open(CONFFILE, ">$conffile"); + print CONFFILE " +[global] + netbios name = $netbiosname + netbios aliases = $netbiosalias + workgroup = $domain + realm = $realm + private dir = $privatedir + pid directory = $piddir + ncalrpc dir = $ncalrpcdir + lock dir = $lockdir + setup directory = $self->{setupdir} + modules dir = $self->{bindir}/modules + js include = $srcdir/scripting/libjs + winbindd socket directory = $winbindd_socket_dir + winbindd privileged socket directory = $winbindd_privileged_socket_dir + ntp signd socket directory = $ntp_signd_socket_dir + winbind separator = / + name resolve order = bcast + interfaces = $interfaces + tls dh params file = $tlsdir/dhparms.pem + panic action = $srcdir/script/gdb_backtrace \%PID% \%PROG% + wins support = yes + server role = $server_role + max xmit = 32K + server max protocol = SMB2 + notify:inotify = false + ldb:nosync = true +#We don't want to pass our self-tests if the PAC code is wrong + gensec:require_pac = true + log level = $smbd_loglevel + +[tmp] + path = $tmpdir + read only = no + ntvfs handler = posix + posix:sharedelay = 100000 + posix:eadb = $lockdir/eadb.tdb + posix:oplocktimeout = 3 + posix:writetimeupdatedelay = 500000 + +[test1] + path = $tmpdir/test1 + read only = no + ntvfs handler = posix + posix:sharedelay = 100000 + posix:eadb = $lockdir/eadb.tdb + posix:oplocktimeout = 3 + posix:writetimeupdatedelay = 500000 + +[test2] + path = $tmpdir/test2 + read only = no + ntvfs handler = posix + posix:sharedelay = 100000 + posix:eadb = $lockdir/eadb.tdb + posix:oplocktimeout = 3 + posix:writetimeupdatedelay = 500000 + +[cifs] + read only = no + ntvfs handler = cifs + cifs:server = $netbiosname + cifs:share = tmp +#There is no username specified here, instead the client is expected +#to log in with kerberos, and smbd will used delegated credentials. + +[simple] + path = $tmpdir + read only = no + ntvfs handler = simple + +[sysvol] + path = $lockdir/sysvol + read only = yes + +[netlogon] + path = $lockdir/sysvol/$dnsname/scripts + read only = no + +[cifsposix] + copy = simple + ntvfs handler = cifsposix +"; + close(CONFFILE); + + $self->mk_keyblobs($tlsdir); + + open(KRB5CONF, ">$krb5_config"); + print KRB5CONF " +#Generated krb5.conf for $realm + +[libdefaults] + default_realm = $realm + dns_lookup_realm = false + dns_lookup_kdc = false + ticket_lifetime = 24h + forwardable = yes + +[realms] + $realm = { + kdc = 127.0.0.1:88 + admin_server = 127.0.0.1:88 + default_domain = $dnsname + } + $dnsname = { + kdc = 127.0.0.1:88 + admin_server = 127.0.0.1:88 + default_domain = $dnsname + } + $domain = { + kdc = 127.0.0.1:88 + admin_server = 127.0.0.1:88 + default_domain = $dnsname + } + +[appdefaults] + pkinit_anchors = FILE:$tlsdir/ca.pem + +[kdc] + enable-pkinit = true + pkinit_identity = FILE:$tlsdir/kdc.pem,$tlsdir/key.pem + pkinit_anchors = FILE:$tlsdir/ca.pem + +[domain_realm] + .$dnsname = $realm +"; + close(KRB5CONF); + + open(PWD, ">$nsswrap_passwd"); + print PWD " +root:x:0:0:root gecos:$prefix_abs:/bin/false +$unix_name:x:$unix_uid:$unix_gids[0]:$unix_name gecos:$prefix_abs:/bin/false +nobody:x:65534:65533:nobody gecos:$prefix_abs:/bin/false +"; + close(PWD); + + open(GRP, ">$nsswrap_group"); + print GRP " +root:x:0: +wheel:x:10: +users:x:100: +nobody:x:65533: +nogroup:x:65534:nobody +"; + close(GRP); + +#Ensure the config file is valid before we start + if (system("$self->{bindir}/testparm $configuration -v --suppress-prompt >/dev/null 2>&1") != 0) { + system("$self->{bindir}/testparm -v --suppress-prompt $configuration >&2"); + die("Failed to create a valid smb.conf configuration!"); + } + + (system("($self->{bindir}/testparm $configuration -v --suppress-prompt --parameter-name=\"netbios name\" --section-name=global 2> /dev/null | grep -i \"^$netbiosname\" ) >/dev/null 2>&1") == 0) or die("Failed to create a valid smb.conf configuration! $self->{bindir}/testparm $configuration -v --suppress-prompt --parameter-name=\"netbios name\" --section-name=global"); + + my @provision_options = (); + push (@provision_options, "NSS_WRAPPER_PASSWD=\"$nsswrap_passwd\""); + push (@provision_options, "NSS_WRAPPER_GROUP=\"$nsswrap_group\""); + if (defined($ENV{GDB_PROVISION})) { + push (@provision_options, "gdb --args python"); + } + if (defined($ENV{VALGRIND_PROVISION})) { + push (@provision_options, "valgrind"); + } + push (@provision_options, "$self->{setupdir}/provision"); + push (@provision_options, split(' ', $configuration)); + push (@provision_options, "--host-name=$netbiosname"); + push (@provision_options, "--host-ip=$ifaceipv4"); + push (@provision_options, "--quiet"); + push (@provision_options, "--domain=$domain"); + push (@provision_options, "--realm=$realm"); + push (@provision_options, "--adminpass=$password"); + push (@provision_options, "--krbtgtpass=krbtgt$password"); + push (@provision_options, "--machinepass=machine$password"); + push (@provision_options, "--root=$unix_name"); + + push (@provision_options, "--server-role=\"$server_role\""); + + my $ldap_uri= "$ldapdir/ldapi"; + $ldap_uri =~ s|/|%2F|g; + $ldap_uri = "ldapi://$ldap_uri"; + + my $ret = { + KRB5_CONFIG => $krb5_config, + PIDDIR => $piddir, + SERVER => $netbiosname, + SERVER_IP => $ifaceipv4, + NETBIOSNAME => $netbiosname, + NETBIOSALIAS => $netbiosalias, + LDAP_URI => $ldap_uri, + DOMAIN => $domain, + USERNAME => $username, + REALM => $realm, + PASSWORD => $password, + LDAPDIR => $ldapdir, + WINBINDD_SOCKET_DIR => $winbindd_socket_dir, + NCALRPCDIR => $ncalrpcdir, + LOCKDIR => $lockdir, + CONFIGURATION => $configuration, + SOCKET_WRAPPER_DEFAULT_IFACE => $swiface, + NSS_WRAPPER_PASSWD => $nsswrap_passwd, + NSS_WRAPPER_GROUP => $nsswrap_group, + }; + + if (defined($self->{ldap})) { + + push (@provision_options, "--ldap-backend=$ldap_uri"); + system("$self->{setupdir}/provision-backend $configuration --ldap-admin-pass=$password --root=$unix_name --realm=$realm --domain=$domain --host-name=$netbiosname --ldap-backend-type=$self->{ldap}>&2") == 0 or die("backend provision failed"); + + push (@provision_options, "--password=$password"); + + if ($self->{ldap} eq "openldap") { + push (@provision_options, "--username=samba-admin"); + ($ret->{SLAPD_CONF}, $ret->{OPENLDAP_PIDFILE}) = $self->mk_openldap($ldapdir, $configuration) or die("Unable to create openldap directories"); + push (@provision_options, "--ldap-backend-type=openldap"); + } elsif ($self->{ldap} eq "fedora-ds") { + push (@provision_options, "--simple-bind-dn=cn=Manager,$localbasedn"); + ($ret->{FEDORA_DS_DIR}, $ret->{FEDORA_DS_PIDFILE}) = $self->mk_fedora_ds($ldapdir, $configuration) or die("Unable to create fedora ds directories"); + push (@provision_options, "--ldap-backend-type=fedora-ds"); + } + + $self->slapd_start($ret) or + die("couldn't start slapd"); + } + + my $provision_cmd = join(" ", @provision_options); + (system($provision_cmd) == 0) or die("Unable to provision: \n$provision_cmd\n"); + + if (defined($self->{ldap})) { + $self->slapd_stop($ret) or + die("couldn't stop slapd"); + } + + return $ret; +} + +sub provision_member($$$) +{ + my ($self, $prefix, $dcvars) = @_; + print "PROVISIONING MEMBER..."; + + my $ret = $self->provision($prefix, + "member server", + "localmember3", + "localmember", + 3, + "localmemberpass"); + + $ret or die("Unable to provision"); + + my $cmd = ""; + $cmd .= "SOCKET_WRAPPER_DEFAULT_IFACE=\"$ret->{SOCKET_WRAPPER_DEFAULT_IFACE}\" "; + $cmd .= "KRB5_CONFIG=\"$ret->{KRB5_CONFIG}\" "; + $cmd .= "$self->{bindir}/net join $ret->{CONFIGURATION} $dcvars->{DOMAIN} member"; + $cmd .= " -U$dcvars->{USERNAME}\%$dcvars->{PASSWORD}"; + + system($cmd) == 0 or die("Join failed\n$cmd"); + + $ret->{SMBD_TEST_FIFO} = "$prefix/smbd_test.fifo"; + $ret->{SMBD_TEST_LOG} = "$prefix/smbd_test.log"; + $ret->{SMBD_TEST_LOG_POS} = 0; + + $ret->{DC_SERVER} = $dcvars->{SERVER}; + $ret->{DC_SERVER_IP} = $dcvars->{SERVER_IP}; + $ret->{DC_NETBIOSNAME} = $dcvars->{NETBIOSNAME}; + $ret->{DC_NETBIOSALIAS} = $dcvars->{NETBIOSALIAS}; + $ret->{DC_USERNAME} = $dcvars->{USERNAME}; + $ret->{DC_PASSWORD} = $dcvars->{PASSWORD}; + + return $ret; +} + +sub provision_dc($$) +{ + my ($self, $prefix) = @_; + + print "PROVISIONING DC..."; + my $ret = $self->provision($prefix, + "domain controller", + "localdc1", + "localdc", + 1, + "localdcpass"); + + $self->add_wins_config("$prefix/private") or + die("Unable to add wins configuration"); + + $ret->{SMBD_TEST_FIFO} = "$prefix/smbd_test.fifo"; + $ret->{SMBD_TEST_LOG} = "$prefix/smbd_test.log"; + $ret->{SMBD_TEST_LOG_POS} = 0; + return $ret; +} + +sub teardown_env($$) +{ + my ($self, $envvars) = @_; + my $pid; + + close(DATA); + + if (-f "$envvars->{PIDDIR}/smbd.pid" ) { + open(IN, "<$envvars->{PIDDIR}/smbd.pid") or die("unable to open smbd pid file"); + $pid = <IN>; + close(IN); + + # Give the process 20 seconds to exit. gcov needs + # this time to write out the covarge data + my $count = 0; + until (kill(0, $pid) == 0) { + # if no process sucessfully signalled, then we are done + sleep(1); + $count++; + last if $count > 20; + } + + # If it is still around, kill it + if ($count > 20) { + print "smbd process $pid took more than $count seconds to exit, killing\n"; + kill 9, $pid; + } + } + + my $failed = $? >> 8; + + $self->slapd_stop($envvars) if ($self->{ldap}); + + print $self->getlog_env($envvars); + + return $failed; +} + +sub getlog_env($$) +{ + my ($self, $envvars) = @_; + my $title = "SMBD LOG of: $envvars->{NETBIOSNAME}\n"; + my $out = $title; + + open(LOG, "<$envvars->{SMBD_TEST_LOG}"); + + seek(LOG, $envvars->{SMBD_TEST_LOG_POS}, SEEK_SET); + while (<LOG>) { + $out .= $_; + } + $envvars->{SMBD_TEST_LOG_POS} = tell(LOG); + close(LOG); + + return "" if $out eq $title; + + return $out; +} + +sub check_env($$) +{ + my ($self, $envvars) = @_; + + return 1 if (-p $envvars->{SMBD_TEST_FIFO}); + + print $self->getlog_env($envvars); + + return 0; +} + +sub setup_env($$$) +{ + my ($self, $envname, $path) = @_; + + if ($envname eq "dc") { + return $self->setup_dc("$path/dc"); + } elsif ($envname eq "member") { + if (not defined($self->{vars}->{dc})) { + $self->setup_dc("$path/dc"); + } + return $self->setup_member("$path/member", $self->{vars}->{dc}); + } else { + die("Samba4 can't provide environment '$envname'"); + } +} + +sub setup_member($$$$) +{ + my ($self, $path, $dc_vars) = @_; + + my $env = $self->provision_member($path, $dc_vars); + + $self->check_or_start($env, ($ENV{SMBD_MAXTIME} or 7500)); + + $self->wait_for_start($env); + + return $env; +} + +sub setup_dc($$) +{ + my ($self, $path) = @_; + + my $env = $self->provision_dc($path); + + $self->check_or_start($env, + ($ENV{SMBD_MAXTIME} or 7500)); + + $self->wait_for_start($env); + + $self->{vars}->{dc} = $env; + + return $env; +} + +sub stop($) +{ + my ($self) = @_; +} + +1; diff --git a/selftest/target/Windows.pm b/selftest/target/Windows.pm new file mode 100644 index 0000000000..d0c90d7f7b --- /dev/null +++ b/selftest/target/Windows.pm @@ -0,0 +1,40 @@ +#!/usr/bin/perl +# Bootstrap Samba and run a number of tests against it. +# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org> +# Published under the GNU GPL, v3 or later. + +package Windows; + +use strict; +use FindBin qw($RealBin); +use POSIX; + +sub new($) +{ + my ($classname) = @_; + my $self = { }; + bless $self; + return $self; +} + +sub provision($$$) +{ + my ($self, $environment, $prefix) = @_; + + die ("Windows tests will not run without root privileges.") + if (`whoami` ne "root"); + + die("Environment variable WINTESTCONF has not been defined.\n". + "Windows tests will not run unconfigured.") if (not defined($ENV{WINTESTCONF})); + + die ("$ENV{WINTESTCONF} could not be read.") if (! -r $ENV{WINTESTCONF}); + + $ENV{WINTEST_DIR}="$ENV{SRCDIR}/selftest/win"; +} + +sub setup_env($$) +{ + my ($self, $name) = @_; +} + +1; diff --git a/selftest/test_samba4.pl b/selftest/test_samba4.pl new file mode 100755 index 0000000000..f2935be66b --- /dev/null +++ b/selftest/test_samba4.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl + +use Test::More tests => 3; +use FindBin qw($RealBin); +use lib $RealBin; +use Samba4; + +my $s = new Samba4($RealBin."/../bin", undef, $RealBin."/../setup"); + +ok($s); + +is($RealBin."/../bin", $s->{bindir}); + +ok($s->write_ldb_file("tmpldb", " +dn: a=b +a: b +c: d +")); + +unlink("tmpldb"); diff --git a/selftest/test_subunit.pl b/selftest/test_subunit.pl new file mode 100755 index 0000000000..28522ed49f --- /dev/null +++ b/selftest/test_subunit.pl @@ -0,0 +1,7 @@ +#!/usr/bin/perl + +use Test::More tests => 0; +use FindBin qw($RealBin); +use lib $RealBin; +use Subunit qw(parse_results); + diff --git a/selftest/test_w2k3.sh b/selftest/test_w2k3.sh new file mode 100755 index 0000000000..3cd034d000 --- /dev/null +++ b/selftest/test_w2k3.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +# tests that should pass against a w2k3 DC, as administrator + +# add tests to this list as they start passing, so we test +# that they stay passing +ncacn_np_tests="RPC-SCHANNEL RPC-DSSETUP RPC-EPMAPPER RPC-SAMR RPC-WKSSVC RPC-SRVSVC RPC-EVENTLOG RPC-NETLOGON RPC-LSA RPC-SAMLOGON RPC-SAMSYNC RPC-MULTIBIND RPC-WINREG RPC-SPOOLSS RPC-SPOOLSS-WIN" +ncacn_ip_tcp_tests="RPC-SCHANNEL RPC-EPMAPPER RPC-SAMR RPC-NETLOGON RPC-LSA RPC-SAMLOGON RPC-SAMSYNC RPC-MULTIBIND" + +if [ $# -lt 4 ]; then +cat <<EOF +Usage: test_w2k3.sh SERVER USERNAME PASSWORD DOMAIN REALM +EOF +exit 1; +fi + +server="$1" +username="$2" +password="$3" +domain="$4" +realm="$5" +shift 5 + +incdir=`dirname $0` +. $incdir/test_functions.sh + +OPTIONS="-U$username%$password -W $domain --option realm=$realm" + +name="RPC-SPOOLSS on ncacn_np" +testit "$name" rpc bin/smbtorture $TORTURE_OPTIONS ncacn_np:"$server" $OPTIONS RPC-SPOOLSS "$*" + +for bindoptions in padcheck connect sign seal ntlm,sign ntlm,seal $VALIDATE bigendian; do + for transport in ncacn_ip_tcp ncacn_np; do + case $transport in + ncacn_np) tests=$ncacn_np_tests ;; + ncacn_ip_tcp) tests=$ncacn_ip_tcp_tests ;; + esac + for t in $tests; do + name="$t on $transport with $bindoptions" + testit "$name" rpc bin/smbtorture $TORTURE_OPTIONS $transport:"$server[$bindoptions]" $OPTIONS $t "$*" + done + done +done + +name="RPC-DRSUAPI on ncacn_ip_tcp with seal" +testit "$name" rpc bin/smbtorture $TORTURE_OPTIONS ncacn_ip_tcp:"$server[seal]" $OPTIONS RPC-DRSUAPI "$*" +name="RPC-DRSUAPI on ncacn_ip_tcp with seal,bigendian" +testit "$name" rpc bin/smbtorture $TORTURE_OPTIONS ncacn_ip_tcp:"$server[seal,bigendian]" $OPTIONS RPC-DRSUAPI "$*" |