diff --git a/.document b/.document index 0145592272..050e20457d 100644 --- a/.document +++ b/.document @@ -1,5 +1,5 @@ -README.markdown lib/**/*.rb -bin/* -features/**/*.feature +- +README.md License.txt +Changelog.md diff --git a/.travis.yml b/.travis.yml index 7f917e3d22..0c51876e69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ +before_install: + - if [[ `gem -v` != 1.8.* ]]; then gem update --system; fi script: "bin/rake --trace 2>&1" -bundler_args: "--binstubs --without development" +bundler_args: "--binstubs" rvm: - 1.8.7 - 1.9.2 diff --git a/.yardopts b/.yardopts index 3a6cdf3c5a..f1508ed378 100644 --- a/.yardopts +++ b/.yardopts @@ -1,6 +1,3 @@ --no-private --exclude features --markup markdown -- -Changelog.md -License.txt diff --git a/Changelog.md b/Changelog.md index 13ae3285bc..a51a431bbd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,26 @@ +### 2.9.0 / 2012-03-17 +[full changelog](http://github.com/rspec/rspec-core/compare/v2.8.0...v2.9.0) + +Enhancements + +* Support for "X minutes X seconds" spec run duration in formatter. (uzzz) +* Strip whitespace from group and example names in doc formatter. +* Removed spork-0.9 shim. If you're using spork-0.8.x, you'll need to upgrade + to 0.9.0. + +Bug fixes + +* Restore `--full_backtrace` option +* Ensure that values passed to `config.filter_run` are respected when running + over DRb (using spork). +* Ensure shared example groups are reset after a run (as example groups are). +* Remove `rescue false` from calls to filters represented as Procs +* Ensure described_class gets the closest constant (pyromaniac) +* In "autorun", don't run the specs in the at_exit hook if there was an + exception (most likely due to a SyntaxError). (sunaku) +* Don't extend groups with modules already used to extend ancestor groups. +* `its` correctly memoizes nil or false values (Yamada Masaki) + ### 2.8.0 / 2012-01-04 [full changelog](http://github.com/rspec/rspec-core/compare/v2.8.0.rc2...v2.8.0) diff --git a/Gemfile b/Gemfile index 90158b730a..cdd553e3b2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "http://rubygems.org" -### rspec libs +gemspec + %w[rspec rspec-core rspec-expectations rspec-mocks].each do |lib| library_path = File.expand_path("../../#{lib}", __FILE__) if File.exist?(library_path) @@ -10,30 +11,10 @@ source "http://rubygems.org" end end -### dev dependencies -gem "rake", "~> 0.9.2" -gem "cucumber", "1.0.1" -gem "aruba", "0.4.2" -gem "ZenTest", "4.6.2" -gem "nokogiri", "1.5.0" -gem "fakefs", "0.4.0", :require => "fakefs/safe" +gem 'rake', '~> 0.9.2' platforms :jruby do gem "jruby-openssl" end -### rspec-core only -gem "mocha", "~> 0.9.10" -gem "rr", "~> 1.0.2" -gem "flexmock", "0.8.8" - -### optional runtime deps -gem "syntax", "1.0.0" - -group :development do - platforms :mri_18, :jruby do - gem "rcov", "0.9.10" - end -end - eval File.read('Gemfile-custom') if File.exist?('Gemfile-custom') diff --git a/README.md b/README.md index 977afdd87a..7fd3de9ded 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ describe Order do end ``` -The `describe` method creates an [ExampleGroup](../RSpec/Core/ExampleGroup). Within the +The `describe` method creates an [ExampleGroup](http://rubydoc.info/gems/rspec-core/RSpec/Core/ExampleGroup). Within the block passed to `describe` you can declare examples using the `it` method. Under the hood, an example group is a class in which the block passed to diff --git a/Rakefile b/Rakefile index 15932f2b41..660c030cc8 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,4 @@ require "bundler" -Bundler.setup Bundler::GemHelper.install_tasks task :build => :raise_if_psych_is_defined diff --git a/benchmarks/check_inclusion.rb b/benchmarks/check_inclusion.rb new file mode 100644 index 0000000000..5f88872ec6 --- /dev/null +++ b/benchmarks/check_inclusion.rb @@ -0,0 +1,125 @@ +require 'benchmark' + +n = 10_000 + +num_modules = 1000 + +class Foo; end +modules = num_modules.times.map { Module.new } +modules.each {|m| Foo.send(:include, m) } + +Benchmark.benchmark do |bm| + 3.times do + bm.report do + n.times do + Foo < modules.first + end + end + end +end + +Benchmark.benchmark do |bm| + 3.times do + bm.report do + n.times do + Foo < modules.last + end + end + end +end + +Benchmark.benchmark do |bm| + 3.times do + bm.report do + n.times do + Foo.included_modules.include?(modules.first) + end + end + end +end + +Benchmark.benchmark do |bm| + 3.times do + bm.report do + n.times do + Foo.included_modules.include?(modules.last) + end + end + end +end + +#### Ruby 1.9.3 +# +# 100 modules +# < modules.first + # 0.010000 0.000000 0.010000 ( 0.005104) + # 0.000000 0.000000 0.000000 ( 0.005114) + # 0.010000 0.000000 0.010000 ( 0.005076) +# < modules.last + # 0.000000 0.000000 0.000000 ( 0.002180) + # 0.000000 0.000000 0.000000 ( 0.002199) + # 0.000000 0.000000 0.000000 ( 0.002189) +# < included_modules.include?(modules.first) + # 0.110000 0.010000 0.120000 ( 0.110062) + # 0.100000 0.000000 0.100000 ( 0.105343) + # 0.100000 0.000000 0.100000 ( 0.102770) +# < included_modules.include?(modules.last) + # 0.050000 0.010000 0.060000 ( 0.048520) + # 0.040000 0.000000 0.040000 ( 0.049013) + # 0.050000 0.000000 0.050000 ( 0.050668) + +# 1000 modules +# < modules.first + # 0.080000 0.000000 0.080000 ( 0.079460) + # 0.080000 0.000000 0.080000 ( 0.078765) + # 0.080000 0.000000 0.080000 ( 0.079560) +# < modules.last + # 0.000000 0.000000 0.000000 ( 0.002195) + # 0.000000 0.000000 0.000000 ( 0.002201) + # 0.000000 0.000000 0.000000 ( 0.002199) +# < included_modules.include?(modules.first) + # 0.860000 0.010000 0.870000 ( 0.887684) + # 0.870000 0.000000 0.870000 ( 0.875158) + # 0.870000 0.000000 0.870000 ( 0.879216) +# < included_modules.include?(modules.last) + # 0.340000 0.000000 0.340000 ( 0.344011) + # 0.350000 0.000000 0.350000 ( 0.346277) + # 0.330000 0.000000 0.330000 ( 0.335607) + +#### Ruby 1.8.7 +# +# 100 modules +# < modules.first + # 0.010000 0.000000 0.010000 ( 0.007132) + # 0.010000 0.000000 0.010000 ( 0.006869) + # 0.000000 0.000000 0.000000 ( 0.005334) +# < modules.last + # 0.010000 0.000000 0.010000 ( 0.003438) + # 0.000000 0.000000 0.000000 ( 0.003454) + # 0.000000 0.000000 0.000000 ( 0.003408) +# < included_modules.include?(modules.first) + # 0.110000 0.010000 0.120000 ( 0.113255) + # 0.110000 0.000000 0.110000 ( 0.112880) + # 0.110000 0.000000 0.110000 ( 0.121003) +# < included_modules.include?(modules.last) + # 0.040000 0.010000 0.050000 ( 0.040736) + # 0.040000 0.000000 0.040000 ( 0.039609) + # 0.030000 0.000000 0.030000 ( 0.039888) + +# 1000 modules +# < modules.first + # 0.040000 0.000000 0.040000 ( 0.044124) + # 0.050000 0.000000 0.050000 ( 0.046110) + # 0.040000 0.000000 0.040000 ( 0.042603) +# < modules.last + # 0.000000 0.000000 0.000000 ( 0.003405) + # 0.010000 0.000000 0.010000 ( 0.005510) + # 0.000000 0.000000 0.000000 ( 0.003562) +# < included_modules.include?(modules.first) + # 0.990000 0.000000 0.990000 ( 1.096331) + # 1.030000 0.010000 1.040000 ( 1.047791) + # 0.990000 0.000000 0.990000 ( 1.019169) +# < included_modules.include?(modules.last) + # 0.260000 0.000000 0.260000 ( 0.265306) + # 0.270000 0.000000 0.270000 ( 0.311985) + # 0.270000 0.000000 0.270000 ( 0.277936) diff --git a/features/mock_framework_integration/use_flexmock.feature b/features/mock_framework_integration/use_flexmock.feature index ab25a52184..261832e5c2 100644 --- a/features/mock_framework_integration/use_flexmock.feature +++ b/features/mock_framework_integration/use_flexmock.feature @@ -9,7 +9,7 @@ Feature: mock with flexmock config.mock_framework = :flexmock end - describe "mocking with RSpec" do + describe "mocking with Flexmock" do it "passes when it should" do receiver = flexmock('receiver') receiver.should_receive(:message).once @@ -27,7 +27,7 @@ Feature: mock with flexmock config.mock_framework = :flexmock end - describe "mocking with RSpec" do + describe "mocking with Flexmock" do it "fails when it should" do receiver = flexmock('receiver') receiver.should_receive(:message).once diff --git a/features/step_definitions/additional_cli_steps.rb b/features/step_definitions/additional_cli_steps.rb index fc2d8704a6..bbbe14e370 100644 --- a/features/step_definitions/additional_cli_steps.rb +++ b/features/step_definitions/additional_cli_steps.rb @@ -11,8 +11,8 @@ end Then /^the example(?:s)? should(?: all)? pass$/ do - Then %q{the output should contain "0 failures"} - Then %q{the exit status should be 0} + step %q{the output should contain "0 failures"} + step %q{the exit status should be 0} end Then /^the file "([^"]*)" should contain:$/ do |file, partial_content| diff --git a/features/subject/attribute_of_subject.feature b/features/subject/attribute_of_subject.feature index 879b1db431..0f09049c5b 100644 --- a/features/subject/attribute_of_subject.feature +++ b/features/subject/attribute_of_subject.feature @@ -1,10 +1,12 @@ Feature: attribute of subject - Use the its() method as a short-hand to generate a nested example group with + WARNING: `its` will be extracted from rspec-core-3.0 into its own gem. + + Use the `its` method as a short-hand to generate a nested example group with a single example that specifies the expected value of an attribute of the subject. This can be used with an implicit or explicit subject. - its() accepts a symbol or a string, and a block representing the example. + `its` accepts a symbol or a string, and a block representing the example. its(:size) { should eq(1) } its("length") { should eq(1) } diff --git a/features/subject/explicit_subject.feature b/features/subject/explicit_subject.feature index 19cf52a566..cdf8ae69b5 100644 --- a/features/subject/explicit_subject.feature +++ b/features/subject/explicit_subject.feature @@ -1,7 +1,11 @@ Feature: explicit subject - Use subject() in the group scope to explicitly define the value that is - returned by the subject() method in the example scope. + Use `subject` in the group scope to explicitly define the value that is + returned by the `subject` method in the example scope. + + Note that while the examples below demonstrate how `subject` can be used as a + user-facing concept, we recommend that you reserve it for support of custom + matchers and/or extension libraries that hide its use from examples. Scenario: subject in top level group Given a file named "top_level_subject_spec.rb" with: diff --git a/features/subject/implicit_receiver.feature b/features/subject/implicit_receiver.feature index 4f5730f0e8..5f9518d521 100644 --- a/features/subject/implicit_receiver.feature +++ b/features/subject/implicit_receiver.feature @@ -1,6 +1,6 @@ Feature: implicit receiver - When should() is called in an example without an explicit receiver, it is + When `should` is called in an example without an explicit receiver, it is invoked against the subject (explicit or implicit). Scenario: implicit subject diff --git a/features/subject/implicit_subject.feature b/features/subject/implicit_subject.feature index 398f6bca36..c7f3ceca55 100644 --- a/features/subject/implicit_subject.feature +++ b/features/subject/implicit_subject.feature @@ -1,14 +1,18 @@ -Feature: implicit subject +Feature: implicitly defined subject If the first argument to the outermost example group is a class, an instance - of that class is exposed to each example via the subject() method. + of that class is exposed to each example via the `subject` method. + + While the examples below demonstrate how `subject` can be used as a + user-facing concept, we recommend that you reserve it for support of custom + matchers and/or extension libraries that hide its use from examples. - Scenario: subject in top level group + Scenario: subject exposed in top level group Given a file named "top_level_subject_spec.rb" with: """ - describe Array, "when first created" do - it "should be empty" do - subject.should eq([]) + describe Array do + it "should be empty when first created" do + subject.should be_empty end end """ @@ -21,7 +25,36 @@ Feature: implicit subject describe Array do describe "when first created" do it "should be empty" do - subject.should eq([]) + subject.should be_empty + end + end + end + """ + When I run `rspec nested_subject_spec.rb` + Then the examples should all pass + + Scenario: subject in a nested group with a different class (outermost wins) + Given a file named "nested_subject_spec.rb" with: + """ + class ArrayWithOneElement < Array + def initialize(*) + super + unshift "first element" + end + end + + describe Array do + describe ArrayWithOneElement do + context "referenced as subject" do + it "should be empty (because it is the Array declared at the top)" do + subject.should be_empty + end + end + + context "created in the example" do + it "should not be empty" do + ArrayWithOneElement.new.should_not be_empty + end end end end diff --git a/lib/autotest/rspec2.rb b/lib/autotest/rspec2.rb index 9a5788612a..50d07f6c1c 100644 --- a/lib/autotest/rspec2.rb +++ b/lib/autotest/rspec2.rb @@ -47,7 +47,7 @@ def consolidate_failures(failed) # Overrides Autotest's implementation to generate the rspec command to run def make_test_cmd(files_to_test) files_to_test.empty? ? '' : - "#{prefix}#{ruby}#{suffix} -S #{RSPEC_EXECUTABLE} --tty #{normalize(files_to_test).keys.flatten.map { |f| "'#{f}'"}.join(' ')}" + "#{prefix}#{ruby}#{suffix} -S '#{RSPEC_EXECUTABLE}' --tty #{normalize(files_to_test).keys.flatten.map { |f| "'#{f}'"}.join(' ')}" end # Generates a map of filenames to Arrays for Autotest diff --git a/lib/rspec/core.rb b/lib/rspec/core.rb index c402112a9d..37f1d9631b 100644 --- a/lib/rspec/core.rb +++ b/lib/rspec/core.rb @@ -10,6 +10,7 @@ def require_rspec(path) end end +require 'set' require_rspec 'core/filter_manager' require_rspec 'core/dsl' require_rspec 'core/extensions' @@ -104,4 +105,3 @@ module Core end require_rspec 'core/backward_compatibility' -require_rspec 'monkey' diff --git a/lib/rspec/core/configuration.rb b/lib/rspec/core/configuration.rb index 4ec09fbcf4..8540273348 100644 --- a/lib/rspec/core/configuration.rb +++ b/lib/rspec/core/configuration.rb @@ -534,10 +534,26 @@ def alias_it_should_behave_like_to(new_name, report_label = '') # or config files (e.g. `.rspec`). # # @example - # filter_run_including :x => 'y' + # # given this declaration + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # any of the following will include that group + # config.filter_run_including :foo => 'bar' + # config.filter_run_including :foo => /^ba/ + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # given a proc with an arity of 1, the lambda is passed the value related to the key, e.g. + # config.filter_run_including :foo => lambda {|v| v == 'bar'} + # + # # given a proc with an arity of 2, the lambda is passed the value related to the key, + # # and the metadata itself e.g. + # config.filter_run_including :foo => lambda {|v,m| m[:foo] == 'bar'} # # # with treat_symbols_as_metadata_keys_with_true_values = true - # filter_run_including :foo # results in {:foo => true} + # filter_run_including :foo # same as filter_run_including :foo => true def filter_run_including(*args) filter_manager.include_with_low_priority build_metadata_hash_from(args) end @@ -576,10 +592,26 @@ def inclusion_filter # or config files (e.g. `.rspec`). # # @example - # filter_run_excluding :x => 'y' + # # given this declaration + # describe "something", :foo => 'bar' do + # # ... + # end + # + # # any of the following will exclude that group + # config.filter_run_excluding :foo => 'bar' + # config.filter_run_excluding :foo => /^ba/ + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} + # + # # given a proc with an arity of 1, the lambda is passed the value related to the key, e.g. + # config.filter_run_excluding :foo => lambda {|v| v == 'bar'} + # + # # given a proc with an arity of 2, the lambda is passed the value related to the key, + # # and the metadata itself e.g. + # config.filter_run_excluding :foo => lambda {|v,m| m[:foo] == 'bar'} # # # with treat_symbols_as_metadata_keys_with_true_values = true - # filter_run_excluding :foo # results in {:foo => true} + # filter_run_excluding :foo # same as filter_run_excluding :foo => true def filter_run_excluding(*args) filter_manager.exclude_with_low_priority build_metadata_hash_from(args) end @@ -677,7 +709,23 @@ def extend(mod, *filters) def configure_group(group) include_or_extend_modules.each do |include_or_extend, mod, filters| next unless filters.empty? || group.any_apply?(filters) - group.send(include_or_extend, mod) + send("safe_#{include_or_extend}", mod, group) + end + end + + # @private + def safe_include(mod, host) + host.send(:include,mod) unless host < mod + end + + # @private + if RUBY_VERSION.to_f >= 1.9 + def safe_extend(mod, host) + host.extend(mod) unless (class << host; self; end) < mod + end + else + def safe_extend(mod, host) + host.extend(mod) unless (class << host; self; end).included_modules.include?(mod) end end diff --git a/lib/rspec/core/configuration_options.rb b/lib/rspec/core/configuration_options.rb index 856e27c966..d52d8b5b2f 100644 --- a/lib/rspec/core/configuration_options.rb +++ b/lib/rspec/core/configuration_options.rb @@ -33,12 +33,15 @@ def drb_argv end def filter_manager - @filter_manager ||= FilterManager.new + @filter_manager ||= RSpec::configuration.filter_manager end private - NON_FORCED_OPTIONS = [:debug, :requires, :libs, :files_or_directories_to_run, :line_numbers, :full_description] + NON_FORCED_OPTIONS = [ + :debug, :requires, :libs, :profile, :drb, :files_or_directories_to_run, + :line_numbers, :full_description, :full_backtrace, :tty + ].to_set def force?(key) !NON_FORCED_OPTIONS.include?(key) diff --git a/lib/rspec/core/drb_options.rb b/lib/rspec/core/drb_options.rb index 9d5b80df07..57e28200e9 100644 --- a/lib/rspec/core/drb_options.rb +++ b/lib/rspec/core/drb_options.rb @@ -52,9 +52,11 @@ def add_line_numbers(argv) end end + CONDITIONAL_FILTERS = [:if, :unless] + def add_filter(argv, name, hash) hash.each_pair do |k, v| - next if [:if,:unless].include?(k) + next if CONDITIONAL_FILTERS.include?(k) tag = name == :inclusion ? k.to_s : "~#{k}" tag << ":#{v}" if v.is_a?(String) argv << "--tag" << tag diff --git a/lib/rspec/core/filter_manager.rb b/lib/rspec/core/filter_manager.rb index 4eeaeef7df..3dbdc70770 100644 --- a/lib/rspec/core/filter_manager.rb +++ b/lib/rspec/core/filter_manager.rb @@ -67,7 +67,7 @@ module Core # @see Configuration#filter_run_excluding class FilterManager DEFAULT_EXCLUSIONS = { - :if => lambda { |value, metadata| metadata.has_key?(:if) && !value }, + :if => lambda { |value| !value }, :unless => lambda { |value| value } } diff --git a/lib/rspec/core/formatters/base_text_formatter.rb b/lib/rspec/core/formatters/base_text_formatter.rb index 9df8901b57..cd771f7128 100644 --- a/lib/rspec/core/formatters/base_text_formatter.rb +++ b/lib/rspec/core/formatters/base_text_formatter.rb @@ -35,7 +35,7 @@ def dump_summary(duration, example_count, failure_count, pending_count) super(duration, example_count, failure_count, pending_count) # Don't print out profiled info if there are failures, it just clutters the output dump_profile if profile_examples? && failure_count == 0 - output.puts "\nFinished in #{format_seconds(duration)} seconds\n" + output.puts "\nFinished in #{format_duration(duration)}\n" output.puts colorise_summary(summary_line(example_count, failure_count, pending_count)) dump_commands_to_rerun_failed_examples end @@ -142,10 +142,6 @@ def long_padding private - def pluralize(count, string) - "#{count} #{string}#{'s' unless count == 1}" - end - def format_caller(caller_info) backtrace_line(caller_info.to_s.split(':in `block').first) end diff --git a/lib/rspec/core/formatters/documentation_formatter.rb b/lib/rspec/core/formatters/documentation_formatter.rb index 38dd1aaa7b..f65d2dfd2e 100644 --- a/lib/rspec/core/formatters/documentation_formatter.rb +++ b/lib/rspec/core/formatters/documentation_formatter.rb @@ -3,9 +3,7 @@ module RSpec module Core module Formatters - class DocumentationFormatter < BaseTextFormatter - def initialize(output) super(output) @group_level = 0 @@ -15,7 +13,7 @@ def example_group_started(example_group) super(example_group) output.puts if @group_level == 0 - output.puts "#{current_indentation}#{example_group.description}" + output.puts "#{current_indentation}#{example_group.description.strip}" @group_level += 1 end @@ -40,7 +38,7 @@ def example_failed(example) end def failure_output(example, exception) - red("#{current_indentation}#{example.description} (FAILED - #{next_failure_index})") + red("#{current_indentation}#{example.description.strip} (FAILED - #{next_failure_index})") end def next_failure_index @@ -49,11 +47,11 @@ def next_failure_index end def passed_output(example) - green("#{current_indentation}#{example.description}") + green("#{current_indentation}#{example.description.strip}") end def pending_output(example, message) - yellow("#{current_indentation}#{example.description} (PENDING: #{message})") + yellow("#{current_indentation}#{example.description.strip} (PENDING: #{message})") end def current_indentation @@ -63,9 +61,7 @@ def current_indentation def example_group_chain example_group.ancestors.reverse end - end - end end end diff --git a/lib/rspec/core/formatters/helpers.rb b/lib/rspec/core/formatters/helpers.rb index 916706d947..5da56896fb 100644 --- a/lib/rspec/core/formatters/helpers.rb +++ b/lib/rspec/core/formatters/helpers.rb @@ -6,6 +6,17 @@ module Helpers SUB_SECOND_PRECISION = 5 DEFAULT_PRECISION = 2 + def format_duration(duration) + if duration > 60 + minutes = duration.to_i / 60 + seconds = duration - minutes * 60 + + "#{pluralize(minutes, 'minute')} #{format_seconds(seconds)} seconds" + else + "#{format_seconds(duration)} seconds" + end + end + def format_seconds(float) precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION formatted = sprintf("%.#{precision}f", float) @@ -17,6 +28,10 @@ def strip_trailing_zeroes(string) stripped.empty? ? "0" : stripped end + def pluralize(count, string) + "#{count} #{string}#{'s' unless count == 1}" + end + end end diff --git a/lib/rspec/core/hooks.rb b/lib/rspec/core/hooks.rb index 1cea9b193b..b57c23ed5c 100644 --- a/lib/rspec/core/hooks.rb +++ b/lib/rspec/core/hooks.rb @@ -398,8 +398,10 @@ def find_hook(hook, scope, example_group_class, example = nil) private + SCOPES = [:each, :all, :suite] + def scope_and_options_from(*args) - scope = if [:each, :all, :suite].include?(args.first) + scope = if SCOPES.include?(args.first) args.shift elsif args.any? { |a| a.is_a?(Symbol) } raise ArgumentError.new("You must explicitly give a scope (:each, :all, or :suite) when using symbols as metadata for a hook.") diff --git a/lib/rspec/core/metadata.rb b/lib/rspec/core/metadata.rb index fcb40a8e58..df2e2c453b 100644 --- a/lib/rspec/core/metadata.rb +++ b/lib/rspec/core/metadata.rb @@ -102,8 +102,8 @@ module GroupMetadataHash def described_class container_stack.each do |g| - return g[:describes] if g.has_key?(:describes) return g[:described_class] if g.has_key?(:described_class) + return g[:describes] if g.has_key?(:describes) end container_stack.reverse.each do |g| @@ -174,20 +174,15 @@ def filter_applies?(key, value, metadata=self) return metadata.location_filter_applies?(value) if key == :locations return metadata.filters_apply?(key, value) if Hash === value + return false unless metadata.has_key?(key) + case value when Regexp metadata[key] =~ value when Proc - if value.arity == 2 - # Pass the metadata hash to allow the proc to check if it even has the key. - # This is necessary for the implicit :if exclusion filter: - # { } # => run the example - # { :if => nil } # => exclude the example - # The value of metadata[:if] is the same in these two cases but - # they need to be treated differently. - value.call(metadata[key], metadata) rescue false - else - value.call(metadata[key]) rescue false + case value.arity + when 1 then value.call(metadata[key]) + when 2 then value.call(metadata[key], metadata) end else metadata[key].to_s == value.to_s diff --git a/lib/rspec/core/runner.rb b/lib/rspec/core/runner.rb index b8c279ea22..4f45c727d7 100644 --- a/lib/rspec/core/runner.rb +++ b/lib/rspec/core/runner.rb @@ -7,7 +7,7 @@ class Runner # Register an at_exit hook that runs the suite. def self.autorun return if autorun_disabled? || installed_at_exit? || running_in_drb? - at_exit { exit run(ARGV, $stderr, $stdout).to_i } + at_exit { exit run(ARGV, $stderr, $stdout).to_i unless $! } @installed_at_exit = true end AT_EXIT_HOOK_BACKTRACE_LINE = "#{__FILE__}:#{__LINE__ - 2}:in `autorun'" @@ -60,27 +60,17 @@ def self.run(args, err=$stderr, out=$stdout) if options.options[:drb] begin - run_over_drb(options, err, out) + DRbCommandLine.new(options).run(err, out) rescue DRb::DRbConnError err.puts "No DRb server is running. Running in local process instead ..." - run_in_process(options, err, out) + CommandLine.new(options).run(err, out) end else - run_in_process(options, err, out) + CommandLine.new(options).run(err, out) end ensure RSpec.reset end - - def self.run_over_drb(options, err, out) - DRbCommandLine.new(options).run(err, out) - end - - def self.run_in_process(options, err, out) - CommandLine.new(options, RSpec::configuration, RSpec::world).run(err, out) - end - end - end end diff --git a/lib/rspec/core/subject.rb b/lib/rspec/core/subject.rb index 39f0fce058..8cf6c31490 100644 --- a/lib/rspec/core/subject.rb +++ b/lib/rspec/core/subject.rb @@ -67,6 +67,16 @@ def should_not(matcher=nil, message=nil) end rescue LoadError end + + def _attribute_chain(attribute) + attribute.to_s.split('.') + end + + def _nested_attribute(subject, attribute) + _attribute_chain(attribute).inject(subject) do |subject, attr| + subject.send(attr) + end + end end module ExampleGroupMethods @@ -122,13 +132,11 @@ def its(attribute, &block) example do self.class.class_eval do define_method(:subject) do - @_subject ||= if attribute.is_a?(Array) - super()[*attribute] - else - attribute.to_s.split('.').inject(super()) do |target, method| - target.send(method) - end - end + if defined?(@_subject) + @_subject + else + @_subject = Array === attribute ? super()[*attribute] : _nested_attribute(super(), attribute) + end end end instance_eval(&block) diff --git a/lib/rspec/core/version.rb b/lib/rspec/core/version.rb index 6dc27714b9..bedc26e560 100644 --- a/lib/rspec/core/version.rb +++ b/lib/rspec/core/version.rb @@ -1,7 +1,7 @@ module RSpec module Core module Version - STRING = '2.8.0' + STRING = '2.9.0' end end end diff --git a/lib/rspec/core/world.rb b/lib/rspec/core/world.rb index e07fac8b74..be5ecc66f3 100644 --- a/lib/rspec/core/world.rb +++ b/lib/rspec/core/world.rb @@ -4,12 +4,13 @@ class World include RSpec::Core::Hooks - attr_reader :example_groups, :filtered_examples, :wants_to_quit - attr_writer :wants_to_quit + attr_reader :example_groups, :shared_example_groups, :filtered_examples + attr_accessor :wants_to_quit def initialize(configuration=RSpec.configuration) @configuration = configuration @example_groups = [].extend(Extensions::Ordered) + @shared_example_groups = {} @filtered_examples = Hash.new { |hash,group| hash[group] = begin examples = group.examples.dup @@ -22,6 +23,7 @@ def initialize(configuration=RSpec.configuration) def reset example_groups.clear + shared_example_groups.clear end def filter_manager @@ -45,12 +47,10 @@ def configure_group(group) @configuration.configure_group(group) end - def shared_example_groups - @shared_example_groups ||= {} - end - def example_count - example_groups.collect {|g| g.descendants}.flatten.inject(0) { |sum, g| sum += g.filtered_examples.size } + example_groups.collect {|g| g.descendants}.flatten.inject(0) do |sum, g| + sum += g.filtered_examples.size + end end def preceding_declaration_line(filter_line) diff --git a/lib/rspec/monkey.rb b/lib/rspec/monkey.rb deleted file mode 100644 index 6b752a358f..0000000000 --- a/lib/rspec/monkey.rb +++ /dev/null @@ -1 +0,0 @@ -require_rspec 'monkey/spork/test_framework/rspec.rb' diff --git a/lib/rspec/monkey/spork/test_framework/rspec.rb b/lib/rspec/monkey/spork/test_framework/rspec.rb deleted file mode 100644 index 1471103b3c..0000000000 --- a/lib/rspec/monkey/spork/test_framework/rspec.rb +++ /dev/null @@ -1,10 +0,0 @@ -# TODO (2011-05-08) - remove this as soon as spork 0.9.0 is released -if defined?(Spork::TestFramework::RSpec) - # @private - class Spork::TestFramework::RSpec < Spork::TestFramework - # @private - def run_tests(argv, err, out) - ::RSpec::Core::CommandLine.new(argv).run(err, out) - end - end -end diff --git a/rspec-core.gemspec b/rspec-core.gemspec index cdee9c28d1..106ec4846d 100644 --- a/rspec-core.gemspec +++ b/rspec-core.gemspec @@ -16,11 +16,21 @@ Gem::Specification.new do |s| s.rubyforge_project = "rspec" s.files = `git ls-files -- lib/*`.split("\n") - s.files += ["License.txt"] + s.files += %w[README.md License.txt Changelog.md .yardopts .document] s.test_files = `git ls-files -- {spec,features}/*`.split("\n") s.bindir = 'exe' s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) } - s.extra_rdoc_files = [ "README.md", "License.txt"] s.rdoc_options = ["--charset=UTF-8"] s.require_path = "lib" + + s.add_development_dependency "cucumber", "~> 1.1.9" + s.add_development_dependency "aruba", "~> 0.4.11" + s.add_development_dependency "ZenTest", "4.6.2" + s.add_development_dependency "nokogiri", "1.5.2" + s.add_development_dependency "fakefs", "0.4.0" + s.add_development_dependency "syntax", "1.0.0" + + s.add_development_dependency "mocha", "~> 0.10.5" + s.add_development_dependency "rr", "~> 1.0.4" + s.add_development_dependency "flexmock", "~> 0.9.0" end diff --git a/spec/autotest/rspec_spec.rb b/spec/autotest/rspec_spec.rb index 67de29ce3c..69d4be262a 100644 --- a/spec/autotest/rspec_spec.rb +++ b/spec/autotest/rspec_spec.rb @@ -30,7 +30,7 @@ it "makes the appropriate test command" do actual_command = rspec_autotest.make_test_cmd(@files_to_test) - expected_command = /#{ruby_cmd}.*#{spec_cmd} (.*)/ + expected_command = /#{ruby_cmd}.*'#{spec_cmd}' (.*)/ actual_command.should match(expected_command) diff --git a/spec/rspec/core/configuration_options_spec.rb b/spec/rspec/core/configuration_options_spec.rb index c783f70dc0..3d0c28acba 100644 --- a/spec/rspec/core/configuration_options_spec.rb +++ b/spec/rspec/core/configuration_options_spec.rb @@ -74,13 +74,9 @@ ["--pattern", "foo/bar", :pattern, "foo/bar"], ["--failure-exit-code", "37", :failure_exit_code, 37], ["--default_path", "behavior", :default_path, "behavior"], - ["--drb", nil, :drb, true], ["--order", "rand", :order, "rand"], ["--seed", "37", :order, "rand:37"], - ["--drb-port", "37", :drb_port, 37], - ["--backtrace", nil, :full_backtrace, true], - ["--profile", nil, :profile_examples, true], - ["--tty", nil, :tty, true] + ["--drb-port", "37", :drb_port, 37] ].each do |cli_option, cli_value, config_key, config_value| it "forces #{config_key}" do opts = config_options_object(*[cli_option, cli_value].compact) @@ -305,6 +301,13 @@ end end + describe "#filter_manager" do + it "returns the same object as RSpec::configuration.filter_manager" do + config_options_object.filter_manager. + should be(RSpec::configuration.filter_manager) + end + end + describe "sources: ~/.rspec, ./.rspec, custom, CLI, and SPEC_OPTS" do before(:each) do FileUtils.mkpath(File.expand_path("~")) diff --git a/spec/rspec/core/configuration_spec.rb b/spec/rspec/core/configuration_spec.rb index a614439157..ac178cdee5 100644 --- a/spec/rspec/core/configuration_spec.rb +++ b/spec/rspec/core/configuration_spec.rb @@ -713,20 +713,16 @@ def metadata_hash(*args) end describe "the default :if filter" do - it "does not exclude a spec with no :if metadata" do - config.exclusion_filter[:if].call(nil, {}).should be_false + it "does not exclude a spec with { :if => true } metadata" do + config.exclusion_filter[:if].call(true).should be_false end - it "does not exclude a spec with { :if => true } metadata" do - config.exclusion_filter[:if].call(true, {:if => true}).should be_false + it "excludes a spec with { :if => false } metadata" do + config.exclusion_filter[:if].call(false).should be_true end - it "excludes a spec with { :if => false } metadata" do - config.exclusion_filter[:if].call(false, {:if => false}).should be_true - end - - it "excludes a spec with { :if => nil } metadata" do - config.exclusion_filter[:if].call(false, {:if => nil}).should be_true + it "excludes a spec with { :if => nil } metadata" do + config.exclusion_filter[:if].call(nil).should be_true end end @@ -1005,6 +1001,38 @@ def self.included(host) group.included_modules.should include(mod1) group.included_modules.should include(mod2) end + + module IncludeOrExtendMeOnce + def self.included(host) + raise "included again" if host.instance_methods.include?(:foobar) + host.class_eval { def foobar; end } + end + + def self.extended(host) + raise "extended again" if host.respond_to?(:foobar) + def host.foobar; end + end + end + + it "doesn't include a module when already included in ancestor" do + config.include(IncludeOrExtendMeOnce, :foo => :bar) + + group = ExampleGroup.describe("group", :foo => :bar) + child = group.describe("child") + + config.configure_group(group) + config.configure_group(child) + end + + it "doesn't extend when ancestor is already extended with same module" do + config.extend(IncludeOrExtendMeOnce, :foo => :bar) + + group = ExampleGroup.describe("group", :foo => :bar) + child = group.describe("child") + + config.configure_group(group) + config.configure_group(child) + end end describe "#alias_example_to" do diff --git a/spec/rspec/core/example_group_spec.rb b/spec/rspec/core/example_group_spec.rb index 6b54cbb8e7..cb7760be41 100644 --- a/spec/rspec/core/example_group_spec.rb +++ b/spec/rspec/core/example_group_spec.rb @@ -293,6 +293,22 @@ def metadata_hash(*args) group.run.should be_true end end + + context "and metadata redefinition after `described_class` call" do + it "is the redefined level constant" do + group = ExampleGroup.describe(String) do + described_class + metadata[:example_group][:described_class] = Object + describe :symbol do + example "described_class is Object" do + described_class.should eq(Object) + end + end + end + + group.run.should be_true + end + end end context "in a nested group" do diff --git a/spec/rspec/core/formatters/documentation_formatter_spec.rb b/spec/rspec/core/formatters/documentation_formatter_spec.rb index 841ee541e5..002f9cb00b 100644 --- a/spec/rspec/core/formatters/documentation_formatter_spec.rb +++ b/spec/rspec/core/formatters/documentation_formatter_spec.rb @@ -28,7 +28,6 @@ module RSpec::Core::Formatters end it "represents nested group using hierarchy tree" do - output = StringIO.new RSpec.configuration.stub(:color_enabled?) { false } @@ -60,6 +59,29 @@ module RSpec::Core::Formatters context 2 nested example 2.1 nested example 2.2 +") + end + + it "strips whitespace for each row" do + output = StringIO.new + RSpec.configuration.stub(:color_enabled?) { false } + + formatter = RSpec::Core::Formatters::DocumentationFormatter.new(output) + + group = RSpec::Core::ExampleGroup.describe(" root ") + context1 = group.describe(" nested ") + context1.example(" example 1 ") {} + context1.example(" example 2 ", :pending => true){} + context1.example(" example 3 ") { fail } + + group.run(RSpec::Core::Reporter.new(formatter)) + + output.string.should eql(" +root + nested + example 1 + example 2 (PENDING: No reason given) + example 3 (FAILED - 1) ") end end diff --git a/spec/rspec/core/formatters/helpers_spec.rb b/spec/rspec/core/formatters/helpers_spec.rb index c9f54c0081..5d12c78503 100644 --- a/spec/rspec/core/formatters/helpers_spec.rb +++ b/spec/rspec/core/formatters/helpers_spec.rb @@ -4,6 +4,26 @@ describe RSpec::Core::Formatters::Helpers do let(:helper) { Object.new.extend(RSpec::Core::Formatters::Helpers) } + describe "format duration" do + context '> 60 and < 120' do + it "returns 'x minute xx seconds' formatted string" do + helper.format_duration(70.14).should eq("1 minute 10.14 seconds") + end + end + + context '> 120' do + it "returns 'x minutes xx seconds' formatted string" do + helper.format_duration(135.14).should eq("2 minutes 15.14 seconds") + end + end + + context '< 60' do + it "returns 'xx seconds' formatted string" do + helper.format_duration(45.5).should eq("45.5 seconds") + end + end + end + describe "format seconds" do context "sub second times" do it "returns 5 digits of precision" do diff --git a/spec/rspec/core/metadata_spec.rb b/spec/rspec/core/metadata_spec.rb index aa31f26338..36187c2ba1 100644 --- a/spec/rspec/core/metadata_spec.rb +++ b/spec/rspec/core/metadata_spec.rb @@ -126,10 +126,9 @@ module Core example_metadata.filter_applies?(:if, lambda { |v| !v }).should be_false end - it "passes the metadata hash as the second argument if a given proc expects 2 args" do - passed_metadata = nil - example_metadata.filter_applies?(:if, lambda { |v, m| passed_metadata = m }) - passed_metadata.should eq(example_metadata) + it "matches a proc with an arity of 2" do + example_metadata[:foo] = nil + example_metadata.filter_applies?(:foo, lambda { |v, m| m == example_metadata }).should be_true end context "with an Array" do diff --git a/spec/rspec/core/shared_example_group_spec.rb b/spec/rspec/core/shared_example_group_spec.rb index 35af023f9e..7c661c940a 100644 --- a/spec/rspec/core/shared_example_group_spec.rb +++ b/spec/rspec/core/shared_example_group_spec.rb @@ -2,7 +2,7 @@ module RSpec::Core describe SharedExampleGroup do - + ExampleModule = Module.new ExampleClass = Class.new @@ -19,7 +19,7 @@ module RSpec::Core group.send(method_name, 'shared group') {} end.should raise_error(ArgumentError, "Shared example group 'shared group' already exists") end - + ["name", :name, ExampleModule, ExampleClass].each do |object| type = object.class.name.downcase context "given a #{type}" do diff --git a/spec/rspec/core/subject_spec.rb b/spec/rspec/core/subject_spec.rb index ef37f6c733..146724cbeb 100644 --- a/spec/rspec/core/subject_spec.rb +++ b/spec/rspec/core/subject_spec.rb @@ -194,6 +194,35 @@ def subject; super().first; end end end + context "with nil subject" do + subject do + Class.new do + def initialize + @counter = -1 + end + def nil_if_first_time + @counter += 1 + @counter == 0 ? nil : true + end + end.new + end + its(:nil_if_first_time) { should be(nil) } + end + + context "with false subject" do + subject do + Class.new do + def initialize + @counter = -1 + end + def false_if_first_time + @counter += 1 + @counter > 0 + end + end.new + end + its(:false_if_first_time) { should be(false) } + end end end end diff --git a/spec/rspec/core/world_spec.rb b/spec/rspec/core/world_spec.rb index 64f2b0af23..a56f87a478 100644 --- a/spec/rspec/core/world_spec.rb +++ b/spec/rspec/core/world_spec.rb @@ -9,6 +9,16 @@ module RSpec::Core let(:configuration) { RSpec::Core::Configuration.new } let(:world) { RSpec::Core::World.new(configuration) } + describe '#reset' do + it 'clears #example_groups and #shared_example_groups' do + world.example_groups << :example_group + world.shared_example_groups[:shared] = :example_group + world.reset + world.example_groups.should be_empty + world.shared_example_groups.should be_empty + end + end + describe "#example_groups" do it "contains all registered example groups" do group = RSpec::Core::ExampleGroup.describe("group"){} @@ -64,7 +74,7 @@ module RSpec::Core context "with two exaples and the second example is registre first" do let(:second_group_declaration_line) { second_group.metadata[:example_group][:line_number] } - before do + before do world.register(second_group) world.register(group) end