* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / bootstraptest / runner.rb
blob080e7b111ed8d2ee0ab4d2ac6cd394a3497e4d93
1 # $Id$
3 # NOTE:
4 # Never use optparse in this file.
5 # Never use test/unit in this file.
6 # Never use Ruby extensions in this file.
8 begin
9   require 'fileutils'
10   require 'tmpdir'
11 rescue LoadError
12   $:.unshift File.join(File.dirname(__FILE__), '../lib')
13   retry
14 end
16 if !Dir.respond_to?(:mktmpdir)
17   # copied from lib/tmpdir.rb
18   def Dir.mktmpdir(prefix="d", tmpdir=nil)
19     tmpdir ||= Dir.tmpdir
20     t = Time.now.strftime("%Y%m%d")
21     n = nil
22     begin
23       path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
24       path << "-#{n}" if n
25       Dir.mkdir(path, 0700)
26     rescue Errno::EEXIST
27       n ||= 0
28       n += 1
29       retry
30     end
32     if block_given?
33       begin
34         yield path
35       ensure
36         FileUtils.remove_entry_secure path
37       end
38     else
39       path
40     end
41   end
42 end
44 def main
45   @ruby = File.expand_path('miniruby')
46   @verbose = false
47   dir = nil
48   quiet = false
49   tests = nil
50   ARGV.delete_if {|arg|
51     case arg
52     when /\A--ruby=(.*)/
53       @ruby = $1
54       @ruby.gsub!(/^([^ ]*)/){File.expand_path($1)}
55       @ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)}
56       @ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)}
57       true
58     when /\A--sets=(.*)/
59       tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb")
60       puts tests.map {|path| File.basename(path) }.inspect
61       true
62     when /\A--dir=(.*)/
63       dir = $1
64       true
65     when /\A(--stress|-s)/
66       $stress = true
67     when /\A(-q|--q(uiet))\z/
68       quiet = true
69       true
70     when /\A(-v|--v(erbose))\z/
71       @verbose = true
72     when /\A(-h|--h(elp)?)\z/
73       puts(<<-End)
74 Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...]
75         --sets=NAME,NAME,...        Name of test sets.
76         --dir=DIRECTORY             Working directory.
77                                     default: /tmp/bootstraptest.tmpwd
78     -s, --stress                    stress test.
79     -v, --verbose                   Output test name before exec.
80     -q, --quiet                     Don\'t print header message.
81     -h, --help                      Print this message and quit.
82 End
83       exit true
84     else
85       false
86     end
87   }
88   if tests and not ARGV.empty?
89     $stderr.puts "--tests and arguments are exclusive"
90     exit false
91   end
92   tests ||= ARGV
93   tests = Dir.glob("#{File.dirname($0)}/test_*.rb") if tests.empty?
94   pathes = tests.map {|path| File.expand_path(path) }
96   unless quiet
97     puts Time.now
98     patchlevel = defined?(RUBY_PATCHLEVEL) ? " patchlevel #{RUBY_PATCHLEVEL}" : ''
99     puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}#{patchlevel}) [#{RUBY_PLATFORM}]"
100     puts "Target is #{`#{@ruby} -v`.chomp}"
101     puts
102     $stdout.flush
103   end
105   in_temporary_working_directory(dir) {
106     exec_test pathes
107   }
110 def exec_test(pathes)
111   @count = 0
112   @error = 0
113   @errbuf = []
114   @location = nil
115   pathes.each do |path|
116     $stderr.print "\n#{File.basename(path)} "
117     load File.expand_path(path)
118   end
119   $stderr.puts
120   if @error == 0
121     $stderr.puts "PASS #{@count} tests"
122     exit true
123   else
124     @errbuf.each do |msg|
125       $stderr.puts msg
126     end
127     $stderr.puts "FAIL #{@error}/#{@count} tests failed"
128     exit false
129   end
132 def assert_check(testsrc, message = '', opt = '')
133   $stderr.puts "\##{@count} #{@location}" if @verbose
134   result = get_result_string(testsrc, opt)
135   check_coredump
136   faildesc = yield(result)
137   if !faildesc
138     $stderr.print '.'
139   else
140     $stderr.print 'F'
141     error faildesc, message
142   end
143 rescue Exception => err
144   $stderr.print 'E'
145   error err.message, message
148 def assert_equal(expected, testsrc, message = '')
149   newtest
150   assert_check(testsrc, message) {|result|
151     if expected == result
152       nil
153     else
154       desc = "#{result.inspect} (expected #{expected.inspect})"
155       pretty(testsrc, desc, result)
156     end
157   }
160 def assert_match(expected_pattern, testsrc, message = '')
161   newtest
162   assert_check(testsrc, message) {|result|
163     if expected_pattern =~ result
164       nil
165     else
166       desc = "#{expected_pattern.inspect} expected to be =~\n#{result.inspect}"
167       pretty(testsrc, desc, result)
168     end
169   }
172 def assert_not_match(unexpected_pattern, testsrc, message = '')
173   newtest
174   assert_check(testsrc, message) {|result|
175     if unexpected_pattern !~ result
176       nil
177     else
178       desc = "#{unexpected_pattern.inspect} expected to be !~\n#{result.inspect}"
179       pretty(testsrc, desc, result)
180     end
181   }
184 def assert_valid_syntax(testsrc, message = '')
185   newtest
186   assert_check(testsrc, message, '-c') {|result|
187     result if /Syntax OK/ !~ result
188   }
191 def assert_normal_exit(testsrc, message = '', ignore_signals = nil)
192   newtest
193   $stderr.puts "\##{@count} #{@location}" if @verbose
194   faildesc = nil
195   filename = make_srcfile(testsrc)
196   old_stderr = $stderr.dup
197   begin
198     $stderr.reopen("assert_normal_exit_stderr.log", "w")
199     `#{@ruby} -W0 #{filename}`
200     status = $?
201   ensure
202     $stderr.reopen(old_stderr)
203     old_stderr.close
204   end
205   if status.signaled?
206     signo = status.termsig
207     signame = Signal.list.invert[signo]
208     unless ignore_signals and ignore_signals.include?(signame)
209       sigdesc = "signal #{signo}"
210       if signame
211         sigdesc = "SIG#{signame} (#{sigdesc})"
212       end
213       faildesc = pretty(testsrc, "killed by #{sigdesc}", nil)
214       stderr_log = File.read("assert_normal_exit_stderr.log")
215       if !stderr_log.empty?
216         faildesc << "\n" if /\n\z/ !~ faildesc
217         stderr_log << "\n" if /\n\z/ !~ stderr_log
218         stderr_log.gsub!(/^.*\n/) { '| ' + $& }
219         faildesc << stderr_log
220       end
221     end
222   end
223   if !faildesc
224     $stderr.print '.'
225     true
226   else
227     $stderr.print 'F'
228     error faildesc, message
229     false
230   end
231 rescue Exception => err
232   $stderr.print 'E'
233   error err.message, message
234   false
237 def assert_finish(timeout_seconds, testsrc, message = '')
238   newtest
239   $stderr.puts "\##{@count} #{@location}" if @verbose
240   faildesc = nil
241   filename = make_srcfile(testsrc)
242   io = IO.popen("#{@ruby} -W0 #{filename}")
243   pid = io.pid
244   waited = false
245   tlimit = Time.now + timeout_seconds
246   while Time.now < tlimit
247     if Process.waitpid pid, Process::WNOHANG
248       waited = true
249       break
250     end
251     sleep 0.1
252   end
253   if !waited
254     Process.kill(:KILL, pid)
255     Process.waitpid pid
256     faildesc = pretty(testsrc, "not finished in #{timeout_seconds} seconds", nil)
257   end
258   io.close
259   if !faildesc
260     $stderr.print '.'
261   else
262     $stderr.print 'F'
263     error faildesc, message
264   end
265 rescue Exception => err
266   $stderr.print 'E'
267   error err.message, message
270 def flunk(message = '')
271   newtest
272   $stderr.print 'F'
273   error message, ''
276 def pretty(src, desc, result)
277   src = src.sub(/\A.*\n/, '')
278   (/\n/ =~ src ? "\n#{adjust_indent(src)}" : src) + "  #=> #{desc}"
281 INDENT = 27
283 def adjust_indent(src)
284   untabify(src).gsub(/^ {#{INDENT}}/o, '').gsub(/^/, '   ')
287 def untabify(str)
288   str.gsub(/^\t+/) {' ' * (8 * $&.size) }
291 def make_srcfile(src)
292   filename = 'bootstraptest.tmp.rb'
293   File.open(filename, 'w') {|f|
294     f.puts "GC.stress = true" if $stress
295     f.puts "print(begin; #{src}; end)"
296   }
297   filename
300 def get_result_string(src, opt = '')
301   if @ruby
302     filename = make_srcfile(src)
303     begin
304       `#{@ruby} -W0 #{opt} #{filename}`
305     ensure
306       raise CoreDumpError, "core dumped" if $? and $?.coredump?
307     end
308   else
309     eval(src).to_s
310   end
313 def newtest
314   @location = File.basename(caller(2).first)
315   @count += 1
316   cleanup_coredump
319 def error(msg, additional_message)
320   @errbuf.push "\##{@count} #{@location}: #{msg}  #{additional_message}"
321   @error += 1
324 def in_temporary_working_directory(dir)
325   if dir
326     Dir.mkdir dir
327     Dir.chdir(dir) {
328       yield
329     }
330   else
331     Dir.mktmpdir("bootstraptest.tmpwd") {|d|
332       Dir.chdir(d) {
333         yield
334       }
335     }
336   end
339 def cleanup_coredump
340   FileUtils.rm_f 'core'
341   FileUtils.rm_f Dir.glob('core.*')
342   FileUtils.rm_f @ruby+'.stackdump' if @ruby
345 class CoreDumpError < StandardError; end
347 def check_coredump
348   if File.file?('core') or not Dir.glob('core.*').empty? or
349       (@ruby and File.exist?(@ruby+'.stackdump'))
350     raise CoreDumpError, "core dumped"
351   end
354 main