PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/test/unit.rb

http://github.com/ruby/ruby
Ruby | 681 lines | 634 code | 46 blank | 1 comment | 23 complexity | 6f6d8f433a2115c8a2e14c0c13f39f18 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0
  1. # test/unit compatibility layer using minitest.
  2. require 'minitest/unit'
  3. require 'test/unit/assertions'
  4. require 'test/unit/testcase'
  5. require 'optparse'
  6. module Test
  7. module Unit
  8. TEST_UNIT_IMPLEMENTATION = 'test/unit compatibility layer using minitest'
  9. module RunCount
  10. @@run_count = 0
  11. def self.have_run?
  12. @@run_count.nonzero?
  13. end
  14. def run(*)
  15. @@run_count += 1
  16. super
  17. end
  18. def run_once
  19. return if have_run?
  20. return if $! # don't run if there was an exception
  21. yield
  22. end
  23. module_function :run_once
  24. end
  25. module Options
  26. def initialize(*, &block)
  27. @init_hook = block
  28. @options = nil
  29. super(&nil)
  30. end
  31. def option_parser
  32. @option_parser ||= OptionParser.new
  33. end
  34. def process_args(args = [])
  35. return @options if @options
  36. orig_args = args.dup
  37. options = {}
  38. opts = option_parser
  39. setup_options(opts, options)
  40. opts.parse!(args)
  41. orig_args -= args
  42. args = @init_hook.call(args, options) if @init_hook
  43. non_options(args, options)
  44. @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
  45. @options = options
  46. if @options[:parallel]
  47. @files = args
  48. @args = orig_args
  49. end
  50. options
  51. end
  52. private
  53. def setup_options(opts, options)
  54. opts.separator 'minitest options:'
  55. opts.version = MiniTest::Unit::VERSION
  56. opts.on '-h', '--help', 'Display this help.' do
  57. puts opts
  58. exit
  59. end
  60. opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m|
  61. options[:seed] = m
  62. end
  63. opts.on '-v', '--verbose', "Verbose. Show progress processing files." do
  64. options[:verbose] = true
  65. self.verbose = options[:verbose]
  66. end
  67. opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a|
  68. options[:filter] = a
  69. end
  70. opts.on '--jobs-status [TYPE]', [:normal, :replace],
  71. "Show status of jobs every file; Disabled when --jobs isn't specified." do |type|
  72. options[:job_status] = type || :normal
  73. end
  74. opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a|
  75. if /^t/ =~ a
  76. options[:testing] = true # For testing
  77. options[:parallel] = a[1..-1].to_i
  78. else
  79. options[:parallel] = a.to_i
  80. end
  81. end
  82. opts.on '--separate', "Restart job process after one testcase has done" do
  83. options[:parallel] ||= 1
  84. options[:separate] = true
  85. end
  86. opts.on '--no-retry', "Don't retry running testcase when --jobs specified" do
  87. options[:no_retry] = true
  88. end
  89. opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a|
  90. options[:ruby] = a.split(/ /).reject(&:empty?)
  91. end
  92. opts.on '-q', '--hide-skip', 'Hide skipped tests' do
  93. options[:hide_skip] = true
  94. end
  95. end
  96. def non_options(files, options)
  97. begin
  98. require "rbconfig"
  99. rescue LoadError
  100. warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument"
  101. options[:parallel] = nil
  102. else
  103. options[:ruby] ||= [RbConfig.ruby]
  104. end
  105. true
  106. end
  107. end
  108. module GlobOption
  109. include Options
  110. @@testfile_prefix = "test"
  111. def setup_options(parser, options)
  112. super
  113. parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir|
  114. options[:base_directory] = dir
  115. end
  116. parser.on '-x', '--exclude PATTERN', 'Exclude test files on pattern.' do |pattern|
  117. (options[:reject] ||= []) << pattern
  118. end
  119. end
  120. def non_options(files, options)
  121. paths = [options.delete(:base_directory), nil].uniq
  122. if reject = options.delete(:reject)
  123. reject_pat = Regexp.union(reject.map {|r| /#{r}/ })
  124. end
  125. files.map! {|f|
  126. f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
  127. ((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix|
  128. if prefix
  129. path = f.empty? ? prefix : "#{prefix}/#{f}"
  130. else
  131. next if f.empty?
  132. path = f
  133. end
  134. if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty?
  135. if reject
  136. match.reject! {|n|
  137. n[(prefix.length+1)..-1] if prefix
  138. reject_pat =~ n
  139. }
  140. end
  141. break match
  142. elsif !reject or reject_pat !~ f and File.exist? path
  143. break path
  144. end
  145. end or
  146. raise ArgumentError, "file not found: #{f}"
  147. }
  148. files.flatten!
  149. super(files, options)
  150. end
  151. end
  152. module LoadPathOption
  153. include Options
  154. def setup_options(parser, options)
  155. super
  156. parser.on '-Idirectory', 'Add library load path' do |dirs|
  157. dirs.split(':').each { |d| $LOAD_PATH.unshift d }
  158. end
  159. end
  160. end
  161. module GCStressOption
  162. def setup_options(parser, options)
  163. super
  164. parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag|
  165. options[:gc_stress] = flag
  166. end
  167. end
  168. def non_options(files, options)
  169. if options.delete(:gc_stress)
  170. MiniTest::Unit::TestCase.class_eval do
  171. oldrun = instance_method(:run)
  172. define_method(:run) do |runner|
  173. begin
  174. gc_stress, GC.stress = GC.stress, true
  175. oldrun.bind(self).call(runner)
  176. ensure
  177. GC.stress = gc_stress
  178. end
  179. end
  180. end
  181. end
  182. super
  183. end
  184. end
  185. module RequireFiles
  186. def non_options(files, options)
  187. return false if !super
  188. result = false
  189. files.each {|f|
  190. d = File.dirname(path = File.expand_path(f))
  191. unless $:.include? d
  192. $: << d
  193. end
  194. begin
  195. require path unless options[:parallel]
  196. result = true
  197. rescue LoadError
  198. puts "#{f}: #{$!}"
  199. end
  200. }
  201. result
  202. end
  203. end
  204. class Runner < MiniTest::Unit
  205. include Test::Unit::Options
  206. include Test::Unit::GlobOption
  207. include Test::Unit::LoadPathOption
  208. include Test::Unit::GCStressOption
  209. include Test::Unit::RunCount
  210. class Worker
  211. def self.launch(ruby,args=[])
  212. io = IO.popen([*ruby,
  213. "#{File.dirname(__FILE__)}/unit/parallel.rb",
  214. *args], "rb+")
  215. new(io, io.pid, :waiting)
  216. end
  217. attr_reader :quit_called
  218. def initialize(io, pid, status)
  219. @io = io
  220. @pid = pid
  221. @status = status
  222. @file = nil
  223. @real_file = nil
  224. @loadpath = []
  225. @hooks = {}
  226. @quit_called = false
  227. end
  228. def puts(*args)
  229. @io.puts(*args)
  230. end
  231. def run(task,type)
  232. @file = File.basename(task, ".rb")
  233. @real_file = task
  234. begin
  235. puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}"
  236. @loadpath = $:.dup
  237. puts "run #{task} #{type}"
  238. @status = :prepare
  239. rescue Errno::EPIPE
  240. died
  241. rescue IOError
  242. raise unless ["stream closed","closed stream"].include? $!.message
  243. died
  244. end
  245. end
  246. def hook(id,&block)
  247. @hooks[id] ||= []
  248. @hooks[id] << block
  249. self
  250. end
  251. def read
  252. res = (@status == :quit) ? @io.read : @io.gets
  253. res && res.chomp
  254. end
  255. def close
  256. begin
  257. @io.close unless @io.closed?
  258. rescue IOError; end
  259. self
  260. end
  261. def quit
  262. return if @io.closed?
  263. @quit_called = true
  264. @io.puts "quit"
  265. @io.close
  266. end
  267. def died(*additional)
  268. @status = :quit
  269. @io.close
  270. call_hook(:dead,*additional)
  271. end
  272. def to_s
  273. if @file
  274. "#{@pid}=#{@file}"
  275. else
  276. "#{@pid}:#{@status.to_s.ljust(7)}"
  277. end
  278. end
  279. attr_reader :io, :pid
  280. attr_accessor :status, :file, :real_file, :loadpath
  281. private
  282. def call_hook(id,*additional)
  283. @hooks[id] ||= []
  284. @hooks[id].each{|hook| hook[self,additional] }
  285. self
  286. end
  287. end
  288. class << self; undef autorun; end
  289. @@stop_auto_run = false
  290. def self.autorun
  291. at_exit {
  292. Test::Unit::RunCount.run_once {
  293. exit(Test::Unit::Runner.new.run(ARGV) || true)
  294. } unless @@stop_auto_run
  295. } unless @@installed_at_exit
  296. @@installed_at_exit = true
  297. end
  298. def after_worker_down(worker, e=nil, c=false)
  299. return unless @options[:parallel]
  300. return if @interrupt
  301. if e
  302. b = e.backtrace
  303. warn "#{b.shift}: #{e.message} (#{e.class})"
  304. STDERR.print b.map{|s| "\tfrom #{s}"}.join("\n")
  305. end
  306. @need_quit = true
  307. warn ""
  308. warn "Some worker was crashed. It seems ruby interpreter's bug"
  309. warn "or, a bug of test/unit/parallel.rb. try again without -j"
  310. warn "option."
  311. warn ""
  312. STDERR.flush
  313. exit c
  314. end
  315. def jobs_status
  316. return unless @options[:job_status]
  317. puts "" unless @options[:verbose]
  318. status_line = @workers.map(&:to_s).join(" ")
  319. if @options[:job_status] == :replace and $stdout.tty?
  320. @terminal_width ||=
  321. begin
  322. require 'io/console'
  323. $stdout.winsize[1]
  324. rescue LoadError, NoMethodError
  325. ENV["COLUMNS"].to_i.nonzero? || 80
  326. end
  327. @jstr_size ||= 0
  328. del_jobs_status
  329. $stdout.flush
  330. print status_line[0...@terminal_width]
  331. $stdout.flush
  332. @jstr_size = [status_line.size, @terminal_width].min
  333. else
  334. puts status_line
  335. end
  336. end
  337. def del_jobs_status
  338. return unless @options[:job_status] == :replace && @jstr_size.nonzero?
  339. print "\r"+" "*@jstr_size+"\r"
  340. end
  341. def after_worker_quit(worker)
  342. return unless @options[:parallel]
  343. return if @interrupt
  344. @workers.delete(worker)
  345. @dead_workers << worker
  346. @ios = @workers.map(&:io)
  347. end
  348. def _run_parallel suites, type, result
  349. if @options[:parallel] < 1
  350. warn "Error: parameter of -j option should be greater than 0."
  351. return
  352. end
  353. begin
  354. # Require needed things for parallel running
  355. require 'thread'
  356. require 'timeout'
  357. @tasks = @files.dup # Array of filenames.
  358. @need_quit = false
  359. @dead_workers = [] # Array of dead workers.
  360. @warnings = []
  361. shutting_down = false
  362. rep = [] # FIXME: more good naming
  363. # Array of workers.
  364. launch_worker = Proc.new {
  365. worker = Worker.launch(@options[:ruby],@args)
  366. worker.hook(:dead) do |w,info|
  367. after_worker_quit w
  368. after_worker_down w, *info if !info.empty? && !worker.quit_called
  369. end
  370. worker
  371. }
  372. @workers = @options[:parallel].times.map(&launch_worker)
  373. # Thread: watchdog
  374. watchdog = Thread.new do
  375. while stat = Process.wait2
  376. break if @interrupt # Break when interrupt
  377. pid, stat = stat
  378. w = (@workers + @dead_workers).find{|x| pid == x.pid }
  379. next unless w
  380. w = w.dup
  381. if w.status != :quit && !w.quit_called?
  382. # Worker down
  383. w.died(nil, !stat.signaled? && stat.exitstatus)
  384. end
  385. end
  386. end
  387. @workers_hash = Hash[@workers.map {|w| [w.io,w] }] # out-IO => worker
  388. @ios = @workers.map{|w| w.io } # Array of worker IOs
  389. while _io = IO.select(@ios)[0]
  390. break unless _io.each do |io|
  391. break if @need_quit
  392. worker = @workers_hash[io]
  393. case worker.read
  394. when /^okay$/
  395. worker.status = :running
  396. jobs_status
  397. when /^ready(!?)$/
  398. bang = $1
  399. worker.status = :ready
  400. if @tasks.empty?
  401. unless @workers.find{|x| [:running, :prepare].include? x.status}
  402. break
  403. end
  404. else
  405. if @options[:separate] && bang.empty?
  406. @workers_hash.delete worker.io
  407. @workers.delete worker
  408. @ios.delete worker.io
  409. new_worker = launch_worker.call()
  410. worker.quit
  411. @workers << new_worker
  412. @ios << new_worker.io
  413. @workers_hash[new_worker.io] = new_worker
  414. worker = new_worker
  415. end
  416. worker.run(@tasks.shift, type)
  417. end
  418. jobs_status
  419. when /^done (.+?)$/
  420. r = Marshal.load($1.unpack("m")[0])
  421. result << r[0..1] unless r[0..1] == [nil,nil]
  422. rep << {file: worker.real_file,
  423. report: r[2], result: r[3], testcase: r[5]}
  424. $:.push(*r[4]).uniq!
  425. when /^p (.+?)$/
  426. del_jobs_status
  427. print $1.unpack("m")[0]
  428. jobs_status if @options[:job_status] == :replace
  429. when /^after (.+?)$/
  430. @warnings << Marshal.load($1.unpack("m")[0])
  431. when /^bye (.+?)$/
  432. after_worker_down worker, Marshal.load($1.unpack("m")[0])
  433. when /^bye$/
  434. if shutting_down || worker.quit_called
  435. after_worker_quit worker
  436. else
  437. after_worker_down worker
  438. end
  439. end
  440. break if @need_quit
  441. end
  442. end
  443. rescue Interrupt => e
  444. @interrupt = e
  445. return result
  446. ensure
  447. shutting_down = true
  448. watchdog.kill if watchdog
  449. if @interrupt
  450. @ios.select!{|x| @workers_hash[x].status == :running }
  451. while !@ios.empty? && (__io = IO.select(@ios,[],[],10))
  452. _io = __io[0]
  453. _io.each do |io|
  454. worker = @workers_hash[io]
  455. case worker.read
  456. when /^done (.+?)$/
  457. r = Marshal.load($1.unpack("m")[0])
  458. result << r[0..1] unless r[0..1] == [nil,nil]
  459. rep << {file: worker.real_file,
  460. report: r[2], result: r[3], testcase: r[5]}
  461. $:.push(*r[4]).uniq!
  462. @ios.delete(io)
  463. end
  464. end
  465. end
  466. end
  467. @workers.each do |worker|
  468. begin
  469. timeout(1) do
  470. worker.quit
  471. end
  472. rescue Errno::EPIPE
  473. rescue Timeout::Error
  474. end
  475. worker.close
  476. end
  477. begin
  478. timeout(0.2*@workers.size) do
  479. Process.waitall
  480. end
  481. rescue Timeout::Error
  482. @workers.each do |worker|
  483. begin
  484. Process.kill(:KILL,worker.pid)
  485. rescue Errno::ESRCH; end
  486. end
  487. end
  488. if @interrupt || @options[:no_retry] || @need_quit
  489. rep.each do |r|
  490. report.push(*r[:report])
  491. end
  492. @errors += rep.map{|x| x[:result][0] }.inject(:+)
  493. @failures += rep.map{|x| x[:result][1] }.inject(:+)
  494. @skips += rep.map{|x| x[:result][2] }.inject(:+)
  495. else
  496. puts ""
  497. puts "Retrying..."
  498. puts ""
  499. rep.each do |r|
  500. if r[:testcase] && r[:file] && !r[:report].empty?
  501. require r[:file]
  502. _run_suite(eval("::"+r[:testcase]),type)
  503. else
  504. report.push(*r[:report])
  505. @errors += r[:result][0]
  506. @failures += r[:result][1]
  507. @skips += r[:result][2]
  508. end
  509. end
  510. end
  511. if @warnings
  512. warn ""
  513. ary = []
  514. @warnings.reject! do |w|
  515. r = ary.include?(w[1].message)
  516. ary << w[1].message
  517. r
  518. end
  519. @warnings.each do |w|
  520. warn "#{w[0]}: #{w[1].message} (#{w[1].class})"
  521. end
  522. warn ""
  523. end
  524. end
  525. end
  526. def _run_suites suites, type
  527. @interrupt = nil
  528. result = []
  529. if @options[:parallel]
  530. _run_parallel suites, type, result
  531. else
  532. suites.each {|suite|
  533. begin
  534. result << _run_suite(suite, type)
  535. rescue Interrupt => e
  536. @interrupt = e
  537. break
  538. end
  539. }
  540. end
  541. report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip]
  542. report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \
  543. (r.start_with?("Failure:") ? 1 : 2) }
  544. result
  545. end
  546. # Overriding of MiniTest::Unit#puke
  547. def puke klass, meth, e
  548. # TODO:
  549. # this overriding is for minitest feature that skip messages are
  550. # hidden when not verbose (-v), note this is temporally.
  551. e = case e
  552. when MiniTest::Skip then
  553. @skips += 1
  554. "Skipped:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
  555. when MiniTest::Assertion then
  556. @failures += 1
  557. "Failure:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n"
  558. else
  559. @errors += 1
  560. bt = MiniTest::filter_backtrace(e.backtrace).join "\n "
  561. "Error:\n#{meth}(#{klass}):\n#{e.class}: #{e.message}\n #{bt}\n"
  562. end
  563. @report << e
  564. e[0, 1]
  565. end
  566. def status(*args)
  567. result = super
  568. raise @interrupt if @interrupt
  569. result
  570. end
  571. def run(*args)
  572. result = super
  573. puts "\nruby -v: #{RUBY_DESCRIPTION}"
  574. result
  575. end
  576. end
  577. class AutoRunner
  578. class Runner < Test::Unit::Runner
  579. include Test::Unit::RequireFiles
  580. end
  581. attr_accessor :to_run, :options
  582. def initialize(force_standalone = false, default_dir = nil, argv = ARGV)
  583. @runner = Runner.new do |files, options|
  584. options[:base_directory] ||= default_dir
  585. files << default_dir if files.empty? and default_dir
  586. @to_run = files
  587. yield self if block_given?
  588. files
  589. end
  590. Runner.runner = @runner
  591. @options = @runner.option_parser
  592. @argv = argv
  593. end
  594. def process_args(*args)
  595. @runner.process_args(*args)
  596. !@to_run.empty?
  597. end
  598. def run
  599. @runner.run(@argv) || true
  600. end
  601. def self.run(*args)
  602. new(*args).run
  603. end
  604. end
  605. end
  606. end
  607. Test::Unit::Runner.autorun