The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
BEGIN {

  foreach (qw(my.cfg test.cfg)) {
    -f and require "$_" and last;
  }

  undef $SERVER_EXE unless $SERVER_EXE and -x $SERVER_EXE;

  # If your host cannot be contacted as localhost, change this
  $HOST     ||= '127.0.0.1';

  # Where to put temporary files while testing
  # the Makefile is setup to delete temp/ when make clean is run
  $TEMPDIR  = "./temp";
  $SCHEMA_DIR ||= "./data";
  $SLAPD_DB ||= 'ldbm';
  $SCHEMA_CHECK = 1 unless defined $SCHEMA_CHECK;

  $TESTDB   = "$TEMPDIR/test-db";
  $CONF     = "$TEMPDIR/conf";
  $PASSWD   = 'secret';
  $BASEDN   = "o=University of Michigan, c=US";
  $MANAGERDN= "cn=Manager, o=University of Michigan, c=US";
  $JAJDN    = "cn=James A Jones 1, ou=Alumni Association, ou=People, o=University of Michigan, c=US";
  $BABSDN   = "cn=Barbara Jensen, ou=Information Technology Division, ou=People, o=University of Michigan, c=US";
  $PORT     = 9009;
  @URL      = ();

  my @server_opts;
  ($SERVER_TYPE,@server_opts) = split(/\+/, $SERVER_TYPE || 'none');

  if ($SERVER_TYPE eq 'openldap1') {
    $CONF_IN	  = "./data/slapd-conf.in";
    @LDAPD	  = ($SERVER_EXE, '-f',$CONF,'-p',$PORT,qw(-d 1));
    $LDAP_VERSION = 2;
  }
  elsif ($SERVER_TYPE eq 'openldap2') {
    $SSL_PORT = 9010 if grep { $_ eq 'ssl' } @server_opts
      and eval { require IO::Socket::SSL; 1};
    ($IPC_SOCK = "$TEMPDIR/ldapi_sock") =~ s,/,%2f,g if grep { $_ eq 'ipc' } @server_opts;
    $SASL = 1 if grep { $_ eq 'sasl' } @server_opts
      and eval { require Authen::SASL; 1 };
    $CONF_IN	  = "./data/slapd2-conf.in";
    push @URL, "ldap://${HOST}:$PORT/";
    push @URL, "ldaps://${HOST}:$SSL_PORT/" if $SSL_PORT;
    push @URL, "ldapi://$IPC_SOCK/" if $IPC_SOCK;
    @LDAPD	  = ($SERVER_EXE, '-f',$CONF,'-h', "@URL",qw(-d 1));
    $LDAP_VERSION = 3;
  }

  $LDAP_VERSION ||= 2;
  mkdir($TEMPDIR,0777);
  die "$TEMPDIR is not a directory" unless -d $TEMPDIR;
}

use Net::LDAP;
use Net::LDAP::LDIF;
use Net::LDAP::Util qw(canonical_dn);
use File::Path qw(rmtree);
use File::Basename qw(basename);

my $pid;

sub start_server {
  my %arg = (version => 2, @_);

  unless ($LDAP_VERSION >= $arg{version}
	and $LDAPD[0] and -x $LDAPD[0]
	and (!$arg{ssl} or $SSL_PORT)
	and (!$arg{ipc} or $IPC_SOCK))
  {
    print "1..0 # Skip No server\n";
    exit;
  }

  if ($CONF_IN and -f $CONF_IN) {
    # Create slapd config file
    open(CONFI,"<$CONF_IN") or die "$!";
    open(CONFO,">$CONF") or die "$!";
    while(<CONFI>) {
      s/\$([A-Z]\w*)/${$1}/g;
      s/^TLS/#TLS/ unless $SSL_PORT;
      s/^(sasl.*)/#$1/ unless $SASL;
      s/^schemacheck.*// unless $SCHEMA_CHECK;
      print CONFO;
    }
    close(CONFI);
    close(CONFO);
  }

  rmtree($TESTDB) if ( -d $TESTDB );
  mkdir($TESTDB,0777);
  die "$TESTDB is not a directory" unless -d $TESTDB;

  warn "@LDAPD" if $ENV{TEST_VERBOSE};

  my $log = $TEMPDIR . "/" . basename($0,'.t');

  unless ($pid = fork) {
    die "fork: $!" unless defined $pid;

    open(STDERR,">$log");
    open(STDOUT,">&STDERR");
    close(STDIN);

    exec(@LDAPD) or die "cannot exec @LDAPD";
  }

  sleep 2; # wait for server to start
}

sub kill_server {
  if ($pid) {
    kill 9, $pid;
    sleep 2;
    undef $pid;
  }
}

END {
  kill_server();
}

sub client {
  my %arg = @_;
  my $ldap;
  my $count;
  local $^W = 0;
  if ($arg{ssl}) {
    require Net::LDAPS;
    until($ldap = Net::LDAPS->new($HOST, port => $SSL_PORT, version => 3)) {
      die "ldaps://$HOST:$SSL_PORT/ $@" if ++$count > 10;
      sleep 1;
    }
  }
  elsif ($arg{ipc}) {
    require Net::LDAPI;
    until($ldap = Net::LDAPI->new($IPC_SOCK)) {
      die "ldapi://$IPC_SOCK/ $@" if ++$count > 10;
      sleep 1;
    }
  }
  elsif ($arg{url}) {
    print "Trying $arg{url}\n";
    until($ldap = Net::LDAP->new($arg{url})) {
      die "$arg{url} $@" if ++$count > 10;
      sleep 1;
    }
  }
  else {
    until($ldap = Net::LDAP->new($HOST, port => $PORT, version => $LDAP_VERSION)) {
      die "ldap://$HOST:$PORT/ $@" if ++$count > 10;
      sleep 1;
    }
  }
  $ldap;
}

sub compare_ldif {
  my($test,$mesg) = splice(@_,0,2);

  unless (ok(!$mesg->code, $mesg->error)) {
    skip(2, $mesg->error);
    return;
  }

  my $ldif = Net::LDAP::LDIF->new("$TEMPDIR/${test}-out.ldif","w", lowercase => 1);
  unless (ok($ldif, "Read ${test}-out.ldif")) {
    skip(1,"Read error");
    return;
  }

  my @canon_opt = (casefold => 'lower', separator => ', ');
  foreach $entry (@_) {
    $entry->dn(canonical_dn($entry->dn, @canon_opt));
    foreach $attr ($entry->attributes) {
      $entry->delete($attr) if $attr =~ /^(modifiersname|modifytimestamp|creatorsname|createtimestamp)$/i;
      if ($attr =~ /^(seealso|member|owner)$/i) {
	$entry->replace($attr => [ map { canonical_dn($_, @canon_opt) } $entry->get_value($attr) ]);
      }
    }
    $ldif->write($entry);
  }

  $ldif->done; # close the file;

  ok(!compare("$TEMPDIR/${test}-out.ldif","data/${test}-cmp.ldif"), "data/${test}-cmp.ldif");
}

require File::Compare;

sub compare($$) {
  local(*FH1,*FH2);
  not( open(FH1,"<".$_[0])
       && open(FH2,"<".$_[1])
       && 0 == File::Compare::compare(*FH1,*FH2, -s FH1)
  );
}

sub ldif_populate {
  my ($ldap, $file, $change) = @_;
  my $ok = 1;

  my $ldif = Net::LDAP::LDIF->new($file,"r", changetype => $change || 'add')
	or return;

  while (my $e = $ldif->read_entry) {
    $mesg = $e->update($ldap);
    if ($mesg->code) {
      $ok = 0;
      Net::LDAP::LDIF->new(qw(- w))->write_entry($e);
      print "# ",$mesg->code,": ",$mesg->error,"\n";
    }
  }
  $ok;
}

my $number = 0;
sub ok {
	my ($condition, $name) = @_;

	my $message = $condition ? "ok " : "not ok ";
	$message .= ++$number;
	$message .= " # $name" if defined $name;
	print $message, "\n";
	return $condition;
}

sub is {
	my ($got, $expected, $name) = @_;

	for ($got, $expected) {
		$_ = 'undef' unless defined $_;
	}

	unless (ok($got eq $expected, $name)) {
		warn "Got: '$got'\nExpected: '$expected'\n" . join(' ', caller) . "\n";
	}
}

sub skip {
	my ($reason, $num) = @_;
	$reason ||= '';
	$number ||= 1;

	for (1 .. $num) {
		$number++;
		print "ok $number # skip $reason\n";
	}
}

1;