Report number of passed and failed tests
[voodoo-lang.git] / test / test.rb
blobba49e83ee047596cd5b0956b5f0e64172b1741bf
1 #### Common functionality for running tests.
3 # Make sure that the lib directory is in $LOAD_PATH
4 $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6 require 'thread'
8 class AtomicInteger
9   def initialize initial_value = 0
10     @value = 0
11     @mutex = Mutex.new
12   end
14   def increment x = 1
15     @mutex.synchronize { @value = @value + x }
16   end
18   def value
19     @mutex.synchronize { @value }
20   end
22   def value= x
23     @mutex.synchronize { @value = x }
24   end
26   def to_i
27     value
28   end
30   def to_s
31     value.to_s
32   end
33 end
35 $failed = AtomicInteger.new
36 $passed = AtomicInteger.new
37 $tests = Queue.new
39 # Fails a test.
40 # +reason+ Should be a short message describing the reason for failure.
41 # +stderr+ If not empty, this text is written to standard error.
42 def fail_test reason, stderr = ''
43   $failed.increment
44   puts "FAIL: #{reason}"
45   $stdout.flush
46   unless stderr.empty?
47     $stderr.puts stderr
48     $stderr.flush
49   end
50   false
51 end
53 # Passes a test.
54 def pass_test
55   $passed.increment
56   puts "pass"
57   $stdout.flush
58   true
59 end
61 # Runs block and verifies that it returns the given value.
62 # If it doesn't, prints an error message and increments $failed.
63 def expect name, value, &block
64   begin
65     print "#{name}..."
66     result = yield
67     if result == value
68       pass_test
69     else
70       fail_test "expected #{value.inspect}, but got #{result.inspect}"
71     end
72   rescue Exception => e
73     fail_test "Unexpected #{e.class}\n#{e.message}"
74   end
75 end
77 # Runs block and verifies that it returns true.
78 # If it doesn't, prints an error message and increments $failed.
79 def expect_true name, &block
80   expect name, true, &block
81 end
83 # Runs block and verifies that it throws an exception of type
84 # +exception_type+. If the block doesn't throw such an exception,
85 # prints an error message and increments $failed.
86 def expect_exception name, exception_type, &block
87   begin
88     print "#{name}..."
89     yield
90     fail_test "Expected exception of type #{exception_type}," +
91       " but no exception was raised"
92   rescue Exception => e
93     if e.kind_of? exception_type
94       pass_test
95     else
96       fail_test "Expected #{exception_type}, but got #{e.class}"
97     end
98   end
99 end
101 $RUBY = ENV['RUBY'] || 'ruby'
102 $VOODOOC = 'env RUBYLIB=../lib ../bin/voodooc'
104 # Runs a command with exec.
105 # With no block given, returns
106 # [status, stdin, stdout, stderr]
107 # With block given, passes
108 # status, stdin, stdout, stderr to block
109 def popen4 *command
110   pw = IO::pipe   # pipe[0] for read, pipe[1] for write
111   pr = IO::pipe
112   pe = IO::pipe
113   
114   pid = fork do
115     # child
116     pw[1].close
117     STDIN.reopen(pw[0])
118     pw[0].close
119     
120     pr[0].close
121     STDOUT.reopen(pr[1])
122     pr[1].close
123     
124     pe[0].close
125     STDERR.reopen(pe[1])
126     pe[1].close
127     
128     exec *command
129   end
131   # parent
132   pw[0].close
133   pr[1].close
134   pe[1].close
135   dummy, status = Process.wait2 pid
136   result = [status, pw[1], pr[0], pe[0]]
137   pw[1].sync = true
138   if block_given?
139     begin
140       return yield(*result)
141     ensure
142       [pw[1], pr[0], pe[0]].each { |p| p.close unless p.closed? }
143     end
144   end
145   result
148 def add_test program, command, expected_output = '', params = {}
149   $tests << [program, command, expected_output, params]
152 def run_test program, command, expected_output = '', params = {}
153   input = params.has_key?(:input) ? params[:input] : nil
154   expected_status = params.has_key?(:expected_status) ?
155     params[:expected_status] : 0
156   expected_errors = params.has_key?(:expected_errors) ?
157     params[:expected_errors] : ''
158   expected_signal = params.has_key?(:expected_signal) ?
159     params[:expected_signal] : nil
161   message = "#{program}..."
162   status, stdin, stdout, stderr = popen4 command
163   if input
164     stdin.write input
165   end
166   exitstatus = status.exited? ? status.exitstatus : (status.to_i >> 8)
167   signal = status.signaled? ? status.termsig : nil
168   output = stdout.read
169   err_output = stderr.read
170   if expected_signal && signal != expected_signal
171     message << "FAIL: expected signal #{expected_signal} but got "
172     if signal
173       message << "signal #{signal}"
174     else
175       message << "no signal"
176     end
177     increment_errors
178   elsif !expected_signal && signal
179     message << "FAIL: got signal #{signal} while not expecting a signal"
180     increment_errors
181   elsif exitstatus != expected_status
182     message << "FAIL: exit status is #{exitstatus}, expected #{expected_status}"
183     increment_errors
184   elsif output != expected_output
185     message << "FAIL: wrong output"
186     increment_errors
187   elsif err_output != expected_errors
188     message << "FAIL: wrong error output"
189     $stderr.puts "--- ERRORS ---\n#{err_output}\n--- EXPECTED ---\n#{expected_errors}\n--- END ERRORS ---"
190     increment_errors
191   else
192     message << 'pass'
193   end
194   puts message
197 def add_test2 program, expected_output
198   add_test program, "./#{program}", expected_output
201 def add_test1 program
202   add_test2 program, `cat #{program}.out`
205 # Reports tests results.
206 # Returns 0 if all tests passed, 1 if some failed.
207 def report_test_results
208   puts "#{$passed.value} tests passed, #{$failed.value} tests failed"
209   if $failed.value == 0
210     exit 0
211   else
212     exit 1
213   end
216 # Runs all tests in the given queue.
217 # Each test is described by a list
218 # [name, command, expected_output, params].
219 # Those parameters are passed to run_test.
220 def run_tests queue, nthreads = 1
221   threads = []
222   nthreads.times do
223     threads << Thread.new do
224       until queue.empty?
225         name, command, output, params = queue.pop
226         run_test name, command, output, params
227       end
228     end
229   end
230   threads.each { |t| t.join }