require'parallel'require'parallel_tests/grouper'require'parallel_tests/railtie'classParallelTestsVERSION=File.read(File.join(File.dirname(__FILE__),'..','VERSION')).strip# parallel:spec[:count, :pattern, :options]defself.parse_rake_args(args)# order as given by userargs=[args[:count],args[:pattern],args[:options]]# count given or empty ?# parallel:spec[2,models,options]# parallel:spec[,models,options]count=args.shiftifargs.first.to_s=~/^\d*$/num_processes=count.to_iunlesscount.to_s.empty?num_processes||=ENV['PARALLEL_TEST_PROCESSORS'].to_iifENV['PARALLEL_TEST_PROCESSORS']num_processes||=Parallel.processor_countpattern=args.shiftoptions=args.shift[num_processes.to_i,pattern.to_s,options.to_s]end# finds all tests and partitions them into groupsdefself.tests_in_groups(root,num_groups,options={})tests=find_tests(root,options)ifoptions[:no_sort]==trueGrouper.in_groups(tests,num_groups)elsetests=with_runtime_info(tests)Grouper.in_even_groups_by_size(tests,num_groups,options)endenddefself.run_tests(test_files,process_number,options)require_list=test_files.map{|filename|%{"#{File.expand_pathfilename}"}}.join(",")cmd="ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"execute_command(cmd,process_number,options)enddefself.execute_command(cmd,process_number,options)cmd="TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"f=open("|#{cmd}",'r')output=fetch_output(f,options)f.close{:stdout=>output,:exit_status=>$?.exitstatus}enddefself.find_results(test_output)test_output.split("\n").map{|line|line=line.gsub(/\.|F|\*/,'')nextunlessline_is_result?(line)line}.compactenddefself.test_env_number(process_number)process_number==0?'':process_number+1enddefself.runtime_log'tmp/parallel_runtime_test.log'enddefself.summarize_results(results)results=results.join(' ').gsub(/s\b/,'')# combine and singularize resultscounts=results.scan(/(\d+) (\w+)/)sums=counts.inject(Hash.new(0))do|sum,(number,word)|sum[word]+=number.to_isumendsums.sort.map{|word,number|"#{number}#{word}#{'s'ifnumber!=1}"}.join(', ')endprotected# read output of the process and print in in chucksdefself.fetch_output(process,options)all=''buffer=''timeout=options[:chunk_timeout]||0.2flushed=Time.now.to_fwhilechar=process.getcchar=(char.is_a?(Fixnum)?char.chr:char)# 1.8 <-> 1.9all<<char# print in chunks so large blocks stay togethernow=Time.now.to_fbuffer<<charifflushed+timeout<nowprintbufferSTDOUT.flushbuffer=''flushed=nowendend# print the remainderprintbufferSTDOUT.flushallend# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfiledefself.bundler_enabled?returntrueifObject.const_defined?(:Bundler)previous=nilcurrent=File.expand_path(Dir.pwd)until!File.directory?(current)||current==previousfilename=File.join(current,"Gemfile")returntrueifFile.exists?(filename)current,previous=File.expand_path("..",current),currentendfalseenddefself.line_is_result?(line)line=~/\d+ failure/enddefself.test_suffix"_test.rb"enddefself.with_runtime_info(tests)lines=File.read(runtime_log).split("\n")rescue[]# use recorded test runtime if we got enough dataiflines.size*1.5>tests.sizeputs"Using recorded test runtime"times=Hash.new(1)lines.eachdo|line|test,time=line.split(":")nextunlesstestandtimetimes[File.expand_path(test)]=time.to_fendtests.sort.map{|test|[test,times[test]]}else# use file sizestests.sort.map{|test|[test,File.stat(test).size]}endenddefself.find_tests(root,options={})ifroot.is_a?(Array)rootelse# follow one symlink and direct children# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-globfiles=Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniqfiles=files.map{|f|f.sub(root+'/','')}files=files.grep(/#{options[:pattern]}/)files.map{|f|"#{root}/#{f}"}endendend