parallel_tests 3.3.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  begin
2
3
  gem "cuke_modeler", "~> 3.0"
3
4
  require 'cuke_modeler'
@@ -12,7 +13,7 @@ module ParallelTests
12
13
  def all(tests, options)
13
14
  ignore_tag_pattern = options[:ignore_tag_pattern].nil? ? nil : Regexp.compile(options[:ignore_tag_pattern])
14
15
  # format of hash will be FILENAME => NUM_STEPS
15
- steps_per_file = tests.each_with_object({}) do |file,steps|
16
+ steps_per_file = tests.each_with_object({}) do |file, steps|
16
17
  feature = ::CukeModeler::FeatureFile.new(file).feature
17
18
 
18
19
  # skip feature if it matches tag regex
@@ -20,8 +21,8 @@ module ParallelTests
20
21
 
21
22
  # count the number of steps in the file
22
23
  # will only include a feature if the regex does not match
23
- all_steps = feature.scenarios.map{|a| a.steps.count if a.tags.grep(ignore_tag_pattern).empty? }.compact
24
- steps[file] = all_steps.inject(0,:+)
24
+ all_steps = feature.scenarios.map { |a| a.steps.count if a.tags.grep(ignore_tag_pattern).empty? }.compact
25
+ steps[file] = all_steps.sum
25
26
  end
26
27
  steps_per_file.sort_by { |_, value| -value }
27
28
  end
@@ -1,16 +1,21 @@
1
+ # frozen_string_literal: true
1
2
  require "parallel_tests/gherkin/runner"
2
3
 
3
4
  module ParallelTests
4
5
  module Cucumber
5
6
  class Runner < ParallelTests::Gherkin::Runner
6
- SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/
7
- SCENARIO_REGEX = /^cucumber features\/.+:\d+/
7
+ SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/.freeze
8
+ SCENARIO_REGEX = %r{^cucumber features/.+:\d+}.freeze
8
9
 
9
10
  class << self
10
11
  def name
11
12
  'cucumber'
12
13
  end
13
14
 
15
+ def default_test_folder
16
+ 'features'
17
+ end
18
+
14
19
  def line_is_result?(line)
15
20
  super || line =~ SCENARIO_REGEX || line =~ SCENARIOS_RESULTS_BOUNDARY_REGEX
16
21
  end
@@ -21,9 +26,7 @@ module ParallelTests
21
26
  scenario_groups = results.slice_before(SCENARIOS_RESULTS_BOUNDARY_REGEX).group_by(&:first)
22
27
  scenario_groups.each do |header, group|
23
28
  scenarios = group.flatten.grep(SCENARIO_REGEX)
24
- if scenarios.any?
25
- output << ([header] + scenarios).join("\n")
26
- end
29
+ output << ([header] + scenarios).join("\n") if scenarios.any?
27
30
  end
28
31
 
29
32
  output << super
@@ -32,8 +35,8 @@ module ParallelTests
32
35
  end
33
36
 
34
37
  def command_with_seed(cmd, seed)
35
- clean = cmd.sub(/\s--order\s+random(:\d+)?\b/, '')
36
- "#{clean} --order random:#{seed}"
38
+ clean = remove_command_arguments(cmd, '--order')
39
+ [*clean, '--order', "random:#{seed}"]
37
40
  end
38
41
  end
39
42
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ParallelTests
2
3
  module Cucumber
3
4
  module Formatters
@@ -10,7 +11,7 @@ module ParallelTests
10
11
  end
11
12
 
12
13
  def visit_feature_element(uri, feature_element, feature_tags, line_numbers: [])
13
- scenario_tags = feature_element.tags.map { |tag| tag.name }
14
+ scenario_tags = feature_element.tags.map(&:name)
14
15
  scenario_tags = feature_tags + scenario_tags
15
16
  if feature_element.is_a?(CukeModeler::Scenario) # :Scenario
16
17
  test_line = feature_element.source_line
@@ -26,7 +27,7 @@ module ParallelTests
26
27
  example_tags = example.tags.map(&:name)
27
28
  example_tags = scenario_tags + example_tags
28
29
  next unless matches_tags?(example_tags)
29
- example.rows[1..-1].each do |row|
30
+ example.rows[1..].each do |row|
30
31
  test_line = row.source_line
31
32
  next if line_numbers.any? && !line_numbers.include?(test_line)
32
33
 
@@ -36,8 +37,7 @@ module ParallelTests
36
37
  end
37
38
  end
38
39
 
39
- def method_missing(*args)
40
- end
40
+ def method_missing(*); end # # rubocop:disable Style/MissingRespondToMissing
41
41
 
42
42
  private
43
43
 
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  require 'cucumber/tag_expressions/parser'
2
3
  require 'cucumber/runtime'
3
4
  require 'cucumber'
4
5
  require 'parallel_tests/cucumber/scenario_line_logger'
5
6
  require 'parallel_tests/gherkin/listener'
6
- require 'shellwords'
7
7
 
8
8
  begin
9
9
  gem "cuke_modeler", "~> 3.0"
@@ -16,11 +16,11 @@ module ParallelTests
16
16
  module Cucumber
17
17
  class Scenarios
18
18
  class << self
19
- def all(files, options={})
19
+ def all(files, options = {})
20
20
  # Parse tag expression from given test options and ignore tag pattern. Refer here to understand how new tag expression syntax works - https://github.com/cucumber/cucumber/tree/master/tag-expressions
21
21
  tags = []
22
- words = options[:test_options].to_s.shellsplit
23
- words.each_with_index { |w,i| tags << words[i+1] if ["-t", "--tags"].include?(w) }
22
+ words = options[:test_options] || []
23
+ words.each_with_index { |w, i| tags << words[i + 1] if ["-t", "--tags"].include?(w) }
24
24
  if ignore = options[:ignore_tag_pattern]
25
25
  tags << "not (#{ignore})"
26
26
  end
@@ -31,8 +31,7 @@ module ParallelTests
31
31
 
32
32
  private
33
33
 
34
- def split_into_scenarios(files, tags='')
35
-
34
+ def split_into_scenarios(files, tags = '')
36
35
  # Create the tag expression instance from cucumber tag expressions parser, this is needed to know if the scenario matches with the tags invoked by the request
37
36
  # Create the ScenarioLineLogger which will filter the scenario we want
38
37
  args = []
@@ -40,7 +39,7 @@ module ParallelTests
40
39
  scenario_line_logger = ParallelTests::Cucumber::Formatters::ScenarioLineLogger.new(*args)
41
40
 
42
41
  # here we loop on the files map, each file will contain one or more scenario
43
- features ||= files.map do |path|
42
+ files.each do |path|
44
43
  # Gather up any line numbers attached to the file path
45
44
  path, *test_lines = path.split(/:(?=\d+)/)
46
45
  test_lines.map!(&:to_i)
@@ -53,7 +52,9 @@ module ParallelTests
53
52
  feature_tags = feature.tags.map(&:name)
54
53
 
55
54
  # We loop on each children of the feature
56
- feature.tests.each do |test|
55
+ test_models = feature.tests
56
+ test_models += feature.rules.flat_map(&:tests) if feature.respond_to?(:rules) # cuke_modeler >= 3.2 supports rules
57
+ test_models.each do |test|
57
58
  # It's a scenario, we add it to the scenario_line_logger
58
59
  scenario_line_logger.visit_feature_element(document.path, test, feature_tags, line_numbers: test_lines)
59
60
  end
@@ -1,9 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests'
2
3
 
3
4
  module ParallelTests
4
5
  module Gherkin
5
6
  module Io
6
-
7
7
  def prepare_io(path_or_io)
8
8
  if path_or_io.respond_to?(:write)
9
9
  path_or_io
@@ -24,7 +24,7 @@ module ParallelTests
24
24
 
25
25
  # do not let multiple processes get in each others way
26
26
  def lock_output
27
- if File === @io
27
+ if @io.is_a?(File)
28
28
  begin
29
29
  @io.flock File::LOCK_EX
30
30
  yield
@@ -35,7 +35,6 @@ module ParallelTests
35
35
  yield
36
36
  end
37
37
  end
38
-
39
38
  end
40
39
  end
41
40
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ParallelTests
2
3
  module Gherkin
3
4
  class Listener
@@ -6,7 +7,8 @@ module ParallelTests
6
7
  attr_writer :ignore_tag_pattern
7
8
 
8
9
  def initialize
9
- @steps, @uris = [], []
10
+ @steps = []
11
+ @uris = []
10
12
  @collect = {}
11
13
  @feature, @ignore_tag_pattern = nil
12
14
  reset_counters!
@@ -16,7 +18,7 @@ module ParallelTests
16
18
  @feature = feature
17
19
  end
18
20
 
19
- def background(*args)
21
+ def background(*)
20
22
  @background = 1
21
23
  end
22
24
 
@@ -31,7 +33,7 @@ module ParallelTests
31
33
  @outline = 1
32
34
  end
33
35
 
34
- def step(*args)
36
+ def step(*)
35
37
  return if @ignoring
36
38
  if @background == 1
37
39
  @background_steps += 1
@@ -51,12 +53,10 @@ module ParallelTests
51
53
  # @param [Gherkin::Formatter::Model::Examples] examples
52
54
  #
53
55
  def examples(examples)
54
- if examples.rows.size > 0
55
- @collect[@uri] += (@outline_steps * examples.rows.size)
56
- end
56
+ @collect[@uri] += (@outline_steps * examples.rows.size) unless examples.rows.empty?
57
57
  end
58
58
 
59
- def eof(*args)
59
+ def eof(*)
60
60
  @collect[@uri] += (@background_steps * @scenarios)
61
61
  reset_counters!
62
62
  end
@@ -67,8 +67,7 @@ module ParallelTests
67
67
  end
68
68
 
69
69
  # ignore lots of other possible callbacks ...
70
- def method_missing(*args)
71
- end
70
+ def method_missing(*); end # rubocop:disable Style/MissingRespondToMissing
72
71
 
73
72
  private
74
73
 
@@ -79,7 +78,7 @@ module ParallelTests
79
78
 
80
79
  # Set @ignoring if we should ignore this scenario/outline based on its tags
81
80
  def should_ignore(scenario)
82
- @ignoring = @ignore_tag_pattern && all_tags(scenario).find{ |tag| @ignore_tag_pattern === tag.name }
81
+ @ignoring = @ignore_tag_pattern && all_tags(scenario).find { |tag| @ignore_tag_pattern === tag.name }
83
82
  end
84
83
  end
85
84
  end
@@ -1,30 +1,27 @@
1
+ # frozen_string_literal: true
1
2
  require "parallel_tests/test/runner"
2
- require 'shellwords'
3
3
 
4
4
  module ParallelTests
5
5
  module Gherkin
6
6
  class Runner < ParallelTests::Test::Runner
7
-
8
7
  class << self
9
8
  def run_tests(test_files, process_number, num_processes, options)
10
9
  combined_scenarios = test_files
11
10
 
12
11
  if options[:group_by] == :scenarios
13
12
  grouped = test_files.map { |t| t.split(':') }.group_by(&:first)
14
- combined_scenarios = grouped.map {|file,files_and_lines| "#{file}:#{files_and_lines.map(&:last).join(':')}" }
13
+ combined_scenarios = grouped.map do |file, files_and_lines|
14
+ "#{file}:#{files_and_lines.map(&:last).join(':')}"
15
+ end
15
16
  end
16
17
 
17
- sanitized_test_files = combined_scenarios.map { |val| WINDOWS ? "\"#{val}\"" : Shellwords.escape(val) }
18
-
19
18
  options[:env] ||= {}
20
- options[:env] = options[:env].merge({'AUTOTEST' => '1'}) if $stdout.tty? # display color when we are in a terminal
21
-
22
- cmd = [
23
- executable,
24
- (runtime_logging if File.directory?(File.dirname(runtime_log))),
25
- cucumber_opts(options[:test_options]),
26
- *sanitized_test_files
27
- ].compact.reject(&:empty?).join(' ')
19
+ options[:env] = options[:env].merge({ 'AUTOTEST' => '1' }) if $stdout.tty?
20
+
21
+ cmd = executable
22
+ cmd += runtime_logging if File.directory?(File.dirname(runtime_log))
23
+ cmd += combined_scenarios
24
+ cmd += cucumber_opts(options[:test_options])
28
25
  execute_command(cmd, process_number, num_processes, options)
29
26
  end
30
27
 
@@ -32,6 +29,10 @@ module ParallelTests
32
29
  @test_file_name || 'feature'
33
30
  end
34
31
 
32
+ def default_test_folder
33
+ 'features'
34
+ end
35
+
35
36
  def test_suffix
36
37
  /\.feature$/
37
38
  end
@@ -44,42 +45,38 @@ module ParallelTests
44
45
  # 1 scenario (1 failed)
45
46
  # 1 step (1 failed)
46
47
  def summarize_results(results)
47
- sort_order = %w[scenario step failed flaky undefined skipped pending passed]
48
+ sort_order = ['scenario', 'step', 'failed', 'flaky', 'undefined', 'skipped', 'pending', 'passed']
48
49
 
49
- %w[scenario step].map do |group|
50
+ ['scenario', 'step'].map do |group|
50
51
  group_results = results.grep(/^\d+ #{group}/)
51
52
  next if group_results.empty?
52
53
 
53
54
  sums = sum_up_results(group_results)
54
55
  sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
55
56
  sums.map! do |word, number|
56
- plural = "s" if word == group and number != 1
57
+ plural = "s" if (word == group) && (number != 1)
57
58
  "#{number} #{word}#{plural}"
58
59
  end
59
- "#{sums[0]} (#{sums[1..-1].join(", ")})"
60
+ "#{sums[0]} (#{sums[1..].join(", ")})"
60
61
  end.compact.join("\n")
61
62
  end
62
63
 
63
64
  def cucumber_opts(given)
64
- if given =~ /--profile/ or given =~ /(^|\s)-p /
65
+ if given&.include?('--profile') || given&.include?('-p')
65
66
  given
66
67
  else
67
- [given, profile_from_config].compact.join(" ")
68
+ [*given, *profile_from_config]
68
69
  end
69
70
  end
70
71
 
71
72
  def profile_from_config
72
73
  # copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
73
74
  config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
74
- if config && File.read(config) =~ /^parallel:/
75
- "--profile parallel"
76
- end
75
+ ['--profile', 'parallel'] if config && File.read(config) =~ /^parallel:/
77
76
  end
78
77
 
79
- def tests_in_groups(tests, num_groups, options={})
80
- if options[:group_by] == :scenarios
81
- @test_file_name = "scenario"
82
- end
78
+ def tests_in_groups(tests, num_groups, options = {})
79
+ @test_file_name = "scenario" if options[:group_by] == :scenarios
83
80
  method = "by_#{options[:group_by]}"
84
81
  if Grouper.respond_to?(method)
85
82
  Grouper.send(method, find_tests(tests, options), num_groups, options)
@@ -88,9 +85,8 @@ module ParallelTests
88
85
  end
89
86
  end
90
87
 
91
-
92
88
  def runtime_logging
93
- "--format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}"
89
+ ['--format', 'ParallelTests::Gherkin::RuntimeLogger', '--out', runtime_log]
94
90
  end
95
91
 
96
92
  def runtime_log
@@ -98,18 +94,16 @@ module ParallelTests
98
94
  end
99
95
 
100
96
  def determine_executable
101
- case
102
- when File.exist?("bin/#{name}")
97
+ if File.exist?("bin/#{name}")
103
98
  ParallelTests.with_ruby_binary("bin/#{name}")
104
- when ParallelTests.bundler_enabled?
105
- "bundle exec #{name}"
106
- when File.file?("script/#{name}")
99
+ elsif ParallelTests.bundler_enabled?
100
+ ["bundle", "exec", name]
101
+ elsif File.file?("script/#{name}")
107
102
  ParallelTests.with_ruby_binary("script/#{name}")
108
103
  else
109
- "#{name}"
104
+ [name.to_s]
110
105
  end
111
106
  end
112
-
113
107
  end
114
108
  end
115
109
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests/gherkin/io'
2
3
 
3
4
  module ParallelTests
@@ -19,7 +20,7 @@ module ParallelTests
19
20
 
20
21
  config.on_event :test_run_finished do |_|
21
22
  lock_output do
22
- @io.puts @example_times.map { |file, time| "#{file}:#{time}" }
23
+ @io.puts(@example_times.map { |file, time| "#{file}:#{time}" })
23
24
  end
24
25
  end
25
26
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ParallelTests
2
3
  class Grouper
3
4
  class << self
@@ -6,13 +7,15 @@ module ParallelTests
6
7
  in_even_groups_by_size(features_with_steps, num_groups)
7
8
  end
8
9
 
9
- def by_scenarios(tests, num_groups, options={})
10
+ def by_scenarios(tests, num_groups, options = {})
10
11
  scenarios = group_by_scenarios(tests, options)
11
12
  in_even_groups_by_size(scenarios, num_groups)
12
13
  end
13
14
 
14
- def in_even_groups_by_size(items, num_groups, options= {})
15
- groups = Array.new(num_groups) { {:items => [], :size => 0} }
15
+ def in_even_groups_by_size(items, num_groups, options = {})
16
+ groups = Array.new(num_groups) { { items: [], size: 0 } }
17
+
18
+ return specify_groups(items, num_groups, options, groups) if options[:specify_groups]
16
19
 
17
20
  # add all files that should run in a single process to one group
18
21
  single_process_patterns = options[:single_process] || []
@@ -27,11 +30,15 @@ module ParallelTests
27
30
  raise 'Number of isolated processes must be less than total the number of processes'
28
31
  end
29
32
 
33
+ if isolate_count >= num_groups
34
+ raise 'Number of isolated processes must be >= total number of processes'
35
+ end
36
+
30
37
  if isolate_count >= 1
31
38
  # add all files that should run in a multiple isolated processes to their own groups
32
39
  group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
33
40
  # group the non-isolated by size
34
- group_features_by_size(items_to_group(items), groups[isolate_count..-1])
41
+ group_features_by_size(items_to_group(items), groups[isolate_count..])
35
42
  else
36
43
  # add all files that should run in a single non-isolated process to first group
37
44
  single_items.each { |item, size| add_to_group(groups.first, item, size) }
@@ -45,6 +52,50 @@ module ParallelTests
45
52
 
46
53
  private
47
54
 
55
+ def specify_groups(items, num_groups, options, groups)
56
+ specify_test_process_groups = options[:specify_groups].split('|')
57
+ if specify_test_process_groups.count > num_groups
58
+ raise 'Number of processes separated by pipe must be less than or equal to the total number of processes'
59
+ end
60
+
61
+ all_specified_tests = specify_test_process_groups.map { |group| group.split(',') }.flatten
62
+ specified_items_found, items = items.partition { |item, _size| all_specified_tests.include?(item) }
63
+
64
+ specified_specs_not_found = all_specified_tests - specified_items_found.map(&:first)
65
+ if specified_specs_not_found.any?
66
+ raise "Could not find #{specified_specs_not_found} from --specify-groups in the selected files & folders"
67
+ end
68
+
69
+ if specify_test_process_groups.count == num_groups && items.flatten.any?
70
+ raise(
71
+ <<~ERROR
72
+ The number of groups in --specify-groups matches the number of groups from -n but there were other specs
73
+ found in the selected files & folders not specified in --specify-groups. Make sure -n is larger than the
74
+ number of processes in --specify-groups if there are other specs that need to be run. The specs that aren't run:
75
+ #{items.map(&:first)}
76
+ ERROR
77
+ )
78
+ end
79
+
80
+ # First order the specify_groups into the main groups array
81
+ specify_test_process_groups.each_with_index do |specify_test_process, i|
82
+ groups[i] = specify_test_process.split(',')
83
+ end
84
+
85
+ # Return early when processed specify_groups tests exactly match the items passed in
86
+ return groups if specify_test_process_groups.count == num_groups
87
+
88
+ # Now sort the rest of the items into the main groups array
89
+ specified_range = specify_test_process_groups.count..-1
90
+ remaining_groups = groups[specified_range]
91
+ group_features_by_size(items_to_group(items), remaining_groups)
92
+ # Don't sort all the groups, only sort the ones not specified in specify_groups
93
+ sorted_groups = remaining_groups.map { |g| g[:items].sort }
94
+ groups[specified_range] = sorted_groups
95
+
96
+ groups
97
+ end
98
+
48
99
  def isolate_count(options)
49
100
  if options[:isolate_count] && options[:isolate_count] > 1
50
101
  options[:isolate_count]
@@ -56,7 +107,7 @@ module ParallelTests
56
107
  end
57
108
 
58
109
  def largest_first(files)
59
- files.sort_by{|_item, size| size }.reverse
110
+ files.sort_by { |_item, size| size }.reverse
60
111
  end
61
112
 
62
113
  def smallest_group(groups)
@@ -73,7 +124,7 @@ module ParallelTests
73
124
  ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
74
125
  end
75
126
 
76
- def group_by_scenarios(tests, options={})
127
+ def group_by_scenarios(tests, options = {})
77
128
  require 'parallel_tests/cucumber/scenarios'
78
129
  ParallelTests::Cucumber::Scenarios.all(tests, options)
79
130
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
 
3
4
  module ParallelTests
@@ -42,18 +43,18 @@ module ParallelTests
42
43
 
43
44
  def read
44
45
  sync do
45
- contents = IO.read(file_path)
46
+ contents = File.read(file_path)
46
47
  return if contents.empty?
47
48
  @pids = JSON.parse(contents)
48
49
  end
49
50
  end
50
51
 
51
52
  def save
52
- sync { IO.write(file_path, pids.to_json) }
53
+ sync { File.write(file_path, pids.to_json) }
53
54
  end
54
55
 
55
- def sync
56
- mutex.synchronize { yield }
56
+ def sync(&block)
57
+ mutex.synchronize(&block)
57
58
  end
58
59
  end
59
60
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # rake tasks for Rails 3+
2
3
  module ParallelTests
3
4
  class Railtie < ::Rails::Railtie
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  require 'parallel_tests/rspec/logger_base'
2
3
  require 'parallel_tests/rspec/runner'
3
4
 
4
5
  class ParallelTests::RSpec::FailuresLogger < ParallelTests::RSpec::LoggerBase
5
6
  if RSPEC_2
6
- def dump_failures(*args)
7
- end
7
+ def dump_failures(*args); end
8
8
  else
9
9
  RSpec::Core::Formatters.register self, :dump_summary
10
10
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ParallelTests
2
3
  module RSpec
3
4
  end
@@ -13,26 +14,27 @@ class ParallelTests::RSpec::LoggerBase < RSpec::Core::Formatters::BaseTextFormat
13
14
 
14
15
  @output ||= args[0]
15
16
 
16
- if String === @output # a path ?
17
+ case @output
18
+ when String # a path ?
17
19
  FileUtils.mkdir_p(File.dirname(@output))
18
- File.open(@output, 'w'){} # overwrite previous results
20
+ File.open(@output, 'w') {} # overwrite previous results
19
21
  @output = File.open(@output, 'a')
20
- elsif File === @output # close and restart in append mode
22
+ when File # close and restart in append mode
21
23
  @output.close
22
24
  @output = File.open(@output.path, 'a')
23
25
  end
24
26
  end
25
27
 
26
- #stolen from Rspec
27
- def close(*args)
28
- @output.close if (IO === @output) & (@output != $stdout)
28
+ # stolen from Rspec
29
+ def close(*)
30
+ @output.close if (IO === @output) & (@output != $stdout)
29
31
  end
30
32
 
31
33
  protected
32
34
 
33
35
  # do not let multiple processes get in each others way
34
36
  def lock_output
35
- if File === @output
37
+ if @output.is_a?(File)
36
38
  begin
37
39
  @output.flock File::LOCK_EX
38
40
  yield