| # apachetest.tcl -- Tcl-based Apache test suite |
| |
| # Copyright 2001-2020 The Apache Tcl Team / The Apache Software Foundation |
| |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # 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. |
| |
| set auto_path [linsert $auto_path 0 [file dirname [info script]]] |
| package provide apachetest 0.1 |
| |
| # kill -- |
| # kill a process and wait until it's really gone |
| # (mimic TclX's kill and wait procs) |
| # |
| # Arguments: |
| # signal - signal to send (e.g. TERM, HUP, KILL...) |
| # pid - process id |
| # |
| # Side Effects: |
| # kills a running process/processes (if permitted) |
| # |
| # Results: |
| # None. |
| |
| proc kill {signal pid} { |
| catch {exec kill -s $signal $pid} |
| after 100 |
| set i 100 |
| while {1} { |
| catch {exec ps -p $pid} out |
| if {[regexp $pid $out]} { |
| incr i 250 |
| after 250 |
| } else { |
| break |
| } |
| } |
| if {$i > 100} { |
| puts "Waiting [expr {$i/1000.0}] seconds until process was killed" |
| } |
| } |
| |
| #package require http 2.4.5 |
| source [file join [file dirname [info script]] http.tcl] |
| |
| namespace eval apachetest { |
| |
| set debug 1 |
| |
| # name of the apache binary, such as /usr/sbin/httpd |
| variable binname "" |
| if ![info exists ::httpd_version ] { |
| puts stderr "Please create httpd_version variable in global namespace" |
| exit 1 |
| } |
| variable httpd_version $::httpd_version |
| # this file should be in the same directory this script is. |
| variable templatefile [file join [file dirname [info script]] template.conf.2.tcl] |
| } |
| |
| # apachetest::need_modules -- |
| # |
| # Tell the test suite which modules we *need* to have. The test |
| # suite will then check to see if these are either 1) compiled |
| # into the server or 2) loaded in its configuration file. |
| # |
| # Arguments: |
| # args |
| # |
| # Side Effects: |
| # Sets up the rest of the apachetest tools to find the needed |
| # modules. |
| # |
| # Results: |
| # None. |
| |
| proc apachetest::need_modules { modlist } { |
| variable module_assoc |
| foreach module_pair $modlist { |
| set module_assoc([lindex $module_pair 0]) [lindex $module_pair 1] |
| } |
| } |
| |
| # apachetest::connect -- |
| # |
| # Attempt to open a socket to the web server we are using in our |
| # tests. Try for 10 seconds before giving up. |
| # |
| # Arguments: |
| # None. |
| |
| proc apachetest::connect { } { |
| set starttime [clock seconds] |
| set diff 0 |
| # We try for 10 seconds. |
| while { $diff < 10 } { |
| if { ! [catch { |
| set sk [socket localhost 8081] |
| } err]} { |
| close $sk |
| return |
| } |
| set diff [expr {[clock seconds] - $starttime}] |
| } |
| } |
| |
| # apachetest::start -- |
| # |
| # Start the web server in the background. After running the |
| # script specified, stop the server. |
| # |
| # Arguments: |
| # options - command line options to pass to the web server. |
| # conftext - text to insert into test.conf. |
| # code - code to run. |
| # |
| # Side Effects: |
| # Runs 'code' in the global namespace. |
| # |
| # Results: |
| # None. |
| |
| proc apachetest::start { options conftext code } { |
| variable serverpid 0 |
| variable binname |
| variable debug |
| variable httpd_version |
| |
| set fn [file join [pwd] test.conf] |
| catch {file delete -force $fn} |
| set fl [open $fn w] |
| puts $fl [uplevel [list subst $conftext]] |
| close $fl |
| |
| #OpenBSD related workaround, their stock apache tries to chroot by default |
| #have to add -u to the arguments to prevent this |
| catch {exec uname} uname_str |
| if {[string equal $uname_str OpenBSD]} { |
| set server_args "-u -X -f" |
| } else { |
| set server_args [list -X -f] |
| } |
| # There has got to be a better way to do this, aside from waiting. |
| set serverpid [eval exec $binname $server_args \ |
| [file join [pwd] server.conf] $options >& apachelog.txt &] |
| apachetest::connect |
| if { $debug > 0 } { |
| puts "Apache started as PID $serverpid" |
| } |
| |
| if { [catch { uplevel $code } err] } { puts stderr $::errorInfo } |
| |
| # apache2 binary started with -X reacts to SIGQUIT and ignores TERM, but KILL is most reliable |
| kill KILL $serverpid |
| } |
| |
| # startserver - start the server with 'options'. |
| |
| proc apachetest::startserver { args } { |
| variable binname |
| variable debug |
| |
| if { [catch { |
| if { $debug } { |
| puts "$binname -X -f [file join [pwd] server.conf] [concat $args]" |
| } |
| set serverpid [eval exec $binname -X -f "[file join [pwd] server.conf]" [concat $args]] |
| } err] } { |
| puts "$err" |
| } |
| } |
| |
| # getbinname - get the name of the apache binary, and check to make |
| # sure it's ok. The user should supply this parameter. |
| |
| proc apachetest::getbinname { bname } { |
| variable binname |
| |
| set binname $bname |
| if { $binname == "" || ! [file executable $binname] } { |
| error "Invalid executable '$binname', please supply the full name and path of the Apache executable on the command line." |
| } |
| 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 module_assoc |
| |
| if {[catch { |
| set bin [open [list | "$binname" -l] r] |
| set compiledin [read $bin] |
| close $bin} e einfo]} { |
| puts "error: $e" |
| puts "error info: $einfo" |
| |
| error "caught error in apachetest::getcompledin" |
| } |
| 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 module_assoc($modname)] } { |
| lappend compiledin $module_assoc($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 |
| |
| if {![file exists $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]} { |
| error "neither '$filename' or '$completename' exists" |
| } |
| return $completename |
| } |
| |
| return $filename |
| } |
| |
| # apachetest::getallincludes -- |
| # |
| # Reads the conf file, and returns its text, plus the text in |
| # all the files that it Includes itself. |
| # |
| # Arguments: |
| # conffile - file to read. |
| # |
| # Side Effects: |
| # None. |
| # |
| # Results: |
| # Text of configuration files. |
| |
| proc apachetest::getallincludes { conffile } { |
| if [file exists $conffile] { |
| set fl [open $conffile r] |
| set data [read $fl] |
| close $fl |
| |
| set newdata {} |
| foreach line [split $data \n] { |
| # Look for Include lines. |
| if { [regexp -line {^[^\#]*Include +(.*)} $line match file] } { |
| puts "including files from $file" |
| set file [string trim $file] |
| |
| # Include directives accept as argument a file, a directory |
| # or a glob-style file matching pattern. Patterns usually match |
| # many files, but are not directories, so we have to handle |
| # all these 3 cases |
| |
| # we use the glob command to tell whether we are dealing with |
| # a pure file expression or a matching pattern |
| |
| set matched_files [glob -nocomplain $file] |
| set matched_files_n [llength $matched_files] |
| if {$matched_files_n > 1} { |
| foreach fl $matched_files { |
| puts "including $fl" |
| if [file exists $fl] { |
| append newdata [getallincludes $fl] |
| } |
| } |
| } elseif {$matched_files_n == 1} { |
| set file $matched_files |
| if { [file isdirectory $file] } { |
| foreach fl [glob -nocomplain [file join $file *]] { |
| puts "including $fl" |
| if [file exists $fl] { |
| append newdata [getallincludes $fl] |
| } |
| } |
| } else { |
| append newdata [getallincludes $file] |
| } |
| } |
| } |
| } |
| append data $newdata |
| return $data |
| } else { |
| return |
| } |
| } |
| |
| # apachetest::getloadmodules -- |
| # |
| # Get the LoadModule lines for modules that we want to load. |
| # |
| # Arguments: |
| # conffile - the name of the conf file to read. |
| # needtoget - list of modules that we want to load. |
| # |
| # Side Effects: |
| # None. |
| # |
| # Results: |
| # Returns a string suitable for inclusion in a conf file. |
| |
| |
| proc apachetest::getloadmodules { conffile needtoget } { |
| puts "checking $conffile " |
| set confdata [getallincludes $conffile] |
| set loadline [list] |
| regexp -line {^[^#]*(ServerRoot[\s]?[\"]?)([^\"]+)()([\"]?)} $confdata \ |
| match sub1 server_root_path sub2 |
| foreach mod $needtoget { |
| |
| # Look for LoadModule lines. |
| puts -nonewline "check conf line for $mod module..." |
| flush stdout |
| if { ! [regexp -line "^\[^\#\]*(LoadModule\\s+$mod\\s+.+)\$"\ |
| $confdata match line] } { |
| error "No LoadModule line for $mod\!" |
| } else { |
| puts ok |
| |
| set raw_path [join [lrange [split $line { }] 2 end]] |
| #trimming leading whitespaces |
| set path [string trimleft $raw_path] |
| if ![string equal [file pathtype $line] "absolute"] { |
| set absolute_path [file join $server_root_path $path] |
| lappend loadline "[join [lrange [split $line " "] 0 1]] $absolute_path" |
| } else { |
| lappend loadline $line |
| } |
| } |
| } |
| return [join $loadline "\n"] |
| } |
| |
| # Compare what's compiled in with what we need. |
| |
| proc apachetest::determinemodules { binname } { |
| variable module_assoc |
| set compiledin [lsort [getcompiledin $binname]] |
| set conffile [gethttpdconf $binname] |
| |
| foreach {n k} [array get module_assoc] { |
| lappend needed $k |
| } |
| set needed [lsort $needed] |
| |
| puts "needed: $needed" |
| |
| set needtoget [list] |
| foreach mod $needed { |
| if { [lsearch $compiledin $mod] == -1 } { |
| lappend needtoget $mod |
| } |
| } |
| if {$needtoget == ""} { |
| return "" |
| } else { |
| return [getloadmodules $conffile $needtoget] |
| } |
| } |
| |
| # apachetest::makeconf -- |
| # |
| # Creates a config file and writes it to disk. |
| # |
| # Arguments: |
| # outfile - the file to create/write to. |
| # extra - extra config options to add. |
| # |
| # Side Effects: |
| # Creates a new config file. |
| # |
| # Results: |
| # None. |
| |
| proc apachetest::makeconf { outfile bridge {extra ""} } { |
| variable binname |
| variable templatefile |
| set CWD [pwd] |
| |
| #getting uid and gid of user |
| catch {exec id} raw_string |
| |
| # username and group no longer needed. User and Group directives removed from |
| # main conffile template (#Bug 53396) |
| |
| # set username [lindex [regexp -inline {(uid=)([\d]+)(\()([^\)]+)(\))} $raw_string] 4] |
| # set group [lindex [regexp -inline {(gid=)([\d]+)(\()([^\)]+)(\))} $raw_string] 4] |
| |
| # replace with determinemodules |
| set LOADMODULES [determinemodules $binname] |
| |
| puts "reading template from $templatefile" |
| set fl [open [file join . $templatefile] r] |
| set template [read $fl] |
| close $fl |
| |
| # configure a specific bridge if -bridge passed as argument |
| |
| if {$bridge == "default"} { |
| set bridge_conf "# Default bridge" |
| } else { |
| set bridge_conf "RivetServerConf MpmBridge $bridge" |
| } |
| set extra [string map [list BRIDGE $bridge_conf] $extra] |
| |
| append template $extra |
| |
| puts $template |
| |
| set out [subst $template] |
| |
| set of [open $outfile w] |
| puts $of "$out" |
| close $of |
| } |