Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Test / Apache.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::Test::Apache;
50 use strict;
51 use warnings;
52
53 my %MODULES = (
54     '2.2' => {
55         "mod_perl" => [qw(authz_host env alias perl)],
56         "fastcgi"  => [qw(authz_host env alias mime fastcgi)],
57     },
58 );
59
60 my $apache_module_prefix = $ENV{RT_TEST_APACHE_MODULES};
61 my $apxs =
62      $ENV{RT_TEST_APXS}
63   || RT::Test->find_executable('apxs')
64   || RT::Test->find_executable('apxs2');
65
66 if ($apxs and not $apache_module_prefix) {
67     $apache_module_prefix = `$apxs -q LIBEXECDIR`;
68     chomp $apache_module_prefix;
69 }
70
71 $apache_module_prefix ||= 'modules';
72
73 sub basic_auth {
74     my $self = shift;
75     my $passwd = File::Spec->rel2abs( File::Spec->catfile(
76         't', 'data', 'configs', 'passwords' ) );
77
78     return <<"EOT";
79     AuthType Basic
80     AuthName "restricted area"
81     AuthUserFile $passwd
82     Require user root
83 EOT
84 }
85
86 sub basic_auth_anon {
87     my $self = shift;
88
89     return <<"EOT";
90     AuthType Basic
91     AuthName "restricted area"
92     AuthBasicProvider anon
93
94     Anonymous *
95     Anonymous_NoUserID On
96     Anonymous_MustGiveEmail Off
97     Anonymous_VerifyEmail Off
98
99     Require valid-user
100 EOT
101 }
102
103 sub start_server {
104     my ($self, %config) = @_;
105     my %tmp = %{$config{tmp}};
106     my %info = $self->apache_server_info( %config );
107
108     RT::Test::diag(do {
109         open( my $fh, '<', $tmp{'config'}{'RT'} ) or die $!;
110         local $/;
111         <$fh>
112     });
113
114     my $tmpl = File::Spec->rel2abs( File::Spec->catfile(
115         't', 'data', 'configs',
116         'apache'. $info{'version'} .'+'. $config{variant} .'.conf'
117     ) );
118     my %opt = (
119         listen         => $config{port},
120         server_root    => $info{'HTTPD_ROOT'} || $ENV{'HTTPD_ROOT'}
121             || Test::More::BAIL_OUT("Couldn't figure out server root"),
122         document_root  => $RT::MasonComponentRoot,
123         tmp_dir        => "$tmp{'directory'}",
124         rt_bin_path    => $RT::BinPath,
125         rt_sbin_path   => $RT::SbinPath,
126         rt_site_config => $ENV{'RT_SITE_CONFIG'},
127         load_modules   => $info{load_modules},
128     );
129     if (not $config{basic_auth}) {
130         $opt{basic_auth} = "";
131     } elsif ($config{basic_auth} eq 'anon') {
132         $opt{basic_auth} = $self->basic_auth_anon;
133     } else {
134         $opt{basic_auth} = $self->basic_auth;
135     }
136     foreach (qw(log pid lock)) {
137         $opt{$_ .'_file'} = File::Spec->catfile(
138             "$tmp{'directory'}", "apache.$_"
139         );
140     }
141
142     $tmp{'config'}{'apache'} = File::Spec->catfile(
143         "$tmp{'directory'}", "apache.conf"
144     );
145     $self->process_in_file(
146         in      => $tmpl, 
147         out     => $tmp{'config'}{'apache'},
148         options => \%opt,
149     );
150
151     $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'});
152     my $pid = do {
153         my $tries = 15;
154         while ( !-s $opt{'pid_file'} ) {
155             $tries--;
156             last unless $tries;
157             sleep 1;
158         }
159         my $pid_fh;
160         unless (-e $opt{'pid_file'} and open($pid_fh, '<', $opt{'pid_file'})) {
161             Test::More::BAIL_OUT("Couldn't start apache server, no pid file (unknown error)")
162                   unless -e $opt{log_file};
163
164             open my $log, "<", $opt{log_file};
165             my $error = do {local $/; <$log>};
166             close $log;
167             $RT::Logger->error($error) if $error;
168             Test::More::BAIL_OUT("Couldn't start apache server!");
169         }
170
171         my $pid = <$pid_fh>;
172         chomp $pid;
173         $pid;
174     };
175
176     Test::More::ok($pid, "Started apache server #$pid");
177     return $pid;
178 }
179
180 sub apache_server_info {
181     my $self = shift;
182     my %res = @_;
183
184     my $bin = $res{'executable'} = $ENV{'RT_TEST_APACHE'}
185         || $self->find_apache_server
186         || Test::More::BAIL_OUT("Couldn't find apache server, use RT_TEST_APACHE");
187
188     Test::More::BAIL_OUT(
189         "Couldn't find apache modules directory (set APXS= or RT_TEST_APACHE_MODULES=)"
190     ) unless -d $apache_module_prefix;
191
192
193     RT::Test::diag("Using '$bin' apache executable for testing");
194
195     my $info = `$bin -V`;
196     ($res{'version'}) = ($info =~ m{Server\s+version:\s+Apache/(\d+\.\d+)\.});
197     Test::More::BAIL_OUT(
198         "Couldn't figure out version of the server"
199     ) unless $res{'version'};
200
201     my %opts = ($info =~ m/^\s*-D\s+([A-Z_]+?)(?:="(.*)")$/mg);
202     %res = (%res, %opts);
203
204     $res{'modules'} = [
205         map {s/^\s+//; s/\s+$//; $_}
206         grep $_ !~ /Compiled in modules/i,
207         split /\r*\n/, `$bin -l`
208     ];
209
210     Test::More::BAIL_OUT(
211         "Unsupported apache version $res{version}"
212     ) unless exists $MODULES{$res{version}};
213
214     Test::More::BAIL_OUT(
215         "Unsupported apache variant $res{variant}"
216     ) unless exists $MODULES{$res{version}}{$res{variant}};
217
218     my @mlist = @{$MODULES{$res{version}}{$res{variant}}};
219     if ($res{basic_auth}) {
220         push @mlist, "auth_basic", "authz_user";
221         push @mlist, $res{basic_auth} eq 'anon' ? "authn_anon" : "authn_file";
222     }
223
224     $res{'load_modules'} = '';
225     foreach my $mod ( @mlist ) {
226         next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $res{'modules'} };
227
228         my $so_file = $apache_module_prefix."/mod_".$mod.".so";
229         Test::More::BAIL_OUT( "Couldn't load $mod module (expected in $so_file)" )
230               unless -f $so_file;
231         $res{'load_modules'} .=
232             "LoadModule ${mod}_module $so_file\n";
233     }
234     return %res;
235 }
236
237 sub find_apache_server {
238     my $self = shift;
239     return $_ foreach grep defined,
240         map RT::Test->find_executable($_),
241         qw(httpd apache apache2 apache1);
242     return undef;
243 }
244
245 sub apache_mpm_type {
246     my $self = shift;
247     my $apache = $self->find_apache_server;
248     my $out = `$apache -l`;
249     if ( $out =~ /^\s*(worker|prefork|event|itk)\.c\s*$/m ) {
250         return $1;
251     }
252 }
253
254 sub fork_exec {
255     my $self = shift;
256
257     RT::Test::__disconnect_rt();
258     my $pid = fork;
259     unless ( defined $pid ) {
260         die "cannot fork: $!";
261     } elsif ( !$pid ) {
262         exec @_;
263         die "can't exec `". join(' ', @_) ."` program: $!";
264     } else {
265         RT::Test::__reconnect_rt();
266         return $pid;
267     }
268 }
269
270 sub process_in_file {
271     my $self = shift;
272     my %args = ( in => undef, options => undef, @_ );
273
274     my $text = RT::Test->file_content( $args{'in'} );
275     while ( my ($opt) = ($text =~ /\%\%(.+?)\%\%/) ) {
276         my $value = $args{'options'}{ lc $opt };
277         die "no value for $opt" unless defined $value;
278
279         $text =~ s/\%\%\Q$opt\E\%\%/$value/g;
280     }
281
282     my ($out_fh, $out_conf);
283     unless ( $args{'out'} ) {
284         ($out_fh, $out_conf) = tempfile();
285     } else {
286         $out_conf = $args{'out'};
287         open( $out_fh, '>', $out_conf )
288             or die "couldn't open '$out_conf': $!";
289     }
290     print $out_fh $text;
291     seek $out_fh, 0, 0;
292
293     return ($out_fh, $out_conf);
294 }
295
296 1;