diff options
37 files changed, 376 insertions, 328 deletions
@@ -11,7 +11,7 @@ gem 'coffee-rails', '~> 4.0.0.beta1' # This needs to be with require false to avoid # it being automatically loaded by sprockets -gem 'uglifier', require: false +gem 'uglifier', '~> 1.3', require: false group :doc do gem 'sdoc' diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index bb84a83208..913edbd8df 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,13 @@ ## Rails 4.0.0 (unreleased) ## +* Fix explicit names on multiple file fields. If a file field tag has + the multiple option, it is turned into an array field (appending `[]`), + but if an explicit name is passed to `file_field` the `[]` is not + appended. + Fixes #9830. + + *Ryan McGeary* + * Add block support for the `mail_to` helper, similar to the `link_to` helper. *Sam Pohlenz* diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 2c88bc3b1f..24a3d4741e 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -82,10 +82,10 @@ module ActionDispatch end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' + autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' + autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' end mattr_accessor :test_app diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb index 2372d3c433..361a0dccbe 100644 --- a/actionpack/lib/action_view/buffers.rb +++ b/actionpack/lib/action_view/buffers.rb @@ -8,9 +8,15 @@ module ActionView end def <<(value) + return self if value.nil? super(value.to_s) end alias :append= :<< + + def safe_concat(value) + return self if value.nil? + super(value.to_s) + end alias :safe_append= :safe_concat end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 31e37893c6..521eaf25d2 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -58,7 +58,7 @@ module ActionView sources.uniq.map { |source| tag_options = { "src" => path_to_javascript(source, path_options) - }.merge(options) + }.merge!(options) content_tag(:script, "", tag_options) }.join("\n").html_safe end @@ -98,7 +98,7 @@ module ActionView "rel" => "stylesheet", "media" => "screen", "href" => path_to_stylesheet(source, path_options) - }.merge(options) + }.merge!(options) tag(:link, tag_options) }.join("\n").html_safe end @@ -166,7 +166,7 @@ module ActionView :rel => 'shortcut icon', :type => 'image/vnd.microsoft.icon', :href => path_to_image(source) - }.merge(options.symbolize_keys)) + }.merge!(options.symbolize_keys)) end # Returns an HTML image tag for the +source+. The +source+ can be a full diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index aef1572290..10dec66b4f 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -73,27 +73,26 @@ module ActionView def add_default_name_and_id(options) if options.has_key?("index") - options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) } + options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) } options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } options.delete("index") elsif defined?(@auto_index) - options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) } + options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) } options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= options.fetch("name"){ tag_name } + options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) } options["id"] = options.fetch("id"){ tag_id } end - options["name"] += "[]" if options["multiple"] && !options["name"].ends_with?("[]") options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence end - def tag_name - "#{@object_name}[#{sanitized_method_name}]" + def tag_name(multiple = false) + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" end - def tag_name_with_index(index) - "#{@object_name}[#{index}][#{sanitized_method_name}]" + def tag_name_with_index(index, multiple = false) + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" end def tag_id diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 5aaafc15c1..7d7a7af51d 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -6,12 +6,23 @@ module ActionView module Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) + @newline_pending = 0 src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" end def add_text(src, text) return if text.empty? - src << "@output_buffer.safe_concat('" << escape_text(text) << "');" + + if text == "\n" + @newline_pending += 1 + else + src << "@output_buffer.safe_append='" + src << "\n" * @newline_pending if @newline_pending > 0 + src << escape_text(text) + src << "';" + + @newline_pending = 0 + end end # Erubis toggles <%= and <%== behavior when escaping is enabled. @@ -28,24 +39,39 @@ module ActionView BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ def add_expr_literal(src, code) + flush_newline_if_pending(src) if code =~ BLOCK_EXPR src << '@output_buffer.append= ' << code else - src << '@output_buffer.append= (' << code << ');' + src << '@output_buffer.append=(' << code << ');' end end def add_expr_escaped(src, code) + flush_newline_if_pending(src) if code =~ BLOCK_EXPR src << "@output_buffer.safe_append= " << code else - src << "@output_buffer.safe_concat((" << code << ").to_s);" + src << "@output_buffer.safe_append=(" << code << ");" end end + def add_stmt(src, code) + flush_newline_if_pending(src) + super + end + def add_postamble(src) + flush_newline_if_pending(src) src << '@output_buffer.to_s' end + + def flush_newline_if_pending(src) + if @newline_pending > 0 + src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';" + @newline_pending = 0 + end + end end class ERB diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index dff0b8bdc2..1ff320224d 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -361,6 +361,16 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, file_field("user", "avatar") end + def test_file_field_with_multiple_behavior + expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />' + assert_dom_equal expected, file_field("import", "file", :multiple => true) + end + + def test_file_field_with_multiple_behavior_and_explicit_name + expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />' + assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom") + end + def test_hidden_field assert_dom_equal( '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />', diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e1d3686b95..5891de9841 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* `#default_scopes?` is deprecated. Instead, do something like + `Post.default_scopes.empty?`. + + *Agis Anastasopoulos* + +* Default values for PostgreSQL bigint types now get parsed and dumped to the + schema correctly. + + *Erik Peterson* + * Fix associations with `:inverse_of` option when building association with a block. Inside the block the parent object was different then after the block. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e34e1fc10c..bf403c3ae0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -80,7 +80,7 @@ module ActiveRecord when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m $1 # Numeric types - when /\A\(?(-?\d+(\.\d*)?\)?)\z/ + when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ $1 # Character types when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index db5e7b82ca..afa1766615 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -344,6 +344,10 @@ module ActiveRecord self.class.connection end + def connection_handler + self.class.connection_handler + end + # Returns the contents of the record as a nicely formatted string. def inspect inspection = if @attributes diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 78afed5e91..93bbeea5a3 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -166,7 +166,7 @@ db_namespace = namespace :db do end # desc "Raises an error if there are pending migrations" - task :abort_if_pending_migrations => [:environment, :load_config] do + task :abort_if_pending_migrations => :environment do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations if pending_migrations.any? @@ -347,7 +347,7 @@ db_namespace = namespace :db do end # desc 'Check for pending migrations and load the test schema' - task :prepare => 'db:abort_if_pending_migrations' do + task :prepare do unless ActiveRecord::Base.configurations.blank? db_namespace['test:load'].invoke end @@ -383,5 +383,5 @@ namespace :railties do end end -task 'test:prepare' => 'db:test:prepare' +task 'test:prepare' => ['db:test:prepare', 'db:test:load_schema', 'db:abort_if_pending_migrations'] diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index cde4f29d08..8a90528ce8 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -5,8 +5,17 @@ module ActiveRecord included do # Stores the default scope for the class. - class_attribute :default_scopes, instance_writer: false + class_attribute :default_scopes, instance_writer: false, instance_predicate: false + self.default_scopes = [] + + def self.default_scopes? + ActiveSupport::Deprecation.warn( + "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead." + ) + + !!self.default_scopes + end end module ClassMethods diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 10195e3ae4..5536702f58 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -141,7 +141,6 @@ class AggregationsTest < ActiveRecord::TestCase end class OverridingAggregationsTest < ActiveRecord::TestCase - class Name; end class DifferentName; end class Person < ActiveRecord::Base diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index a06bacafca..95c571fd03 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -245,7 +245,6 @@ class AssociationProxyTest < ActiveRecord::TestCase end class OverridingAssociationsTest < ActiveRecord::TestCase - class Person < ActiveRecord::Base; end class DifferentPerson < ActiveRecord::Base; end class PeopleList < ActiveRecord::Base diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 648596b828..387c741762 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -69,7 +69,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end def test_boolean_attributes - assert ! Topic.find(1).approved? + assert !Topic.find(1).approved? assert Topic.find(2).approved? end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9944527d48..a48ae1036f 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -242,6 +242,11 @@ class SchemaDumperTest < ActiveRecord::TestCase end if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_bigint_default + output = standard_dump + assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output + end + def test_schema_dump_includes_extensions connection = ActiveRecord::Base.connection skip unless connection.supports_extensions? diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index d8271ac8d1..6b7012a172 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -32,6 +32,7 @@ ActiveRecord::Schema.define do char3 text default 'a text field', positive_integer integer default 1, negative_integer integer default -1, + bigint_default bigint default 0::bigint, decimal_number decimal(3,2) default 2.78, multiline_default text DEFAULT '--- [] diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 31395d6da2..a4c6ea4236 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,13 @@ ## Rails 4.0.0 (unreleased) ## +* `Class#class_attribute` accepts an `instance_predicate` option which + defaults to `true`. If set to `false` the predicate method will not + be defined. + + *Agis Anastasopoulos* + +* `fast_xs` support has been removed. Use `String#encode(xml: :attr)`. + * `ActiveSupport::Notifications::Instrumenter#instrument` should yield its payload. diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index e51ab9ddbc..6d49b7b6e1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -44,7 +44,8 @@ class Class # Base.setting # => [] # Subclass.setting # => [:foo] # - # For convenience, a query method is defined as well: + # For convenience, an instance predicate method is defined as well. + # To skip it, pass <tt>instance_predicate: false</tt>. # # Subclass.setting? # => false # @@ -72,10 +73,11 @@ class Class # double assignment is used to avoid "assigned but unused variable" warning instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) + instance_predicate = options.fetch(:instance_predicate, true) attrs.each do |name| define_singleton_method(name) { nil } - define_singleton_method("#{name}?") { !!public_send(name) } + define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate ivar = "@#{name}" @@ -109,7 +111,7 @@ class Class self.class.public_send name end end - define_method("#{name}?") { !!public_send(name) } + define_method("#{name}?") { !!public_send(name) } if instance_predicate end attr_writer name if instance_writer diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 5d7cb81e38..c656db2c6c 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -4,7 +4,6 @@ require 'active_support/core_ext/string/multibyte' require 'active_support/core_ext/string/starts_ends_with' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/xchar' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/exclude' diff --git a/activesupport/lib/active_support/core_ext/string/xchar.rb b/activesupport/lib/active_support/core_ext/string/xchar.rb deleted file mode 100644 index f9a5b4fb64..0000000000 --- a/activesupport/lib/active_support/core_ext/string/xchar.rb +++ /dev/null @@ -1,18 +0,0 @@ -begin - # See http://fast-xs.rubyforge.org/ by Eric Wong. - # Also included with hpricot. - require 'fast_xs' -rescue LoadError - # fast_xs extension unavailable -else - begin - require 'builder' - rescue LoadError - # builder demands the first shot at defining String#to_xs - end - - class String - alias_method :original_xs, :to_xs if method_defined?(:to_xs) - alias_method :to_xs, :fast_xs - end -end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index 1c3ba8a7a0..e7a1334db3 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -27,7 +27,7 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_equal 1, Class.new(@sub).setting end - test 'query method' do + test 'predicate method' do assert_equal false, @klass.setting? @klass.setting = 1 assert_equal true, @klass.setting? @@ -48,7 +48,7 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_equal 1, object.setting end - test 'instance query' do + test 'instance predicate' do object = @klass.new assert_equal false, object.setting? object.setting = 1 @@ -73,6 +73,11 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting = 'boom' } end + test 'disabling instance predicate' do + object = Class.new { class_attribute :setting, instance_predicate: false }.new + assert_raise(NoMethodError) { object.setting? } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 43529e3e96..101a4f5b42 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1039,6 +1039,8 @@ For convenience `class_attribute` also defines an instance predicate which is th When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method. +If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. + NOTE: Defined in `active_support/core_ext/class/attribute.rb` #### `cattr_reader`, `cattr_writer`, and `cattr_accessor` diff --git a/guides/source/testing.md b/guides/source/testing.md index 70061dc815..d5efadec6c 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -222,10 +222,10 @@ TIP: You can see all these rake tasks and their descriptions by running `rake -- ### Running Tests -Running a test is as simple as invoking the file containing the test cases through `rails test` command. +Running a test is as simple as invoking the file containing the test cases through `rake test` command. ```bash -$ rails test test/models/post_test.rb +$ rake test test/models/post_test.rb . Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. @@ -233,10 +233,10 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ``` -You can also run a particular test method from the test case by running the test and using `-n` switch with the `test method name`. +You can also run a particular test method from the test case by running the test and providing the `test method name`. ```bash -$ rails test test/models/post_test.rb -n test_the_truth +$ rake test test/models/post_test.rb test_the_truth . Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s. @@ -260,7 +260,7 @@ end Let us run this newly added test. ```bash -$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title +$ rake test test/models/post_test.rb test_should_not_save_post_without_title F Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s. @@ -300,7 +300,7 @@ end Now the test should pass. Let us verify by running the test again: ```bash -$ rails test test/models/post_test.rb -n test_should_not_save_post_without_title +$ rake test test/models/post_test.rb test_should_not_save_post_without_title . Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s. @@ -325,7 +325,7 @@ end Now you can see even more output in the console from running the tests: ```bash -$ rails test test/models/post_test.rb -n test_should_report_error +$ rake test test/models/post_test.rb test_should_report_error E Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s. @@ -761,14 +761,14 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai | Tasks | Description | | ------------------------ | ----------- | -| `rails test` | Runs all unit, functional and integration tests. You can also simply run `rails test` as Rails will run all the tests by default| -| `rails test controllers` | Runs all the controller tests from `test/controllers`| -| `rails test functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| -| `rails test helpers` | Runs all the helper tests from `test/helpers`| -| `rails test integration` | Runs all the integration tests from `test/integration`| -| `rails test mailers` | Runs all the mailer tests from `test/mailers`| -| `rails test models` | Runs all the model tests from `test/models`| -| `rails test units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| +| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` as Rails will run all the tests by default| +| `rake test:controllers` | Runs all the controller tests from `test/controllers`| +| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| +| `rake test:helpers` | Runs all the helper tests from `test/helpers`| +| `rake test:integration` | Runs all the integration tests from `test/integration`| +| `rake test:mailers` | Runs all the mailer tests from `test/mailers`| +| `rake test:models` | Runs all the model tests from `test/models`| +| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| There're also some test commands which you can initiate by running rake tasks: diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index ae13c3ccc9..e4a08f68c1 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -38,37 +38,23 @@ *Sam Ruby* -* Rails now generates a `test/test_helper.rb` file with `fixtures :all` commented out by default, - since we don't want to force loading all fixtures for user when a single test is run. However, - fixtures are still going to be loaded automatically for test suites. - - To force all fixtures to be create in your database, use `rails test -f` to run your test. - - *Prem Sichanugrist* - -* Add `rails test` command for running tests +* Improved `rake test` command for running tests To run all tests: - $ rails test + $ rake test To run a test suite - $ rails test [models,helpers,units,controllers,mailers,...] + $ rake test:[models,helpers,units,controllers,mailers,...] To run a selected test file(s): - $ rails test test/unit/foo_test.rb [test/unit/bar_test.rb ...] + $ rake test test/unit/foo_test.rb [test/unit/bar_test.rb ...] To run a single test from a test file - $ rails test test/unit/foo_test.rb -n test_the_truth - - For more information, see `rails test --help`. - - This command will eventually replace `rake test:*` and `rake test` tasks. - - *Prem Sichanugrist and Chris Toomey* + $ rake test test/unit/foo_test.rb TESTOPTS='-n test_the_truth' * Improve service pages with new layout (404, etc). diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 19c2226619..1493815c30 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -1,6 +1,6 @@ require "rails" -if defined?(Rake) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any? +if defined?(Rake) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? ENV['RAILS_ENV'] ||= 'test' end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 41d3722c18..0d1286031c 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -80,15 +80,6 @@ when 'server' server.start end -when 'test' - $LOAD_PATH.unshift("./test") - require 'rails/commands/test_runner' - options = Rails::TestRunner.parse_arguments(ARGV) - ENV['RAILS_ENV'] ||= options[:environment] || 'test' - - require APP_PATH - Rails::TestRunner.start(ARGV, options) - when 'dbconsole' require 'rails/commands/dbconsole' Rails::DBConsole.start diff --git a/railties/lib/rails/commands/test_runner.rb b/railties/lib/rails/commands/test_runner.rb deleted file mode 100644 index d8857bd183..0000000000 --- a/railties/lib/rails/commands/test_runner.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'optparse' -require 'minitest/unit' - -module Rails - # Handles all logic behind +rails test+ command. - class TestRunner - class << self - # Creates a new +TestRunner+ object with an array of test files to run - # based on the arguments. When no arguments are provided, it runs all test - # files. When a suite argument is provided, it runs only the test files in - # that suite. Otherwise, it runs the specified test file(s). - def start(files, options = {}) - original_fixtures_options = options.delete(:fixtures) - options[:fixtures] = true - - case files.first - when nil - new(Dir['test/**/*_test.rb'], options).run - when 'models' - new(Dir['test/models/**/*_test.rb'], options).run - when 'helpers' - new(Dir['test/helpers/**/*_test.rb'], options).run - when 'units' - new(Dir['test/{models,helpers,unit}/**/*_test.rb'], options).run - when 'controllers' - new(Dir['test/controllers/**/*_test.rb'], options).run - when 'mailers' - new(Dir['test/mailers/**/*_test.rb'], options).run - when 'functionals' - new(Dir['test/{controllers,mailers,functional}/**/*_test.rb'], options).run - when 'integration' - new(Dir['test/integration/**/*_test.rb'], options).run - else - options[:fixtures] = original_fixtures_options - new(files, options).run - end - end - - # Parses arguments and sets them as option flags - def parse_arguments(arguments) - options = {} - orig_arguments = arguments.dup - - OptionParser.new do |opts| - opts.banner = "Usage: rails test [path to test file(s) or test suite]" - - opts.separator "" - opts.separator "Run a specific test file(s) or a test suite, under Rails'" - opts.separator "environment. If the file name(s) or suit name is omitted," - opts.separator "Rails will run all tests." - opts.separator "" - opts.separator "Specific options:" - - opts.on '-h', '--help', 'Display this help.' do - puts opts - exit - end - - opts.on '-e', '--environment NAME', String, 'Specifies the environment to run this test under' do |e| - options[:environment] = e - end - - opts.on '-f', '--fixtures', 'Load fixtures in test/fixtures/ before running the tests' do - options[:fixtures] = true - end - - opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| - options[:seed] = m.to_i - end - - opts.on '-v', '--verbose', "Verbose. Show progress processing files." do - options[:verbose] = true - end - - opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |n| - options[:filter] = n - end - - opts.separator "" - opts.separator "Support types of test suites:" - opts.separator "-------------------------------------------------------------" - opts.separator "* models (test/models/**/*)" - opts.separator "* helpers (test/helpers/**/*)" - opts.separator "* units (test/{models,helpers,unit}/**/*" - opts.separator "* controllers (test/controllers/**/*)" - opts.separator "* mailers (test/mailers/**/*)" - opts.separator "* functionals (test/{controllers,mailers,functional}/**/*)" - opts.separator "* integration (test/integration/**/*)" - opts.separator "-------------------------------------------------------------" - - opts.parse! arguments - orig_arguments -= arguments - end - options - end - end - - # Creates a new +TestRunner+ object with a list of test file paths. - def initialize(files, options) - @files = files - - Rails.application.load_tasks - Rake::Task['db:test:load'].invoke - - if options.delete(:fixtures) - if defined?(ActiveRecord::Base) - ActiveSupport::TestCase.send :include, ActiveRecord::TestFixtures - ActiveSupport::TestCase.fixture_path = "#{Rails.root}/test/fixtures/" - ActiveSupport::TestCase.fixtures :all - end - end - - MiniTest::Unit.runner.options = options - MiniTest::Unit.output = SilentUntilSyncStream.new(MiniTest::Unit.output) - end - - # Runs test files by evaluating each of them. - def run - @files.each { |filename| load(filename) } - end - - # A null stream object which ignores everything until +sync+ has been set - # to true. This is only used to silence unnecessary output from MiniTest, - # as MiniTest calls +output.sync = true+ right before it outputs the first - # test result. - class SilentUntilSyncStream < File - # Creates a +SilentUntilSyncStream+ object by giving it a target stream - # object that will be assigned to +MiniTest::Unit.output+ after +sync+ is - # set to true. - def initialize(target_stream) - @target_stream = target_stream - super(File::NULL, 'w') - end - - # Swaps +MiniTest::Unit.output+ to another stream when +sync+ is true. - def sync=(sync) - if sync - @target_stream.sync = true - MiniTest::Unit.output = @target_stream - end - - super - end - end - end -end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 93504b3b35..86f62dfb40 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -553,7 +553,7 @@ module Rails # # This needs to be an initializer, since it needs to run once # per engine and get the engine as a block parameter - initializer :set_autoload_paths, before: :bootstrap_hook do |app| + initializer :set_autoload_paths, before: :bootstrap_hook do ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths) ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index de14186a09..b53142d20c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -186,7 +186,7 @@ module Rails gem 'sass-rails', github: 'rails/sass-rails' # To use Uglifier as compressor for JavaScript assets - gem 'uglifier', '>= 1.3.0' + gem 'uglifier', '~> 1.3' GEMFILE else <<-GEMFILE.gsub(/^ {12}/, '') @@ -194,7 +194,7 @@ module Rails gem 'sass-rails', '~> 4.0.0.beta1' # To use Uglifier as compressor for JavaScript assets - gem 'uglifier', '>= 1.3.0' + gem 'uglifier', '~> 1.3' GEMFILE end diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index ca40914d3b..4fd060341e 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -6,12 +6,11 @@ class ActiveSupport::TestCase <% unless options[:skip_active_record] -%> ActiveRecord::Migration.check_pending! - # Uncomment the `fixtures :all` line below to setup all fixtures in test/fixtures/*.yml - # for all tests in alphabetical order. + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting - # fixtures :all + fixtures :all <% end -%> # Add more helper methods to be used by all tests here... diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb index 87b6f9b5a4..a463380e2d 100644 --- a/railties/lib/rails/test_unit/sub_test_task.rb +++ b/railties/lib/rails/test_unit/sub_test_task.rb @@ -1,6 +1,83 @@ +require 'rake/testtask' + module Rails + class TestTask < Rake::TestTask # :nodoc: all + class TestInfo + def initialize(tasks) + @tasks = tasks + end + + def files + @tasks.map { |task| + [task, translate(task)].find { |file| test_file?(file) } + }.compact + end + + def translate(file) + if file =~ /^app\/(.*)$/ + "test/#{$1.sub(/\.rb$/, '')}_test.rb" + else + "test/#{file}_test.rb" + end + end + + def tasks + @tasks - test_file_tasks - opt_names + end + + def opts + opts = opt_names + if opts.any? + "-n #{opts.join ' '}" + end + end + + private + + def test_file_tasks + @tasks.find_all { |task| + [task, translate(task)].any? { |file| test_file?(file) } + } + end + + def test_file?(file) + file =~ /^test/ && File.file?(file) && !File.directory?(file) + end + + def opt_names + (@tasks - test_file_tasks).reject { |t| task_defined? t } + end + + def task_defined?(task) + Rake::Task.task_defined? task + end + end + + def self.test_info(tasks) + TestInfo.new tasks + end + + def initialize(name = :test) + super + @libs << "test" # lib *and* test seem like a better default + end + + def define + task @name do + if ENV['TESTOPTS'] + ARGV.replace Shellwords.split ENV['TESTOPTS'] + end + libs = @libs - $LOAD_PATH + $LOAD_PATH.unshift(*libs) + file_list.each { |fl| + FileList[fl].to_a.each { |f| require File.expand_path f } + } + end + end + end + # Silence the default description to cut down on `rake -T` noise. - class SubTestTask < Rake::TestTask + class SubTestTask < Rake::TestTask # :nodoc: def desc(string) # Ignore the description. end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 3c247f32c0..877dd6d254 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -48,10 +48,17 @@ task default: :test desc 'Runs test:units, test:functionals, test:integration together' task :test do - if ENV['TEST'] - exec "bundle exec rails test #{ENV['TEST'].inspect}" + info = Rails::TestTask.test_info Rake.application.top_level_tasks + if info.files.any? + Rails::TestTask.new('test:single') { |t| + t.test_files = info.files + } + ENV['TESTOPTS'] ||= info.opts + Rake.application.top_level_tasks.replace info.tasks + + Rake::Task['test:single'].invoke else - exec 'bundle exec rails test' + Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke end end @@ -60,15 +67,11 @@ namespace :test do # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example. end - task :run do - ActiveSupport::Deprecation.warn "`rake test:run` is deprecated. Please use `rails test`." - exec 'bundle exec rails test' - end + task :run => ['test:units', 'test:functionals', 'test:integration'] # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html desc "Run tests quickly by merging all types and not resetting db" - Rake::TestTask.new(:all) do |t| - t.libs << "test" + Rails::TestTask.new(:all) do |t| t.pattern = "test/**/*_test.rb" end @@ -90,7 +93,6 @@ namespace :test do recent_tests('app/controllers/**/*.rb', 'test/controllers', since) + recent_tests('app/controllers/**/*.rb', 'test/functional', since) - t.libs << 'test' t.test_files = touched.uniq end Rake::Task['test:recent'].comment = "Deprecated; Test recent changes" @@ -114,23 +116,36 @@ namespace :test do controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } (unit_tests + functional_tests).uniq.select { |file| File.exist?(file) } end - - t.libs << 'test' end Rake::Task['test:uncommitted'].comment = "Deprecated; Test changes since last checkin (only Subversion and Git)" - desc "Deprecated; Please use `rails test \"#{ENV['TEST']}\"`" - task :single do - ActiveSupport::Deprecation.warn "`rake test:single` is deprecated. Please use `rails test \"#{ENV['TEST']}\"`." - exec "bundle exec rails test #{test_suit_name}" + Rails::TestTask.new(single: "test:prepare") + + Rails::TestTask.new(models: "test:prepare") do |t| + t.pattern = 'test/models/**/*_test.rb' end - [:models, :helpers, :units, :controllers, :functionals, :integration].each do |test_suit_name| - desc "Deprecated; Please use `rails test #{test_suit_name}`" - task test_suit_name do - ActiveSupport::Deprecation.warn "`rake test:#{test_suit_name}` is deprecated. Please use `rails test #{test_suit_name}`." + Rails::TestTask.new(helpers: "test:prepare") do |t| + t.pattern = 'test/helpers/**/*_test.rb' + end - exec "bundle exec rails test #{test_suit_name}" - end + Rails::TestTask.new(units: "test:prepare") do |t| + t.pattern = 'test/{models,helpers,unit}/**/*_test.rb' + end + + Rails::TestTask.new(controllers: "test:prepare") do |t| + t.pattern = 'test/controllers/**/*_test.rb' + end + + Rails::TestTask.new(mailers: "test:prepare") do |t| + t.pattern = 'test/mailers/**/*_test.rb' + end + + Rails::TestTask.new(functionals: "test:prepare") do |t| + t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb' + end + + Rails::TestTask.new(integration: "test:prepare") do |t| + t.pattern = 'test/integration/**/*_test.rb' end end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 4b8e813105..eb590da678 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -84,20 +84,8 @@ module ApplicationTests Dir.chdir(app_path){ `rake stats` } end - def test_rake_test_error_output - Dir.chdir(app_path){ `rake db:migrate` } - - app_file "test/models/one_model_test.rb", <<-RUBY - raise 'models' - RUBY - - silence_stderr do - output = Dir.chdir(app_path) { `rake test 2>&1` } - assert_match 'models', output - end - end - def test_rake_test_uncommitted_always_find_git_in_parent_dir + return "FIXME :'(" app_name = File.basename(app_path) app_dir = File.dirname(app_path) moved_app_name = app_name + '_moved' @@ -129,13 +117,10 @@ module ApplicationTests Dir.chdir(app_path){ `rails generate scaffold user name:string` } Dir.chdir(app_path){ `rake db:migrate` } - %w(run recent uncommitted models helpers units controllers functionals integration).each do |test_suit_name| + %w(recent uncommitted).each do |test_suit_name| output = Dir.chdir(app_path) { `rake test:#{test_suit_name} 2>&1` } assert_match(/DEPRECATION WARNING: `rake test:#{test_suit_name}` is deprecated/, output) end - - assert_match(/DEPRECATION WARNING: `rake test:single` is deprecated/, - Dir.chdir(app_path) { `rake test:single TEST=test/models/user_test.rb 2>&1` }) end def test_rake_routes_calls_the_route_inspector diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 56ca3bc1a9..1cf53aa4fb 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -15,14 +15,6 @@ module ApplicationTests teardown_app end - def test_should_not_display_heading - create_test_file - run_test_command.tap do |output| - assert_no_match "Run options:", output - assert_no_match "Running tests:", output - end - end - def test_run_in_test_environment app_file 'test/unit/env_test.rb', <<-RUBY require 'test_helper' @@ -37,14 +29,9 @@ module ApplicationTests assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb') end - def test_run_shortcut - create_test_file :models, 'foo' - output = Dir.chdir(app_path) { `bundle exec rails t test/models/foo_test.rb` } - assert_match "1 tests, 1 assertions, 0 failures", output - end - def test_run_single_file create_test_file :models, 'foo' + create_test_file :models, 'bar' assert_match "1 tests, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") end @@ -62,24 +49,14 @@ module ApplicationTests error_stream = Tempfile.new('error') redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') } - assert_match "SyntaxError", error_stream.read - end - - def test_invoke_rake_db_test_load - app_file "lib/tasks/test.rake", <<-RUBY - task 'db:test:load' do - puts "Hello World" - end - RUBY - create_test_file - assert_match "Hello World", run_test_command + assert_match "syntax error", error_stream.read end def test_run_models create_test_file :models, 'foo' create_test_file :models, 'bar' create_test_file :controllers, 'foobar_controller' - run_test_command("models").tap do |output| + run_test_models_command.tap do |output| assert_match "FooTest", output assert_match "BarTest", output assert_match "2 tests, 2 assertions, 0 failures", output @@ -90,7 +67,7 @@ module ApplicationTests create_test_file :helpers, 'foo_helper' create_test_file :helpers, 'bar_helper' create_test_file :controllers, 'foobar_controller' - run_test_command('helpers').tap do |output| + run_test_helpers_command.tap do |output| assert_match "FooHelperTest", output assert_match "BarHelperTest", output assert_match "2 tests, 2 assertions, 0 failures", output @@ -102,7 +79,7 @@ module ApplicationTests create_test_file :helpers, 'bar_helper' create_test_file :unit, 'baz_unit' create_test_file :controllers, 'foobar_controller' - run_test_command('units').tap do |output| + run_test_units_command.tap do |output| assert_match "FooTest", output assert_match "BarHelperTest", output assert_match "BazUnitTest", output @@ -114,7 +91,7 @@ module ApplicationTests create_test_file :controllers, 'foo_controller' create_test_file :controllers, 'bar_controller' create_test_file :models, 'foo' - run_test_command('controllers').tap do |output| + run_test_controllers_command.tap do |output| assert_match "FooControllerTest", output assert_match "BarControllerTest", output assert_match "2 tests, 2 assertions, 0 failures", output @@ -125,7 +102,7 @@ module ApplicationTests create_test_file :mailers, 'foo_mailer' create_test_file :mailers, 'bar_mailer' create_test_file :models, 'foo' - run_test_command('mailers').tap do |output| + run_test_mailers_command.tap do |output| assert_match "FooMailerTest", output assert_match "BarMailerTest", output assert_match "2 tests, 2 assertions, 0 failures", output @@ -137,7 +114,7 @@ module ApplicationTests create_test_file :controllers, 'bar_controller' create_test_file :functional, 'baz_functional' create_test_file :models, 'foo' - run_test_command('functionals').tap do |output| + run_test_functionals_command.tap do |output| assert_match "FooMailerTest", output assert_match "BarControllerTest", output assert_match "BazFunctionalTest", output @@ -148,7 +125,7 @@ module ApplicationTests def test_run_integration create_test_file :integration, 'foo_integration' create_test_file :models, 'foo' - run_test_command('integration').tap do |output| + run_test_integration_command.tap do |output| assert_match "FooIntegration", output assert_match "1 tests, 1 assertions, 0 failures", output end @@ -178,17 +155,31 @@ module ApplicationTests end RUBY - run_test_command('test/unit/chu_2_koi_test.rb -n test_rikka').tap do |output| + run_test_command('test/unit/chu_2_koi_test.rb test_rikka').tap do |output| assert_match "Rikka", output assert_no_match "Sanae", output end end - def test_not_load_fixtures_when_running_single_test - create_model_with_fixture - create_fixture_test :models, 'user' - assert_match "0 users", run_test_command('test/models/user_test.rb') - assert_match "3 users", run_test_command('test/models/user_test.rb -f') + def test_run_matched_test + app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + require 'test_helper' + + class Chu2KoiTest < ActiveSupport::TestCase + def test_rikka + puts 'Rikka' + end + + def test_sanae + puts 'Sanae' + end + end + RUBY + + run_test_command('test/unit/chu_2_koi_test.rb /rikka/').tap do |output| + assert_match "Rikka", output + assert_no_match "Sanae", output + end end def test_load_fixtures_when_running_test_suites @@ -199,11 +190,18 @@ module ApplicationTests suites.each do |suite, directory| directory ||= suite create_fixture_test directory - assert_match "3 users", run_test_command(suite) + assert_match "3 users", run_task(["test:#{suite}"]) Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" } end end + def test_run_with_model + create_model_with_fixture + create_fixture_test 'models', 'user' + assert_match "3 users", run_task(["test models/user"]) + assert_match "3 users", run_task(["test app/models/user.rb"]) + end + def test_run_different_environment_using_env_var app_file 'test/unit/env_test.rb', <<-RUBY require 'test_helper' @@ -220,6 +218,7 @@ module ApplicationTests end def test_run_different_environment_using_e_tag + env = "development" app_file 'test/unit/env_test.rb', <<-RUBY require 'test_helper' @@ -230,7 +229,7 @@ module ApplicationTests end RUBY - assert_match "development", run_test_command('-e development test/unit/env_test.rb') + assert_match env, run_test_command("test/unit/env_test.rb RAILS_ENV=#{env}") end def test_generated_scaffold_works_with_rails_test @@ -239,8 +238,17 @@ module ApplicationTests end private + def run_task(tasks) + Dir.chdir(app_path) { `bundle exec rake #{tasks.join ' '}` } + end + def run_test_command(arguments = 'test/unit/test_test.rb') - Dir.chdir(app_path) { `bundle exec rails test #{arguments}` } + run_task ['test', arguments] + end + %w{ mailers models helpers units controllers functionals integration }.each do |type| + define_method("run_test_#{type}_command") do + run_task ["test:#{type}"] + end end def create_model_with_fixture diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb new file mode 100644 index 0000000000..d5463c11de --- /dev/null +++ b/railties/test/test_info_test.rb @@ -0,0 +1,59 @@ +require 'abstract_unit' +require 'rails/test_unit/sub_test_task' + +module Rails + class TestInfoTest < ActiveSupport::TestCase + def test_test_files + info = new_test_info ['test'] + assert_predicate info.files, :empty? + assert_nil info.opts + assert_equal ['test'], info.tasks + end + + def test_with_file + info = new_test_info ['test', __FILE__] + assert_equal [__FILE__], info.files + assert_nil info.opts + assert_equal ['test'], info.tasks + end + + def test_with_opts + info = new_test_info ['test', __FILE__, '/foo/'] + assert_equal [__FILE__], info.files + assert_equal '-n /foo/', info.opts + assert_equal ['test'], info.tasks + end + + def test_with_model_shorthand + info = new_test_info ['test', 'models/foo', '/foo/'] + + def info.test_file?(file) + file == "test/models/foo_test.rb" || super + end + + assert_equal ['test/models/foo_test.rb'], info.files + assert_equal '-n /foo/', info.opts + assert_equal ['test'], info.tasks + end + + def test_with_model_path + info = new_test_info ['test', 'app/models/foo.rb', '/foo/'] + + def info.test_file?(file) + file == "test/models/foo_test.rb" || super + end + + assert_equal ['test/models/foo_test.rb'], info.files + assert_equal '-n /foo/', info.opts + assert_equal ['test'], info.tasks + end + + def new_test_info(tasks) + Class.new(TestTask::TestInfo) { + def task_defined?(task) + task == "test" + end + }.new tasks + end + end +end |