diff options
Diffstat (limited to 'rt/lib/RT/Test.pm')
-rw-r--r-- | rt/lib/RT/Test.pm | 1299 |
1 files changed, 0 insertions, 1299 deletions
diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm deleted file mode 100644 index b8d1683d0..000000000 --- a/rt/lib/RT/Test.pm +++ /dev/null @@ -1,1299 +0,0 @@ -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# <jesse@bestpractical.com> -# -# (Except where explicitly superseded by other copyright notices) -# -# -# LICENSE: -# -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -# -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301 or visit their web page on the internet at -# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# -# CONTRIBUTION SUBMISSION POLICY: -# -# (The following paragraph is not intended to limit the rights granted -# to you to modify and distribute this software under the terms of -# the GNU General Public License and is only of importance to you if -# you choose to contribute your changes and enhancements to the -# community by submitting them to Best Practical Solutions, LLC.) -# -# By intentionally submitting any modifications, corrections or -# derivatives to this work, or any other work intended for use with -# Request Tracker, to Best Practical Solutions, LLC, you confirm that -# you are the copyright holder for those contributions and you grant -# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, -# royalty-free, perpetual, license to use, copy, create derivative -# works based on those contributions, and sublicense and distribute -# those contributions and any derivatives thereof. -# -# END BPS TAGGED BLOCK }}} - -package RT::Test; - -use strict; -use warnings; - -use base 'Test::More'; - -use Socket; -use File::Temp qw(tempfile); -use File::Path qw(mkpath); -use File::Spec; - -our $SKIP_REQUEST_WORK_AROUND = 0; - -use HTTP::Request::Common (); -use Hook::LexWrap; -wrap 'HTTP::Request::Common::form_data', - post => sub { - return if $SKIP_REQUEST_WORK_AROUND; - my $data = $_[-1]; - if (ref $data) { - $data->[0] = Encode::encode_utf8($data->[0]); - } - else { - $_[-1] = Encode::encode_utf8($_[-1]); - } - }; - - -our @EXPORT = qw(is_empty); -our ($port, $dbname); -our @SERVERS; - -my %tmp = ( - directory => undef, - config => { - RT => undef, - apache => undef, - }, - mailbox => undef, -); - -=head1 NAME - -RT::Test - RT Testing - -=head1 NOTES - -=head2 COVERAGE - -To run the rt test suite with coverage support, install L<Devel::Cover> and run: - - make test RT_DBA_USER=.. RT_DBA_PASSWORD=.. HARNESS_PERL_SWITCHES=-MDevel::Cover - cover -ignore_re '^var/mason_data/' -ignore_re '^t/' - -The coverage tests have DevelMode turned off, and have -C<named_component_subs> enabled for L<HTML::Mason> to avoid an optimizer -problem in Perl that hides the top-level optree from L<Devel::Cover>. - -=cut - -sub generate_port { - my $self = shift; - my $port = 1024 + int rand(10000) + $$ % 1024; - - my $paddr = sockaddr_in( $port, inet_aton('localhost') ); - socket( SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp') ) - or die "socket: $!"; - if ( connect( SOCK, $paddr ) ) { - close(SOCK); - return generate_port(); - } - close(SOCK); - - return $port; -} - -BEGIN { - $port = generate_port(); - $dbname = $ENV{RT_TEST_PARALLEL}? "rt3test_$port" : "rt3test"; -}; - -sub import { - my $class = shift; - my %args = @_; - - # Spit out a plan (if we got one) *before* we load modules - if ( $args{'tests'} ) { - $class->builder->plan( tests => $args{'tests'} ) - unless $args{'tests'} eq 'no_declare'; - } - else { - $class->builder->no_plan unless $class->builder->has_plan; - } - - $class->bootstrap_tempdir; - - $class->bootstrap_config( %args ); - - use RT; - RT::LoadConfig; - - if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - - $class->bootstrap_db( %args ); - - RT->Init; - - $class->bootstrap_plugins( %args ); - - $class->set_config_wrapper; - - my $screen_logger = $RT::Logger->remove( 'screen' ); - require Log::Dispatch::Perl; - $RT::Logger->add( Log::Dispatch::Perl->new - ( name => 'rttest', - min_level => $screen_logger->min_level, - action => { error => 'warn', - critical => 'warn' } ) ); - - # XXX: this should really be totally isolated environment so we - # can parallelize and be sane - mkpath [ $RT::MasonSessionDir ] - if RT->Config->Get('DatabaseType'); - - my $level = 1; - while ( my ($package) = caller($level-1) ) { - last unless $package =~ /Test/; - $level++; - } - - Test::More->export_to_level($level); - __PACKAGE__->export_to_level($level); -} - -sub is_empty($;$) { - my ($v, $d) = shift; - local $Test::Builder::Level = $Test::Builder::Level + 1; - return Test::More::ok(1, $d) unless defined $v; - return Test::More::ok(1, $d) unless length $v; - return Test::More::is($v, '', $d); -} - -my $created_new_db; # have we created new db? mainly for parallel testing - -sub db_requires_no_dba { - my $self = shift; - my $db_type = RT->Config->Get('DatabaseType'); - return 1 if $db_type eq 'SQLite'; -} - -sub bootstrap_tempdir { - my $self = shift; - my $test_file = ( - File::Spec->rel2abs((caller)[1]) - =~ m{(?:^|[\\/])t[/\\](.*)} - ); - my $dir_name = File::Spec->rel2abs('t/tmp/'. $test_file); - mkpath( $dir_name ); - return $tmp{'directory'} = File::Temp->newdir( - DIR => $dir_name - ); -} - -sub bootstrap_config { - my $self = shift; - my %args = @_; - - $tmp{'config'}{'RT'} = File::Spec->catfile( - "$tmp{'directory'}", 'RT_SiteConfig.pm' - ); - open my $config, '>', $tmp{'config'}{'RT'} - or die "Couldn't open $tmp{'config'}{'RT'}: $!"; - - print $config qq{ -Set( \$WebPort , $port); -Set( \$WebBaseURL , "http://localhost:\$WebPort"); -Set( \$LogToSyslog , undef); -Set( \$LogToScreen , "warning"); -Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/); -Set( \$MailCommand, 'testfile'); -}; - if ( $ENV{'RT_TEST_DB_SID'} ) { # oracle case - print $config "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n"; - print $config "Set( \$DatabaseUser , '$dbname');\n"; - } else { - print $config "Set( \$DatabaseName , '$dbname');\n"; - print $config "Set( \$DatabaseUser , 'u${dbname}');\n"; - } - print $config "Set( \$DevelMode, 0 );\n" - if $INC{'Devel/Cover.pm'}; - - # set mail catcher - my $mail_catcher = $tmp{'mailbox'} = File::Spec->catfile( - $tmp{'directory'}->dirname, 'mailbox.eml' - ); - print $config <<END; -Set( \$MailCommand, sub { - my \$MIME = shift; - - open my \$handle, '>>', '$mail_catcher' - or die "Unable to open '$mail_catcher' for appending: \$!"; - - \$MIME->print(\$handle); - print \$handle "%% split me! %%\n"; - close \$handle; -} ); -END - - print $config $args{'config'} if $args{'config'}; - - print $config "\n1;\n"; - $ENV{'RT_SITE_CONFIG'} = $tmp{'config'}{'RT'}; - close $config; - - return $config; -} - -sub set_config_wrapper { - my $self = shift; - - my $old_sub = \&RT::Config::Set; - no warnings 'redefine'; - *RT::Config::Set = sub { - my @caller = caller; - if ( ($caller[1]||'') =~ /\.t$/ ) { - my ($self, $name) = @_; - my $type = $RT::Config::META{$name}->{'Type'} || 'SCALAR'; - my %sigils = ( - HASH => '%', - ARRAY => '@', - SCALAR => '$', - ); - my $sigil = $sigils{$type} || $sigils{'SCALAR'}; - open my $fh, '>>', $tmp{'config'}{'RT'} - or die "Couldn't open config file: $!"; - require Data::Dumper; - my $dump = Data::Dumper::Dumper([@_[2 .. $#_]]); - $dump =~ s/;\s+$//; - print $fh - "\nSet(${sigil}${name}, \@{". $dump ."}); 1;\n"; - close $fh; - - if ( @SERVERS ) { - warn "you're changing config option in a test file" - ." when server is active"; - } - } - return $old_sub->(@_); - }; -} - -sub bootstrap_db { - my $self = shift; - my %args = @_; - - unless (defined $ENV{'RT_DBA_USER'} && defined $ENV{'RT_DBA_PASSWORD'}) { - Test::More::BAIL_OUT( - "RT_DBA_USER and RT_DBA_PASSWORD environment variables need" - ." to be set in order to run 'make test'" - ) unless $self->db_requires_no_dba; - } - - require RT::Handle; - # bootstrap with dba cred - my $dbh = _get_dbh(RT::Handle->SystemDSN, - $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD}); - - unless ( $ENV{RT_TEST_PARALLEL} ) { - # already dropped db in parallel tests, need to do so for other cases. - RT::Handle->DropDatabase( $dbh, Force => 1 ); - } - - RT::Handle->CreateDatabase( $dbh ); - $dbh->disconnect; - $created_new_db++; - - $dbh = _get_dbh(RT::Handle->DSN, - $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD}); - - $RT::Handle = new RT::Handle; - $RT::Handle->dbh( $dbh ); - $RT::Handle->InsertSchema( $dbh ); - - my $db_type = RT->Config->Get('DatabaseType'); - $RT::Handle->InsertACL( $dbh ) unless $db_type eq 'Oracle'; - - $RT::Handle = new RT::Handle; - $RT::Handle->dbh( undef ); - RT->ConnectToDatabase; - RT->InitLogging; - RT->InitSystemObjects; - $RT::Handle->InsertInitialData; - - DBIx::SearchBuilder::Record::Cachable->FlushCache; - $RT::Handle = new RT::Handle; - $RT::Handle->dbh( undef ); - RT->Init; - - $RT::Handle->PrintError; - $RT::Handle->dbh->{PrintError} = 1; - - unless ( $args{'nodata'} ) { - $RT::Handle->InsertData( $RT::EtcPath . "/initialdata" ); - } - DBIx::SearchBuilder::Record::Cachable->FlushCache; -} - -sub bootstrap_plugins { - my $self = shift; - my %args = @_; - - return unless $args{'requires'}; - - my @plugins = @{ $args{'requires'} }; - push @plugins, $args{'testing'} - if $args{'testing'}; - - require RT::Plugin; - my $cwd; - if ( $args{'testing'} ) { - require Cwd; - $cwd = Cwd::getcwd(); - } - - my $old_func = \&RT::Plugin::_BasePath; - no warnings 'redefine'; - *RT::Plugin::_BasePath = sub { - my $name = $_[0]->{'name'}; - - return $cwd if $args{'testing'} && $name eq $args{'testing'}; - - if ( grep $name eq $_, @plugins ) { - my $variants = join "(?:|::|-|_)", map "\Q$_\E", split /::/, $name; - my ($path) = map $ENV{$_}, grep /^CHIMPS_(?:$variants).*_ROOT$/i, keys %ENV; - return $path if $path; - } - return $old_func->(@_); - }; - - RT->Config->Set( Plugins => @plugins ); - RT->InitPluginPaths; - - require File::Spec; - foreach my $name ( @plugins ) { - my $plugin = RT::Plugin->new( name => $name ); - Test::More::diag( "Initializing DB for the $name plugin" ) - if $ENV{'TEST_VERBOSE'}; - - my $etc_path = $plugin->Path('etc'); - Test::More::diag( "etc path of the plugin is '$etc_path'" ) - if $ENV{'TEST_VERBOSE'}; - - if ( -e $etc_path ) { - my ($ret, $msg) = $RT::Handle->InsertSchema( undef, $etc_path ); - Test::More::ok($ret || $msg =~ /^Couldn't find schema/, "Created schema: ".($msg||'')); - - ($ret, $msg) = $RT::Handle->InsertACL( undef, $etc_path ); - Test::More::ok($ret || $msg =~ /^Couldn't find ACLs/, "Created ACL: ".($msg||'')); - - my $data_file = File::Spec->catfile( $etc_path, 'initialdata' ); - if ( -e $data_file ) { - ($ret, $msg) = $RT::Handle->InsertData( $data_file );; - Test::More::ok($ret, "Inserted data".($msg||'')); - } else { - Test::More::ok(1, "There is no data file" ); - } - } - else { -# we can not say if plugin has no data or we screwed with etc path - Test::More::ok(1, "There is no etc dir: no schema" ); - Test::More::ok(1, "There is no etc dir: no ACLs" ); - Test::More::ok(1, "There is no etc dir: no data" ); - } - - $RT::Handle->Connect; # XXX: strange but mysql can loose connection - } -} - -sub _get_dbh { - my ($dsn, $user, $pass) = @_; - if ( $dsn =~ /Oracle/i ) { - $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8"; - $ENV{'NLS_NCHAR'} = "AL32UTF8"; - } - my $dbh = DBI->connect( - $dsn, $user, $pass, - { RaiseError => 0, PrintError => 1 }, - ); - unless ( $dbh ) { - my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr; - print STDERR $msg; exit -1; - } - return $dbh; -} - -=head1 UTILITIES - -=head2 load_or_create_user - -=cut - -sub load_or_create_user { - my $self = shift; - my %args = ( Privileged => 1, Disabled => 0, @_ ); - - my $MemberOf = delete $args{'MemberOf'}; - $MemberOf = [ $MemberOf ] if defined $MemberOf && !ref $MemberOf; - $MemberOf ||= []; - - my $obj = RT::User->new( $RT::SystemUser ); - if ( $args{'Name'} ) { - $obj->LoadByCols( Name => $args{'Name'} ); - } elsif ( $args{'EmailAddress'} ) { - $obj->LoadByCols( EmailAddress => $args{'EmailAddress'} ); - } else { - die "Name or EmailAddress is required"; - } - if ( $obj->id ) { - # cool - $obj->SetPrivileged( $args{'Privileged'} || 0 ) - if ($args{'Privileged'}||0) != ($obj->Privileged||0); - $obj->SetDisabled( $args{'Disabled'} || 0 ) - if ($args{'Disabled'}||0) != ($obj->Disabled||0); - } else { - my ($val, $msg) = $obj->Create( %args ); - die "$msg" unless $val; - } - - # clean group membership - { - require RT::GroupMembers; - my $gms = RT::GroupMembers->new( $RT::SystemUser ); - my $groups_alias = $gms->Join( - FIELD1 => 'GroupId', TABLE2 => 'Groups', FIELD2 => 'id', - ); - $gms->Limit( ALIAS => $groups_alias, FIELD => 'Domain', VALUE => 'UserDefined' ); - $gms->Limit( FIELD => 'MemberId', VALUE => $obj->id ); - while ( my $group_member_record = $gms->Next ) { - $group_member_record->Delete; - } - } - - # add new user to groups - foreach ( @$MemberOf ) { - my $group = RT::Group->new( RT::SystemUser() ); - $group->LoadUserDefinedGroup( $_ ); - die "couldn't load group '$_'" unless $group->id; - $group->AddMember( $obj->id ); - } - - return $obj; -} - -=head2 load_or_create_queue - -=cut - -sub load_or_create_queue { - my $self = shift; - my %args = ( Disabled => 0, @_ ); - my $obj = RT::Queue->new( $RT::SystemUser ); - if ( $args{'Name'} ) { - $obj->LoadByCols( Name => $args{'Name'} ); - } else { - die "Name is required"; - } - unless ( $obj->id ) { - my ($val, $msg) = $obj->Create( %args ); - die "$msg" unless $val; - } else { - my @fields = qw(CorrespondAddress CommentAddress); - foreach my $field ( @fields ) { - next unless exists $args{ $field }; - next if $args{ $field } eq $obj->$field; - - no warnings 'uninitialized'; - my $method = 'Set'. $field; - my ($val, $msg) = $obj->$method( $args{ $field } ); - die "$msg" unless $val; - } - } - - return $obj; -} - -=head2 load_or_create_custom_field - -=cut - -sub load_or_create_custom_field { - my $self = shift; - my %args = ( Disabled => 0, @_ ); - my $obj = RT::CustomField->new( $RT::SystemUser ); - if ( $args{'Name'} ) { - $obj->LoadByName( Name => $args{'Name'}, Queue => $args{'Queue'} ); - } else { - die "Name is required"; - } - unless ( $obj->id ) { - my ($val, $msg) = $obj->Create( %args ); - die "$msg" unless $val; - } - - return $obj; -} - -sub last_ticket { - my $self = shift; - my $current = shift; - $current = $current ? RT::CurrentUser->new($current) : $RT::SystemUser; - my $tickets = RT::Tickets->new( $current ); - $tickets->OrderBy( FIELD => 'id', ORDER => 'DESC' ); - $tickets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => '0' ); - $tickets->RowsPerPage( 1 ); - return $tickets->First; -} - -sub store_rights { - my $self = shift; - - require RT::ACE; - # fake construction - RT::ACE->new( $RT::SystemUser ); - my @fields = keys %{ RT::ACE->_ClassAccessible }; - - require RT::ACL; - my $acl = RT::ACL->new( $RT::SystemUser ); - $acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' ); - - my @res; - while ( my $ace = $acl->Next ) { - my $obj = $ace->PrincipalObj->Object; - if ( $obj->isa('RT::Group') && $obj->Type eq 'UserEquiv' && $obj->Instance == $RT::Nobody->id ) { - next; - } - - my %tmp = (); - foreach my $field( @fields ) { - $tmp{ $field } = $ace->__Value( $field ); - } - push @res, \%tmp; - } - return @res; -} - -sub restore_rights { - my $self = shift; - my @entries = @_; - foreach my $entry ( @entries ) { - my $ace = RT::ACE->new( $RT::SystemUser ); - my ($status, $msg) = $ace->RT::Record::Create( %$entry ); - unless ( $status ) { - Test::More::diag "couldn't create a record: $msg"; - } - } -} - -sub set_rights { - my $self = shift; - - require RT::ACL; - my $acl = RT::ACL->new( $RT::SystemUser ); - $acl->Limit( FIELD => 'RightName', OPERATOR => '!=', VALUE => 'SuperUser' ); - while ( my $ace = $acl->Next ) { - my $obj = $ace->PrincipalObj->Object; - if ( $obj->isa('RT::Group') && $obj->Type eq 'UserEquiv' && $obj->Instance == $RT::Nobody->id ) { - next; - } - $ace->Delete; - } - return $self->add_rights( @_ ); -} - -sub add_rights { - my $self = shift; - my @list = ref $_[0]? @_: @_? { @_ }: (); - - require RT::ACL; - foreach my $e (@list) { - my $principal = delete $e->{'Principal'}; - unless ( ref $principal ) { - if ( $principal =~ /^(everyone|(?:un)?privileged)$/i ) { - $principal = RT::Group->new( $RT::SystemUser ); - $principal->LoadSystemInternalGroup($1); - } elsif ( $principal =~ /^(Owner|Requestor|(?:Admin)?Cc)$/i ) { - $principal = RT::Group->new( $RT::SystemUser ); - $principal->LoadByCols( - Domain => (ref($e->{'Object'})||'RT::System').'-Role', - Type => $1, - ref($e->{'Object'})? (Instance => $e->{'Object'}->id): (), - ); - } else { - die "principal is not an object, but also is not name of a system group"; - } - } - unless ( $principal->isa('RT::Principal') ) { - if ( $principal->can('PrincipalObj') ) { - $principal = $principal->PrincipalObj; - } - } - my @rights = ref $e->{'Right'}? @{ $e->{'Right'} }: ($e->{'Right'}); - foreach my $right ( @rights ) { - my ($status, $msg) = $principal->GrantRight( %$e, Right => $right ); - $RT::Logger->debug($msg); - } - } - return 1; -} - -sub run_mailgate { - my $self = shift; - - require RT::Test::Web; - my %args = ( - url => RT::Test::Web->rt_base_url, - message => '', - action => 'correspond', - queue => 'General', - debug => 1, - command => $RT::BinPath .'/rt-mailgate', - @_ - ); - my $message = delete $args{'message'}; - - $args{after_open} = sub { - my $child_in = shift; - if ( UNIVERSAL::isa($message, 'MIME::Entity') ) { - $message->print( $child_in ); - } else { - print $child_in $message; - } - }; - - $self->run_and_capture(%args); -} - -sub run_and_capture { - my $self = shift; - my %args = @_; - - my $cmd = delete $args{'command'}; - die "Couldn't find command ($cmd)" unless -f $cmd; - - $cmd .= ' --debug' if delete $args{'debug'}; - - while( my ($k,$v) = each %args ) { - next unless $v; - $cmd .= " --$k '$v'"; - } - $cmd .= ' 2>&1'; - - DBIx::SearchBuilder::Record::Cachable->FlushCache; - - require IPC::Open2; - my ($child_out, $child_in); - my $pid = IPC::Open2::open2($child_out, $child_in, $cmd); - - $args{after_open}->($child_in, $child_out) if $args{after_open}; - - close $child_in; - - my $result = do { local $/; <$child_out> }; - close $child_out; - waitpid $pid, 0; - return ($?, $result); -} - -sub send_via_mailgate { - my $self = shift; - my $message = shift; - my %args = (@_); - - my ($status, $gate_result) = $self->run_mailgate( - message => $message, %args - ); - - my $id; - unless ( $status >> 8 ) { - ($id) = ($gate_result =~ /Ticket:\s*(\d+)/i); - unless ( $id ) { - Test::More::diag "Couldn't find ticket id in text:\n$gate_result" - if $ENV{'TEST_VERBOSE'}; - } - } else { - Test::More::diag "Mailgate output:\n$gate_result" - if $ENV{'TEST_VERBOSE'}; - } - return ($status, $id); -} - -sub open_mailgate_ok { - my $class = shift; - my $baseurl = shift; - my $queue = shift || 'general'; - my $action = shift || 'correspond'; - Test::More::ok(open(my $mail, "|$RT::BinPath/rt-mailgate --url $baseurl --queue $queue --action $action"), "Opened the mailgate - $!"); - return $mail; -} - - -sub close_mailgate_ok { - my $class = shift; - my $mail = shift; - close $mail; - Test::More::is ($? >> 8, 0, "The mail gateway exited normally. yay"); -} - -sub mailsent_ok { - my $class = shift; - my $expected = shift; - - my $mailsent = scalar grep /\S/, split /%% split me! %%\n/, - RT::Test->file_content( - $tmp{'mailbox'}, - 'unlink' => 0, - noexist => 1 - ); - - Test::More::is( - $mailsent, $expected, - "The number of mail sent ($expected) matches. yay" - ); -} - -sub set_mail_catcher { - my $self = shift; - return 1; -} - -sub fetch_caught_mails { - my $self = shift; - return grep /\S/, split /%% split me! %%\n/, - RT::Test->file_content( - $tmp{'mailbox'}, - 'unlink' => 1, - noexist => 1 - ); -} - -sub clean_caught_mails { - unlink $tmp{'mailbox'}; -} - -=head2 get_relocatable_dir - -Takes a path relative to the location of the test file that is being -run and returns a path that takes the invocation path into account. - -e.g. RT::Test::get_relocatable_dir(File::Spec->updir(), 'data', 'emails') - -=cut - -sub get_relocatable_dir { - (my $volume, my $directories, my $file) = File::Spec->splitpath($0); - if (File::Spec->file_name_is_absolute($directories)) { - return File::Spec->catdir($directories, @_); - } else { - return File::Spec->catdir(File::Spec->curdir(), $directories, @_); - } -} - -=head2 get_relocatable_file - -Same as get_relocatable_dir, but takes a file and a path instead -of just a path. - -e.g. RT::Test::get_relocatable_file('test-email', - (File::Spec->updir(), 'data', 'emails')) - -=cut - -sub get_relocatable_file { - my $file = shift; - return File::Spec->catfile(get_relocatable_dir(@_), $file); -} - -sub get_abs_relocatable_dir { - (my $volume, my $directories, my $file) = File::Spec->splitpath($0); - if (File::Spec->file_name_is_absolute($directories)) { - return File::Spec->catdir($directories, @_); - } else { - return File::Spec->catdir(Cwd->getcwd(), $directories, @_); - } -} - -sub import_gnupg_key { - my $self = shift; - my $key = shift; - my $type = shift || 'secret'; - - $key =~ s/\@/-at-/g; - $key .= ".$type.key"; - - require RT::Crypt::GnuPG; - - # simple strategy find data/gnupg/keys, from the dir where test file lives - # to updirs, try 3 times in total - my $path = File::Spec->catfile( 'data', 'gnupg', 'keys' ); - my $abs_path; - for my $up ( 0 .. 2 ) { - my $p = get_relocatable_dir($path); - if ( -e $p ) { - $abs_path = $p; - last; - } - else { - $path = File::Spec->catfile( File::Spec->updir(), $path ); - } - } - - die "can't find the dir where gnupg keys are stored" - unless $abs_path; - - return RT::Crypt::GnuPG::ImportKey( - RT::Test->file_content( [ $abs_path, $key ] ) ); -} - - -sub lsign_gnupg_key { - my $self = shift; - my $key = shift; - - require RT::Crypt::GnuPG; require GnuPG::Interface; - my $gnupg = new GnuPG::Interface; - my %opt = RT->Config->Get('GnuPGOptions'); - $gnupg->options->hash_init( - RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ), - meta_interactive => 0, - ); - - my %handle; - my $handles = GnuPG::Handles->new( - stdin => ($handle{'input'} = new IO::Handle), - stdout => ($handle{'output'} = new IO::Handle), - stderr => ($handle{'error'} = new IO::Handle), - logger => ($handle{'logger'} = new IO::Handle), - status => ($handle{'status'} = new IO::Handle), - command => ($handle{'command'} = new IO::Handle), - ); - - eval { - local $SIG{'CHLD'} = 'DEFAULT'; - local @ENV{'LANG', 'LC_ALL'} = ('C', 'C'); - my $pid = $gnupg->wrap_call( - handles => $handles, - commands => ['--lsign-key'], - command_args => [$key], - ); - close $handle{'input'}; - while ( my $str = readline $handle{'status'} ) { - if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) { - print { $handle{'command'} } "y\n"; - } - } - waitpid $pid, 0; - }; - my $err = $@; - close $handle{'output'}; - - my %res; - $res{'exit_code'} = $?; - foreach ( qw(error logger status) ) { - $res{$_} = do { local $/; readline $handle{$_} }; - delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s; - close $handle{$_}; - } - $RT::Logger->debug( $res{'status'} ) if $res{'status'}; - $RT::Logger->warning( $res{'error'} ) if $res{'error'}; - $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?; - if ( $err || $res{'exit_code'} ) { - $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8); - } - return %res; -} - -sub trust_gnupg_key { - my $self = shift; - my $key = shift; - - require RT::Crypt::GnuPG; require GnuPG::Interface; - my $gnupg = new GnuPG::Interface; - my %opt = RT->Config->Get('GnuPGOptions'); - $gnupg->options->hash_init( - RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ), - meta_interactive => 0, - ); - - my %handle; - my $handles = GnuPG::Handles->new( - stdin => ($handle{'input'} = new IO::Handle), - stdout => ($handle{'output'} = new IO::Handle), - stderr => ($handle{'error'} = new IO::Handle), - logger => ($handle{'logger'} = new IO::Handle), - status => ($handle{'status'} = new IO::Handle), - command => ($handle{'command'} = new IO::Handle), - ); - - eval { - local $SIG{'CHLD'} = 'DEFAULT'; - local @ENV{'LANG', 'LC_ALL'} = ('C', 'C'); - my $pid = $gnupg->wrap_call( - handles => $handles, - commands => ['--edit-key'], - command_args => [$key], - ); - close $handle{'input'}; - - my $done = 0; - while ( my $str = readline $handle{'status'} ) { - if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) { - if ( $done ) { - print { $handle{'command'} } "quit\n"; - } else { - print { $handle{'command'} } "trust\n"; - } - } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) { - print { $handle{'command'} } "5\n"; - } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) { - print { $handle{'command'} } "y\n"; - $done = 1; - } - } - waitpid $pid, 0; - }; - my $err = $@; - close $handle{'output'}; - - my %res; - $res{'exit_code'} = $?; - foreach ( qw(error logger status) ) { - $res{$_} = do { local $/; readline $handle{$_} }; - delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s; - close $handle{$_}; - } - $RT::Logger->debug( $res{'status'} ) if $res{'status'}; - $RT::Logger->warning( $res{'error'} ) if $res{'error'}; - $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?; - if ( $err || $res{'exit_code'} ) { - $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8); - } - return %res; -} - -sub started_ok { - my $self = shift; - - require RT::Test::Web; - - my $which = $ENV{'RT_TEST_WEB_HANDLER'} || 'standalone'; - my ($server, $variant) = split /\+/, $which, 2; - - my $function = 'start_'. $server .'_server'; - unless ( $self->can($function) ) { - die "Don't know how to start server '$server'"; - } - return $self->$function( $variant, @_ ); -} - -sub start_standalone_server { - my $self = shift; - - - require RT::Interface::Web::Standalone; - - require Test::HTTP::Server::Simple::StashWarnings; - unshift @RT::Interface::Web::Standalone::ISA, - 'Test::HTTP::Server::Simple::StashWarnings'; - *RT::Interface::Web::Standalone::test_warning_path = sub { - "/__test_warnings"; - }; - - my $s = RT::Interface::Web::Standalone->new($port); - - my $ret = $s->started_ok; - push @SERVERS, $s->pids; - - $RT::Handle = new RT::Handle; - $RT::Handle->dbh( undef ); - RT->ConnectToDatabase; - - return ($ret, RT::Test::Web->new); -} - -sub start_apache_server { - my $self = shift; - my $variant = shift || 'mod_perl'; - - my %info = $self->apache_server_info( variant => $variant ); - - Test::More::diag(do { - open my $fh, '<', $tmp{'config'}{'RT'}; - local $/; - <$fh> - }); - - my $tmpl = File::Spec->rel2abs( File::Spec->catfile( - 't', 'data', 'configs', - 'apache'. $info{'version'} .'+'. $variant .'.conf' - ) ); - my %opt = ( - listen => $port, - server_root => $info{'HTTPD_ROOT'} || $ENV{'HTTPD_ROOT'} - || Test::More::BAIL_OUT("Couldn't figure out server root"), - document_root => $RT::MasonComponentRoot, - tmp_dir => "$tmp{'directory'}", - rt_bin_path => $RT::BinPath, - rt_site_config => $ENV{'RT_SITE_CONFIG'}, - ); - foreach (qw(log pid lock)) { - $opt{$_ .'_file'} = File::Spec->catfile( - "$tmp{'directory'}", "apache.$_" - ); - } - { - my $method = 'apache_'.$variant.'_server_options'; - $self->$method( \%info, \%opt ); - } - $tmp{'config'}{'apache'} = File::Spec->catfile( - "$tmp{'directory'}", "apache.conf" - ); - $self->process_in_file( - in => $tmpl, - out => $tmp{'config'}{'apache'}, - options => \%opt, - ); - - $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'}); - my $pid = do { - my $tries = 10; - while ( !-e $opt{'pid_file'} ) { - $tries--; - last unless $tries; - sleep 1; - } - Test::More::BAIL_OUT("Couldn't start apache server, no pid file") - unless -e $opt{'pid_file'}; - open my $pid_fh, '<', $opt{'pid_file'} - or Test::More::BAIL_OUT("Couldn't open pid file: $!"); - my $pid = <$pid_fh>; - chomp $pid; - $pid; - }; - - Test::More::ok($pid, "Started apache server #$pid"); - - push @SERVERS, $pid; - - return (RT->Config->Get('WebURL'), RT::Test::Web->new); -} - -sub apache_server_info { - my $self = shift; - my %res = @_; - - my $bin = $res{'executable'} = $ENV{'RT_TEST_APACHE'} - || $self->find_apache_server - || Test::More::BAIL_OUT("Couldn't find apache server, use RT_TEST_APACHE"); - - Test::More::diag("Using '$bin' apache executable for testing") - if $ENV{'TEST_VERBOSE'}; - - my $info = `$bin -V`; - ($res{'version'}) = ($info =~ m{Server\s+version:\s+Apache/(\d+\.\d+)\.}); - Test::More::BAIL_OUT( - "Couldn't figure out version of the server" - ) unless $res{'version'}; - - my %opts = ($info =~ m/^\s*-D\s+([A-Z_]+?)(?:="(.*)")$/mg); - %res = (%res, %opts); - - $res{'modules'} = [ - map {s/^\s+//; s/\s+$//; $_} - grep $_ !~ /Compiled in modules/i, - split /\r*\n/, `$bin -l` - ]; - - return %res; -} - -sub apache_mod_perl_server_options { - my $self = shift; - my %info = %{ shift() }; - my $current = shift; - - my %required_modules = ( - '2.2' => [qw(authz_host log_config env alias perl)], - ); - my @mlist = @{ $required_modules{ $info{'version'} } }; - - $current->{'load_modules'} = ''; - foreach my $mod ( @mlist ) { - next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $info{'modules'} }; - - $current->{'load_modules'} .= - "LoadModule ${mod}_module modules/mod_${mod}.so\n"; - } - return; -} - -sub apache_fastcgi_server_options { - my $self = shift; - my %info = %{ shift() }; - my $current = shift; - - my %required_modules = ( - '2.2' => [qw(authz_host log_config env alias mime fastcgi)], - ); - my @mlist = @{ $required_modules{ $info{'version'} } }; - - $current->{'load_modules'} = ''; - foreach my $mod ( @mlist ) { - next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $info{'modules'} }; - - $current->{'load_modules'} .= - "LoadModule ${mod}_module modules/mod_${mod}.so\n"; - } - return; -} - -sub find_apache_server { - my $self = shift; - return $_ foreach grep defined, - map $self->find_executable($_), - qw(httpd apache apache2 apache1); - return undef; -} - -sub stop_server { - my $self = shift; - - my $sig = 'TERM'; - $sig = 'INT' if !$ENV{'RT_TEST_WEB_HANDLER'} - || $ENV{'RT_TEST_WEB_HANDLER'} =~/^standalone(?:\+|\z)/; - kill $sig, @SERVERS; - foreach my $pid (@SERVERS) { - waitpid $pid, 0; - } -} - -sub file_content { - my $self = shift; - my $path = shift; - my %args = @_; - - $path = File::Spec->catfile( @$path ) if ref $path eq 'ARRAY'; - - Test::More::diag "reading content of '$path'" if $ENV{'TEST_VERBOSE'}; - - open my $fh, "<:raw", $path - or do { - warn "couldn't open file '$path': $!" unless $args{noexist}; - return '' - }; - my $content = do { local $/; <$fh> }; - close $fh; - - unlink $path if $args{'unlink'}; - - return $content; -} - -sub find_executable { - my $self = shift; - my $name = shift; - - require File::Spec; - foreach my $dir ( split /:/, $ENV{'PATH'} ) { - my $fpath = File::Spec->catpath( - (File::Spec->splitpath( $dir, 'no file' ))[0..1], $name - ); - next unless -e $fpath && -r _ && -x _; - return $fpath; - } - return undef; -} - -sub fork_exec { - my $self = shift; - - my $pid = fork; - unless ( defined $pid ) { - die "cannot fork: $!"; - } elsif ( !$pid ) { - exec @_; - die "can't exec `". join(' ', @_) ."` program: $!"; - } else { - return $pid; - } -} - -sub process_in_file { - my $self = shift; - my %args = ( in => undef, options => undef, @_ ); - - my $text = $self->file_content( $args{'in'} ); - while ( my ($opt) = ($text =~ /\%\%(.+?)\%\%/) ) { - my $value = $args{'options'}{ lc $opt }; - die "no value for $opt" unless defined $value; - - $text =~ s/\%\%\Q$opt\E\%\%/$value/g; - } - - my ($out_fh, $out_conf); - unless ( $args{'out'} ) { - ($out_fh, $out_conf) = tempfile(); - } else { - $out_conf = $args{'out'}; - open $out_fh, '>', $out_conf - or die "couldn't open '$out_conf': $!"; - } - print $out_fh $text; - seek $out_fh, 0, 0; - - return ($out_fh, $out_conf); -} - -END { - my $Test = RT::Test->builder; - return if $Test->{Original_Pid} != $$; - - - # we are in END block and should protect our exit code - # so calls below may call system or kill that clobbers $? - local $?; - - RT::Test->stop_server; - - # not success - if ( !$Test->summary || grep !$_, $Test->summary ) { - $tmp{'directory'}->unlink_on_destroy(0); - - Test::More::diag( - "Some tests failed or we bailed out, tmp directory" - ." '$tmp{directory}' is not cleaned" - ); - } - - if ( $ENV{RT_TEST_PARALLEL} && $created_new_db ) { - - # Pg doesn't like if you issue a DROP DATABASE while still connected - my $dbh = $RT::Handle->dbh; - $dbh->disconnect if $dbh; - - $dbh = _get_dbh( RT::Handle->SystemDSN, $ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD} ); - RT::Handle->DropDatabase( $dbh, Force => 1 ); - $dbh->disconnect; - } -} - -1; |