blob: 239188158e8b73265b14dd2a3530b71788d3ff58 [file] [log] [blame]
# $Id$
# Tcl based Apache test suite, by David N. Welton <davidw@dedasys.com>
# This test suite provides a means to create configuration files, and
# start apache with user-specified options. All it needs to run is
# the name of the Apache executable, which must, however, be compiled
# with the right options.
package provide apachetest 0.1
namespace eval apachetest {
# Associate module names with their internal names.
array set module_assoc {
mod_log_config {config_log_module log_config_module}
mod_mime mime_module
mod_negotiation negotiation_module
mod_dir dir_module
mod_access access_module
mod_auth auth_module
}
## Apache 2.2 has different names for some modules
array set module_assoc_22 {
mod_log_config {config_log_module log_config_module}
mod_mime mime_module
mod_negotiation negotiation_module
mod_dir dir_module
mod_authz_host authz_host_module
mod_auth_basic auth_basic_module
mod_authn_file authn_file_module
mod_authz_user authz_user_module
mod_authz_groupfile authz_groupfile_module
}
## Apache 2.4 also requires mod_unixd
array set module_assoc_24 {
mod_authn_core authn_core_module
mod_authz_core authz_core_module
mod_access_compat access_compat_module
mod_unixd unixd_module
mod_log_config {config_log_module log_config_module}
mod_mime mime_module
mod_negotiation negotiation_module
mod_dir dir_module
mod_authz_host authz_host_module
mod_auth_basic auth_basic_module
mod_authn_file authn_file_module
mod_authz_user authz_user_module
mod_authz_groupfile authz_groupfile_module
}
# name of the apache binary, such as /usr/sbin/httpd
variable binname ""
# this file should be in the same directory this script is.
variable templatefile [file join conf server.conf.tcl]
variable templatewebshconf [file join conf websh.conf.tcl]
}
# make sure we can connect to the server
proc apachetest::connect { } {
global port
global waiting
set waiting 10
set connect [after 5000 {set waiting 0}]
while {$waiting > 9} {
if { ! [catch {
set sk [socket localhost $port]
} err]} {
close $sk
after cancel $connect
return 1
}
after 10 {incr waiting}
vwait waiting
}
return 0
}
# start - start the server in the background with 'options' and then
# run 'code'.
proc apachetest::start { options code } {
variable serverpid 0
variable binname
# There has got to be a better way to do this, aside from waiting.
# set serverpid [eval exec $binname -X -f \
# [file join [pwd] server.conf] $options &]
set serverhandle [open "|\"$binname\" -X -f [file join [pwd] conf server.conf]" r]
set serverpid [pid $serverhandle]
fconfigure $serverhandle -blocking 0
puts "Apache started as PID $serverpid"
if {[apachetest::connect]} {
if { [catch {
uplevel $code
} err] } {
puts $err
}
} else {
error "Could not connect to Apache"
}
global tcl_platform
global kerr
set kerr 0
if {[string equal $tcl_platform(platform) "windows"]} {
if {[catch {exec taskkill /f /pid $serverpid} msg]} {
puts stderr "Couldn't stop Apache: $msg"
incr kerr
}
set forcekill "catch {exec taskkill /f /pid $serverpid} msg"
} else {
if {[catch {exec kill $serverpid}]} {
puts stderr "Couldn't stop Apache: $msg"
incr kerr
}
set forcekill "catch {exec kill -9 $serverpid} msg"
}
set kill9 [after 2500 "
puts stderr \"Can't kill process, trying with kill -9\";
if {\[$forcekill\]} {
puts stderr \"Couldn't stop Apache: \$msg\"
incr kerr
}
"]
global waiting
set waiting 1
while {![eof $serverhandle]} {
gets $serverhandle
after 500 {incr waiting}
vwait waiting
}
after cancel $kill9
if {!$kerr} {
puts "Apache stopped"
}
catch {file delete logs/httpd.pid}
}
# startserver - start the server with 'options'.
proc apachetest::startserver {{options ""}} {
variable binname
if { [catch {
eval exec [list $binname] -X -f \
"[file join [pwd] conf server.conf]" $options
} err] } {
puts $err
}
}
# setbinname - set the name of the apache binary
proc apachetest::setbinname { name } {
variable binname
global argv0
if {![file exists $name]} {
puts stderr "Please supply the full name and path of the Apache executable"
puts stderr "on the command line (or in the HTTPD_BIN environment variable):"
puts stderr "$argv0 /path/to/httpd"
exit 1
}
set binname $name
return $binname
}
# get the modules that are compiled into Apache directly, and return
# the XXX_module name. Check also for the existence of mod_so, which
# we need to load the shared object in the directory above...
proc apachetest::getcompiledin { binname } {
variable modules
set bin [open [list | "$binname" -l] r]
set compiledin [read $bin]
catch {close $bin}
set modlist [split $compiledin]
set compiledin [list]
set mod_so_present 0
foreach entry $modlist {
if { [regexp {(.*)\.c$} $entry match modname] } {
if { $modname == "mod_so" } {
set mod_so_present 1
}
if { [info exists modules($modname)] } {
lappend compiledin $modules($modname)
}
}
}
if { $mod_so_present == 0 } {
error "We need mod_so in Apache to run these tests"
}
return $compiledin
}
# find the httpd.conf file
proc apachetest::gethttpdconf { binname } {
set bin [ open [list | "$binname" -V ] r ]
set options [ read $bin ]
catch {close $bin}
regexp {SERVER_CONFIG_FILE="(.*?)"} "$options" match filename
# try to find conf file relative to installation first
set path [file dirname [file dirname $binname]]
if {[file exists [file join $path $filename]]} {
return [file join $path $filename]
}
# special case of Windows binary distributino for Apache 1.3
set path [file dirname $binname]
if {[file exists [file join $path $filename]]} {
return [file join $path $filename]
}
# try locally
if {[file exists $filename]} {
return $filename
}
# see if we can find something by combining HTTP_ROOT + SERVER_CONFIG_FILE
regexp {HTTPD_ROOT="(.*?)"} "$options" match httpdroot
set completename [file join $httpdroot $filename]
if {[file exists $completename]} {
return $completename
}
error "Did not find Apache config file '$filename'"
}
# if we need to load some modules, find out how to do it from the
# 'real' (the one installed on the system) conf file, with this proc
proc apachetest::getloadmodules { conffile needtoget } {
set fl [open $conffile r]
set confdata [read $fl]
close $fl
set loadline [list]
# get server root
if {![regexp -line "^.*?ServerRoot\\s+\"?(.+?)\"?\\s\$" $confdata match serverroot]} {
error "ServerRoot not found in configuration"
}
# try to also parse included conf files (to be sure we get all modules)
foreach {include path} [regexp -all -inline -line "^.*?Include\\s+\"?(.+?)\"?\$"\
$confdata] {
# Some Debian setups enable modules like this:
# # Include module configuration:
# Include mods-enabled/*.load
# Include mods-enabled/*.conf
set ifiles [glob -nocomplain [file join $serverroot $path]]
foreach ifile $ifiles {
set fl [open $ifile r]
append confdata [read $fl]
close $fl
}
}
foreach mod $needtoget {
set found 0
foreach m $mod {
if {[regexp -line "^.*?LoadModule\\s+$m\\s+\"?(.+?)\"?\$"\
$confdata match line]} {
set found 1
lappend loadline "LoadModule $m \"[file join $serverroot $line]\""
break
}
}
if {!$found} {
error "No LoadModule line for $mod!"
}
}
return [join $loadline "\n"]
}
# compare what's compiled in with what we need
proc apachetest::determinemodules { binname } {
variable module_assoc
variable module_assoc_22
variable module_assoc_24
variable modules
variable version
set ver ""
catch {exec $binname -v} ver
if {![regexp "Apache" $ver]} {
error "Couldn't get version from Apache binary: $ver"
}
if {[regexp {Apache/1\.3\.} $ver] || [regexp {Apache/2\.0\.} $ver]} {
array set modules [array get module_assoc]
set version 20
} elseif {[regexp {Apache/2\.4\.} $ver]} {
array set modules [array get module_assoc_24]
set version 24
} else {
array set modules [array get module_assoc_22]
set version 22
}
set compiledin [lsort [getcompiledin $binname]]
set conffile [gethttpdconf $binname]
foreach {n k} [array get modules] {
lappend needed $k
}
set needed [lsort $needed]
set needtoget [list]
foreach mod $needed {
if { [lsearch $compiledin $mod] == -1 } {
lappend needtoget $mod
}
}
if { $needtoget == "" } {
return ""
} else {
return [getloadmodules $conffile $needtoget]
}
}
# dump out a config
# outfile is the file to write to.
# extra is for extra config things we want to tack on.
proc apachetest::makeconf { outfile {extra ""} } {
global env
global port
variable binname
variable templatefile
variable version
set CWD [pwd]
# create directory for log files and lock etc.
file mkdir logs
# replace with determinemodules
set LOADMODULES [determinemodules $binname]
if {$version < 24} {
set LockFile "LockFile \"$CWD/logs/accept.lock\"\n"
} else {
# Apache 2.4. does not have a LockFile configuration anymore
set LockFile ""
}
set fl [open [file join . $templatefile] r]
set template [read $fl]
close $fl
append template $extra
set out [subst $template]
set of [open $outfile w]
puts $of "$out"
close $of
}
proc apachetest::makewebshconf {outfile} {
global env
variable templatewebshconf
set CWD [pwd]
# replace with determinemodules
set fl [open [file join . $templatewebshconf] r]
set template [read $fl]
close $fl
set out [subst $template]
set of [open $outfile w]
puts $of "$out"
close $of
}