Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'feature/cucumber-integration'

* feature/cucumber-integration:
  The remainder of the specs, I think.  Not test-driving is painful...
  Big pile o' passing specs. I suppose it's testing that it interacts correctly with the underlying API...
  Spec out the CucumberFailure.
  Nuke the CucumberDoc version for now.
  Inherit directly from Ast::Visitor.
  Accept (and demonstrate) that Cucumber doesn't differentiate between failures and errors.
  Implement catching and reporting of errors.
  If there are 0 errors, don't check for errors elements.
  Reimplement the visitor.  I must have been smoking something on Friday.
  Pending steps should not be reported as errors.
  Start of an implementation of the visitor.
  Expect a slightly different filename.
  We have to require the cucumber loader slightly differently otherwise it doesn't autoload step definitions.
  Acceptance tests for cucumber integration.
  Add in a place to stick specs for the Cucumber integration.
  Add in some tests for the Cucumber setup rake task.
  Infrastructure for Cucumber integration.
  • Loading branch information...
commit 71d6ae240cf18eeb6fd9dcbbaf248d337de49665 2 parents 509a989 + c5b9fdc
@mathie mathie authored
View
1  Rakefile
@@ -63,6 +63,7 @@ task :generate_output do
begin
`ruby -Ilib acceptance/test_unit_example_test.rb` rescue nil
`ruby -Ilib -S spec --require ci/reporter/rake/rspec_loader --format CI::Reporter::RSpec acceptance/rspec_example_spec.rb` rescue nil
+ `ruby -Ilib -rci/reporter/rake/cucumber_loader -S cucumber --format CI::Reporter::Cucumber acceptance/cucumber` rescue nil
ensure
ENV.delete 'CI_REPORTS'
end
View
19 acceptance/cucumber/cucumber_example.feature
@@ -0,0 +1,19 @@
+Feature: Example feature
+ As a conscientious developer who writes features
+ I want to be able to see my features passing on the CI Server
+ So that I can bask in the glow of a green bar
+
+ Scenario: Conscientious developer
+ Given that I am a conscientious developer
+ And I write cucumber features
+ Then I should see a green bar
+
+ Scenario: Lazy hacker
+ Given that I am a lazy hacker
+ And I don't bother writing cucumber features
+ Then I should be fired
+
+ Scenario: Bad coder
+ Given that I can't code for peanuts
+ And I write step definitions that throw exceptions
+ Then I shouldn't be allowed out in public
View
30 acceptance/cucumber/step_definitions/development_steps.rb
@@ -0,0 +1,30 @@
+require 'spec/expectations'
+
+Given /^that I am a conscientious developer$/ do
+end
+
+Given /^I write cucumber features$/ do
+end
+
+Then /^I should see a green bar$/ do
+end
+
+Given /^that I am a lazy hacker$/ do
+end
+
+Given /^I don't bother writing cucumber features$/ do
+ false.should be_true
+end
+
+Then /^I should be fired$/ do
+end
+
+Given /^that I can't code for peanuts$/ do
+end
+
+Given /^I write step definitions that throw exceptions$/ do
+ raise RuntimeError, "User error!"
+end
+
+Then /^I shouldn't be allowed out in public$/ do
+end
View
48 acceptance/verification_spec.rb
@@ -62,3 +62,51 @@
doc.root.elements.to_a("/testsuite/testcase").size.should == 1
end
end
+
+describe "Cucumber acceptance" do
+ it "should generate three XML files" do
+ File.exist?(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Conscientious-developer.xml')).should == true
+ File.exist?(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Lazy-hacker.xml')).should == true
+ File.exist?(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Bad-coder.xml')).should == true
+
+ Dir["#{REPORTS_DIR}/CUCUMBER-*.xml"].length.should == 3
+ end
+
+ it "should have three tests and no failures for the conscientious developer" do
+ doc = File.open(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Conscientious-developer.xml')) do |f|
+ REXML::Document.new(f)
+ end
+ doc.root.attributes["errors"].should == "0"
+ doc.root.attributes["failures"].should == "0"
+ doc.root.attributes["tests"].should == "3"
+ doc.root.elements.to_a("/testsuite/testcase").size.should == 3
+ end
+
+ it "should have three tests and one failure for the lazy hacker" do
+ doc = File.open(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Lazy-hacker.xml')) do |f|
+ REXML::Document.new(f)
+ end
+ doc.root.attributes["errors"].should == "0"
+ doc.root.attributes["failures"].should == "1"
+ doc.root.attributes["tests"].should == "3"
+ doc.root.elements.to_a("/testsuite/testcase").size.should == 3
+
+ failures = doc.root.elements.to_a("/testsuite/testcase/failure")
+ failures.size.should == 1
+ failures.first.attributes["type"].should == "Spec::Expectations::ExpectationNotMetError"
+ end
+
+ it "should have three tests and one failure for the bad coder" do
+ doc = File.open(File.join(REPORTS_DIR, 'CUCUMBER-Feature-Example-feature-Bad-coder.xml')) do |f|
+ REXML::Document.new(f)
+ end
+ doc.root.attributes["errors"].should == "0"
+ doc.root.attributes["failures"].should == "1"
+ doc.root.attributes["tests"].should == "3"
+ doc.root.elements.to_a("/testsuite/testcase").size.should == 3
+
+ failures = doc.root.elements.to_a("/testsuite/testcase/failure")
+ failures.size.should == 1
+ failures.first.attributes["type"].should == "RuntimeError"
+ end
+end
View
99 lib/ci/reporter/cucumber.rb
@@ -0,0 +1,99 @@
+# (c) Copyright 2006-2009 Nick Sieger <nicksieger@gmail.com>
+# See the file LICENSE.txt included with the distribution for
+# software license details.
+
+require 'ci/reporter/core'
+tried_gem = false
+begin
+ require 'cucumber'
+ require 'cucumber/ast/visitor'
+rescue LoadError
+ unless tried_gem
+ tried_gem = true
+ require 'rubygems'
+ gem 'cucumber'
+ retry
+ end
+end
+
+module CI
+ module Reporter
+ class CucumberFailure
+ attr_reader :step
+
+ def initialize(step)
+ @step = step
+ end
+
+ def failure?
+ true
+ end
+
+ def error?
+ !failure?
+ end
+
+ def name
+ step.exception.class.name
+ end
+
+ def message
+ step.exception.message
+ end
+
+ def location
+ step.exception.backtrace.join("\n")
+ end
+ end
+
+ class Cucumber < ::Cucumber::Ast::Visitor
+
+ attr_accessor :test_suite, :report_manager, :feature_name
+
+ def initialize(step_mother, io, options)
+ self.report_manager = ReportManager.new("cucumber")
+ super(step_mother)
+ end
+
+ def visit_feature_name(name)
+ self.feature_name = name.split("\n").first
+ super
+ end
+
+ def visit_feature_element(feature_element)
+ self.test_suite = TestSuite.new("#{feature_name} #{feature_element.instance_variable_get("@name")}")
+ test_suite.start
+
+ return_value = super
+
+ test_suite.finish
+ report_manager.write_report(test_suite)
+ self.test_suite = nil
+
+ return_value
+ end
+
+ def visit_step(step)
+ test_case = TestCase.new(step.name)
+ test_case.start
+
+ return_value = super
+
+ test_case.finish
+
+ case step.status
+ when :pending, :undefined
+ test_case.name = "#{test_case.name} (PENDING)"
+ when :skipped
+ test_case.name = "#{test_case.name} (SKIPPED)"
+ when :failed
+ test_case.failures << CucumberFailure.new(step)
+ end
+
+ test_suite.testcases << test_case
+
+ return_value
+ end
+ end
+ end
+end
View
17 lib/ci/reporter/rake/cucumber.rb
@@ -0,0 +1,17 @@
+# (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
+# See the file LICENSE.txt included with the distribution for
+# software license details.
+
+namespace :ci do
+ namespace :setup do
+ task :cucumber_report_cleanup do
+ rm_rf ENV["CI_REPORTS"] || "features/reports"
+ end
+
+ task :cucumber => :cucumber_report_cleanup do
+ spec_opts = ["--require", "#{File.dirname(__FILE__)}/cucumber_loader.rb",
+ "--format", "CI::Reporter::Cucumber"].join(" ")
+ ENV["CUCUMBER_OPTS"] = "#{ENV['CUCUMBER_OPTS']} #{spec_opts}"
+ end
+ end
+end
View
6 lib/ci/reporter/rake/cucumber_loader.rb
@@ -0,0 +1,6 @@
+# (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
+# See the file LICENSE.txt included with the distribution for
+# software license details.
+
+$: << File.dirname(__FILE__) + "/../../.."
+require 'ci/reporter/cucumber'
View
204 spec/ci/reporter/cucumber_spec.rb
@@ -0,0 +1,204 @@
+# (c) Copyright 2006-2008 Nick Sieger <nicksieger@gmail.com>
+# See the file LICENSE.txt included with the distribution for
+# software license details.
+
+require File.dirname(__FILE__) + "/../../spec_helper.rb"
+require 'ci/reporter/cucumber'
+
+describe "The Cucumber reporter" do
+ describe CI::Reporter::CucumberFailure do
+ before(:each) do
+ @klass = mock("class")
+ @klass.stub!(:name).and_return("Exception name")
+
+ @exception = mock("exception")
+ @exception.stub!(:class).and_return(@klass)
+ @exception.stub!(:message).and_return("Exception message")
+ @exception.stub!(:backtrace).and_return(["First line", "Second line"])
+
+ @step = mock("step")
+ @step.stub!(:exception).and_return(@exception)
+
+ @cucumber_failure = CI::Reporter::CucumberFailure.new(@step)
+ end
+
+ it "should always return true for failure?" do
+ @cucumber_failure.should be_failure
+ end
+
+ it "should always return false for error?" do
+ @cucumber_failure.should_not be_error
+ end
+
+ it "should propagate the name as the underlying exception's class name" do
+ @step.should_receive(:exception)
+ @exception.should_receive(:class)
+ @klass.should_receive(:name)
+
+ @cucumber_failure.name.should == "Exception name"
+ end
+
+ it "should propagate the message as the underlying exception's message" do
+ @step.should_receive(:exception)
+ @exception.should_receive(:message)
+
+ @cucumber_failure.message.should == "Exception message"
+ end
+
+ it "should propagate and format the exception's backtrace" do
+ @step.should_receive(:exception)
+ @exception.should_receive(:backtrace)
+
+ @cucumber_failure.location.should == "First line\nSecond line"
+ end
+ end
+
+ describe CI::Reporter::Cucumber do
+ before(:each) do
+ @step_mother = mock("step_mother")
+ @io = mock("io")
+
+ @report_manager = mock("report_manager")
+ CI::Reporter::ReportManager.stub!(:new).and_return(@report_manager)
+ end
+
+ def new_instance
+ CI::Reporter::Cucumber.new(@step_mother, @io, {})
+ end
+
+ it "should create a new report manager to report on test success/failure" do
+ CI::Reporter::ReportManager.should_receive(:new)
+ new_instance
+ end
+
+ it "should record the feature name when a new feature is visited" do
+ cucumber = new_instance
+ cucumber.visit_feature_name("Some feature name")
+ cucumber.feature_name.should == "Some feature name"
+ end
+
+ it "should record only the first line of a feature name" do
+ cucumber = new_instance
+ cucumber.visit_feature_name("Some feature name\nLonger description")
+ cucumber.feature_name.should == "Some feature name"
+ end
+
+ describe "when visiting a new scenario" do
+ before(:each) do
+ @cucumber = new_instance
+ @cucumber.visit_feature_name("Demo feature")
+
+ @test_suite = mock("test_suite", :start => nil, :finish => nil)
+ CI::Reporter::TestSuite.stub!(:new).and_return(@test_suite)
+
+ @feature_element = mock("feature_element", :accept => true)
+
+ @report_manager.stub!(:write_report)
+ end
+
+ it "should create a new test suite" do
+ # FIXME: @name is feature_element purely as a by-product of the
+ # mocking framework implementation. But then again, using
+ # +instance_variable_get+ in the first place is a bit icky.
+ CI::Reporter::TestSuite.should_receive(:new).with("Demo feature feature_element")
+ @cucumber.visit_feature_element(@feature_element)
+ end
+
+ it "should indicate that the test suite has started" do
+ @test_suite.should_receive(:start)
+ @cucumber.visit_feature_element(@feature_element)
+ end
+
+ it "should indicate that the test suite has finished" do
+ @test_suite.should_receive(:finish)
+ @cucumber.visit_feature_element(@feature_element)
+ end
+
+ it "should ask the report manager to write a report" do
+ @report_manager.should_receive(:write_report).with(@test_suite)
+ @cucumber.visit_feature_element(@feature_element)
+ end
+ end
+
+ describe "when visiting a step inside a scenario" do
+ before(:each) do
+ @testcases = []
+
+ @test_suite = mock("test_suite", :testcases => @testcases)
+
+ @cucumber = new_instance
+ @cucumber.stub!(:test_suite).and_return(@test_suite)
+
+ @test_case = mock("test_case", :start => nil, :finish => nil, :name => "Step Name")
+ CI::Reporter::TestCase.stub!(:new).and_return(@test_case)
+
+ @step = mock("step", :accept => true, :status => :passed)
+ @step.stub!(:name).and_return("Step Name")
+ end
+
+ it "should create a new test case" do
+ CI::Reporter::TestCase.should_receive(:new).with("Step Name")
+ @cucumber.visit_step(@step)
+ end
+
+ it "should indicate that the test case has started" do
+ @test_case.should_receive(:start)
+ @cucumber.visit_step(@step)
+ end
+
+ it "should indicate that the test case has finished" do
+ @test_case.should_receive(:finish)
+ @cucumber.visit_step(@step)
+ end
+
+ it "should add the test case to the suite's list of cases" do
+ @testcases.should be_empty
+ @cucumber.visit_step(@step)
+ @testcases.should_not be_empty
+ @testcases.first.should == @test_case
+ end
+
+ it "should alter the name of a test case that is pending to include '(PENDING)'" do
+ @step.stub!(:status).and_return(:pending)
+ @test_case.should_receive(:name=).with("Step Name (PENDING)")
+ @cucumber.visit_step(@step)
+ end
+
+ it "should alter the name of a test case that is undefined to include '(PENDING)'" do
+ @step.stub!(:status).and_return(:undefined)
+ @test_case.should_receive(:name=).with("Step Name (PENDING)")
+ @cucumber.visit_step(@step)
+ end
+
+ it "should alter the name of a test case that was skipped to include '(SKIPPED)'" do
+ @step.stub!(:status).and_return(:skipped)
+ @test_case.should_receive(:name=).with("Step Name (SKIPPED)")
+ @cucumber.visit_step(@step)
+ end
+
+ describe "that fails" do
+ before(:each) do
+ @step.stub!(:status).and_return(:failed)
+
+ @failures = []
+ @test_case.stub!(:failures).and_return(@failures)
+
+ @cucumber_failure = mock("cucumber_failure")
+ CI::Reporter::CucumberFailure.stub!(:new).and_return(@cucumber_failure)
+ end
+
+ it "should create a new cucumber failure with that step" do
+ CI::Reporter::CucumberFailure.should_receive(:new).with(@step)
+ @cucumber.visit_step(@step)
+ end
+
+ it "should add the failure to the suite's list of failures" do
+ @failures.should be_empty
+ @cucumber.visit_step(@step)
+ @failures.should_not be_empty
+ @failures.first.should == @cucumber_failure
+ end
+ end
+ end
+ end
+end
View
27 spec/ci/reporter/rake/rake_tasks_spec.rb
@@ -71,3 +71,30 @@ def restore_env(v)
ENV["SPEC_OPTS"].should =~ /somevalue.*--require.*rspec_loader.*--format.*CI::Reporter::RSpec/
end
end
+
+describe "ci_reporter ci:setup:cucumber task" do
+ before(:each) do
+ @rake = Rake::Application.new
+ Rake.application = @rake
+ load CI_REPORTER_LIB + '/ci/reporter/rake/cucumber.rb'
+ save_env "CI_REPORTS"
+ save_env "CUCUMBER_OPTS"
+ ENV["CI_REPORTS"] = "some-bogus-nonexistent-directory-that-wont-fail-rm_rf"
+ end
+ after(:each) do
+ restore_env "CUCUMBER_OPTS"
+ restore_env "CI_REPORTS"
+ Rake.application = nil
+ end
+
+ it "should set ENV['CUCUMBER_OPTS'] to include cucumber formatter args" do
+ @rake["ci:setup:cucumber"].invoke
+ ENV["CUCUMBER_OPTS"].should =~ /--require.*cucumber_loader.*--format.*CI::Reporter::Cucumber/
+ end
+
+ it "should append to ENV['CUCUMBER_OPTS'] if it already contains a value" do
+ ENV["CUCUMBER_OPTS"] = "somevalue".freeze
+ @rake["ci:setup:cucumber"].invoke
+ ENV["CUCUMBER_OPTS"].should =~ /somevalue.*--require.*cucumber_loader.*--format.*CI::Reporter::Cucumber/
+ end
+end
View
1  stub.rake
@@ -9,5 +9,6 @@
#
load File.dirname(__FILE__) + '/lib/ci/reporter/rake/rspec.rb'
+load File.dirname(__FILE__) + '/lib/ci/reporter/rake/cucumber.rb'
load File.dirname(__FILE__) + '/lib/ci/reporter/rake/test_unit.rb'
load 'Rakefile'
View
2  tasks/ci_reporter.rake
@@ -8,9 +8,11 @@ rescue Gem::LoadError
$: << File.dirname(__FILE__) + "/../lib"
end
require 'ci/reporter/rake/rspec'
+require 'ci/reporter/rake/cucumber'
require 'ci/reporter/rake/test_unit'
namespace :ci do
task :setup_rspec => "ci:setup:rspec"
+ task :setup_cucumber => "ci:setup:cucumber"
task :setup_testunit => "ci:setup:testunit"
end
Please sign in to comment.
Something went wrong with that request. Please try again.