scrab 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|