From 224c9818461c8f2ea3cec280e7920620b3dd4313 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 20 Nov 2011 10:38:41 -0800 Subject: [PATCH] Test: Add "timeout" parameter to execute in tests --- .../support/isolated_environment.rb | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/test/acceptance/support/isolated_environment.rb b/test/acceptance/support/isolated_environment.rb index b08f0fdbf..efd72a672 100644 --- a/test/acceptance/support/isolated_environment.rb +++ b/test/acceptance/support/isolated_environment.rb @@ -60,6 +60,9 @@ module Acceptance options = argN.last options[:chdir] ||= @workdir.to_s + # Determine the timeout for the process + timeout = options.delete(:timeout) + # Execute in a separate process, wait for it to complete, and # return the IO streams. @logger.info("Executing: #{command} #{argN.inspect}. Output will stream in...") @@ -71,7 +74,12 @@ module Acceptance stderr => "" } - while results = IO.select([stdout, stderr], [stdin], nil, 5) + # Record the start time for timeout purposes + start_time = Time.now.to_i + + while results = IO.select([stdout, stderr], [stdin], nil, timeout || 5) + raise TimeoutExceeded, pid if timeout && (Time.now.to_i - start_time) > timeout + # Check the readers first to see if they're ready readers = results[0] if !readers.empty? @@ -102,11 +110,24 @@ module Acceptance end end - # Only load the exit status if we don't already have it, since - # it is possible that it could've been obtained in the above - # while loop. - _pid, status = Process.waitpid2(pid) if !status - @logger.debug("Exit status: #{status.exitstatus}") + # Continually try to wait for the process to end, but do so asynchronously + # so that we can also check to see if we have exceeded a timeout. + while true + # Break if status because it was already obtained above + break if status + + # Try to wait for the PID to exit, and exit this loop if it does + exitpid, status = Process.waitpid2(pid, Process::WNOHANG) + break if exitpid + + # Check to see if we exceeded our process timeout while waiting for + # it to end. + raise TimeoutExceeded, pid if timeout && (Time.now.to_i - start_time) > timeout + + # Sleep between checks so that we're not constantly hitting the syscall + sleep 0.5 + end + @logger.debug("Exit status: #{status.exitstatus}") return ExecuteProcess.new(status.exitstatus, io_data[stdout], io_data[stderr]) end @@ -169,5 +190,16 @@ module Acceptance @exit_status == 0 end end + + # This exception is raised if the timeout for a process is exceeded. + class TimeoutExceeded < StandardError + attr_reader :pid + + def initialize(pid) + @pid = pid + + super() + end + end end