about summary refs log tree commit homepage
path: root/projects
diff options
context:
space:
mode:
Diffstat (limited to 'projects')
-rw-r--r--projects/fastthread/Rakefile36
-rw-r--r--projects/gem_plugin/CHANGELOG2
-rw-r--r--projects/gem_plugin/Rakefile7
-rw-r--r--projects/gem_plugin/lib/gem_plugin.rb18
-rw-r--r--projects/mongrel_cluster/CHANGELOG2
-rw-r--r--projects/mongrel_cluster/lib/mongrel_cluster/init.rb46
-rw-r--r--projects/mongrel_cluster/lib/mongrel_cluster/recipes.rb2
-rw-r--r--projects/mongrel_cluster/lib/mongrel_cluster/recipes_2.rb3
-rw-r--r--projects/mongrel_service/CHANGELOG5
-rw-r--r--projects/mongrel_service/Rakefile43
-rw-r--r--projects/mongrel_service/lib/ServiceFB/ServiceFB_Utils.bas17
-rw-r--r--projects/mongrel_service/lib/ServiceFB/_utils_internals.bi2
-rw-r--r--projects/mongrel_service/native/console_process.bas22
-rw-r--r--projects/mongrel_service/tests/all_tests.bas18
-rw-r--r--projects/mongrel_service/tests/fixtures/mock_process.bas92
-rw-r--r--projects/mongrel_service/tests/test_console_process.bas402
-rw-r--r--projects/mongrel_service/tests/test_helpers.bas35
-rw-r--r--projects/mongrel_service/tests/test_helpers.bi8
-rw-r--r--projects/mongrel_service/tools/freebasic.rb26
19 files changed, 709 insertions, 77 deletions
diff --git a/projects/fastthread/Rakefile b/projects/fastthread/Rakefile
index c7d1269..e418a5a 100644
--- a/projects/fastthread/Rakefile
+++ b/projects/fastthread/Rakefile
@@ -1,48 +1,28 @@
 
+require 'rubygems'
+gem 'echoe', '>=2.7.11'
 require 'echoe'
 
 Echoe.new("fastthread") do |p|
   p.project = "mongrel"
   p.author = "MenTaLguY <mental@rydia.net>"
+  p.email = "mental@rydia.net"
   p.summary = "Optimized replacement for thread.rb primitives"
   p.extensions = "ext/fastthread/extconf.rb"
   p.clean_pattern = ['build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', "ext/fastthread/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/fastthread/Makefile", "pkg", "lib/*.bundle", "*.gem", ".config"]
 
   p.need_tar_gz = false
   p.need_tgz = true
-  # FIXME: find a workaround to have multiple key chains outside the Rakefile
-  # tried GEM_CERTIFICATE_CHAIN but produces an asn1 error
-  p.certificate_chain = [
-    '~/projects/gem_certificates/mongrel-public_cert.pem',
-    '~/projects/gem_certificates/luislavena-mongrel-public_cert.pem'
-  ]
-  #p.certificate_chain = ['/Users/eweaver/p/configuration/gem_certificates/mongrel/mongrel-public_cert.pem',
-  #  '/Users/eweaver/p/configuration/gem_certificates/evan_weaver-mongrel-public_cert.pem']    
+  p.certificate_chain = ['/Users/eweaver/p/configuration/gem_certificates/mongrel/mongrel-public_cert.pem',
+    '/Users/eweaver/p/configuration/gem_certificates/evan_weaver-mongrel-public_cert.pem']    
   p.require_signed = true
 
-  p.eval = proc do  
-    if RUBY_PLATFORM.match("win32")
-      extensions.clear
-      self.files += ['lib/fastthread.so']
+  p.eval = proc do
+    if Platform.windows?
       self.platform = Gem::Platform::CURRENT
+      self.files += ['lib/fastthread.so']
       task :package => [:clean, :compile]
     end
   end
-end
 
-def move_extensions
-  Dir["ext/**/*.#{Config::CONFIG['DLEXT']}"].each { |file| mv file, "lib/" }
-end
-
-case RUBY_PLATFORM
-when /mswin/
-  filename = "lib/fastthread.so"
-  file filename do
-    Dir.chdir("ext/fastthread") do
-      ruby "extconf.rb"
-      system(PLATFORM =~ /mswin/ ? 'nmake' : 'make')
-    end
-    move_extensions
-  end
-  task :compile => [filename]
 end
diff --git a/projects/gem_plugin/CHANGELOG b/projects/gem_plugin/CHANGELOG
index 7e1103d..cf39b5b 100644
--- a/projects/gem_plugin/CHANGELOG
+++ b/projects/gem_plugin/CHANGELOG
@@ -1,2 +1,4 @@
 
+v0.3. Use Gem.path, not Gem.dir, so that local gem repositories work (rooster).
+
 v0.2.3. Signed gem.
diff --git a/projects/gem_plugin/Rakefile b/projects/gem_plugin/Rakefile
index f1bd428..b312bfe 100644
--- a/projects/gem_plugin/Rakefile
+++ b/projects/gem_plugin/Rakefile
@@ -1,8 +1,11 @@
 
+require 'rubygems'
+gem 'echoe', '>=2.7.11'
 require 'echoe'
 
 Echoe.new("gem_plugin") do |p|
-  p.author="Zed A. Shaw"
+  p.author= "Zed A. Shaw"
+  p.email = "mongrel-development@rubyforge.org"
   p.project = "mongrel"
   p.summary = "A plugin system based on rubygems that uses dependencies only"
 
@@ -15,7 +18,7 @@ Echoe.new("gem_plugin") do |p|
   p.test_pattern = "test/test_plugins.rb"
   p.clean_pattern += ["pkg", "lib/*.bundle", "*.gem", ".config"]
   p.rdoc_pattern = ['README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc']
-  p.rdoc_template = `allison --path`.chomp  
+  p.rdoc_template = `#{Platform.windows? ? 'allison.bat' : 'allison'} --path`.chomp  
 end
 
 namespace :site do
diff --git a/projects/gem_plugin/lib/gem_plugin.rb b/projects/gem_plugin/lib/gem_plugin.rb
index 1996a61..782a990 100644
--- a/projects/gem_plugin/lib/gem_plugin.rb
+++ b/projects/gem_plugin/lib/gem_plugin.rb
@@ -105,8 +105,8 @@ module GemPlugin
     # To prevent this load requires the full path to the "init.rb" file, which
     # avoids the RubyGems autorequire magic.
     def load(needs = {})
-      sdir = File.join(Gem.dir, "specifications")
-      gems = Gem::SourceIndex.from_installed_gems(sdir)
+      sdirs = Gem::SourceIndex.installed_spec_directories
+      gems = Gem::SourceIndex.from_gems_in(sdirs)
       needs = needs.merge({"gem_plugin" => INCLUDE})
       
       gems.each do |path, gem|
@@ -128,11 +128,17 @@ module GemPlugin
           # looks like no needs were set to false, so it's good
           
           # Previously was set wrong, we already have the correct gem path!
-          #gem_dir = File.join(Gem.dir, "gems", "#{gem.name}-#{gem.version}")
-          gem_dir = File.join(Gem.dir, "gems", path)
+          gem_dir = ""
+          Gem.path.each do |gem_path|
+            gem_dir = File.join(gem_path, "gems", path)
+            break if File.exist?(gem_dir)
+          end
           
-          require File.join(gem_dir, "lib", gem.name, "init.rb")
-          @gems[gem.name] = gem_dir
+          gem_init = File.join(gem_dir, "lib", gem.name, "init.rb")
+          if File.exist?(gem_init)
+            require gem_init
+            @gems[gem.name] = gem_dir
+          end
         end
       end
 
diff --git a/projects/mongrel_cluster/CHANGELOG b/projects/mongrel_cluster/CHANGELOG
index 6e6f31e..d860d6d 100644
--- a/projects/mongrel_cluster/CHANGELOG
+++ b/projects/mongrel_cluster/CHANGELOG
@@ -1,5 +1,5 @@
 
-v1.0.5. Close #15406 (find_pid returning non-false) (Eden Li); close #15616 (wrong Cap 2 detection) (Ryan McGeary).
+v1.0.5. Close #15406 (find_pid returning non-false) (Eden Li).
 
 v1.0.4. Actually ship the Cap 2 tasks; use an autorequire based on whether namespace() is available (Kevin Runde).
 
diff --git a/projects/mongrel_cluster/lib/mongrel_cluster/init.rb b/projects/mongrel_cluster/lib/mongrel_cluster/init.rb
index 9e678d3..0f18fe9 100644
--- a/projects/mongrel_cluster/lib/mongrel_cluster/init.rb
+++ b/projects/mongrel_cluster/lib/mongrel_cluster/init.rb
@@ -13,7 +13,7 @@ module Cluster
       valid_exists?(@config_file, "Configuration file does not exist. Run mongrel_rails cluster::configure.")
       @valid
     end
-    
+      
     def read_options
       @options = {
         "environment" => ENV['RAILS_ENV'] || "development",
@@ -59,7 +59,7 @@ module Cluster
     def start
       read_options
       
-      argv = [ "mongrel_rails" ]
+      argv = @options['mongrel_rails']
       argv << "start"
       argv << "-d"
       argv << "-e #{@options['environment']}" if @options['environment']
@@ -80,30 +80,30 @@ module Cluster
       @ports.each do |port|              
         if @clean && pid_file_exists?(port) && !check_process(port)
           pid_file = port_pid_file(port)        
-          Mongrel.log("missing process: removing #{pid_file}")
+          log "missing process: removing #{pid_file}"
           chdir_cwd do
             File.unlink(pid_file)
           end
         end
         
         if pid_file_exists?(port) && check_process(port)
-          Mongrel.log("already started port #{port}")
+          log "already started port #{port}"        
           next
         end
 
         exec_cmd = cmd + " -p #{port} -P #{port_pid_file(port)}"
         exec_cmd += " -l #{port_log_file(port)}"
-        Mongrel.log("starting port #{port}")
+        log "starting port #{port}"          
         log_verbose exec_cmd
         output = `#{exec_cmd}`
-        Mongrel.log(:error, output) unless $?.success?
+        log_error output unless $?.success?
       end
     end
       
     def stop
       read_options
     
-      argv = [ "mongrel_rails" ]
+      argv = @options['mongrel_rails']
       argv << "stop"
       argv << "-c #{@options["cwd"]}" if @options["cwd"]
       argv << "-f" if @force
@@ -112,20 +112,20 @@ module Cluster
       @ports.each do |port|
         pid = check_process(port)        
         if @clean && pid && !pid_file_exists?(port)      
-          Mongrel.log("missing pid_file: killing mongrel_rails port #{port}, pid #{pid}")
+          log "missing pid_file: killing mongrel_rails port #{port}, pid #{pid}"
           Process.kill("KILL", pid.to_i)  
         end
         
         if !check_process(port)
-          Mongrel.log("already stopped port #{port}")
+          log "already stopped port #{port}"                  
           next      
         end
 
         exec_cmd = cmd + " -P #{port_pid_file(port)}"
-        Mongrel.log("stopping port #{port}")
+        log "stopping port #{port}"          
         log_verbose exec_cmd
         output = `#{exec_cmd}`
-        Mongrel.log(:error, output) unless $?.success?
+        log_error output unless $?.success?
         
       end
     end
@@ -138,18 +138,18 @@ module Cluster
       @ports.each do |port|
         pid = check_process(port)        
         unless pid_file_exists?(port)        
-          Mongrel.log(:error, "missing pid_file: #{port_pid_file(port)}")
+          log "missing pid_file: #{port_pid_file(port)}"  
           status = STATUS_ERROR
         else
-          Mongrel.log("found pid_file: #{port_pid_file(port)}")
+          log "found pid_file: #{port_pid_file(port)}"
         end    
         if pid
-          Mongrel.log("found mongrel_rails: port #{port}, pid #{pid}")
+          log "found mongrel_rails: port #{port}, pid #{pid}"
         else
-          Mongrel.log(:error, "missing mongrel_rails: port #{port}")
+          log "missing mongrel_rails: port #{port}"
           status = STATUS_ERROR
         end
-        Mongrel.log("")
+        puts ""
       end
 
       status
@@ -211,12 +211,18 @@ module Cluster
       nil
     end
     
+    def log_error(message)
+      log(message)
+    end
+
     def log_verbose(message)
-      Mongrel.log(message) if @verbose
+      log(message) if @verbose
     end
 
+    def log(message)
+      puts message
+    end  
   end
-
   class Start < GemPlugin::Plugin "/commands"
     include ExecBase
     
@@ -294,6 +300,7 @@ module Cluster
         ['-C', '--config PATH', "Path to cluster configuration file", :@config_file, "config/mongrel_cluster.yml"],
         ['', '--user USER', "User to run as", :@user, nil],
         ['', '--group GROUP', "Group to run as", :@group, nil],
+        ['', '--mongrel_rails PATH', "Full path to mongrel_rails script", :@mongrel_rails, "mongrel_rails"],
         ['', '--prefix PREFIX', "Rails prefix to use", :@prefix, nil]
       ]
     end
@@ -328,8 +335,9 @@ module Cluster
       @options["user"] = @user if @user
       @options["group"] = @group if @group
       @options["prefix"] = @prefix if @prefix
+      @options["mongrel_rails"] = @mongrel_rails if @mongrel_rails
       
-      Mongrel.log("Writing configuration file to #{@config_file}.")
+      log "Writing configuration file to #{@config_file}."
       File.open(@config_file,"w") {|f| f.write(@options.to_yaml)}
     end  
   end
diff --git a/projects/mongrel_cluster/lib/mongrel_cluster/recipes.rb b/projects/mongrel_cluster/lib/mongrel_cluster/recipes.rb
index 6b62712..74ec468 100644
--- a/projects/mongrel_cluster/lib/mongrel_cluster/recipes.rb
+++ b/projects/mongrel_cluster/lib/mongrel_cluster/recipes.rb
@@ -1,5 +1,5 @@
 
-if Capistrano::Configuration.respond_to?(:instance)
+if respond_to?(:namespace)
   require 'mongrel_cluster/recipes_2' # Cap 2
 else  
   require 'mongrel_cluster/recipes_1' # Cap 1
diff --git a/projects/mongrel_cluster/lib/mongrel_cluster/recipes_2.rb b/projects/mongrel_cluster/lib/mongrel_cluster/recipes_2.rb
index a82c424..312ef5c 100644
--- a/projects/mongrel_cluster/lib/mongrel_cluster/recipes_2.rb
+++ b/projects/mongrel_cluster/lib/mongrel_cluster/recipes_2.rb
@@ -7,7 +7,7 @@ Capistrano::Configuration.instance.load do
   set :mongrel_user, nil
   set :mongrel_group, nil
   set :mongrel_prefix, nil
-  set :mongrel_rails, 'mongrel_rails'
+  set :mongrel_rails, "mongrel_rails"
   set :mongrel_clean, false
   set :mongrel_pid_file, nil
   set :mongrel_log_file, nil
@@ -37,6 +37,7 @@ Capistrano::Configuration.instance.load do
         argv << "--group #{mongrel_group}" if mongrel_group
         argv << "--prefix #{mongrel_prefix}" if mongrel_prefix
         argv << "-S #{mongrel_config_script}" if mongrel_config_script
+        argv << "--mongrel_rails #{mongrel_rails}" if mongrel_rails        
         cmd = argv.join " "
         send(run_method, cmd)
       end
diff --git a/projects/mongrel_service/CHANGELOG b/projects/mongrel_service/CHANGELOG
index 67f5086..074c87a 100644
--- a/projects/mongrel_service/CHANGELOG
+++ b/projects/mongrel_service/CHANGELOG
@@ -1,3 +1,8 @@
+* 0.3.5 *
+
+    * Wait longer for child process terminate properly (max 20 seconds). Imported
+    tests from RubyServices project. (Closes #18).
+    * Updated ServiceFB to work with FB > 0.18.
 
 * 0.3.4 *
     
diff --git a/projects/mongrel_service/Rakefile b/projects/mongrel_service/Rakefile
index 1584ca3..efc9810 100644
--- a/projects/mongrel_service/Rakefile
+++ b/projects/mongrel_service/Rakefile
@@ -1,3 +1,6 @@
+
+require 'rubygems'
+gem 'echoe', '>=2.7.11'
 require 'echoe'
 require 'tools/freebasic'
 
@@ -10,6 +13,7 @@ echoe_spec = Echoe.new("mongrel_service") do |p|
   p.summary += " (debug build)" unless ENV['RELEASE']
   p.description = "This plugin offer native win32 services for rails, powered by Mongrel."
   p.author = "Luis Lavena"
+  p.email = "luislavena@gmail.com"
   p.platform = Gem::Platform::CURRENT
   p.dependencies = [['gem_plugin', '>=0.2.3', '<0.3.0'],
                     ['mongrel', '>=1.0.2', '<1.2.0'],
@@ -93,3 +97,42 @@ end
 #include_projects_of :native
 task :native_service => "native:build"
 task :clean => "native:clobber"
+
+project_task :mock_process do
+  executable  :mock_process
+  build_to    'tests'
+  
+  main        'tests/fixtures/mock_process.bas'
+  
+  option      OPTIONS
+end
+
+task "all_tests:build" => "lib:build"
+project_task :all_tests do
+  executable  :all_tests
+  build_to    'tests'
+  
+  search_path 'src', 'lib', 'native'
+  lib_path    'lib'
+  
+  main        'tests/all_tests.bas'
+  
+  # this temporally fix the inverse namespace ctors of FB
+  source      Dir.glob("tests/test_*.bas").reverse
+  
+  library     'testly'
+  
+  source      'native/console_process.bas'
+  
+  option      OPTIONS
+end
+
+desc "Run all the internal tests for the library"
+task "all_tests:run" => ["mock_process:build", "all_tests:build"] do
+  Dir.chdir('tests') do
+    sh %{all_tests}
+  end
+end
+
+desc "Run all the test for this project"
+task :test => "all_tests:run"
diff --git a/projects/mongrel_service/lib/ServiceFB/ServiceFB_Utils.bas b/projects/mongrel_service/lib/ServiceFB/ServiceFB_Utils.bas
index c5150ea..c8a77a3 100644
--- a/projects/mongrel_service/lib/ServiceFB/ServiceFB_Utils.bas
+++ b/projects/mongrel_service/lib/ServiceFB/ServiceFB_Utils.bas
@@ -15,6 +15,8 @@ namespace svc
 namespace utils   '# fb.svc.utils
     '# private (internals) for ServiceProcess.Console()
     dim shared _svc_stop_signal as any ptr
+    dim shared _svc_stop_mutex as any ptr
+    dim shared _svc_stopped as BOOL
     dim shared _svc_in_console as ServiceProcess ptr
     dim shared _svc_in_console_stop_flag as BOOL
     
@@ -168,6 +170,7 @@ namespace utils   '# fb.svc.utils
                 
                 '# create the signal used to stop the service thread.
                 _svc_stop_signal = condcreate()
+                _svc_stop_mutex = mutexcreate()
                 
                 '# register the Console Handler
                 SetConsoleCtrlHandler(@_console_handler, TRUE)
@@ -189,6 +192,9 @@ namespace utils   '# fb.svc.utils
                     if not (service->onStart = 0) then
                         '# create the thread
                         working_thread = threadcreate(@ServiceProcess.call_onStart, service)
+                        if (working_thread = 0) then
+                            print "Failed to create working thread."
+                        end if
                     end if
                     
                     print "Service is in running state."
@@ -197,7 +203,11 @@ namespace utils   '# fb.svc.utils
                     '# now that onStart is running, must monitor the stop_signal
                     '# in case it arrives, the service state must change to exit the
                     '# working thread.
-                    condwait(_svc_stop_signal)
+                    mutexlock(_svc_stop_mutex)
+                    do while (_svc_stopped = FALSE)
+                        condwait(_svc_stop_signal, _svc_stop_mutex)
+                    loop
+                    mutexunlock(_svc_stop_mutex)
                     
                     print "Stop signal received, stopping..."
                     
@@ -222,7 +232,7 @@ namespace utils   '# fb.svc.utils
                 
                 '# now that service was stopped, destroy the references.
                 conddestroy(_svc_stop_signal)
-                
+                mutexdestroy(_svc_stop_mutex)
                 print "Done."
             end if
         else
@@ -287,9 +297,12 @@ namespace utils   '# fb.svc.utils
                     
                     '# now fire the signal
                     _dprint("fire stop signal")
+                    mutexlock(_svc_stop_mutex)
                     condsignal(_svc_stop_signal)
                     result = TRUE
                     _svc_in_console_stop_flag = FALSE
+                    _svc_stopped = TRUE
+                    mutexunlock(_svc_stop_mutex)
                     
                 case else:
                     _dprint("unsupported CTRL EVENT")
diff --git a/projects/mongrel_service/lib/ServiceFB/_utils_internals.bi b/projects/mongrel_service/lib/ServiceFB/_utils_internals.bi
index 67acc87..fab00f0 100644
--- a/projects/mongrel_service/lib/ServiceFB/_utils_internals.bi
+++ b/projects/mongrel_service/lib/ServiceFB/_utils_internals.bi
@@ -42,6 +42,8 @@ namespace utils   '# fb.svc.utils
     '# will raise in case Ctrl+C / Ctrl+Break or other events are
     '# received.
     extern _svc_stop_signal as any ptr
+    extern _svc_stop_mutex as any ptr
+    extern _svc_stopped as BOOL
     extern _svc_in_console as ServiceProcess ptr
     extern _svc_in_console_stop_flag as BOOL
 end namespace     '# fb.svc.utils
diff --git a/projects/mongrel_service/native/console_process.bas b/projects/mongrel_service/native/console_process.bas
index 78dc1a0..a2bb5c9 100644
--- a/projects/mongrel_service/native/console_process.bas
+++ b/projects/mongrel_service/native/console_process.bas
@@ -81,6 +81,7 @@ property ConsoleProcess.pid() as uinteger
 end property
 
 property ConsoleProcess.exit_code() as uinteger
+    static previous_code as uinteger
     dim result as uinteger
     
     result = 0
@@ -90,9 +91,16 @@ property ConsoleProcess.exit_code() as uinteger
         if not (_process_info.hProcess = NULL) then
             '# the process reference is valid, get the exit_code
             if not (GetExitCodeProcess(_process_info.hProcess, @result) = 0) then
+                previous_code = result
                 '# OK
                 '# no error in the query, get result
+                if not (result = STILL_ACTIVE) then
+                    CloseHandle(_process_info.hProcess)
+                    _process_info.hProcess = NULL
+                end if '# (result = STILL_ACTIVE)
             end if '# not (GetExitCodeProcess() = 0)
+        else
+            result = previous_code
         end if '# not (proc = NULL)
     end if '# not (_pid = 0)
     
@@ -228,7 +236,7 @@ function ConsoleProcess.start() as boolean
             else
                 '# use pipes instead
                 '# StdOut
-                if (CreatePipe(@StdErrRd, @StdErrWr, @proc_sa, 0) = 0) then
+                if (CreatePipe(@StdErrRd, @StdErrWr, @proc_sa, 0) = 0) then
                     success = false
                 end if
                 
@@ -283,10 +291,10 @@ function ConsoleProcess.start() as boolean
                 CloseHandle(StdErrRd)
                 CloseHandle(StdErrWr)
                 
-                '# close children main Thread handle
-                'CloseHandle(proc.hThread)
-                'CloseHandle(proc.hProcess)
-                
+                '# close children main Thread handle and
+                '# NULLify to avoid issues
+                CloseHandle(_process_info.hThread)
+                _process_info.hThread = NULL
             end if '# (CreateProcess() = 0)
         else
             result = false
@@ -322,7 +330,7 @@ function ConsoleProcess.terminate(byval force as boolean = false) as boolean
                     '# send CTRL_C_EVENT and wait for result
                     if not (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) = 0) then
                         '# it worked, wait 5 seconds terminates.
-                        wait_code = WaitForSingleObject(proc, 5000)
+                        wait_code = WaitForSingleObject(proc, 10000)
                         if not (wait_code = WAIT_TIMEOUT) then
                             success = true
                         end if
@@ -335,7 +343,7 @@ function ConsoleProcess.terminate(byval force as boolean = false) as boolean
                         '# send CTRL_BREAK_EVENT and wait for result
                         if not (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0) = 0) then
                             '# it worked, wait 5 seconds terminates.
-                            wait_code = WaitForSingleObject(proc, 5000)
+                            wait_code = WaitForSingleObject(proc, 10000)
                             if not (wait_code = WAIT_TIMEOUT) then
                                 success = true
                             end if
diff --git a/projects/mongrel_service/tests/all_tests.bas b/projects/mongrel_service/tests/all_tests.bas
new file mode 100644
index 0000000..0a8864f
--- /dev/null
+++ b/projects/mongrel_service/tests/all_tests.bas
@@ -0,0 +1,18 @@
+'#--
+'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
+'#
+'# This source code is released under the MIT License.
+'# See MIT-LICENSE file for details
+'#++
+
+#include once "testly.bi"
+
+'# the code in this module runs after all
+'# the other modules have "registered" their suites.
+
+'# evaluate the result from run_tests() to
+'# return a error to the OS or not.
+if (run_tests() = false) then
+    end 1
+end if
+
diff --git a/projects/mongrel_service/tests/fixtures/mock_process.bas b/projects/mongrel_service/tests/fixtures/mock_process.bas
new file mode 100644
index 0000000..e9405ab
--- /dev/null
+++ b/projects/mongrel_service/tests/fixtures/mock_process.bas
@@ -0,0 +1,92 @@
+'#--
+'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
+'#
+'# This source code is released under the MIT License.
+'# See MIT-LICENSE file for details
+'#++
+
+'# this program mock a common process that will:
+'# output some text to stdout
+'# output some error messages to stderr
+'# will wait until Ctrl-C is hit (only if commandline contains "wait")
+'# or drop an error if commandline contains "error"
+
+#include once "crt.bi"
+#include once "windows.bi"
+
+dim shared as any ptr control_signal, control_mutex
+dim shared flagged as byte
+dim shared result as integer
+
+function slow_console_handler(byval dwCtrlType as DWORD) as BOOL
+    dim result as BOOL
+    
+    if (dwCtrlType = CTRL_C_EVENT) then
+        fprintf(stdout, !"out: CTRL-C received\r\n")
+        mutexlock(control_mutex)
+        result = 1
+        flagged = 1
+        condsignal(control_signal)
+        mutexunlock(control_mutex)
+    elseif (dwCtrlType = CTRL_BREAK_EVENT) then
+        fprintf(stdout, !"out: CTRL-BREAK received\r\n")
+        mutexlock(control_mutex)
+        result = 1
+        flagged = 2
+        condsignal(control_signal)
+        mutexunlock(control_mutex)
+    end if
+    
+    return result
+end function
+
+sub wait_for(byval flag_level as integer)
+    flagged = 0
+    '# set handler
+    if (SetConsoleCtrlHandler(@slow_console_handler, 1) = 0) then
+        fprintf(stderr, !"err: cannot set console handler\r\n")
+    end if
+    fprintf(stdout, !"out: waiting for keyboard signal\r\n")
+    mutexlock(control_mutex)
+    do until (flagged = flag_level)
+        condwait(control_signal, control_mutex)
+    loop
+    mutexunlock(control_mutex)
+    fprintf(stdout, !"out: got keyboard signal\r\n")
+    if (SetConsoleCtrlHandler(@slow_console_handler, 0) = 0) then
+        fprintf(stderr, !"err: cannot unset console handler\r\n")
+    end if
+end sub
+
+function main() as integer
+    fprintf(stdout, !"out: message\r\n")
+    fprintf(stderr, !"err: error\r\n")
+    
+    select case lcase(command(1))
+        case "wait":
+            sleep
+            return 0
+
+        case "error":
+            '# terminate with error code
+            return 1
+        
+        case "slow1":
+            wait_for(1)
+            return 10
+        
+        case "slow2":
+            wait_for(2)
+            return 20
+    end select
+end function
+
+control_signal = condcreate()
+control_mutex = mutexcreate()
+
+result = main()
+
+conddestroy(control_signal)
+mutexdestroy(control_mutex)
+
+end result
diff --git a/projects/mongrel_service/tests/test_console_process.bas b/projects/mongrel_service/tests/test_console_process.bas
new file mode 100644
index 0000000..d41c0fb
--- /dev/null
+++ b/projects/mongrel_service/tests/test_console_process.bas
@@ -0,0 +1,402 @@
+'#--
+'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
+'#
+'# This source code is released under the MIT License.
+'# See MIT-LICENSE file for details
+'#++
+
+#include once "console_process.bi"
+#include once "file.bi"
+#include once "testly.bi"
+#include once "test_helpers.bi"
+
+namespace Suite_Test_Console_Process
+    '# test helpers
+    declare function process_cleanup() as boolean
+    
+    dim shared child as ConsoleProcess ptr
+    
+    sub before_all()
+        kill("out.log")
+        kill("err.log")
+        kill("both.log")
+        kill("both_slow.log")
+        kill("both_forced.log")
+    end sub
+    
+    sub after_each()
+        process_cleanup()
+    end sub
+    
+    sub test_process_create()
+        child = new ConsoleProcess()
+        assert_not_equal(0, child)
+        assert_equal("", child->filename)
+        assert_equal("", child->arguments)
+        assert_false(child->running)
+        delete child
+    end sub
+    
+    sub test_process_create_args()
+        child = new ConsoleProcess("mock_process.exe", "some params")
+        assert_equal("mock_process.exe", child->filename)
+        assert_equal("some params", child->arguments)
+        delete child
+    end sub
+    
+    sub test_properly_quoted_filename()
+        child = new ConsoleProcess("C:\path with spaces\my_executable.exe", "some params")
+        assert_not_equal(0, instr(child->filename, !"\""))
+        delete child
+    end sub
+    
+    sub test_failed_unexistant_process()
+        child = new ConsoleProcess("no_valid_file.exe", "some params")
+        assert_false(child->start())
+        assert_equal(0, child->pid)
+        assert_false(child->running)
+        delete child
+    end sub
+    
+    sub test_process_spawn_exit_code()
+        child = new ConsoleProcess("mock_process.exe", "error")
+        
+        '# start() should return true since it started, no matter if was terminated
+        '# improperly
+        assert_true(child->start())
+        sleep 500
+        
+        '# should not be running, but pid should be != than 0
+        assert_not_equal(0, child->pid)
+        
+        '# we need to wait a bit prior asking for state
+        '# the process could be still running
+        assert_false(child->running)
+        
+        '# get exit code, should be 1
+        assert_equal(1, child->exit_code)
+        
+        delete child
+    end sub
+    
+    sub test_redirected_output()
+        assert_false(fileexists("out.log"))
+        assert_false(fileexists("err.log"))
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe")
+        
+        '# redirect stdout
+        assert_true(child->redirect(ProcessStdOut, "out.log"))
+        assert_string_equal("out.log", child->redirected_stdout)
+        
+        '# redirect stderr
+        assert_true(child->redirect(ProcessStdErr, "err.log"))
+        assert_string_equal("err.log", child->redirected_stderr)
+        
+        '# start() will be true since process terminated nicely
+        assert_true(child->start())
+        sleep 500
+        
+        '# running should be false
+        assert_false(child->running)
+        
+        '# exit_code should be 0
+        assert_equal(0, child->exit_code)
+        
+        delete child
+        
+        '# now out.log and err.log must exist and content must be valid.
+        assert_true(fileexists("out.log"))
+        assert_string_equal("out: message", content_of_file("out.log"))
+        
+        assert_true(fileexists("err.log"))
+        assert_string_equal("err: error", content_of_file("err.log"))
+        
+        assert_equal(0, kill("out.log"))
+        assert_equal(0, kill("err.log"))
+    end sub
+    
+    sub test_redirected_merged_output()
+        dim content as string
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe")
+
+        '# redirect both stdout and stderr
+        child->redirect(ProcessStdBoth, "both.log")
+        assert_equal("both.log", child->redirected_stdout)
+        assert_equal("both.log", child->redirected_stderr)
+        
+        '# start() will be true since process terminated nicely
+        assert_true(child->start())
+        sleep 500
+        
+        '# running should be false
+        assert_false(child->running)
+        
+        '# exit_code should be 0
+        assert_equal(0, child->exit_code)
+        
+        delete child
+        
+        '# file must exists
+        assert_true(fileexists("both.log"))
+        
+        '# contents must match
+        content = content_of_file("both.log")
+        
+        assert_not_equal(0, instr(content, "out: message"))
+        assert_not_equal(0, instr(content, "err: error"))
+        
+        assert_equal(0, kill("both.log"))
+    end sub
+    
+    sub test_redirected_output_append()
+        dim content as string
+        
+        child = new ConsoleProcess("mock_process.exe")
+        
+        '# redirect both stdout and stderr
+        child->redirect(ProcessStdBoth, "both.log")
+        
+        '# start() will be true since process terminated nicely
+        assert_true(child->start())
+        sleep 500
+        
+        content = content_of_file("both.log")
+        
+        '# start() again
+        assert_true(child->start())
+        sleep 500
+        
+        delete child
+        
+        assert_not_equal(len(content), len(content_of_file("both.log")))
+        
+        assert_equal(0, kill("both.log"))
+    end sub
+    
+    sub test_process_terminate()
+        dim content as string
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe", "wait")
+        child->redirect(ProcessStdBoth, "both.log")
+        
+        '# start
+        assert_true(child->start())
+        sleep 500
+        
+        '# validate if running
+        assert_true(child->running)
+        
+        '# validate PID
+        assert_not_equal(0, child->pid)
+        
+        '# now terminates it
+        assert_true(child->terminate())
+        sleep 500
+        
+        assert_equal(9, child->exit_code)
+        
+        '# it should be done
+        assert_false(child->running)
+        
+        delete child
+        
+        '# validate output
+        '# file must exists
+        assert_true(fileexists("both.log"))
+        
+        '# contents must match
+        content = content_of_file("both.log")
+        
+        assert_not_equal(0, instr(content, "out: message"))
+        assert_not_equal(0, instr(content, "err: error"))
+        assert_not_equal(0, instr(content, "interrupted"))
+        
+        assert_equal(0, kill("both.log"))
+    end sub
+    
+    sub test_process_terminate_slow1()
+        dim content as string
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe", "slow1")
+        child->redirect(ProcessStdBoth, "both_slow1.log")
+        
+        '# start
+        assert_true(child->start())
+        sleep 500
+        
+        '# validate if running
+        assert_true(child->running)
+        
+        '# validate PID
+        assert_not_equal(0, child->pid)
+        
+        '# now terminates it
+        assert_true(child->terminate())
+        sleep 500
+        
+        assert_equal(10, child->exit_code)
+        
+        '# it should be done
+        assert_false(child->running)
+        
+        delete child
+        
+        '# validate output
+        '# file must exists
+        assert_true(fileexists("both_slow1.log"))
+        
+        '# contents must match
+        content = content_of_file("both_slow1.log")
+        
+        assert_equal(0, instr(content, "interrupted"))
+        assert_not_equal(0, instr(content, "out: CTRL-C received"))
+        assert_equal(0, instr(content, "out: CTRL-BREAK received"))
+        
+        assert_equal(0, kill("both_slow1.log"))
+    end sub
+    
+    sub test_process_terminate_slow2()
+        dim content as string
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe", "slow2")
+        child->redirect(ProcessStdBoth, "both_slow2.log")
+        
+        '# start
+        assert_true(child->start())
+        sleep 500
+        
+        '# validate if running
+        assert_true(child->running)
+        
+        '# validate PID
+        assert_not_equal(0, child->pid)
+        
+        '# now terminates it
+        assert_true(child->terminate())
+        sleep 500
+        
+        assert_equal(20, child->exit_code)
+        
+        '# it should be done
+        assert_false(child->running)
+        
+        delete child
+        
+        '# validate output
+        '# file must exists
+        assert_true(fileexists("both_slow2.log"))
+        
+        '# contents must match
+        content = content_of_file("both_slow2.log")
+        
+        assert_equal(0, instr(content, "interrupted"))
+        assert_not_equal(0, instr(content, "out: CTRL-C received"))
+        assert_not_equal(0, instr(content, "out: CTRL-BREAK received"))
+        
+        '# cleanup
+        assert_equal(0, kill("both_slow2.log"))
+    end sub
+
+    sub test_process_terminate_forced()
+        dim content as string
+        
+        '# redirected output is used with logging files.
+        child = new ConsoleProcess("mock_process.exe", "wait")
+        child->redirect(ProcessStdBoth, "both_forced.log")
+        
+        '# start
+        assert_true(child->start())
+        sleep 500
+        
+        '# validate if running
+        assert_true(child->running)
+        
+        '# validate PID
+        assert_not_equal(0, child->pid)
+        
+        '# now terminates it
+        assert_true(child->terminate(true))
+        sleep 500
+        
+        '# it should be done
+        assert_false(child->running)
+        
+        '# look for termination code
+        assert_equal(0, child->exit_code)
+        
+        delete child
+        
+        '# validate output
+        '# file must exists
+        assert_true(fileexists("both_forced.log"))
+        
+        '# contents must match
+        content = content_of_file("both_forced.log")
+        
+        assert_equal(0, instr(content, "out: message"))
+        assert_equal(0, instr(content, "err: error"))
+        assert_equal(0, instr(content, "interrupted"))
+        
+        assert_equal(0, kill("both_forced.log"))
+    end sub
+    
+    sub test_reuse_object_instance()
+        dim first_pid as uinteger
+        
+        child = new ConsoleProcess("mock_process.exe")
+        
+        '# start
+        assert_true(child->start())
+        sleep 500
+        
+        '# validate not running
+        assert_false(child->running)
+        
+        '# validate PID
+        assert_not_equal(0, child->pid)
+        
+        '# saves PID
+        first_pid = child->pid
+        
+        '# start it again
+        assert_true(child->start())
+        sleep 500
+        
+        '# it should have stopped by now
+        assert_false(child->running)
+        assert_not_equal(0, child->pid)
+        assert_not_equal(first_pid, child->pid)
+        
+        delete child
+    end sub
+    
+    private sub register() constructor
+        add_suite(Suite_Test_Console_Process)
+        add_test(test_process_create)
+        add_test(test_process_create_args)
+        add_test(test_properly_quoted_filename)
+        add_test(test_failed_unexistant_process)
+        add_test(test_process_spawn_exit_code)
+        add_test(test_redirected_output)
+        add_test(test_redirected_merged_output)
+        add_test(test_redirected_output_append)
+        add_test(test_process_terminate)
+        add_test(test_process_terminate_slow1)
+        add_test(test_process_terminate_slow2)
+        add_test(test_process_terminate_forced)
+        add_test(test_reuse_object_instance)
+    end sub
+    
+    '# test helpers below this point
+    private function process_cleanup() as boolean
+        shell "taskkill /f /im mock_process.exe 1>NUL 2>&1"
+        return true
+    end function
+end namespace
diff --git a/projects/mongrel_service/tests/test_helpers.bas b/projects/mongrel_service/tests/test_helpers.bas
new file mode 100644
index 0000000..c4647c0
--- /dev/null
+++ b/projects/mongrel_service/tests/test_helpers.bas
@@ -0,0 +1,35 @@
+'#--
+'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
+'#
+'# This source code is released under the MIT License.
+'# See MIT-LICENSE file for details
+'#++
+
+#include once "testly.bi"
+#include once "test_helpers.bi"
+#include once "file.bi"
+
+'# Global Helpers
+function content_of_file(byref filename as string) as string
+    dim result as string
+    dim handle as integer
+    dim buffer as string
+    
+    result = ""
+    buffer = ""
+    
+    if (fileexists(filename) = true) then
+        handle = freefile
+        open filename for input as #handle
+        do while not (eof(handle))
+            input #handle, buffer
+            result += buffer
+            buffer = ""
+        loop
+        close #handle
+    else
+        result = ""
+    end if
+    
+    return result
+end function
diff --git a/projects/mongrel_service/tests/test_helpers.bi b/projects/mongrel_service/tests/test_helpers.bi
new file mode 100644
index 0000000..9397962
--- /dev/null
+++ b/projects/mongrel_service/tests/test_helpers.bi
@@ -0,0 +1,8 @@
+'#--
+'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
+'#
+'# This source code is released under the MIT License.
+'# See MIT-LICENSE file for details
+'#++
+
+declare function content_of_file(byref as string) as string \ No newline at end of file
diff --git a/projects/mongrel_service/tools/freebasic.rb b/projects/mongrel_service/tools/freebasic.rb
index 8902059..0b3e445 100644
--- a/projects/mongrel_service/tools/freebasic.rb
+++ b/projects/mongrel_service/tools/freebasic.rb
@@ -63,7 +63,7 @@ module FreeBASIC
       @libraries_path = []
       @options = {}
       
-      instance_eval &block
+      instance_eval(&block) if block_given?
       
       do_cleanup
       
@@ -90,7 +90,7 @@ module FreeBASIC
       # as output_name for the project
       def lib(lib_name)
         @type = :lib
-        @output_name = lib_name
+        @output_name = "#{lib_name}"
         @real_file_name = "lib#{lib_name}.a"
       end
       
@@ -197,7 +197,9 @@ module FreeBASIC
       # return the compiled name version of the passed source file (src)
       # compiled_form("test.bas") => "test.o"
       def compiled_form(src)
-        src.ext({ ".bas" => "o", ".rc" => "obj" }[File.extname(src)])
+        unless src.nil?
+          src.ext({ ".bas" => "o", ".rc" => "obj" }[File.extname(src)])
+        end
       end
       
       def compiled_project_file
@@ -207,11 +209,11 @@ module FreeBASIC
       def fbc_compile(source, target, main = nil)
         cmdline = []
         cmdline << "fbc"
+        cmdline << "-w pedantic" if (@options.has_key?(:pedantic) && @options[:pedantic] == true)
         cmdline << "-g" if (@options.has_key?(:debug) && @options[:debug] == true)
         cmdline << "-#{@options[:errorchecking].to_s}" if @options.has_key?(:errorchecking)
-        cmdline << "-profile" if (@options.has_key?(:profile) && @options[:profile] == true)
         cmdline << "-mt" if (@options.has_key?(:mt) && @options[:mt] == true)
-        cmdline << "-w pedantic" if (@options.has_key?(:pedantic) && @options[:pedantic] == true)
+        cmdline << "-profile" if (@options.has_key?(:profile) && @options[:profile] == true)
         cmdline << "-c #{source}"
         cmdline << "-o #{target}"
         cmdline << "-m #{main}" unless main.nil?
@@ -224,8 +226,8 @@ module FreeBASIC
         cmdline = []
         cmdline << "fbc"
         cmdline << "-g" if (@options.has_key?(:debug) && @options[:debug] == true)
-        cmdline << "-profile" if (@options.has_key?(:profile) && @options[:profile] == true)
         cmdline << "-mt" if (@options.has_key?(:mt) && @options[:mt] == true)
+        cmdline << "-profile" if (@options.has_key?(:profile) && @options[:profile] == true)
         cmdline << "-#{@type.to_s}" unless @type == :executable
         cmdline << "-x #{target}"
         cmdline << files << extra_files
@@ -241,11 +243,15 @@ module FreeBASIC
         desc "Remove all compiled files for #{@name}"
         task :clobber do
           # remove compiled and linked file
-          rm compiled_project_file rescue nil #unless @type == :lib
-          rm File.join(@build_path, @complement_file) rescue nil if @type == :dylib
+          rm compiled_project_file rescue nil if File.exist?(compiled_project_file)
+          if @type == :dylib
+            rm File.join(@build_path, @complement_file) rescue nil if File.exist?(File.join(@build_path, @complement_file))
+          end
           
           # remove main file
-          rm compiled_form(@main_file) rescue nil
+          unless @main_file.nil? || !File.exists?(compiled_form(@main_file))
+            rm compiled_form(@main_file) rescue nil
+          end
           
           # now the sources files
           # avoid attempt to remove the file two times (this is a bug in Rake)
@@ -255,7 +261,7 @@ module FreeBASIC
               target = compiled_form(src)
               unless CLOBBER.include?(target)
                 CLOBBER.include(target)
-                rm target rescue nil
+                rm target rescue nil if File.exist?(target)
               end
             end
           end