parallel_tests 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/README.markdown +119 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/bin/parallel_cucumber +2 -0
- data/bin/parallel_spec +2 -0
- data/bin/parallel_test +74 -0
- data/lib/parallel_cucumber.rb +33 -0
- data/lib/parallel_specs.rb +27 -0
- data/lib/parallel_specs/spec_runtime_logger.rb +49 -0
- data/lib/parallel_tests.rb +119 -0
- data/parallel_tests.gemspec +61 -0
- data/spec/integration_spec.rb +83 -0
- data/spec/parallel_cucumber_spec.rb +101 -0
- data/spec/parallel_specs_spec.rb +134 -0
- data/spec/parallel_tests_spec.rb +130 -0
- data/spec/spec_helper.rb +78 -0
- data/tasks/parallel_specs.rake +56 -0
- metadata +85 -0
    
        data/.gitignore
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            *.sh
         | 
    
        data/README.markdown
    ADDED
    
    | @@ -0,0 +1,119 @@ | |
| 1 | 
            +
            Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Setup for Rails
         | 
| 4 | 
            +
            ===============
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                sudo gem install parallel
         | 
| 7 | 
            +
                script/plugin install git://github.com/grosser/parallel_tests.git
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ### 1: Add to `config/database.yml`
         | 
| 10 | 
            +
                test:
         | 
| 11 | 
            +
                  database: xxx_test<%= ENV['TEST_ENV_NUMBER'] %>
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ### 2: Create additional database(s)
         | 
| 14 | 
            +
                script/db_console
         | 
| 15 | 
            +
                create database xxx_test2;
         | 
| 16 | 
            +
                ...
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ### 3: Copy development schema (repeat after migrations)
         | 
| 19 | 
            +
                rake parallel:prepare
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ### 4: Run!
         | 
| 22 | 
            +
                rake parallel:test          # Test::Unit
         | 
| 23 | 
            +
                rake parallel:spec          # RSpec
         | 
| 24 | 
            +
                rake parallel:features      # Cucumber
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                rake parallel:test[1] --> force 1 CPU --> 86 seconds
         | 
| 27 | 
            +
                rake parallel:test    --> got 2 CPUs? --> 47 seconds
         | 
| 28 | 
            +
                rake parallel:test    --> got 4 CPUs? --> 26 seconds
         | 
| 29 | 
            +
                ...
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Test just a subfolder (e.g. use one integration server per subfolder)
         | 
| 32 | 
            +
                rake parallel:test[models]
         | 
| 33 | 
            +
                rake parallel:test[something/else]
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                partial paths are OK too...
         | 
| 36 | 
            +
                rake parallel:test[functional] == rake parallel:test[fun]
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Example output
         | 
| 39 | 
            +
            --------------
         | 
| 40 | 
            +
                2 processes for 210 specs, ~ 105 specs per process
         | 
| 41 | 
            +
                ... test output ...
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                Results:
         | 
| 44 | 
            +
                877 examples, 0 failures, 11 pending
         | 
| 45 | 
            +
                843 examples, 0 failures, 1 pending
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                Took 29.925333 seconds
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            Even process runtimes (for specs only atm)
         | 
| 50 | 
            +
            -----------------
         | 
| 51 | 
            +
            Add to your `spec/parallel_specs.opts` (or `spec/spec.opts`) :
         | 
| 52 | 
            +
                --format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
         | 
| 53 | 
            +
            It will log test runtime and partition the test-load accordingly.
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            Setup for non-rails
         | 
| 56 | 
            +
            ===================
         | 
| 57 | 
            +
                sudo gem install parallel_tests
         | 
| 58 | 
            +
                # go to your project dir
         | 
| 59 | 
            +
                parallel_test OR parallel_spec OR parallel_cucumber
         | 
| 60 | 
            +
                # [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests for separate db/resources/etc.
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            Options are:
         | 
| 63 | 
            +
                -n [PROCESSES]                   How many processes to use, default: available CPUs
         | 
| 64 | 
            +
                -p, --path [PATH]                run tests inside this path only
         | 
| 65 | 
            +
                -r, --root [PATH]                execute test commands from this path
         | 
| 66 | 
            +
                -e, --exec [COMMAND]             execute this code parallel and with ENV['TEST_ENV_NUM']
         | 
| 67 | 
            +
                -o, --test-options [SOMETHING]   execute test commands with those options
         | 
| 68 | 
            +
                -t, --type [TYPE]                which type of tests to run? test, spec or features
         | 
| 69 | 
            +
                -v, --version                    Show Version
         | 
| 70 | 
            +
                -h, --help                       Show this.
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            You can run any kind of code with -e / --execute
         | 
| 73 | 
            +
                parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
         | 
| 74 | 
            +
                hello from process "2"
         | 
| 75 | 
            +
                hello from process ""
         | 
| 76 | 
            +
                hello from process "3"
         | 
| 77 | 
            +
                hello from process "5"
         | 
| 78 | 
            +
                hello from process "4"
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            <table>
         | 
| 81 | 
            +
            <tr><td></td><td>1 Process</td><td>2 Processes</td><td>4 Processes</td></tr>
         | 
| 82 | 
            +
            <tr><td>RSpec spec-suite</td><td>18</td><td>14</td><td>10</td></tr>
         | 
| 83 | 
            +
            <tr><td>Rails-ActionPack</td><td>88</td><td>53</td><td>44</td></tr>
         | 
| 84 | 
            +
            </table>
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            TIPS
         | 
| 87 | 
            +
            ====
         | 
| 88 | 
            +
             - [RSpec] add a `spec/parallel_spec.opts` to use different options, e.g. no --drb (default: `spec/spec.opts`) 
         | 
| 89 | 
            +
             - [RSpec] if something looks fishy try to delete `script/spec`
         | 
| 90 | 
            +
             - [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
         | 
| 91 | 
            +
             - [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
         | 
| 92 | 
            +
             - [RSpec] `./script/generate rspec` if you are running rspec from gems (this plugin uses script/spec which may fail if rspec files are outdated)
         | 
| 93 | 
            +
             - [Bundler] if you have a `.bundle/environment.rb` then `bundle exec xxx` will be used to run tests 
         | 
| 94 | 
            +
             - with zsh this would be `rake "parallel:prepare[3]"`
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            TODO
         | 
| 97 | 
            +
            ====
         | 
| 98 | 
            +
             - build parallel:bootstrap [idea/basics](http://github.com/garnierjm/parallel_specs/commit/dd8005a2639923dc5adc6400551c4dd4de82bf9a)
         | 
| 99 | 
            +
             - make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
         | 
| 100 | 
            +
             - make windows compatible (does anyone care ?)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            Authors
         | 
| 103 | 
            +
            ====
         | 
| 104 | 
            +
            inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-parallelize-your-rspec-suite)  
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ###Contributors (alphabetical)
         | 
| 107 | 
            +
             - [Charles Finkel](http://charlesfinkel.com/)
         | 
| 108 | 
            +
             - [Jason Morrison](http://jayunit.net)
         | 
| 109 | 
            +
             - [Joakim Kolsjö](http://www.rubyblocks.se)
         | 
| 110 | 
            +
             - [Kpumuk](http://kpumuk.info/)
         | 
| 111 | 
            +
             - [Maksim Horbu](http://github.com/mhorbul)
         | 
| 112 | 
            +
             - [Rohan Deshpande](http://github.com/rdeshpande)
         | 
| 113 | 
            +
             - [Tchandy](http://thiagopradi.net/)
         | 
| 114 | 
            +
             - [Terence Lee](http://hone.heroku.com/)
         | 
| 115 | 
            +
             - [Will Bryant](http://willbryant.net/)
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            [Michael Grosser](http://pragmatig.wordpress.com)  
         | 
| 118 | 
            +
            grosser.michael@gmail.com  
         | 
| 119 | 
            +
            Hereby placed under public domain, do what you want, just do not hold me accountable...
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            task :default => :spec
         | 
| 2 | 
            +
            require 'spec/rake/spectask'
         | 
| 3 | 
            +
            Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            begin
         | 
| 6 | 
            +
              require 'jeweler'
         | 
| 7 | 
            +
              project_name = 'parallel_tests'
         | 
| 8 | 
            +
              Jeweler::Tasks.new do |gem|
         | 
| 9 | 
            +
                gem.name = project_name
         | 
| 10 | 
            +
                gem.summary = "Run tests / specs / features in parallel"
         | 
| 11 | 
            +
                gem.email = "grosser.michael@gmail.com"
         | 
| 12 | 
            +
                gem.homepage = "http://github.com/grosser/#{project_name}"
         | 
| 13 | 
            +
                gem.authors = ["Michael Grosser"]
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              Jeweler::GemcutterTasks.new
         | 
| 17 | 
            +
            rescue LoadError
         | 
| 18 | 
            +
              puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
         | 
| 19 | 
            +
            end
         | 
    
        data/VERSION
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            0.3.0
         | 
    
        data/bin/parallel_spec
    ADDED
    
    
    
        data/bin/parallel_test
    ADDED
    
    | @@ -0,0 +1,74 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            require 'rubygems'
         | 
| 3 | 
            +
            require 'optparse'
         | 
| 4 | 
            +
            lib_folder = File.join(File.dirname(__FILE__), '..', 'lib')
         | 
| 5 | 
            +
            require File.join(lib_folder, "parallel_tests")
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            options = {}
         | 
| 8 | 
            +
            OptionParser.new do |opts|
         | 
| 9 | 
            +
              opts.banner = <<BANNER
         | 
| 10 | 
            +
            Run tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Options are:
         | 
| 13 | 
            +
            BANNER
         | 
| 14 | 
            +
              opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs"){|n| options[:count] = n }
         | 
| 15 | 
            +
              opts.on("-p", '--path [PATH]', "run tests inside this path only"){|path| options[:path_prefix] = path }
         | 
| 16 | 
            +
              opts.on("-r", '--root [PATH]', "execute test commands from this path"){|path| options[:root] = path }
         | 
| 17 | 
            +
              opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
         | 
| 18 | 
            +
              opts.on("-o", '--test-options [SOMETHING]', "execute test commands with those options"){|arg| options[:test_options] = arg }
         | 
| 19 | 
            +
              opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
         | 
| 20 | 
            +
              opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
         | 
| 21 | 
            +
              opts.on("-h", "--help", "Show this.") { puts opts; exit }
         | 
| 22 | 
            +
            end.parse!
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            require 'parallel'
         | 
| 25 | 
            +
            num_processes = options[:count] || Parallel.processor_count
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            if options[:execute]
         | 
| 28 | 
            +
              require File.join(lib_folder, "parallel_tests")
         | 
| 29 | 
            +
              Parallel.in_processes(num_processes) do |i|
         | 
| 30 | 
            +
                ParallelTests.execute_command(options[:execute], i)
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            else
         | 
| 33 | 
            +
              lib, name, task = {
         | 
| 34 | 
            +
                'test' => ["tests", "test", "test"],
         | 
| 35 | 
            +
                'spec' => ["specs", "spec", "spec"],
         | 
| 36 | 
            +
                'features' => ["cucumber", "feature", "features"]
         | 
| 37 | 
            +
              }[options[:type]||'test']
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              require File.join(lib_folder, "parallel_#{lib}")
         | 
| 40 | 
            +
              klass = eval("Parallel#{lib.capitalize}")
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              start = Time.now
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              tests_folder = File.join(task, options[:path_prefix].to_s)
         | 
| 45 | 
            +
              tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              groups = klass.tests_in_groups(tests_folder, num_processes)
         | 
| 48 | 
            +
              num_processes = groups.size
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              #adjust processes to groups
         | 
| 51 | 
            +
              abort "no #{name}s found!" if groups.size == 0
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              num_tests = groups.inject(0){|sum,item| sum + item.size }
         | 
| 54 | 
            +
              puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              output = Parallel.map(groups, :in_processes => num_processes) do |group|
         | 
| 57 | 
            +
                klass.run_tests(group, groups.index(group), options[:test_options])
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              #parse and print results
         | 
| 61 | 
            +
              results = klass.find_results(output*"")
         | 
| 62 | 
            +
              puts ""
         | 
| 63 | 
            +
              puts "Results:"
         | 
| 64 | 
            +
              results.each{|r| puts r}
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              #report total time taken
         | 
| 67 | 
            +
              puts ""
         | 
| 68 | 
            +
              puts "Took #{Time.now - start} seconds"
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              #exit with correct status code
         | 
| 71 | 
            +
              # - rake parallel:test && echo 123 ==> 123 should not show up when test failed
         | 
| 72 | 
            +
              # - rake parallel:test db:reset ==> works when tests succeed
         | 
| 73 | 
            +
              abort "#{name.capitalize}s Failed" if klass.failed?(results)
         | 
| 74 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), 'parallel_tests')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ParallelCucumber < ParallelTests
         | 
| 4 | 
            +
              def self.run_tests(test_files, process_number, options)
         | 
| 5 | 
            +
                color = ($stdout.tty? ? 'export AUTOTEST=1 ;' : '')#display color when we are in a terminal
         | 
| 6 | 
            +
                cmd = "export RAILS_ENV=test ; #{color} #{executable} #{options} #{test_files*' '}"
         | 
| 7 | 
            +
                execute_command(cmd, process_number)
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def self.executable
         | 
| 11 | 
            +
                if File.file?(".bundle/environment.rb")
         | 
| 12 | 
            +
                  "bundle exec cucumber"
         | 
| 13 | 
            +
                elsif File.file?("script/cucumber")
         | 
| 14 | 
            +
                  "script/cucumber"
         | 
| 15 | 
            +
                else
         | 
| 16 | 
            +
                  "cucumber"
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              protected
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def self.line_is_result?(line)
         | 
| 23 | 
            +
                line =~ /^\d+ (steps|scenarios)/
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
              
         | 
| 26 | 
            +
              def self.line_is_failure?(line)
         | 
| 27 | 
            +
                line =~ /^\d+ (steps|scenarios).*(\d{2,}|[1-9]) failed/
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def self.find_tests(root)
         | 
| 31 | 
            +
                Dir["#{root}**/**/*.feature"]
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), 'parallel_tests')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ParallelSpecs < ParallelTests
         | 
| 4 | 
            +
              def self.run_tests(test_files, process_number, options)
         | 
| 5 | 
            +
                spec_opts = ['spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
         | 
| 6 | 
            +
                spec_opts = (spec_opts ? "-O #{spec_opts}" : nil)
         | 
| 7 | 
            +
                color = ($stdout.tty? ? 'export RSPEC_COLOR=1 ;' : '')#display color when we are in a terminal
         | 
| 8 | 
            +
                cmd = "export RAILS_ENV=test ; #{color} #{executable} #{options} #{spec_opts} #{test_files*' '}"
         | 
| 9 | 
            +
                execute_command(cmd, process_number)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def self.executable
         | 
| 13 | 
            +
                if File.file?(".bundle/environment.rb")
         | 
| 14 | 
            +
                  "bundle exec spec"
         | 
| 15 | 
            +
                elsif File.file?("script/spec")
         | 
| 16 | 
            +
                  "script/spec"
         | 
| 17 | 
            +
                else
         | 
| 18 | 
            +
                  "spec"
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              protected
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def self.find_tests(root)
         | 
| 25 | 
            +
                Dir["#{root}**/**/*_spec.rb"]
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require 'spec/runner/formatter/progress_bar_formatter'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ParallelSpecs::SpecRuntimeLogger < Spec::Runner::Formatter::BaseTextFormatter
         | 
| 4 | 
            +
              def initialize(options, output)
         | 
| 5 | 
            +
                if String === output
         | 
| 6 | 
            +
                  FileUtils.mkdir_p(File.dirname(output))
         | 
| 7 | 
            +
                  File.open(output,'w'){|f| f.write ''} # clean the file
         | 
| 8 | 
            +
                  @output = File.open(output, 'a+') #append so that multiple processes can write at once
         | 
| 9 | 
            +
                else
         | 
| 10 | 
            +
                  @output = output
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                @example_times = Hash.new(0)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def example_started(*args)
         | 
| 16 | 
            +
                @time = Time.now
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def example_passed(example)
         | 
| 20 | 
            +
                file = example.location.split(':').first
         | 
| 21 | 
            +
                @example_times[file] += Time.now - @time
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def start_dump(*args)
         | 
| 25 | 
            +
                return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
         | 
| 26 | 
            +
                # TODO: Figure out why sometimes time can be less than 0
         | 
| 27 | 
            +
                @output.puts @example_times.map { |file, time| "#{file}:#{time > 0 ? time : 0}" }
         | 
| 28 | 
            +
                @output.flush
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              # stubs so that rspec doe not crash
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def example_pending(*args)
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def dump_summary(*args)
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def dump_pending(*args)
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def dump_failure(*args)
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              #stolen from Rspec
         | 
| 46 | 
            +
              def close
         | 
| 47 | 
            +
                @output.close  if (IO === @output) & (@output != $stdout)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,119 @@ | |
| 1 | 
            +
            require 'parallel'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ParallelTests
         | 
| 4 | 
            +
              VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              # parallel:spec[2,controller] <-> parallel:spec[controller]
         | 
| 7 | 
            +
              def self.parse_rake_args (args)
         | 
| 8 | 
            +
                num_processes = Parallel.processor_count
         | 
| 9 | 
            +
                options = ""
         | 
| 10 | 
            +
                if args[:count].to_s =~ /^\d*$/ # number or empty
         | 
| 11 | 
            +
                  num_processes = args[:count] unless args[:count].to_s.empty?
         | 
| 12 | 
            +
                  prefix = args[:path_prefix]
         | 
| 13 | 
            +
                  options = args[:options] if args[:options]
         | 
| 14 | 
            +
                else # something stringy
         | 
| 15 | 
            +
                  prefix = args[:count]
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
                [num_processes.to_i, prefix.to_s, options]
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              # finds all tests and partitions them into groups
         | 
| 21 | 
            +
              def self.tests_in_groups(root, num)
         | 
| 22 | 
            +
                tests_with_sizes = slow_specs_first(find_tests_with_sizes(root))
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                groups = []
         | 
| 25 | 
            +
                current_group = current_size = 0
         | 
| 26 | 
            +
                tests_with_sizes.each do |test, size|
         | 
| 27 | 
            +
                  # inserts into next group if current is full and we are not in the last group
         | 
| 28 | 
            +
                  if (0.5*size + current_size) > group_size(tests_with_sizes, num) and num > current_group + 1
         | 
| 29 | 
            +
                    current_size = size
         | 
| 30 | 
            +
                    current_group += 1
         | 
| 31 | 
            +
                  else
         | 
| 32 | 
            +
                    current_size += size
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                  groups[current_group] ||= []
         | 
| 35 | 
            +
                  groups[current_group] << test
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                groups.compact
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def self.run_tests(test_files, process_number, options)
         | 
| 41 | 
            +
                require_list = test_files.map { |filename| "\"#{filename}\"" }.join(",")
         | 
| 42 | 
            +
                cmd = "export RAILS_ENV=test ; ruby -Itest #{options} -e '[#{require_list}].each {|f| require f }'"
         | 
| 43 | 
            +
                execute_command(cmd, process_number)
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def self.execute_command(cmd, process_number)
         | 
| 47 | 
            +
                cmd = "export TEST_ENV_NUMBER=#{test_env_number(process_number)} ; #{cmd}"
         | 
| 48 | 
            +
                f = open("|#{cmd}", 'r')
         | 
| 49 | 
            +
                all = ''
         | 
| 50 | 
            +
                while char = f.getc
         | 
| 51 | 
            +
                  char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
         | 
| 52 | 
            +
                  all << char
         | 
| 53 | 
            +
                  print char
         | 
| 54 | 
            +
                  STDOUT.flush
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
                all
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def self.find_results(test_output)
         | 
| 60 | 
            +
                test_output.split("\n").map {|line|
         | 
| 61 | 
            +
                  line = line.gsub(/\.|F|\*/,'')
         | 
| 62 | 
            +
                  next unless line_is_result?(line)
         | 
| 63 | 
            +
                  line
         | 
| 64 | 
            +
                }.compact
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              def self.failed?(results)
         | 
| 68 | 
            +
                return true if results.empty?
         | 
| 69 | 
            +
                !! results.detect{|line| line_is_failure?(line)}
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def self.test_env_number(process_number)
         | 
| 73 | 
            +
                process_number == 0 ? '' : process_number + 1
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              protected
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              def self.slow_specs_first(tests)
         | 
| 79 | 
            +
                tests.sort_by{|test, size| size }.reverse
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
              
         | 
| 82 | 
            +
              def self.line_is_result?(line)
         | 
| 83 | 
            +
                line =~ /\d+ failure/
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
              
         | 
| 86 | 
            +
              def self.line_is_failure?(line)
         | 
| 87 | 
            +
                line =~ /(\d{2,}|[1-9]) (failure|error)/
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def self.group_size(tests_with_sizes, num_groups)
         | 
| 91 | 
            +
                total_size = tests_with_sizes.inject(0) { |sum, test| sum += test[1] }
         | 
| 92 | 
            +
                total_size / num_groups.to_f
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              def self.find_tests_with_sizes(root)
         | 
| 96 | 
            +
                tests = find_tests(root).sort
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                #TODO get the real root, atm this only works for complete runs when root point to e.g. real_root/spec
         | 
| 99 | 
            +
                runtime_file = File.join(root,'..','tmp','parallel_profile.log')
         | 
| 100 | 
            +
                lines = File.read(runtime_file).split("\n") rescue []
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                if lines.size * 1.5 > tests.size
         | 
| 103 | 
            +
                  # use recorded test runtime if we got enough data
         | 
| 104 | 
            +
                  times = Hash.new(1)
         | 
| 105 | 
            +
                  lines.each do |line|
         | 
| 106 | 
            +
                    test, time = line.split(":")
         | 
| 107 | 
            +
                    times[test] = time.to_f
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
                  tests.map { |test| [ test, times[test] ] }
         | 
| 110 | 
            +
                else
         | 
| 111 | 
            +
                  # use file sizes
         | 
| 112 | 
            +
                  tests.map { |test| [ test, File.stat(test).size ] }
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              def self.find_tests(root)
         | 
| 117 | 
            +
                Dir["#{root}**/**/*_test.rb"]
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            # Generated by jeweler
         | 
| 2 | 
            +
            # DO NOT EDIT THIS FILE DIRECTLY
         | 
| 3 | 
            +
            # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
         | 
| 4 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |s|
         | 
| 7 | 
            +
              s.name = %q{parallel_tests}
         | 
| 8 | 
            +
              s.version = "0.3.0"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 | 
            +
              s.authors = ["Michael Grosser"]
         | 
| 12 | 
            +
              s.date = %q{2010-03-02}
         | 
| 13 | 
            +
              s.email = %q{grosser.michael@gmail.com}
         | 
| 14 | 
            +
              s.executables = ["parallel_test", "parallel_spec", "parallel_cucumber"]
         | 
| 15 | 
            +
              s.extra_rdoc_files = [
         | 
| 16 | 
            +
                "README.markdown"
         | 
| 17 | 
            +
              ]
         | 
| 18 | 
            +
              s.files = [
         | 
| 19 | 
            +
                ".gitignore",
         | 
| 20 | 
            +
                 "README.markdown",
         | 
| 21 | 
            +
                 "Rakefile",
         | 
| 22 | 
            +
                 "VERSION",
         | 
| 23 | 
            +
                 "bin/parallel_cucumber",
         | 
| 24 | 
            +
                 "bin/parallel_spec",
         | 
| 25 | 
            +
                 "bin/parallel_test",
         | 
| 26 | 
            +
                 "lib/parallel_cucumber.rb",
         | 
| 27 | 
            +
                 "lib/parallel_specs.rb",
         | 
| 28 | 
            +
                 "lib/parallel_specs/spec_runtime_logger.rb",
         | 
| 29 | 
            +
                 "lib/parallel_tests.rb",
         | 
| 30 | 
            +
                 "parallel_tests.gemspec",
         | 
| 31 | 
            +
                 "spec/integration_spec.rb",
         | 
| 32 | 
            +
                 "spec/parallel_cucumber_spec.rb",
         | 
| 33 | 
            +
                 "spec/parallel_specs_spec.rb",
         | 
| 34 | 
            +
                 "spec/parallel_tests_spec.rb",
         | 
| 35 | 
            +
                 "spec/spec_helper.rb",
         | 
| 36 | 
            +
                 "tasks/parallel_specs.rake"
         | 
| 37 | 
            +
              ]
         | 
| 38 | 
            +
              s.homepage = %q{http://github.com/grosser/parallel_tests}
         | 
| 39 | 
            +
              s.rdoc_options = ["--charset=UTF-8"]
         | 
| 40 | 
            +
              s.require_paths = ["lib"]
         | 
| 41 | 
            +
              s.rubygems_version = %q{1.3.6}
         | 
| 42 | 
            +
              s.summary = %q{Run tests / specs / features in parallel}
         | 
| 43 | 
            +
              s.test_files = [
         | 
| 44 | 
            +
                "spec/spec_helper.rb",
         | 
| 45 | 
            +
                 "spec/parallel_tests_spec.rb",
         | 
| 46 | 
            +
                 "spec/parallel_specs_spec.rb",
         | 
| 47 | 
            +
                 "spec/parallel_cucumber_spec.rb",
         | 
| 48 | 
            +
                 "spec/integration_spec.rb"
         | 
| 49 | 
            +
              ]
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              if s.respond_to? :specification_version then
         | 
| 52 | 
            +
                current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
         | 
| 53 | 
            +
                s.specification_version = 3
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
         | 
| 56 | 
            +
                else
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              else
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
             | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            describe 'CLI' do
         | 
| 2 | 
            +
              before do
         | 
| 3 | 
            +
                `rm -rf #{folder}`
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              after do
         | 
| 7 | 
            +
                `rm -rf #{folder}`
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def folder
         | 
| 11 | 
            +
                "/tmp/parallel_tests_tests"
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def write(file, content)
         | 
| 15 | 
            +
                path = "#{folder}/spec/#{file}"
         | 
| 16 | 
            +
                `mkdir -p #{File.dirname(path)}` unless File.exist?(File.dirname(path))
         | 
| 17 | 
            +
                File.open(path, 'w'){|f| f.write content }
         | 
| 18 | 
            +
                path
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def bin_folder
         | 
| 22 | 
            +
                "#{File.expand_path(File.dirname(__FILE__))}/../bin"
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def executable
         | 
| 26 | 
            +
                "#{bin_folder}/parallel_test"
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def run_specs(options={})
         | 
| 30 | 
            +
                `cd #{folder} && #{executable} -t spec -n #{options[:processes]||2} 2>&1 && echo 'i ran!'`
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              it "runs tests in parallel" do
         | 
| 34 | 
            +
                write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
         | 
| 35 | 
            +
                write 'xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
         | 
| 36 | 
            +
                result = run_specs
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # test ran and gave their puts
         | 
| 39 | 
            +
                result.should include('TEST1')
         | 
| 40 | 
            +
                result.should include('TEST2')
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # all results present
         | 
| 43 | 
            +
                result.scan('1 example, 0 failure').size.should == 4 # 2 results + 2 result summary
         | 
| 44 | 
            +
                result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
         | 
| 45 | 
            +
                result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                result.should include('i ran!')
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              it "fails when tests fail" do
         | 
| 51 | 
            +
                write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
         | 
| 52 | 
            +
                write 'xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
         | 
| 53 | 
            +
                result = run_specs
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                result.scan('1 example, 1 failure').size.should == 2
         | 
| 56 | 
            +
                result.scan('1 example, 0 failure').size.should == 2
         | 
| 57 | 
            +
                result.should =~ /specs failed/i
         | 
| 58 | 
            +
                result.should_not include('i ran!')
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              it "can exec given commands with ENV['TEST_ENV_NUM']" do
         | 
| 62 | 
            +
                result = `#{executable} -e 'ruby -e "puts ENV[:TEST_ENV_NUMBER.to_s].inspect"' -n 4`
         | 
| 63 | 
            +
                result.split("\n").sort.should == %w["" "2" "3" "4"]
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              it "can run through parallel_spec / parallel_cucumber" do
         | 
| 67 | 
            +
                version = `#{executable} -v`
         | 
| 68 | 
            +
                `#{bin_folder}/parallel_spec -v`.should == version
         | 
| 69 | 
            +
                `#{bin_folder}/parallel_cucumber -v`.should == version
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              it "runs faster with more processes" do
         | 
| 73 | 
            +
                write 'xxx_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 74 | 
            +
                write 'xxx2_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 75 | 
            +
                write 'xxx3_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 76 | 
            +
                write 'xxx4_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 77 | 
            +
                write 'xxx5_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 78 | 
            +
                write 'xxx6_spec.rb', 'describe("it"){it("should"){sleep 2}}'
         | 
| 79 | 
            +
                t = Time.now
         | 
| 80 | 
            +
                run_specs :processes => 6
         | 
| 81 | 
            +
                (Time.now - t).should < 5
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe ParallelCucumber do
         | 
| 4 | 
            +
              test_tests_in_groups(ParallelCucumber, 'features', ".feature")
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe :run_tests do
         | 
| 7 | 
            +
                before(:each) do
         | 
| 8 | 
            +
                  File.stub!(:file?).with('.bundle/environment.rb').and_return false
         | 
| 9 | 
            +
                  File.stub!(:file?).with('script/cucumber').and_return true
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                it "uses TEST_ENV_NUMBER=blank when called for process 0" do
         | 
| 13 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
         | 
| 14 | 
            +
                  ParallelCucumber.run_tests(['xxx'],0,'')
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                it "uses TEST_ENV_NUMBER=2 when called for process 1" do
         | 
| 18 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
         | 
| 19 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'')
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it "returns the output" do
         | 
| 23 | 
            +
                  io = open('spec/spec_helper.rb')
         | 
| 24 | 
            +
                  ParallelCucumber.stub!(:print)
         | 
| 25 | 
            +
                  ParallelCucumber.should_receive(:open).and_return io
         | 
| 26 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                it "runs bundle exec cucumber when on bundler 0.9" do
         | 
| 30 | 
            +
                  File.stub!(:file?).with('.bundle/environment.rb').and_return true
         | 
| 31 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{bundle exec cucumber}}.and_return mock(:getc=>false)
         | 
| 32 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'')
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                it "runs script/cucumber when script/cucumber is found" do
         | 
| 36 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber}}.and_return mock(:getc=>false)
         | 
| 37 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'')
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                it "runs cucumber by default" do
         | 
| 41 | 
            +
                  File.stub!(:file?).with('script/cucumber').and_return false
         | 
| 42 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x !~ %r{(script/cucumber)|(bundle exec cucumber)}}.and_return mock(:getc=>false)
         | 
| 43 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'')
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it "uses options passed in" do
         | 
| 47 | 
            +
                  ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber -p default}}.and_return mock(:getc=>false)
         | 
| 48 | 
            +
                  ParallelCucumber.run_tests(['xxx'],1,'-p default')
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              describe :find_results do
         | 
| 53 | 
            +
                it "finds multiple results in test output" do
         | 
| 54 | 
            +
                  output = <<EOF
         | 
| 55 | 
            +
            And I should not see "/en/"                                       # features/step_definitions/webrat_steps.rb:87
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            7 scenarios (3 failed, 4 passed)
         | 
| 58 | 
            +
            33 steps (3 failed, 2 skipped, 28 passed)
         | 
| 59 | 
            +
            /apps/rs/features/signup.feature:2
         | 
| 60 | 
            +
                Given I am on "/"                                           # features/step_definitions/common_steps.rb:12
         | 
| 61 | 
            +
                When I click "register"                                     # features/step_definitions/common_steps.rb:6
         | 
| 62 | 
            +
                And I should have "2" emails                                # features/step_definitions/user_steps.rb:25
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            4 scenarios (4 passed)
         | 
| 65 | 
            +
            40 steps (40 passed)
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            EOF
         | 
| 68 | 
            +
                  ParallelCucumber.find_results(output).should == ["7 scenarios (3 failed, 4 passed)", "33 steps (3 failed, 2 skipped, 28 passed)", "4 scenarios (4 passed)", "40 steps (40 passed)"]
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              describe :failed do
         | 
| 73 | 
            +
                it "fails with single failed" do
         | 
| 74 | 
            +
                  ParallelCucumber.failed?(['40 steps (40 passed)','33 steps (3 failed, 2 skipped, 28 passed)']).should == true
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                it "fails with multiple failed tests" do
         | 
| 78 | 
            +
                  ParallelCucumber.failed?(['33 steps (3 failed, 2 skipped, 28 passed)','33 steps (3 failed, 2 skipped, 28 passed)']).should == true
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                it "fails with a single scenario failure during setup phase" do
         | 
| 82 | 
            +
                  ParallelCucumber.failed?(['1 scenarios (1 failed)']).should == true
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                it "fails with scenario failures during setup phase when other steps pass" do
         | 
| 86 | 
            +
                  ParallelCucumber.failed?(['7 scenarios (3 failed, 4 passed)','40 steps (40 passed)']).should == true
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                it "does not fail with successful tests" do
         | 
| 90 | 
            +
                  ParallelCucumber.failed?(['4 scenarios (4 passed)','40 steps (40 passed)','4 scenarios (4 passed)','40 steps (40 passed)']).should == false
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                it "does not fail with 0 failures" do
         | 
| 94 | 
            +
                  ParallelCucumber.failed?(['4 scenarios (4 passed 0 failed)','40 steps (40 passed 0 failed)','4 scenarios (4 passed 0 failed)','40 steps (40 passed)']).should == false
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                it "does fail with 10 failures" do
         | 
| 98 | 
            +
                  ParallelCucumber.failed?(['40 steps (40 passed 10 failed)','40 steps (40 passed)']).should == true
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,134 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe ParallelSpecs do
         | 
| 4 | 
            +
              test_tests_in_groups(ParallelSpecs, 'spec', '_spec.rb')
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe :run_tests do
         | 
| 7 | 
            +
                before do
         | 
| 8 | 
            +
                  File.stub!(:file?).with('.bundle/environment.rb').and_return false
         | 
| 9 | 
            +
                  File.stub!(:file?).with('script/spec').and_return true
         | 
| 10 | 
            +
                  File.stub!(:file?).with('spec/spec.opts').and_return true
         | 
| 11 | 
            +
                  File.stub!(:file?).with('spec/parallel_spec.opts').and_return false
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it "uses TEST_ENV_NUMBER=blank when called for process 0" do
         | 
| 15 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
         | 
| 16 | 
            +
                  ParallelSpecs.run_tests(['xxx'],0,'')
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                it "uses TEST_ENV_NUMBER=2 when called for process 1" do
         | 
| 20 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
         | 
| 21 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                it "runs with color when called from cmdline" do
         | 
| 25 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x=~/RSPEC_COLOR=1/}.and_return mock(:getc=>false)
         | 
| 26 | 
            +
                  $stdout.should_receive(:tty?).and_return true
         | 
| 27 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                it "runs without color when not called from cmdline" do
         | 
| 31 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x !~ /RSPEC_COLOR/}.and_return mock(:getc=>false)
         | 
| 32 | 
            +
                  $stdout.should_receive(:tty?).and_return false
         | 
| 33 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                it "run bundle exec spec when on bundler 0.9" do
         | 
| 37 | 
            +
                  File.stub!(:file?).with('.bundle/environment.rb').and_return true
         | 
| 38 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{bundle exec spec}}.and_return mock(:getc=>false)
         | 
| 39 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                it "runs script/spec when script/spec can be found" do
         | 
| 43 | 
            +
                  File.should_receive(:file?).with('script/spec').and_return true
         | 
| 44 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec}}.and_return mock(:getc=>false)
         | 
| 45 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                it "runs spec when script/spec cannot be found" do
         | 
| 49 | 
            +
                  File.stub!(:file?).with('script/spec').and_return false
         | 
| 50 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{(script/spec)|(bundle exec spec)}}.and_return mock(:getc=>false)
         | 
| 51 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                it "uses no -O when no opts where found" do
         | 
| 55 | 
            +
                  File.stub!(:file?).with('spec/spec.opts').and_return false
         | 
| 56 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{spec/spec.opts}}.and_return mock(:getc=>false)
         | 
| 57 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                it "uses spec/spec.opts when found" do
         | 
| 61 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+-O spec/spec.opts}}.and_return mock(:getc=>false)
         | 
| 62 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                it "uses spec/parallel_spec.opts when found" do
         | 
| 66 | 
            +
                  File.should_receive(:file?).with('spec/parallel_spec.opts').and_return true
         | 
| 67 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+-O spec/parallel_spec.opts}}.and_return mock(:getc=>false)
         | 
| 68 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'')
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                it "uses options passed in" do
         | 
| 72 | 
            +
                  ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec -f n}}.and_return mock(:getc=>false)
         | 
| 73 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'-f n')
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                it "returns the output" do
         | 
| 77 | 
            +
                  io = open('spec/spec_helper.rb')
         | 
| 78 | 
            +
                  ParallelSpecs.stub!(:print)
         | 
| 79 | 
            +
                  ParallelSpecs.should_receive(:open).and_return io
         | 
| 80 | 
            +
                  ParallelSpecs.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              describe :find_results do
         | 
| 85 | 
            +
                it "finds multiple results in spec output" do
         | 
| 86 | 
            +
                  output = <<EOF
         | 
| 87 | 
            +
            ....F...
         | 
| 88 | 
            +
            ..
         | 
| 89 | 
            +
            failute fsddsfsd
         | 
| 90 | 
            +
            ...
         | 
| 91 | 
            +
            ff.**..
         | 
| 92 | 
            +
            0 examples, 0 failures, 0 pending
         | 
| 93 | 
            +
            ff.**..
         | 
| 94 | 
            +
            1 example, 1 failure, 1 pending
         | 
| 95 | 
            +
            EOF
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 example, 1 failure, 1 pending']
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                it "is robust against scrambeled output" do
         | 
| 101 | 
            +
                  output = <<EOF
         | 
| 102 | 
            +
            ....F...
         | 
| 103 | 
            +
            ..
         | 
| 104 | 
            +
            failute fsddsfsd
         | 
| 105 | 
            +
            ...
         | 
| 106 | 
            +
            ff.**..
         | 
| 107 | 
            +
            0 exFampl*es, 0 failures, 0 pend.ing
         | 
| 108 | 
            +
            ff.**..
         | 
| 109 | 
            +
            1 exampF.les, 1 failures, 1 pend.ing
         | 
| 110 | 
            +
            EOF
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 examples, 1 failures, 1 pending']
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              describe :failed do
         | 
| 117 | 
            +
                it "fails with single failed specs" do
         | 
| 118 | 
            +
                  ParallelSpecs.failed?(['0 examples, 0 failures, 0 pending','1 examples, 1 failure, 1 pending']).should == true
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                it "fails with multiple failed specs" do
         | 
| 122 | 
            +
                  ParallelSpecs.failed?(['0 examples, 1 failure, 0 pending','1 examples, 111 failures, 1 pending']).should == true
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                it "does not fail with successful specs" do
         | 
| 126 | 
            +
                  ParallelSpecs.failed?(['0 examples, 0 failures, 0 pending','1 examples, 0 failures, 1 pending']).should == false
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                it "does fail with 10 failures" do
         | 
| 130 | 
            +
                  ParallelSpecs.failed?(['0 examples, 10 failures, 0 pending','1 examples, 0 failures, 1 pending']).should == true
         | 
| 131 | 
            +
                end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
            end
         | 
| @@ -0,0 +1,130 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + '/spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe ParallelTests do
         | 
| 4 | 
            +
              test_tests_in_groups(ParallelTests, 'test', '_test.rb')
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe :parse_rake_args do
         | 
| 7 | 
            +
                it "should return the count" do
         | 
| 8 | 
            +
                  args = {:count => 2}
         | 
| 9 | 
            +
                  ParallelTests.parse_rake_args(args).should == [2, '', ""]
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                it "should default to the prefix" do
         | 
| 13 | 
            +
                  args = {:count => "models"}
         | 
| 14 | 
            +
                  ParallelTests.parse_rake_args(args).should == [2, "models", ""]
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                it "should return the count and prefix" do
         | 
| 18 | 
            +
                  args = {:count => 2, :path_prefix => "models"}
         | 
| 19 | 
            +
                  ParallelTests.parse_rake_args(args).should == [2, "models", ""]
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it "should return the count, prefix, and options" do
         | 
| 23 | 
            +
                  args = {:count => 2, :path_prefix => "plain", :options => "-p default" }
         | 
| 24 | 
            +
                  ParallelTests.parse_rake_args(args).should == [2, "plain", "-p default"]
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              describe :run_tests do
         | 
| 29 | 
            +
                it "uses TEST_ENV_NUMBER=blank when called for process 0" do
         | 
| 30 | 
            +
                  ParallelTests.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
         | 
| 31 | 
            +
                  ParallelTests.run_tests(['xxx'],0,'')
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                it "uses TEST_ENV_NUMBER=2 when called for process 1" do
         | 
| 35 | 
            +
                  ParallelTests.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
         | 
| 36 | 
            +
                  ParallelTests.run_tests(['xxx'],1,'')
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                it "uses options" do
         | 
| 40 | 
            +
                  ParallelTests.should_receive(:open).with{|x,y| x=~ %r{ruby -Itest -v}}.and_return mock(:getc=>false)
         | 
| 41 | 
            +
                  ParallelTests.run_tests(['xxx'],1,'-v')
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                it "returns the output" do
         | 
| 45 | 
            +
                  io = open('spec/spec_helper.rb')
         | 
| 46 | 
            +
                  ParallelTests.stub!(:print)
         | 
| 47 | 
            +
                  ParallelTests.should_receive(:open).and_return io
         | 
| 48 | 
            +
                  ParallelTests.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              describe :find_results do
         | 
| 53 | 
            +
                it "finds multiple results in test output" do
         | 
| 54 | 
            +
                  output = <<EOF
         | 
| 55 | 
            +
            Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
         | 
| 56 | 
            +
            Started
         | 
| 57 | 
            +
            ..............
         | 
| 58 | 
            +
            Finished in 0.145069 seconds.
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            10 tests, 20 assertions, 0 failures, 0 errors
         | 
| 61 | 
            +
            Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
         | 
| 62 | 
            +
            Started
         | 
| 63 | 
            +
            ..............
         | 
| 64 | 
            +
            Finished in 0.145069 seconds.
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            14 tests, 20 assertions, 0 failures, 0 errors
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            EOF
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  ParallelTests.find_results(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tests, 20 assertions, 0 failures, 0 errors']
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                it "is robust against scrambeled output" do
         | 
| 74 | 
            +
                  output = <<EOF
         | 
| 75 | 
            +
            Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
         | 
| 76 | 
            +
            Started
         | 
| 77 | 
            +
            ..............
         | 
| 78 | 
            +
            Finished in 0.145069 seconds.
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            10 tests, 20 assertions, 0 failures, 0 errors
         | 
| 81 | 
            +
            Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
         | 
| 82 | 
            +
            Started
         | 
| 83 | 
            +
            ..............
         | 
| 84 | 
            +
            Finished in 0.145069 seconds.
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            14 te.dsts, 20 assertions, 0 failures, 0 errors
         | 
| 87 | 
            +
            EOF
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  ParallelTests.find_results(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tedsts, 20 assertions, 0 failures, 0 errors']
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              describe :failed do
         | 
| 94 | 
            +
                it "fails with single failed" do
         | 
| 95 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 0 errors','10 tests, 20 assertions, 1 failure, 0 errors']).should == true
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                it "fails with single error" do
         | 
| 99 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 1 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == true
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                it "fails with failed and error" do
         | 
| 103 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 1 errors','10 tests, 20 assertions, 1 failures, 1 errors']).should == true
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                it "fails with multiple failed tests" do
         | 
| 107 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 2 failures, 0 errors','10 tests, 1 assertion, 1 failures, 0 errors']).should == true
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                it "does not fail with successful tests" do
         | 
| 111 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 0 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == false
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                it "does fail with 10 failures" do
         | 
| 115 | 
            +
                  ParallelTests.failed?(['10 tests, 20 assertions, 10 failures, 0 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == true
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                it "is not failed with empty results" do
         | 
| 119 | 
            +
                  ParallelTests.failed?(['0 tests, 0 assertions, 0 failures, 0 errors']).should == false
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                it "is failed when there are no results" do
         | 
| 123 | 
            +
                  ParallelTests.failed?([]).should == true
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
              it "has a version" do
         | 
| 128 | 
            +
                ParallelTests::VERSION.should =~ /^\d+\.\d+\.\d+$/
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            # ---- requirements
         | 
| 2 | 
            +
            $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
         | 
| 3 | 
            +
            require 'rubygems'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            FAKE_RAILS_ROOT = '/tmp/pspecs/fixtures'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require 'parallel_specs'
         | 
| 8 | 
            +
            require 'parallel_cucumber'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            def size_of(group)
         | 
| 11 | 
            +
              group.inject(0) { |sum, test| sum += File.stat(test).size }
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            def test_tests_in_groups(klass, folder, suffix)
         | 
| 15 | 
            +
              test_root = "#{FAKE_RAILS_ROOT}/#{folder}"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              describe :tests_in_groups do
         | 
| 18 | 
            +
                before :all do
         | 
| 19 | 
            +
                  system "rm -rf #{FAKE_RAILS_ROOT}; mkdir -p #{test_root}/temp"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  @files = [0,1,2,3,4,5,6,7].map do |i|
         | 
| 22 | 
            +
                    size = 99
         | 
| 23 | 
            +
                    file = "#{test_root}/temp/x#{i}#{suffix}"
         | 
| 24 | 
            +
                    File.open(file, 'w') { |f| f.puts 'x' * size }
         | 
| 25 | 
            +
                    file
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  @log = "#{FAKE_RAILS_ROOT}/tmp/parallel_profile.log"
         | 
| 29 | 
            +
                  `mkdir #{File.dirname(@log)}`
         | 
| 30 | 
            +
                  `rm -f #{@log}`
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                it "finds all tests" do
         | 
| 34 | 
            +
                  found = klass.tests_in_groups(test_root, 1)
         | 
| 35 | 
            +
                  all = [ Dir["#{test_root}/**/*#{suffix}"] ]
         | 
| 36 | 
            +
                  (found.flatten - all.flatten).should == []
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                it "partitions them into groups by equal size" do
         | 
| 40 | 
            +
                  groups = klass.tests_in_groups(test_root, 2)
         | 
| 41 | 
            +
                  groups.size.should == 2
         | 
| 42 | 
            +
                  size_of(groups[0]).should == 400
         | 
| 43 | 
            +
                  size_of(groups[1]).should == 400
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it 'should partition correctly with a group size of 4' do
         | 
| 47 | 
            +
                  groups = klass.tests_in_groups(test_root, 4)
         | 
| 48 | 
            +
                  groups.size.should == 4
         | 
| 49 | 
            +
                  size_of(groups[0]).should == 200
         | 
| 50 | 
            +
                  size_of(groups[1]).should == 200
         | 
| 51 | 
            +
                  size_of(groups[2]).should == 200
         | 
| 52 | 
            +
                  size_of(groups[3]).should == 200
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                it 'should partition correctly with an uneven group size' do
         | 
| 56 | 
            +
                  groups = klass.tests_in_groups(test_root, 3)
         | 
| 57 | 
            +
                  groups.size.should == 3
         | 
| 58 | 
            +
                  size_of(groups[0]).should == 300
         | 
| 59 | 
            +
                  size_of(groups[1]).should == 300
         | 
| 60 | 
            +
                  size_of(groups[2]).should == 200
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                it "partitions by runtime when runtime-data is available" do
         | 
| 64 | 
            +
                  File.open(@log,'w') do |f|
         | 
| 65 | 
            +
                    @files[1..-1].each{|file| f.puts "#{file}:#{@files.index(file)}"}
         | 
| 66 | 
            +
                    f.puts "#{@files[0]}:10"
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  groups = klass.tests_in_groups(test_root, 2)
         | 
| 70 | 
            +
                  groups.size.should == 2
         | 
| 71 | 
            +
                  # 10 + 7 = 17
         | 
| 72 | 
            +
                  groups[0].should == [@files[0],@files[7]]
         | 
| 73 | 
            +
                  # 6+5+4+3+2+1 = 21
         | 
| 74 | 
            +
                  # still room for optimization...
         | 
| 75 | 
            +
                  groups[1].should == [@files[6],@files[5],@files[4],@files[3],@files[2],@files[1]]
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            namespace :parallel do
         | 
| 2 | 
            +
              desc "update test databases by running db:test:prepare for each --> parallel:prepare[num_cpus]"
         | 
| 3 | 
            +
              task :prepare, :count do |t,args|
         | 
| 4 | 
            +
                require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                Parallel.in_processes(args[:count] ? args[:count].to_i : nil) do |i|
         | 
| 7 | 
            +
                  puts "Preparing test database #{i + 1}"
         | 
| 8 | 
            +
                  `export TEST_ENV_NUMBER=#{ParallelTests.test_env_number(i)} ; rake db:test:prepare`
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              # Useful when dumping/resetting takes too long
         | 
| 13 | 
            +
              desc "update test databases by running db:mgrate for each --> parallel:migrate[num_cpus]"
         | 
| 14 | 
            +
              task :migrate, :count do |t,args|
         | 
| 15 | 
            +
                require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                Parallel.in_processes(args[:count] ? args[:count].to_i : nil) do |i|
         | 
| 18 | 
            +
                  puts "Migrating test database #{i + 1}"
         | 
| 19 | 
            +
                  `export TEST_ENV_NUMBER=#{ParallelTests.test_env_number(i)} ; rake db:migrate RAILS_ENV=test`
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              ['test', 'spec', 'features'].each do |type|
         | 
| 24 | 
            +
                desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
         | 
| 25 | 
            +
                task type, :count, :path_prefix, :options do |t,args|
         | 
| 26 | 
            +
                  require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
         | 
| 27 | 
            +
                  count, prefix, options = ParallelTests.parse_rake_args(args)
         | 
| 28 | 
            +
                  sh "#{File.join(File.dirname(__FILE__), '..', 'bin', 'parallel_test')} --type #{type} -n #{count} -p '#{prefix}' -r '#{RAILS_ROOT}' -o '#{options}'"
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            #backwards compatability
         | 
| 34 | 
            +
            #spec:parallel:prepare
         | 
| 35 | 
            +
            #spec:parallel
         | 
| 36 | 
            +
            #test:parallel
         | 
| 37 | 
            +
            namespace :spec do
         | 
| 38 | 
            +
              namespace :parallel do
         | 
| 39 | 
            +
                task :prepare, :count do |t,args|
         | 
| 40 | 
            +
                  $stderr.puts "WARNING -- Deprecated!  use parallel:prepare"
         | 
| 41 | 
            +
                  Rake::Task['parallel:prepare'].invoke(args[:count])
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              task :parallel, :count, :path_prefix do |t,args|
         | 
| 46 | 
            +
                $stderr.puts "WARNING -- Deprecated! use parallel:spec"
         | 
| 47 | 
            +
                Rake::Task['parallel:spec'].invoke(args[:count], args[:path_prefix])
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            namespace :test do
         | 
| 52 | 
            +
              task :parallel, :count, :path_prefix do |t,args|
         | 
| 53 | 
            +
                $stderr.puts "WARNING -- Deprecated! use parallel:test"
         | 
| 54 | 
            +
                Rake::Task['parallel:test'].invoke(args[:count], args[:path_prefix])
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: parallel_tests
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              prerelease: false
         | 
| 5 | 
            +
              segments: 
         | 
| 6 | 
            +
              - 0
         | 
| 7 | 
            +
              - 3
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              version: 0.3.0
         | 
| 10 | 
            +
            platform: ruby
         | 
| 11 | 
            +
            authors: 
         | 
| 12 | 
            +
            - Michael Grosser
         | 
| 13 | 
            +
            autorequire: 
         | 
| 14 | 
            +
            bindir: bin
         | 
| 15 | 
            +
            cert_chain: []
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            date: 2010-03-02 00:00:00 +01:00
         | 
| 18 | 
            +
            default_executable: 
         | 
| 19 | 
            +
            dependencies: []
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            description: 
         | 
| 22 | 
            +
            email: grosser.michael@gmail.com
         | 
| 23 | 
            +
            executables: 
         | 
| 24 | 
            +
            - parallel_test
         | 
| 25 | 
            +
            - parallel_spec
         | 
| 26 | 
            +
            - parallel_cucumber
         | 
| 27 | 
            +
            extensions: []
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            extra_rdoc_files: 
         | 
| 30 | 
            +
            - README.markdown
         | 
| 31 | 
            +
            files: 
         | 
| 32 | 
            +
            - .gitignore
         | 
| 33 | 
            +
            - README.markdown
         | 
| 34 | 
            +
            - Rakefile
         | 
| 35 | 
            +
            - VERSION
         | 
| 36 | 
            +
            - bin/parallel_cucumber
         | 
| 37 | 
            +
            - bin/parallel_spec
         | 
| 38 | 
            +
            - bin/parallel_test
         | 
| 39 | 
            +
            - lib/parallel_cucumber.rb
         | 
| 40 | 
            +
            - lib/parallel_specs.rb
         | 
| 41 | 
            +
            - lib/parallel_specs/spec_runtime_logger.rb
         | 
| 42 | 
            +
            - lib/parallel_tests.rb
         | 
| 43 | 
            +
            - parallel_tests.gemspec
         | 
| 44 | 
            +
            - spec/integration_spec.rb
         | 
| 45 | 
            +
            - spec/parallel_cucumber_spec.rb
         | 
| 46 | 
            +
            - spec/parallel_specs_spec.rb
         | 
| 47 | 
            +
            - spec/parallel_tests_spec.rb
         | 
| 48 | 
            +
            - spec/spec_helper.rb
         | 
| 49 | 
            +
            - tasks/parallel_specs.rake
         | 
| 50 | 
            +
            has_rdoc: true
         | 
| 51 | 
            +
            homepage: http://github.com/grosser/parallel_tests
         | 
| 52 | 
            +
            licenses: []
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            post_install_message: 
         | 
| 55 | 
            +
            rdoc_options: 
         | 
| 56 | 
            +
            - --charset=UTF-8
         | 
| 57 | 
            +
            require_paths: 
         | 
| 58 | 
            +
            - lib
         | 
| 59 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 60 | 
            +
              requirements: 
         | 
| 61 | 
            +
              - - ">="
         | 
| 62 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 63 | 
            +
                  segments: 
         | 
| 64 | 
            +
                  - 0
         | 
| 65 | 
            +
                  version: "0"
         | 
| 66 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 67 | 
            +
              requirements: 
         | 
| 68 | 
            +
              - - ">="
         | 
| 69 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 70 | 
            +
                  segments: 
         | 
| 71 | 
            +
                  - 0
         | 
| 72 | 
            +
                  version: "0"
         | 
| 73 | 
            +
            requirements: []
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            rubyforge_project: 
         | 
| 76 | 
            +
            rubygems_version: 1.3.6
         | 
| 77 | 
            +
            signing_key: 
         | 
| 78 | 
            +
            specification_version: 3
         | 
| 79 | 
            +
            summary: Run tests / specs / features in parallel
         | 
| 80 | 
            +
            test_files: 
         | 
| 81 | 
            +
            - spec/spec_helper.rb
         | 
| 82 | 
            +
            - spec/parallel_tests_spec.rb
         | 
| 83 | 
            +
            - spec/parallel_specs_spec.rb
         | 
| 84 | 
            +
            - spec/parallel_cucumber_spec.rb
         | 
| 85 | 
            +
            - spec/integration_spec.rb
         |