From d68c40366d4dfc69f2d736f0e4d373bf200aefb1 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Mon, 2 Nov 2015 12:27:42 +0100 Subject: [PATCH 1/9] Refactor plugin load path helper methods As we need to make plugin parallel testable we need to know about loaded plugin paths --- lib/plugins/load_path_helper.rb | 50 +++++++++++++++++++++++++++++++++ lib/tasks/cucumber.rake | 26 ++++++----------- lib/tasks/plugin_specs.rake | 31 ++------------------ 3 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 lib/plugins/load_path_helper.rb diff --git a/lib/plugins/load_path_helper.rb b/lib/plugins/load_path_helper.rb new file mode 100644 index 0000000000..ba25d1db08 --- /dev/null +++ b/lib/plugins/load_path_helper.rb @@ -0,0 +1,50 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +module Plugins + module LoadPathHelper + def self.spec_load_paths + plugin_load_paths.map do |path| + File.join(path, 'spec') + end + end + + def self.cucumber_load_paths + plugin_load_paths.map do |path| + File.join(path, 'features') + end + end + + private + + # fetch load paths for available plugins + def self.plugin_load_paths + Rails.application.config.plugins_to_test_paths.map(&:to_s) + end + end +end diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake index d12ca5449e..a553b63c68 100644 --- a/lib/tasks/cucumber.rake +++ b/lib/tasks/cucumber.rake @@ -50,28 +50,17 @@ unless ARGV.any? { |a| a =~ /\Agems/ } # Don't load anything when running the ge ::CodeStatistics::TEST_TYPES << 'Cucumber features' if File.exist?('features') end - def get_plugin_features(prefix = nil) - features = [] - Rails.application.config.plugins_to_test_paths.each do |dir| - feature_dir = Shellwords.escape(File.join(dir, 'features')) - if File.directory?(feature_dir) - features << prefix unless prefix.nil? - features << feature_dir - end - end - features - end - def define_cucumber_task(name, description, arguments = []) desc description - task name, arguments => ['db:test:prepare', 'assets:webpack'] do |_t, args| + # task name, arguments => ['db:test:prepare', 'assets:webpack'] do |_t, args| + task name, arguments => ['db:test:prepare'] do |_t, args| if name == :custom if not args[:features] raise 'Please provide :features argument, e.g. rake cucumber:custom[features/my_feature.feature]' end features = args[:features].split(/\s+/) else - features = get_plugin_features + features = Plugins::LoadPathHelper.cucumber_load_paths if name == :plugins # in case we want to run cucumber plugins and there are none @@ -85,7 +74,7 @@ unless ARGV.any? { |a| a =~ /\Agems/ } # Don't load anything when running the ge end if name == :all - features += [File.join(Rails.root, 'features')] + features << File.join(Rails.root, 'features') end end @@ -94,9 +83,10 @@ unless ARGV.any? { |a| a =~ /\Agems/ } # Don't load anything when running the ge ENV.delete('CUCUMBER_OPTS') opts += args[:options].split(/\s+/) if args[:options] - # load feature support files from Rails root - support_files = ['-r', Shellwords.escape(File.join(Rails.root, 'features'))] - support_files += get_plugin_features(prefix = '-r') + support_files = [Rails.root.join('features').to_s] + Plugins::LoadPathHelper.cucumber_load_paths + support_files = support_files.map { |path| + ['-r', Shellwords.escape(path)] + }.flatten t.cucumber_opts = opts + support_files + features diff --git a/lib/tasks/plugin_specs.rake b/lib/tasks/plugin_specs.rake index 0c03fa4732..af85d3d195 100644 --- a/lib/tasks/plugin_specs.rake +++ b/lib/tasks/plugin_specs.rake @@ -48,47 +48,22 @@ begin namespace :spec do desc 'Run core and plugin specs' RSpec::Core::RakeTask.new(all: [:environment, 'assets:webpack']) do |t| - pattern = [] - dirs = get_plugins_to_test - dirs << File.join(Rails.root).to_s - dirs.each do |dir| - if File.directory?(dir) - pattern << File.join(dir, 'spec', '**', '*_spec.rb').to_s - end - end - t.pattern = pattern + t.pattern = [Rails.root.join('spec').to_s] + Plugins::LoadPathHelper.spec_load_paths end desc 'Run plugin specs' RSpec::Core::RakeTask.new(plugins: [:environment, 'assets:webpack']) do |t| - pattern = [] - get_plugins_to_test.each do |dir| - if File.directory?(dir) - pattern << File.join(dir, 'spec', '**', '*_spec.rb').to_s - end - end - t.pattern = pattern + t.pattern = Plugins::LoadPathHelper.spec_load_paths # in case we want to run plugins' specs and there are none # we exit with positive message - if pattern.empty? + if t.pattern.empty? puts puts '##### There are no specs for OpenProject plugins to be run.' puts exit(0) end - end end rescue LoadError end - -def get_plugins_to_test - plugin_paths = [] - Rails.application.config.plugins_to_test_paths.each do |dir| - if File.directory?(dir) - plugin_paths << File.join(dir).to_s - end - end - plugin_paths -end From af224c7b821fc207e5456e7af340768bc66e7481 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Mon, 2 Nov 2015 13:51:24 +0100 Subject: [PATCH 2/9] Ensure spec and features directories exist --- lib/plugins/load_path_helper.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/load_path_helper.rb b/lib/plugins/load_path_helper.rb index ba25d1db08..05d47a3bea 100644 --- a/lib/plugins/load_path_helper.rb +++ b/lib/plugins/load_path_helper.rb @@ -29,15 +29,15 @@ module Plugins module LoadPathHelper def self.spec_load_paths - plugin_load_paths.map do |path| + plugin_load_paths.map { |path| File.join(path, 'spec') - end + }.keep_if{ |path| File.directory?(path) } end def self.cucumber_load_paths - plugin_load_paths.map do |path| + plugin_load_paths.map { |path| File.join(path, 'features') - end + }.keep_if{ |path| File.directory?(path) } end private From 13d900c9a152957e1cd542d9bc2c7d3b5bc4ebbb Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Mon, 2 Nov 2015 13:53:03 +0100 Subject: [PATCH 3/9] Rake tasks to run plugin specs and cukes in parallel --- lib/tasks/parallel_testing.rake | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 lib/tasks/parallel_testing.rake diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake new file mode 100644 index 0000000000..2417a1c9d4 --- /dev/null +++ b/lib/tasks/parallel_testing.rake @@ -0,0 +1,89 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +namespace :parallel do + namespace :plugins do + + desc "Run plugin specs in parallel" + task :spec => [:environment] do + ParallelTests::Tasks.check_for_pending_migrations + + num_cpus = ENV['GROUP_SIZE'] + group = ENV['GROUP'] + runtime_log = ENV['RUNTIME_LOG'] || 'tmp/parallel_runtime_plugins_rspec.log' + record_runtime = ENV['RECORD_RUNTIME'] || false + + group_options = num_cpus ? "-n #{num_cpus}" : '' + group_options += " --only-group #{group}" if group + runtime_log_option = "--runtime-log #{runtime_log}" + + if record_runtime + record_runtime_option = "-o '--format progress --format ParallelTests::RSpec::RuntimeLogger --out #{runtime_log}'" + end + + spec_folders = Plugins::LoadPathHelper.spec_load_paths.join(' ') + + cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders}" + + p cmd + sh cmd + end + + desc "Run plugin cucumber features in parallel" + task :cucumber => [:environment] do + ParallelTests::Tasks.check_for_pending_migrations + + num_cpus = ENV['GROUP_SIZE'] + group = ENV['GROUP'] + runtime_log = ENV['RUNTIME_LOG'] || 'tmp/parallel_runtime_plugins_cucumber.log' + record_runtime = ENV['RECORD_RUNTIME'] || false + + group_options = num_cpus ? "-n #{num_cpus}" : '' + group_options += " --only-group #{group}" if group + runtime_log_option = "--runtime-log #{runtime_log}" + + if record_runtime + record_runtime_option = "-o '--format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}'" + end + + support_files = [Rails.root.join('features').to_s] + Plugins::LoadPathHelper.cucumber_load_paths + support_files = support_files.map { |path| + ['-r', Shellwords.escape(path)] + }.flatten.join(' ') + + feature_folders = Plugins::LoadPathHelper.cucumber_load_paths.join(' ') + cucumber_options = "-o '#{support_files}'" + + cmd = "bundle exec parallel_test --type cucumber #{cucumber_options} #{runtime_log_option} #{group_options} #{feature_folders}" + + p cmd + sh cmd + end + end +end From be6e142c59e9bbc9363352de7c71c5e4e717a155 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 12:37:28 +0100 Subject: [PATCH 4/9] Add tasks ro run all test suites in parallel (one by one) --- lib/tasks/parallel_testing.rake | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake index 2417a1c9d4..463d66dcd5 100644 --- a/lib/tasks/parallel_testing.rake +++ b/lib/tasks/parallel_testing.rake @@ -30,8 +30,8 @@ namespace :parallel do namespace :plugins do - desc "Run plugin specs in parallel" - task :spec => [:environment] do + desc 'Run plugin specs in parallel' + task spec: [:environment] do ParallelTests::Tasks.check_for_pending_migrations num_cpus = ENV['GROUP_SIZE'] @@ -49,14 +49,14 @@ namespace :parallel do spec_folders = Plugins::LoadPathHelper.spec_load_paths.join(' ') - cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders}" + cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders}" p cmd sh cmd end - desc "Run plugin cucumber features in parallel" - task :cucumber => [:environment] do + desc 'Run plugin cucumber features in parallel' + task cucumber: [:environment] do ParallelTests::Tasks.check_for_pending_migrations num_cpus = ENV['GROUP_SIZE'] @@ -73,17 +73,25 @@ namespace :parallel do end support_files = [Rails.root.join('features').to_s] + Plugins::LoadPathHelper.cucumber_load_paths - support_files = support_files.map { |path| + support_files = support_files.map { |path| ['-r', Shellwords.escape(path)] }.flatten.join(' ') feature_folders = Plugins::LoadPathHelper.cucumber_load_paths.join(' ') cucumber_options = "-o '#{support_files}'" - cmd = "bundle exec parallel_test --type cucumber #{cucumber_options} #{runtime_log_option} #{group_options} #{feature_folders}" + cmd = "bundle exec parallel_test --type cucumber #{cucumber_options} #{runtime_log_option} #{group_options} #{feature_folders}" p cmd sh cmd end end + + desc 'Run all suites in parallel (one after another)' + task all: [:spec, :cucumber, :spec_legacy, 'parallel:plugins:spec', 'parallel:plugins:cucumber'] + + desc 'Run legacy specs in parallel' + task :spec_legacy do + sh "bundle exec parallel_test --type rspec -o '-I spec_legacy' spec_legacy" + end end From daa890263efd45d7c431770bb1d2b1cbfdf0d588 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 14:56:56 +0100 Subject: [PATCH 5/9] Add custom rake task to run cucumber features in parallel. The official one provided by parallel_test gem requires some optional parameter which makes the rake task uncomfortable to use. --- lib/tasks/parallel_testing.rake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake index 463d66dcd5..bb3d3e86fe 100644 --- a/lib/tasks/parallel_testing.rake +++ b/lib/tasks/parallel_testing.rake @@ -94,4 +94,9 @@ namespace :parallel do task :spec_legacy do sh "bundle exec parallel_test --type rspec -o '-I spec_legacy' spec_legacy" end + + desc 'Run cucumber features in parallel (custom task)' + task :cucumber do + sh "bundle exec parallel_test --type cucumber -o '-r features' features" + end end From 5abe08068e6ad6414fbbef6899139c59b9301268 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 15:00:23 +0100 Subject: [PATCH 6/9] Add documentation about using parallel testing tasks [ci skip] --- doc/RUNNING_TESTS.md | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/doc/RUNNING_TESTS.md b/doc/RUNNING_TESTS.md index f1a89ba191..f06a6d8e16 100644 --- a/doc/RUNNING_TESTS.md +++ b/doc/RUNNING_TESTS.md @@ -100,7 +100,7 @@ You can run the specs with the following commands: ### Cucumber -NOTE: *We do not write new cucumber features. The current plan is to move away from +**Note:** *We do not write new cucumber features. The current plan is to move away from cucumber towards regular specs using Capybara. For the time being however, please keep the existing cucumber features green or write your feature specs in Capybara for any code that is not already covered by cucumber.* @@ -178,6 +178,7 @@ If you need Firebug and Firepath while debugging a scenario, just replace Running tests in parallel makes usage of all available cores of the machine. Functionality is being provided by [parallel_tests](https://github.com/grosser/parallel_tests) gem. +See the github page for any options like number of cpus used. #### Prepare @@ -195,28 +196,50 @@ Prepare all databases: `RAILS_ENV=test parallel_test -e "rake db:drop db:create db:migrate"` -**Note: Until `rake db:schema:load` we have to use the command above. Then we +**Note: Until `rake db:schema:load` works we have to use the command above. Then we can use `rake parallel:prepare`** +You may also just dump your current schema with `rake db:schema:dump` (db/schema.rb) +is not part of the repository. Then you can just use `rake parallel:prepare` to prepare +test databases. #### RSpec legacy specs -Run all legacy specs in parallel: +Run all legacy specs in parallel with `rake parallel:spec_legacy` -`parallel_test -t rspec -o '-I spec_legacy' spec_legacy` +Or run them manually with `parallel_test -t rspec -o '-I spec_legacy' spec_legacy` #### RSpec specs -Run all specs in parallel: +Run all specs in parallel with `rake parallel:spec` -`parallel_test -t rspec spec` +Or run them manually with `parallel_test -t rspec spec`. #### Cucumber -Run all cucumber features in parallel: +Run all cucumber features in parallel with `rake parallel:cucumber`. -`parallel_test -t cucumber -o '-r features' features` +Or run them manually with `parallel_test -t cucumber -o '-r features' features`. +**Note:** there is also a official rake task to run cucumber features but the OpenProject cucumber +test suite requires `-r features` to run correctly. This needs to be passed to the command +thus it looks not very handy `rake parallel:features\[,,"-r features"\]` +(this is zsh compatible, command takes three arguments but we just want to pass the last one here.) + +#### Plugins + +Run specs for all activated plugins with `rake parallel:plugins:spec`. + +Run cucumber features for all activated plugins with `rake parallel:plugins:cucumber`. + +#### Full test suite + +You may run all existing parts of OpenProject test suite in parallel with +`rake parallel:all` + +**Note:** This will run core specs, core cucumber features, core legacy specs, +plugin specs and plugin cucumber features. This task will take around 40 minutes +on a machine with 8 parallel instances. ## For the fancy programmer From 4a88d954abb847dd8c3f4adcfd04bab3cfdbe95f Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 17:45:10 +0100 Subject: [PATCH 7/9] Rerun failed plugin specs Some plugin specs in openproject-reporting are failing due to some bad loading conditions. Running them in isolation solves the problem. So we just rerun the failing specs in a second run. --- lib/tasks/parallel_testing.rake | 6 +++++- spec/support/rspec_failures.rb | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake index bb3d3e86fe..62940ffce8 100644 --- a/lib/tasks/parallel_testing.rake +++ b/lib/tasks/parallel_testing.rake @@ -49,7 +49,11 @@ namespace :parallel do spec_folders = Plugins::LoadPathHelper.spec_load_paths.join(' ') - cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders}" + # Change this if changed in spec/support/rspec_failures.rb + sh 'rm tmp/rspec-examples.txt' + + cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders} || \ + bundle exec rspec --only-failures" p cmd sh cmd diff --git a/spec/support/rspec_failures.rb b/spec/support/rspec_failures.rb index 384c84f4ed..3a58277453 100644 --- a/spec/support/rspec_failures.rb +++ b/spec/support/rspec_failures.rb @@ -1,5 +1,6 @@ RSpec.configure do |c| + # If the filename is being changed change it in lib/tasks/parallel_testing.rake c.example_status_persistence_file_path = "tmp/rspec-examples.txt" c.run_all_when_everything_filtered = true end From 1d393bbb963a4ca2fb94b78bbca17d4b59c7b3b0 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 23:27:14 +0100 Subject: [PATCH 8/9] Cleanup parallel testing rake tasks --- lib/tasks/parallel_testing.rake | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake index 62940ffce8..8a943c2f1b 100644 --- a/lib/tasks/parallel_testing.rake +++ b/lib/tasks/parallel_testing.rake @@ -37,25 +37,21 @@ namespace :parallel do num_cpus = ENV['GROUP_SIZE'] group = ENV['GROUP'] runtime_log = ENV['RUNTIME_LOG'] || 'tmp/parallel_runtime_plugins_rspec.log' - record_runtime = ENV['RECORD_RUNTIME'] || false group_options = num_cpus ? "-n #{num_cpus}" : '' group_options += " --only-group #{group}" if group runtime_log_option = "--runtime-log #{runtime_log}" - if record_runtime - record_runtime_option = "-o '--format progress --format ParallelTests::RSpec::RuntimeLogger --out #{runtime_log}'" - end - spec_folders = Plugins::LoadPathHelper.spec_load_paths.join(' ') # Change this if changed in spec/support/rspec_failures.rb - sh 'rm tmp/rspec-examples.txt' + if File.exist? 'tmp/rspec-examples.txt' + sh 'rm tmp/rspec-examples.txt' + end - cmd = "bundle exec parallel_test --type rspec #{record_runtime_option} #{runtime_log_option} #{group_options} #{spec_folders} || \ + cmd = "bundle exec parallel_test --type rspec #{runtime_log_option} #{group_options} #{spec_folders} || \ bundle exec rspec --only-failures" - p cmd sh cmd end @@ -66,16 +62,11 @@ namespace :parallel do num_cpus = ENV['GROUP_SIZE'] group = ENV['GROUP'] runtime_log = ENV['RUNTIME_LOG'] || 'tmp/parallel_runtime_plugins_cucumber.log' - record_runtime = ENV['RECORD_RUNTIME'] || false group_options = num_cpus ? "-n #{num_cpus}" : '' group_options += " --only-group #{group}" if group runtime_log_option = "--runtime-log #{runtime_log}" - if record_runtime - record_runtime_option = "-o '--format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}'" - end - support_files = [Rails.root.join('features').to_s] + Plugins::LoadPathHelper.cucumber_load_paths support_files = support_files.map { |path| ['-r', Shellwords.escape(path)] @@ -86,7 +77,6 @@ namespace :parallel do cmd = "bundle exec parallel_test --type cucumber #{cucumber_options} #{runtime_log_option} #{group_options} #{feature_folders}" - p cmd sh cmd end end @@ -94,6 +84,7 @@ namespace :parallel do desc 'Run all suites in parallel (one after another)' task all: [:spec, :cucumber, :spec_legacy, 'parallel:plugins:spec', 'parallel:plugins:cucumber'] + desc 'Run legacy specs in parallel' task :spec_legacy do sh "bundle exec parallel_test --type rspec -o '-I spec_legacy' spec_legacy" From bf6b6d158350bb31347c95b3d55765c75e9cc025 Mon Sep 17 00:00:00 2001 From: Stefan Botzenhart Date: Tue, 3 Nov 2015 23:30:58 +0100 Subject: [PATCH 9/9] Rerun legacy specs in case some failed --- lib/tasks/parallel_testing.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tasks/parallel_testing.rake b/lib/tasks/parallel_testing.rake index 8a943c2f1b..71b5663061 100644 --- a/lib/tasks/parallel_testing.rake +++ b/lib/tasks/parallel_testing.rake @@ -87,7 +87,8 @@ namespace :parallel do desc 'Run legacy specs in parallel' task :spec_legacy do - sh "bundle exec parallel_test --type rspec -o '-I spec_legacy' spec_legacy" + sh "bundle exec parallel_test --type rspec -o '-I spec_legacy' spec_legacy || \ + bundle exec rspec -I spec_legacy spec_legacy" end desc 'Run cucumber features in parallel (custom task)'