aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrem Sichanugrist and Chris Toomey <prem+chris@thoughtbot.com>2013-01-25 13:44:36 -0500
committerPrem Sichanugrist <s@sikac.hu>2013-03-09 16:03:54 -0500
commitb4df25366a3c8f133f8329bc35f1d53926704b5a (patch)
tree5a904251263f996bc8fd3eeed6fb9b4d3902692e
parent15970efb2acc7767f2f20c5d649e53ace2e2ddb5 (diff)
downloadrails-b4df25366a3c8f133f8329bc35f1d53926704b5a.tar.gz
rails-b4df25366a3c8f133f8329bc35f1d53926704b5a.tar.bz2
rails-b4df25366a3c8f133f8329bc35f1d53926704b5a.zip
Add `rails test` command to run the test suite
To run the whole test suite: $ rails test To run the test file(s): $ rails test test/unit/foo_test.rb [test/unit/bar_test.rb ...] To run the test suite $ rails test [models,helpers,units,controllers,mailers,...] For more information, see `rails test --help`. This command will eventually replacing `rake test:*`, and `rake test` command will actually invoking `rails test` instead.
-rw-r--r--railties/CHANGELOG.md21
-rw-r--r--railties/lib/rails/commands.rb15
-rw-r--r--railties/lib/rails/commands/test_runner.rb92
-rw-r--r--railties/test/application/test_runner_test.rb183
4 files changed, 311 insertions, 0 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 420ed476b2..6bf9b22a2d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -19,6 +19,27 @@
*Terence Lee*
+* Add `rails test` command to run the test suite
+
+ To run the whole test suite:
+
+ $ rails test
+
+ To run the test file(s):
+
+ $ rails test test/unit/foo_test.rb [test/unit/bar_test.rb ...]
+
+ To run the test suite
+
+ $ rails test [models,helpers,units,controllers,mailers,...]
+
+ For more information, see `rails test --help`.
+
+ This command will eventually replacing `rake test:*`, and `rake test`
+ command will actually invoking `rails test` instead.
+
+ *Prem Sichanugrist and Chris Toomey*
+
* Add notice message for destroy action in scaffold generator.
*Rahul P. Chaudhari*
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index aacde52cfc..c76af7d01f 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -5,6 +5,7 @@ aliases = {
"d" => "destroy",
"c" => "console",
"s" => "server",
+ "t" => "test",
"db" => "dbconsole",
"r" => "runner"
}
@@ -16,6 +17,7 @@ The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
+ test Running the test file (short-cut alias: "t")
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
new Create a new Rails application. "rails new my_app" creates a
@@ -78,6 +80,19 @@ when 'server'
server.start
end
+when 'test'
+ $LOAD_PATH.unshift("./test")
+ require 'rails/commands/test_runner'
+ if ["-h", "--help"].include?(ARGV.first)
+ Rails::TestRunner.help_message
+ exit
+ else
+ require APP_PATH
+ Rails.application.require_environment!
+ Rails.application.load_tasks
+ Rails::TestRunner.start(ARGV)
+ end
+
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
new file mode 100644
index 0000000000..a65b508505
--- /dev/null
+++ b/railties/lib/rails/commands/test_runner.rb
@@ -0,0 +1,92 @@
+require 'optparse'
+require 'minitest/unit'
+
+module Rails
+ # Handling the all the logic behind +rails test+ command.
+ class TestRunner
+ class << self
+ # Parse the test suite name from the arguments array and pass in a list
+ # of file to a new +TestRunner+ object, then invoke the evaluation. If
+ # the argument is not a test suite name, it will be treated as a file
+ # name and passed to the +TestRunner+ instance right away.
+ def start(arguments)
+ case arguments.first
+ when nil
+ new(Dir['test/**/*_test.rb']).run
+ when 'models'
+ new(Dir['test/models/**/*_test.rb']).run
+ when 'helpers'
+ new(Dir['test/helpers/**/*_test.rb']).run
+ when 'units'
+ new(Dir['test/{models,helpers,unit}/**/*_test.rb']).run
+ when 'controllers'
+ new(Dir['test/controllers/**/*_test.rb']).run
+ when 'mailers'
+ new(Dir['test/mailers/**/*_test.rb']).run
+ when 'functionals'
+ new(Dir['test/{controllers,mailers,functional}/**/*_test.rb']).run
+ when 'integration'
+ new(Dir['test/integration/**/*_test.rb']).run
+ else
+ new(arguments).run
+ end
+ end
+
+ # Print out the help message which listed all of the test suite names.
+ def help_message
+ puts "Usage: rails test [path to test file(s) or test suite type]"
+ puts ""
+ puts "Run single test file, or a test suite, under Rails'"
+ puts "environment. If the file name(s) or suit name is omitted,"
+ puts "Rails will run all the test suites."
+ puts ""
+ puts "Support types of test suites:"
+ puts "-------------------------------------------------------------"
+ puts "* models (test/models/**/*)"
+ puts "* helpers (test/helpers/**/*)"
+ puts "* units (test/{models,helpers,unit}/**/*"
+ puts "* controllers (test/controllers/**/*)"
+ puts "* mailers (test/mailers/**/*)"
+ puts "* functionals (test/{controllers,mailers,functional}/**/*)"
+ puts "* integration (test/integration/**/*)"
+ puts "-------------------------------------------------------------"
+ end
+ end
+
+ # Create a new +TestRunner+ object with a list of test file paths.
+ def initialize(files)
+ @files = files
+ Rake::Task['test:prepare'].invoke
+ MiniTest::Unit.output = SilentUntilSyncStream.new(MiniTest::Unit.output)
+ end
+
+ # Run the test files by evaluate 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 to be used to silence unnecessary output from
+ # MiniTest, as MiniTest calls +output.sync = true+ right before output the
+ # first test result.
+ class SilentUntilSyncStream < File
+ # Create a +SilentUntilSyncStream+ object by given a stream object that
+ # this stream should set +MiniTest::Unit.output+ to after +sync+ has been
+ # set to true.
+ def initialize(target_stream)
+ @target_stream = target_stream
+ super(File::NULL, 'w')
+ end
+
+ # Swap +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/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
new file mode 100644
index 0000000000..49bce508ee
--- /dev/null
+++ b/railties/test/application/test_runner_test.rb
@@ -0,0 +1,183 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class TestRunnerTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ create_schema
+ end
+
+ def teardown
+ 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_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'
+ assert_match /1 tests, 1 assertions, 0 failures/, run_test_command("test/models/foo_test.rb")
+ end
+
+ def test_run_multiple_files
+ create_test_file :models, 'foo'
+ create_test_file :models, 'bar'
+ assert_match /2 tests, 2 assertions, 0 failures/, run_test_command("test/models/foo_test.rb test/models/bar_test.rb")
+ end
+
+ def test_run_file_with_syntax_error
+ app_file 'test/models/error_test.rb', <<-RUBY
+ require 'test_helper'
+ def; end
+ RUBY
+
+ 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_test_prepare
+ app_file "lib/tasks/test.rake", <<-RUBY
+ namespace :test do
+ task :prepare do
+ puts "Hello World"
+ end
+ end
+ RUBY
+ create_test_file
+ assert_match /Hello World/, run_test_command
+ 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|
+ assert_match /FooTest/, output
+ assert_match /BarTest/, output
+ assert_match /2 tests, 2 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_helpers
+ 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|
+ assert_match /FooHelperTest/, output
+ assert_match /BarHelperTest/, output
+ assert_match /2 tests, 2 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_units
+ create_test_file :models, 'foo'
+ 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|
+ assert_match /FooTest/, output
+ assert_match /BarHelperTest/, output
+ assert_match /BazUnitTest/, output
+ assert_match /3 tests, 3 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_controllers
+ create_test_file :controllers, 'foo_controller'
+ create_test_file :controllers, 'bar_controller'
+ create_test_file :models, 'foo'
+ run_test_command('controllers').tap do |output|
+ assert_match /FooControllerTest/, output
+ assert_match /BarControllerTest/, output
+ assert_match /2 tests, 2 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_mailers
+ create_test_file :mailers, 'foo_mailer'
+ create_test_file :mailers, 'bar_mailer'
+ create_test_file :models, 'foo'
+ run_test_command('mailers').tap do |output|
+ assert_match /FooMailerTest/, output
+ assert_match /BarMailerTest/, output
+ assert_match /2 tests, 2 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_functionals
+ create_test_file :mailers, 'foo_mailer'
+ create_test_file :controllers, 'bar_controller'
+ create_test_file :functional, 'baz_functional'
+ create_test_file :models, 'foo'
+ run_test_command('functionals').tap do |output|
+ assert_match /FooMailerTest/, output
+ assert_match /BarControllerTest/, output
+ assert_match /BazFunctionalTest/, output
+ assert_match /3 tests, 3 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_integration
+ create_test_file :integration, 'foo_integration'
+ create_test_file :models, 'foo'
+ run_test_command('integration').tap do |output|
+ assert_match /FooIntegration/, output
+ assert_match /1 tests, 1 assertions, 0 failures/, output
+ end
+ end
+
+ def test_run_whole_suite
+ types = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration]
+ types.each { |type| create_test_file type, "foo_#{type}" }
+ run_test_command('') .tap do |output|
+ types.each { |type| assert_match /Foo#{type.to_s.camelize}Test/, output }
+ assert_match /7 tests, 7 assertions, 0 failures/, output
+ end
+ end
+
+ private
+ def run_test_command(arguments = 'test/unit/test_test.rb')
+ Dir.chdir(app_path) { `bundle exec rails test #{arguments}` }
+ end
+
+ def create_schema
+ app_file 'db/schema.rb', ''
+ end
+
+ def redirect_stderr(target_stream)
+ previous_stderr = STDERR.dup
+ $stderr.reopen(target_stream)
+ yield
+ target_stream.rewind
+ ensure
+ $stderr = previous_stderr
+ end
+
+ def create_test_file(path = :unit, name = 'test')
+ app_file "test/#{path}/#{name}_test.rb", <<-RUBY
+ require 'test_helper'
+
+ class #{name.camelize}Test < ActiveSupport::TestCase
+ def test_truth
+ puts "#{name.camelize}Test"
+ assert true
+ end
+ end
+ RUBY
+ end
+ end
+end