scrab 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rally.sample +3 -0
- data/Gemfile +4 -0
- data/Guardfile +22 -0
- data/Rakefile +1 -0
- data/bin/scrab +34 -0
- data/custom_formatter.rb +84 -0
- data/lib/scrab.rb +38 -0
- data/lib/scrab/command_set.rb +29 -0
- data/lib/scrab/commands/push.rb +36 -0
- data/lib/scrab/feature_parser.rb +67 -0
- data/lib/scrab/rally.rb +60 -0
- data/lib/scrab/scenario.rb +8 -0
- data/lib/scrab/step.rb +7 -0
- data/lib/scrab/story.rb +8 -0
- data/lib/scrab/version.rb +3 -0
- data/scrab.gemspec +31 -0
- data/spec/acceptance/pushing_story_spec.rb +7 -0
- data/spec/fixtures/invalid-name.feature +4 -0
- data/spec/fixtures/story.feature +9 -0
- data/spec/objects/command_set_spec.rb +26 -0
- data/spec/objects/commands/push_spec.rb +38 -0
- data/spec/objects/feature_parse_spec.rb +47 -0
- data/spec/objects/rally_spec.rb +53 -0
- data/spec/objects/scenario_spec.rb +21 -0
- data/spec/objects/step_spec.rb +20 -0
- data/spec/objects/story_spec.rb +34 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/commands.rb +40 -0
- data/spec/support/fixtures.rb +13 -0
- metadata +188 -0
data/.gitignore
ADDED
data/.rally.sample
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
13
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
14
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
15
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
16
|
+
watch('spec/spec_helper.rb') { "spec" }
|
17
|
+
watch('config/routes.rb') { "spec/routing" }
|
18
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
19
|
+
# Capybara request specs
|
20
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
21
|
+
end
|
22
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/scrab
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "scrab"
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
opt_parser = OptionParser.new do |o|
|
9
|
+
o.banner = "Usage: scrab <command> [options*]"
|
10
|
+
o.separator ""
|
11
|
+
|
12
|
+
o.separator "Available commands:"
|
13
|
+
o.separator " push"
|
14
|
+
o.separator ""
|
15
|
+
|
16
|
+
o.separator "Global options:"
|
17
|
+
|
18
|
+
o.on("-h", "--help", "Show this message") do
|
19
|
+
puts opt_parser
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
opt_parser.parse!
|
25
|
+
|
26
|
+
command_name = ARGV.shift
|
27
|
+
|
28
|
+
begin
|
29
|
+
command = Scrab::CommandFactory.get(command_name, STDOUT)
|
30
|
+
command.run(options, *ARGV)
|
31
|
+
rescue Scrab::StandardError => e
|
32
|
+
puts e.message
|
33
|
+
puts opt_parser
|
34
|
+
end
|
data/custom_formatter.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "gherkin"
|
3
|
+
|
4
|
+
class CustomFormatter
|
5
|
+
def uri(uri)
|
6
|
+
@uri = uri
|
7
|
+
end
|
8
|
+
|
9
|
+
def feature(feature)
|
10
|
+
@feature = feature
|
11
|
+
end
|
12
|
+
|
13
|
+
def background(background)
|
14
|
+
@background = background
|
15
|
+
end
|
16
|
+
|
17
|
+
def step(step)
|
18
|
+
@step = step
|
19
|
+
end
|
20
|
+
|
21
|
+
def scenario(scenario)
|
22
|
+
@scenario = scenario
|
23
|
+
end
|
24
|
+
|
25
|
+
def scenario_outline(outline)
|
26
|
+
@outline = outline
|
27
|
+
end
|
28
|
+
|
29
|
+
def examples(examples)
|
30
|
+
@examples = examples
|
31
|
+
end
|
32
|
+
|
33
|
+
def eof
|
34
|
+
@feature
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
fmt = CustomFormatter.new
|
39
|
+
prs = Gherkin::Parser::Parser.new(fmt)
|
40
|
+
p prs.parse(File.read(ARGV[0]), nil, 0)
|
41
|
+
|
42
|
+
#require "cucumber/formatter/io"
|
43
|
+
#
|
44
|
+
#
|
45
|
+
#class CustomFormatter
|
46
|
+
# include Cucumber::Formatter::Io
|
47
|
+
#
|
48
|
+
# def initialize(step_mother, path_or_io, options)
|
49
|
+
# @step_mother, @io, @options = step_mother, ensure_io(path_or_io, "fuubar"), options
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def after_features(features)
|
53
|
+
# puts "==== AFTER FEATURES"
|
54
|
+
# p features
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def before_features(features)
|
58
|
+
# puts "==== BEFORE FEATURES"
|
59
|
+
# p features
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# def before_background(background)
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# def after_background(background)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
|
69
|
+
# puts "==== STEP RESULT"
|
70
|
+
# p [keyword, step_match, multiline_arg, status, exception, source_indent, background]
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# def before_examples(examples)
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# def after_examples(examples)
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def before_table_row(table_row)
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# def after_table_row(table_row)
|
83
|
+
# end
|
84
|
+
#end
|
data/lib/scrab.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup(:default)
|
3
|
+
|
4
|
+
require "rally_rest_api"
|
5
|
+
require "gherkin"
|
6
|
+
require "scrab/version"
|
7
|
+
require "scrab/command_set"
|
8
|
+
require "scrab/rally"
|
9
|
+
require "scrab/story"
|
10
|
+
require "scrab/scenario"
|
11
|
+
require "scrab/step"
|
12
|
+
require "scrab/feature_parser"
|
13
|
+
|
14
|
+
module Scrab
|
15
|
+
class StandardError < ::StandardError; end
|
16
|
+
|
17
|
+
LibPath = Pathname.new(File.dirname(__FILE__) + "/scrab")
|
18
|
+
CommandsPath = LibPath.join('commands')
|
19
|
+
Commands = Module.new
|
20
|
+
CommandFactory = CommandSet.new(Commands, CommandsPath)
|
21
|
+
|
22
|
+
def self.rally_config_path
|
23
|
+
"#{ENV['HOME']}/.rally"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.rally_config
|
27
|
+
raise "Please configure rally at #{rally_config_path}, check .rally.sample for an example" unless File.exist?(rally_config_path)
|
28
|
+
@rally_config ||= YAML.load(File.read(rally_config_path))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.rally
|
32
|
+
@rally ||= Rally.new(rally_config.merge(:logger => logger))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.logger
|
36
|
+
@logger ||= Logger.new(File.dirname(__FILE__) + "/../logs/rally.log")
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Scrab::CommandSet
|
2
|
+
class CommandNotFoundError < StandardError
|
3
|
+
def initialize(command_name)
|
4
|
+
super "Command not found: #{command_name}"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(namespace, commands_path)
|
9
|
+
@namespace = namespace
|
10
|
+
@commands_path = Pathname.new(commands_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(command_name, out)
|
14
|
+
begin
|
15
|
+
Kernel.load(@commands_path.join("#{command_name}.rb").to_s)
|
16
|
+
rescue LoadError => e
|
17
|
+
raise CommandNotFoundError.new(command_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
const = @namespace
|
21
|
+
|
22
|
+
command_name.split("/").each do |command_part|
|
23
|
+
const_name = command_part.capitalize.gsub(/_(.)/) { $1.upcase }
|
24
|
+
const = const.const_get(const_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
const.new(out)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Scrab::Commands::Push
|
2
|
+
def initialize(out, parser = nil)
|
3
|
+
@out = out
|
4
|
+
@parser = parser || Scrab::FeatureParser.new(Scrab::Story)
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(options, *features)
|
8
|
+
determine_feature_paths(features).each do |feature|
|
9
|
+
begin
|
10
|
+
story = @parser.parse(feature)
|
11
|
+
|
12
|
+
@out.print "Pushing '[#{story.id}] #{story.name}' to Rally... "
|
13
|
+
|
14
|
+
if Scrab.rally.update(story)
|
15
|
+
@out.puts "OK."
|
16
|
+
else
|
17
|
+
@out.puts "FAILED."
|
18
|
+
end
|
19
|
+
rescue Scrab::FeatureParser::ParsingError => e
|
20
|
+
@out.puts "Failed to parse '#{feature}':\n\t#{e.message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def determine_feature_paths(features)
|
26
|
+
features = ['features/'] if features.empty?
|
27
|
+
|
28
|
+
features.map do |feature_path|
|
29
|
+
if File.directory?(feature_path)
|
30
|
+
`find #{feature_path} -type f -name '*.feature'`.split(/\n/)
|
31
|
+
else
|
32
|
+
feature_path
|
33
|
+
end
|
34
|
+
end.flatten
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Scrab::FeatureParser
|
2
|
+
class ParsingError < StandardError
|
3
|
+
def initialize(line, desc)
|
4
|
+
super "Couldn't parse '#{line}': #{desc}"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class GherkinFormatter
|
9
|
+
def initialize(story)
|
10
|
+
@story = story
|
11
|
+
end
|
12
|
+
|
13
|
+
def feature(feature)
|
14
|
+
if feature.name.strip =~ /^\[(US\d+)\]\s+(.*)$/
|
15
|
+
@story.id = $1
|
16
|
+
@story.name = $2
|
17
|
+
else
|
18
|
+
raise ParsingError.new(feature.name, "expect feature title to be in '[US0000] Story name' format")
|
19
|
+
end
|
20
|
+
|
21
|
+
@story.narrative = feature.description
|
22
|
+
end
|
23
|
+
|
24
|
+
def background(background)
|
25
|
+
end
|
26
|
+
|
27
|
+
def scenario(scenario)
|
28
|
+
@scenario = Scrab::Scenario.new
|
29
|
+
@scenario.name = scenario.name
|
30
|
+
@scenario.description = scenario.description
|
31
|
+
@story.scenarios << @scenario
|
32
|
+
end
|
33
|
+
|
34
|
+
def step(step)
|
35
|
+
@step = Scrab::Step.new
|
36
|
+
@step.keyword = step.keyword
|
37
|
+
@step.name = step.name
|
38
|
+
@scenario.steps << @step
|
39
|
+
end
|
40
|
+
|
41
|
+
def scenario_outline(outline)
|
42
|
+
end
|
43
|
+
|
44
|
+
def examples(examples)
|
45
|
+
end
|
46
|
+
|
47
|
+
def uri(uri)
|
48
|
+
# no-op
|
49
|
+
end
|
50
|
+
|
51
|
+
def eof
|
52
|
+
# no-op
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# TODO: Pass story instance to parse instead of the class here
|
57
|
+
def initialize(story_klass)
|
58
|
+
@story_klass = story_klass
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse(feature_path)
|
62
|
+
story = @story_klass.new
|
63
|
+
parser = Gherkin::Parser::Parser.new(GherkinFormatter.new(story))
|
64
|
+
parser.parse(File.read(feature_path), nil, 0)
|
65
|
+
story
|
66
|
+
end
|
67
|
+
end
|
data/lib/scrab/rally.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
class Scrab::Rally
|
2
|
+
class ProjectNotFoundError < StandardError
|
3
|
+
def initialize(project)
|
4
|
+
super "Project '#{project}' not found"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@conn = RallyRestAPI.new(options)
|
10
|
+
@project = @conn.find(:project, :pagesize => 1) { equal :name, options[:project] }.first
|
11
|
+
raise ProjectNotFoundError.new(@project) if @project.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(story)
|
15
|
+
update_story(story)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def update_story(story)
|
20
|
+
rest_object = @conn.find(:hierarchical_requirement, :pagesize => 1, :project => @project) { equal :formatted_i_d, story.id }.first
|
21
|
+
@conn.update(rest_object, :name => story.name, :description => story.narrative)
|
22
|
+
update_story_scenarios(rest_object, story.scenarios)
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_story_scenarios(story_rest, scenarios)
|
26
|
+
scenarios.each do |scenario|
|
27
|
+
description = scenario.description
|
28
|
+
description = description && !description.empty? ? description : scenario.name
|
29
|
+
|
30
|
+
values = {
|
31
|
+
:project => @project,
|
32
|
+
:work_product => story_rest,
|
33
|
+
:name => scenario.name,
|
34
|
+
:description => description,
|
35
|
+
:priority => 'Important',
|
36
|
+
:type => 'Acceptance',
|
37
|
+
:method => 'Automated',
|
38
|
+
:risk => 'Medium',
|
39
|
+
:pre_conditions => "N/A",
|
40
|
+
:post_conditions => "N/A"
|
41
|
+
}
|
42
|
+
|
43
|
+
if rest_object = @conn.find(:test_case, :pagesize => 1, :project => @project) { equal :name, scenario.name.strip }.first
|
44
|
+
@conn.update(rest_object, values)
|
45
|
+
else
|
46
|
+
rest_object = @conn.create(:test_case, values)
|
47
|
+
end
|
48
|
+
|
49
|
+
update_scenario_steps(rest_object, scenario.steps)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_scenario_steps(scenario_rest, steps)
|
54
|
+
Array(scenario_rest.steps).each { |step_rest| step_rest.delete }
|
55
|
+
|
56
|
+
steps.each_with_index do |step, idx|
|
57
|
+
@conn.create(:test_case_step, :test_case => scenario_rest, :project => @project, :step_index => idx, :input => step.full_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/scrab/step.rb
ADDED
data/lib/scrab/story.rb
ADDED
data/scrab.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "scrab/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "scrab"
|
7
|
+
s.version = Scrab::VERSION
|
8
|
+
s.authors = ["Rodrigo Kochenburger"]
|
9
|
+
s.email = ["divoxx@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Simple tool for pushing cucumber features into Rally testcases}
|
12
|
+
s.description = %q{Simple tool for pushing cucumber features into Rally testcases}
|
13
|
+
|
14
|
+
s.rubyforge_project = "scrab"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'builder' # RallyRestAPI dep
|
22
|
+
s.add_runtime_dependency 'rally_rest_api'
|
23
|
+
s.add_runtime_dependency 'gherkin'
|
24
|
+
|
25
|
+
s.add_development_dependency 'rspec'
|
26
|
+
s.add_development_dependency 'guard'
|
27
|
+
s.add_development_dependency 'guard-rspec'
|
28
|
+
s.add_development_dependency 'rb-fsevent'
|
29
|
+
s.add_development_dependency 'growl_notify'
|
30
|
+
s.add_development_dependency 'fakeweb'
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/command_set"
|
3
|
+
|
4
|
+
describe CommandSet do
|
5
|
+
let(:command) { mock(:command) }
|
6
|
+
let(:command_klass) { mock(:command_klass) }
|
7
|
+
let(:out) { mock(:out) }
|
8
|
+
let(:namespace) { Module.new }
|
9
|
+
let(:commands_path) { "some/path/for/commands" }
|
10
|
+
|
11
|
+
subject { CommandSet.new(namespace, commands_path) }
|
12
|
+
|
13
|
+
before do
|
14
|
+
namespace.const_set(:Command, command_klass)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "loads commands dynamically" do
|
18
|
+
Kernel.should_receive(:load).with("#{commands_path}/command.rb").and_return
|
19
|
+
command_klass.should_receive(:new).with(out).and_return(command)
|
20
|
+
subject.get('command', out).should == command
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises a CommandNotFoundError when asking for a non-existing command" do
|
24
|
+
expect { subject.get('invalid-command', out) }.to raise_error(CommandSet::CommandNotFoundError)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/commands/push"
|
3
|
+
|
4
|
+
describe Commands::Push do
|
5
|
+
let(:rally) { mock(:rally, :update => true) }
|
6
|
+
let(:story) { mock(:story, :id => "US0000", :name => "Story name") }
|
7
|
+
let(:parser) { mock(:parser, :parse => story) }
|
8
|
+
let(:out) { StringIO.new }
|
9
|
+
let(:path) { "some/path.feature" }
|
10
|
+
subject { Commands::Push.new(out, parser) }
|
11
|
+
before { Scrab.stub(:rally => rally) }
|
12
|
+
|
13
|
+
it "outputs message for success" do
|
14
|
+
subject.run({}, path)
|
15
|
+
out.rewind
|
16
|
+
out.read.should =~ /OK/
|
17
|
+
end
|
18
|
+
|
19
|
+
it "outputs parsing error message" do
|
20
|
+
parser.should_receive(:parse).and_raise(Scrab::FeatureParser::ParsingError.new('foo', 'bar'))
|
21
|
+
subject.run({}, path)
|
22
|
+
out.rewind
|
23
|
+
out.read.should =~ /Couldn't parse/
|
24
|
+
end
|
25
|
+
|
26
|
+
it "parses and update the story" do
|
27
|
+
parser.should_receive(:parse).with(path).and_return(story)
|
28
|
+
rally.should_receive(:update).with(story).and_return(true)
|
29
|
+
subject.run({}, path)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "outputs a failed message when fail to push story" do
|
33
|
+
rally.should_receive(:update).with(story).and_return(false)
|
34
|
+
subject.run({}, path)
|
35
|
+
out.rewind
|
36
|
+
out.read.should =~ /FAILED/
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/feature_parser"
|
3
|
+
|
4
|
+
describe FeatureParser do
|
5
|
+
subject { FeatureParser.new(Story) }
|
6
|
+
|
7
|
+
def do_parse
|
8
|
+
subject.parse(fixture_path('story.feature'))
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses a feature and return a story" do
|
12
|
+
do_parse.should be_a(Story)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets the id" do
|
16
|
+
story = do_parse
|
17
|
+
story.id.should == "US5836"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets the story name" do
|
21
|
+
story = do_parse
|
22
|
+
story.name.should == "Story name"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "sets the story narrative" do
|
26
|
+
story = do_parse
|
27
|
+
story.narrative.should == <<-EOS.strip
|
28
|
+
As a user
|
29
|
+
I want to do something
|
30
|
+
So that that something is done
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises an parsing error if cant parse the id and story name" do
|
35
|
+
expect { subject.parse(fixture_path('invalid-name.feature')) }.to raise_error(FeatureParser::ParsingError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sets the scenarios" do
|
39
|
+
story = do_parse
|
40
|
+
story.should have(1).scenarios
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets the steps" do
|
44
|
+
story = do_parse
|
45
|
+
story.scenarios.first.should have(3).steps
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/rally"
|
3
|
+
|
4
|
+
describe Scrab::Rally do
|
5
|
+
let(:conn) { mock(:conn) }
|
6
|
+
let(:project) { mock(:project) }
|
7
|
+
let(:scenario) { mock(:scenario, :name => "Scenario name", :description => "scenario desc", :steps => []) }
|
8
|
+
let(:story) { mock(:story, :name => "Story name", :narrative => "Story narrative", :scenarios => []) }
|
9
|
+
let(:rest_obj) { mock(:rest_obj) }
|
10
|
+
subject { Scrab::Rally.new(:username => 'username', :password => 'password', :project => "project") }
|
11
|
+
|
12
|
+
before do
|
13
|
+
RallyRestAPI.stub(:new => conn)
|
14
|
+
conn.stub(:find).with(:project, anything).and_return(mock(:first => project))
|
15
|
+
conn.stub(:find).with(:hierarchical_requirement, anything).and_return(mock(:first => rest_obj))
|
16
|
+
conn.stub(:update).with(rest_obj, anything)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "establishes a rally connection" do
|
20
|
+
RallyRestAPI.should_receive(:new).with(hash_including(:username => 'username', :password => 'password'))
|
21
|
+
Scrab::Rally.new(:username => 'username', :password => 'password')
|
22
|
+
end
|
23
|
+
|
24
|
+
it "updates a given story" do
|
25
|
+
# FIXME: No idea how to test the yielded block (conditions) because its called with instance_eval
|
26
|
+
story.stub(:scenarios => [])
|
27
|
+
conn.should_receive(:find).with(:hierarchical_requirement, :pagesize => 1, :project => project).and_return(mock(:first => rest_obj))
|
28
|
+
conn.should_receive(:update).with(rest_obj, :name => story.name, :description => story.narrative)
|
29
|
+
subject.update(story)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "creates new testcases" do
|
33
|
+
story.stub(:scenarios => [scenario])
|
34
|
+
|
35
|
+
conn.should_receive(:find).with(:test_case, :pagesize => 1, :project => project).and_return(mock(:first => nil))
|
36
|
+
conn.should_receive(:create).with(:test_case,
|
37
|
+
:project => project,
|
38
|
+
:work_product => rest_obj,
|
39
|
+
:name => scenario.name,
|
40
|
+
:description => scenario.description,
|
41
|
+
:priority => 'Important',
|
42
|
+
:type => 'Acceptance',
|
43
|
+
:method => "Automated",
|
44
|
+
:risk => 'Medium',
|
45
|
+
:pre_conditions => "N/A",
|
46
|
+
:post_conditions => "N/A"
|
47
|
+
).and_return(rest_obj)
|
48
|
+
|
49
|
+
rest_obj.stub(:steps).and_return([])
|
50
|
+
|
51
|
+
subject.update(story)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/scenario"
|
3
|
+
|
4
|
+
describe Scenario do
|
5
|
+
let(:name) { "Scenario name" }
|
6
|
+
let(:description) { "Scenario desc" }
|
7
|
+
|
8
|
+
it "has a name" do
|
9
|
+
subject.name = name
|
10
|
+
subject.name.should == name
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a description" do
|
14
|
+
subject.description = description
|
15
|
+
subject.description.should == description
|
16
|
+
end
|
17
|
+
|
18
|
+
it "has steps" do
|
19
|
+
subject.steps.should be_a(Enumerable)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/step"
|
3
|
+
|
4
|
+
describe Step do
|
5
|
+
it "has a keyword" do
|
6
|
+
subject.keyword = "Given"
|
7
|
+
subject.keyword.should == "Given"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "has a name" do
|
11
|
+
subject.name = "name"
|
12
|
+
subject.name.should == "name"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has a full_name" do
|
16
|
+
subject.keyword = "Given"
|
17
|
+
subject.name = "I am on that page"
|
18
|
+
subject.full_name.should == "Given I am on that page"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "scrab/story"
|
3
|
+
|
4
|
+
describe Story do
|
5
|
+
let(:id) { "US0000" }
|
6
|
+
let(:name) { "Story name" }
|
7
|
+
let(:narrative) { <<-EOS }
|
8
|
+
As a user
|
9
|
+
I want to do something
|
10
|
+
So that a get that value
|
11
|
+
EOS
|
12
|
+
|
13
|
+
let(:scenario) { mock(:scenario) }
|
14
|
+
|
15
|
+
it "has an id" do
|
16
|
+
subject.id = id
|
17
|
+
subject.id.should == id
|
18
|
+
end
|
19
|
+
|
20
|
+
it "has a name" do
|
21
|
+
subject.name = name
|
22
|
+
subject.name.should == name
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has a narrative" do
|
26
|
+
subject.narrative = narrative
|
27
|
+
subject.narrative.should == narrative
|
28
|
+
end
|
29
|
+
|
30
|
+
it "has scenarios" do
|
31
|
+
subject.scenarios << scenario
|
32
|
+
subject.scenarios.should == [scenario]
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "scrab"
|
2
|
+
Bundler.setup(:development)
|
3
|
+
include Scrab
|
4
|
+
|
5
|
+
require "rspec"
|
6
|
+
require "fakeweb"
|
7
|
+
|
8
|
+
require "support/fixtures"
|
9
|
+
require "support/commands"
|
10
|
+
|
11
|
+
RSpec.configure do |c|
|
12
|
+
c.include(Support::Fixtures)
|
13
|
+
c.include(Support::Commands)
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Support
|
2
|
+
module Commands
|
3
|
+
def scrab(cmd)
|
4
|
+
ScrabProcess.new("ruby -Ilib bin/scrab #{cmd}")
|
5
|
+
end
|
6
|
+
|
7
|
+
class ScrabProcess
|
8
|
+
attr_reader :cmd, :out
|
9
|
+
|
10
|
+
def initialize(cmd)
|
11
|
+
@cmd = cmd
|
12
|
+
|
13
|
+
IO.popen(cmd) do |io|
|
14
|
+
@out = io.read.freeze
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec::Matchers.define :output do |expected_out|
|
22
|
+
match do |process|
|
23
|
+
process.out.strip == expected_out.strip
|
24
|
+
end
|
25
|
+
|
26
|
+
failure_message_for_should do |process|
|
27
|
+
fmt_expected_out = expected_out.split(/\n/).join("\n\t\t")
|
28
|
+
fmt_actual_out = process.out.split(/\n/).join("\n\t\t")
|
29
|
+
|
30
|
+
msg = <<-EOS
|
31
|
+
\tExpected command '#{process.cmd}' output to match:
|
32
|
+
\t\t#{fmt_expected_out}
|
33
|
+
|
34
|
+
\tBut got:
|
35
|
+
\t\t#{fmt_actual_out}
|
36
|
+
EOS
|
37
|
+
|
38
|
+
msg
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scrab
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rodrigo Kochenburger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: builder
|
16
|
+
requirement: &70203933972840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70203933972840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rally_rest_api
|
27
|
+
requirement: &70203933972400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70203933972400
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: gherkin
|
38
|
+
requirement: &70203933971880 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70203933971880
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70203933971420 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70203933971420
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard
|
60
|
+
requirement: &70203933970880 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70203933970880
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: &70203933970320 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70203933970320
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rb-fsevent
|
82
|
+
requirement: &70203933969740 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70203933969740
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: growl_notify
|
93
|
+
requirement: &70203933968800 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70203933968800
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: fakeweb
|
104
|
+
requirement: &70203933968320 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70203933968320
|
113
|
+
description: Simple tool for pushing cucumber features into Rally testcases
|
114
|
+
email:
|
115
|
+
- divoxx@gmail.com
|
116
|
+
executables:
|
117
|
+
- scrab
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- .gitignore
|
122
|
+
- .rally.sample
|
123
|
+
- Gemfile
|
124
|
+
- Guardfile
|
125
|
+
- Rakefile
|
126
|
+
- bin/scrab
|
127
|
+
- custom_formatter.rb
|
128
|
+
- lib/scrab.rb
|
129
|
+
- lib/scrab/command_set.rb
|
130
|
+
- lib/scrab/commands/push.rb
|
131
|
+
- lib/scrab/feature_parser.rb
|
132
|
+
- lib/scrab/rally.rb
|
133
|
+
- lib/scrab/scenario.rb
|
134
|
+
- lib/scrab/step.rb
|
135
|
+
- lib/scrab/story.rb
|
136
|
+
- lib/scrab/version.rb
|
137
|
+
- scrab.gemspec
|
138
|
+
- spec/acceptance/pushing_story_spec.rb
|
139
|
+
- spec/fixtures/invalid-name.feature
|
140
|
+
- spec/fixtures/story.feature
|
141
|
+
- spec/objects/command_set_spec.rb
|
142
|
+
- spec/objects/commands/push_spec.rb
|
143
|
+
- spec/objects/feature_parse_spec.rb
|
144
|
+
- spec/objects/rally_spec.rb
|
145
|
+
- spec/objects/scenario_spec.rb
|
146
|
+
- spec/objects/step_spec.rb
|
147
|
+
- spec/objects/story_spec.rb
|
148
|
+
- spec/spec_helper.rb
|
149
|
+
- spec/support/commands.rb
|
150
|
+
- spec/support/fixtures.rb
|
151
|
+
homepage: ''
|
152
|
+
licenses: []
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project: scrab
|
171
|
+
rubygems_version: 1.8.11
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: Simple tool for pushing cucumber features into Rally testcases
|
175
|
+
test_files:
|
176
|
+
- spec/acceptance/pushing_story_spec.rb
|
177
|
+
- spec/fixtures/invalid-name.feature
|
178
|
+
- spec/fixtures/story.feature
|
179
|
+
- spec/objects/command_set_spec.rb
|
180
|
+
- spec/objects/commands/push_spec.rb
|
181
|
+
- spec/objects/feature_parse_spec.rb
|
182
|
+
- spec/objects/rally_spec.rb
|
183
|
+
- spec/objects/scenario_spec.rb
|
184
|
+
- spec/objects/step_spec.rb
|
185
|
+
- spec/objects/story_spec.rb
|
186
|
+
- spec/spec_helper.rb
|
187
|
+
- spec/support/commands.rb
|
188
|
+
- spec/support/fixtures.rb
|