Permalink
Browse files

PTY-based spec runner

  • Loading branch information...
1 parent b6b3823 commit 4b57067a574976fc36c9c91c9ad3724d1d96bdbd @mattgreen mattgreen committed Sep 11, 2012
View
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
gem.add_dependency 'guard', '>= 1.1.0'
gem.add_dependency 'rake', '>= 0.9'
- gem.add_development_dependency 'bundler', '~> 1.1.0'
+ gem.add_development_dependency 'bundler', '>= 1.1.0'
gem.add_development_dependency 'rspec', '~> 2.10'
gem.add_development_dependency 'guard-rspec', '~> 1.1'
end
View
@@ -5,7 +5,8 @@
module Guard
class Motion < Guard
- autoload :Runner, 'guard/motion/runner'
+ autoload :ResultsParser, 'guard/motion/results_parser'
+ autoload :Runner, 'guard/motion/runner'
# Initialize a Guard.
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
@@ -0,0 +1,28 @@
+module Guard
+ class Motion
+ class ResultsParser
+ attr_reader :errors
+ attr_reader :failures
+ attr_reader :specs
+
+ def parse(output)
+ matched = false
+
+ stats_regex = /(\d+) (tests|specifications),? \(?\d+ (assertions|requirements)\)?, (\d+) failures, (\d+) errors/
+ stats_regex.match(output) do |m|
+ matched = true
+
+ @specs = m[1].to_i
+ @failures = m[4].to_i
+ @errors = m[5].to_i
+ end
+
+ matched
+ end
+
+ def success?
+ errors == 0 && failures == 0
+ end
+ end
+ end
+end
View
@@ -1,7 +1,8 @@
+require 'pty'
+
module Guard
class Motion
class Runner
-
def initialize(options = {})
@options = {
:bundler => true,
@@ -22,7 +23,27 @@ def run(paths = nil, options = {})
UI.info(message, :reset => true)
- run_via_shell rake_command(paths)
+ output = run_via_pty rake_command(paths)
+
+ if @options[:notification]
+ notify(output)
+ end
+ end
+
+ def notify(output)
+ message = "Failed"
+ type = :failed
+
+ parser = ResultsParser.new
+ if parser.parse(output)
+ message = "#{parser.specs} specs, #{parser.failures} failures, #{parser.errors} errors"
+ end
+
+ if parser.success?
+ type = :success
+ end
+
+ Notifier.notify(message, :type => type, :image => type, :title => 'RubyMotion Spec Results', :priority => 2)
end
def rake_executable
@@ -39,14 +60,24 @@ def rake_command(paths)
cmd_parts.compact.join(' ')
end
- def run_via_shell(command)
- success = system(command)
+ def run_via_pty(command)
+ output = ""
+
+ PTY.spawn(command) do |r, w, pid|
+ begin
+ loop do
+ chunk = r.readpartial(1024)
+ output += chunk
+
+ print chunk
+ end
+ rescue EOFError
+ end
- if @options[:notification] && !success
- Notifier.notify("Failed", :title => "Motion spec results", :image => :failed, :priority => 2)
+ Process.wait(pid)
end
- success
+ output
end
def all_spec_paths
@@ -92,4 +123,4 @@ def bundle_exec?
end
end
end
-end
+end
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+module Guard
+ class Motion
+ describe ResultsParser do
+ describe "#parse" do
+ before do
+ @result = subject.parse(output)
+ end
+
+ context "when the output contains spec output" do
+ context "and specs failed" do
+ let(:output) { ".................FFF...\n18 tests, 21 assertions, 3 failures, 1 errors\n" }
+
+ it "returns true" do
+ @result.should be_true
+ end
+
+ it "does not consider it successful" do
+ subject.success?.should be_false
+ end
+
+ it "returns the number of tests run" do
+ subject.specs.should == 18
+ end
+
+ it "returns the number of failures" do
+ subject.failures.should == 3
+ end
+
+ it "returns the number of errors" do
+ subject.errors.should == 1
+ end
+ end
+
+ context "and specs passed" do
+ let(:output) { ".................FFF...\n18 tests, 21 assertions, 0 failures, 0 errors\n" }
+
+ it "returns true" do
+ @result.should be_true
+ end
+
+ it "considers the test run successful" do
+ subject.success?.should be_true
+ end
+
+ it "returns the number of tests run" do
+ subject.specs.should == 18
+ end
+
+ it "returns the number of failures" do
+ subject.failures.should == 0
+ end
+
+ it "returns the number of errors" do
+ subject.errors.should == 0
+ end
+ end
+ end
+
+ context "when the output does not contain spec output" do
+ let(:output) { "" }
+
+ it "returns false" do
+ @result.should be_false
+ end
+
+ it "does not consider the test successful" do
+ subject.success?.should be_false
+ end
+
+ it "returns nil for test count" do
+ subject.specs.should_not be
+ end
+
+ it "returns nil for failure count" do
+ subject.failures.should_not be
+ end
+
+ it "returns nil for error count" do
+ subject.errors.should_not be
+ end
+ end
+ end
+ end
+ end
+end
@@ -6,6 +6,14 @@ class Motion
describe Runner do
subject { described_class.new }
+ let(:output) { "..............................\n\n31 tests, 35 assertions, 2 failures, 0 errors\n" }
+
+ before do
+ PTY.stub(:spawn).and_yield(StringIO.new(output), StringIO.new, 123)
+ subject.stub(:print)
+ Process.stub(:wait)
+ end
+
describe '#run' do
context 'when passed an empty paths list' do
it 'returns false' do
@@ -19,9 +27,9 @@ class Motion
end
it 'runs without bundler' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"rake spec:specific[\"something\"]"
- ).and_return(true)
+ )
subject.run(['something'])
end
@@ -33,28 +41,47 @@ class Motion
end
it 'runs with Bundler' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"bundle exec rake spec:specific[\"something\"]"
- ).and_return(true)
+ )
subject.run(['something'])
end
describe 'notification' do
- it 'notifies when Motion specs fails to execute' do
- subject.should_receive(:rake_command) { "`exit 1`" }
- ::Guard::Notifier.should_receive(:notify).with(
- 'Failed', :title => 'Motion spec results', :image => :failed, :priority => 2
- )
+ context "when the specs fail to execute" do
+ let(:output) { "" }
+
+ it "sends a failure notification" do
+ ::Guard::Notifier.should_receive(:notify).with(
+ 'Failed', :title => 'RubyMotion Spec Results', :type => :failed, :image => :failed, :priority => 2
+ )
+
+ subject.run(['spec'])
+ end
+ end
- subject.run(['spec'])
+ context "when the specs fail" do
+ let(:output) { "..............................\n\n31 tests, 35 assertions, 2 failures, 0 errors\n" }
+
+ it "sends a spec failed notification" do
+ ::Guard::Notifier.should_receive(:notify).with(
+ '31 specs, 2 failures, 0 errors', :title => 'RubyMotion Spec Results', :type => :failed, :image => :failed, :priority => 2)
+
+ subject.run(["spec"])
+ end
end
- it 'does not notify that Motion specs failed when the specs pass' do
- subject.should_receive(:rake_command) { "`exit 0`" }
- ::Guard::Notifier.should_not_receive(:notify)
+ context "when specs pass" do
+ let(:output) { "..............................\n\n31 tests, 35 assertions, 0 failures, 0 errors\n" }
+
+ it "sends a success notification" do
+ ::Guard::Notifier.should_receive(:notify).with(
+ '31 specs, 0 failures, 0 errors', :title => 'RubyMotion Spec Results', :type => :success, :image => :success, :priority => 2
+ )
- subject.run(['spec'])
+ subject.run(['spec'])
+ end
end
end
@@ -65,9 +92,9 @@ class Motion
subject { described_class.new(:bundler => false) }
it 'runs without Bundler' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"rake spec:specific[\"spec\"]"
- ).and_return(true)
+ )
subject.run(['spec'])
end
@@ -79,9 +106,9 @@ class Motion
subject { described_class.new(:bundler => false, :binstubs => true) }
it 'runs without Bundler and with binstubs' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"bin/rake spec:specific[\"spec\"]"
- ).and_return(true)
+ )
subject.run(['spec'])
end
@@ -91,9 +118,9 @@ class Motion
subject { described_class.new(:bundler => true, :binstubs => true) }
it 'runs without Bundler and binstubs' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"bin/rake spec:specific[\"spec\"]"
- ).and_return(true)
+ )
subject.run(['spec'])
end
@@ -103,9 +130,9 @@ class Motion
subject { described_class.new(:bundler => true, :binstubs => 'dir') }
it 'runs without Bundler and binstubs in custom directory' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"dir/rake spec:specific[\"spec\"]"
- ).and_return(true)
+ )
subject.run(['spec'])
end
@@ -117,15 +144,18 @@ class Motion
subject { described_class.new(:notification => false) }
it 'runs without notification formatter' do
- subject.should_receive(:system).with(
+ PTY.should_receive(:spawn).with(
"bundle exec rake spec:specific[\"spec\"]"
- ).and_return(true)
+ )
subject.run(['spec'])
end
it "doesn't notify when specs fails" do
- subject.should_receive(:system) { mock('res', :success? => false, :exitstatus => 2) }
+ PTY.should_receive(:spawn).with(
+ "bundle exec rake spec:specific[\"spec\"]"
+ )
+
::Guard::Notifier.should_not_receive(:notify)
subject.run(['spec'])
@@ -137,4 +167,4 @@ class Motion
end
end
end
-end
+end

0 comments on commit 4b57067

Please sign in to comment.