crab 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -41,7 +41,7 @@ Thankfully, that part is easy:
41
41
  Logged in as cv@lixo.org.
42
42
 
43
43
  $ crab project "World Domination 3000"
44
- $ crab list
44
+ $ crab find
45
45
  US1001: Arms Rockets Upon Successful Boot
46
46
  US1002: Launches Rockets Upon Command from Evil Mastermind
47
47
  US1003: Transfers $0.01 From All Bank Accounts
@@ -82,10 +82,31 @@ features to have:
82
82
 
83
83
  There are more switches. Check out `crab update --help` to find out more.
84
84
 
85
+ i18n Support
86
+ ------------
87
+
88
+ `crab` uses [Gherkin][3] internally, so all languages supported by Cucumber are also
89
+ included:
90
+
91
+ $ crab show US1001 -l ja
92
+ 機能: [US1001] Arms Rockets Upon Successful Boot
93
+ ...
94
+ シナリオ: [TC10388] Rocket Silo Is Unlocked
95
+ Given a silo where the rockets are stored
96
+ ...
97
+
98
+ Unfortunately, we could not think of a decent way to translate the steps themselves
99
+ (see the `Given` there?), without using Gherkin to parse each step individually and
100
+ check that it can be used, which seemed a little overkill for now.
101
+
102
+ Hopefully this will be enough for your case, but if not please let us know!
103
+
104
+ [3]: https://github.com/cucumber/gherkin
105
+
85
106
  Developing
86
107
  ----------
87
108
 
88
- To develop `crab`, you are going to need [Bundler][3], [Aruba][4] and a
109
+ To develop `crab`, you are going to need [Bundler][4], [Aruba][5] and a
89
110
  working Rally account with a project set up where you can edit things. The
90
111
  supplied `Gemfile` should take care of everything else:
91
112
 
@@ -96,8 +117,8 @@ supplied `Gemfile` should take care of everything else:
96
117
 
97
118
  If you have any problems, please let us know.
98
119
 
99
- [3]: http://gembundler.com
100
- [4]: http://github.com/cucumber/aruba
120
+ [4]: http://gembundler.com
121
+ [5]: https://github.com/cucumber/aruba
101
122
 
102
123
  To do
103
124
  -----
@@ -116,9 +137,9 @@ To do
116
137
  - Add a `move` subcommand which moves the story from one state to the next (potentially, `move --back`)
117
138
  - Add a Cucumber Formatter that updates Test Runs in Rally with results from CI
118
139
  - Investigate use of other fields like Priority and Risk in Rally Test Cases
119
- - Support i18n Cucumber Features
120
140
  - Make it possible to associate defects with Features (essentially treating defects like stories)
121
141
  - Test in Ruby 1.9
142
+ - Use the Gherkin models and formatters instead of dumb string templates to generate feature files
122
143
 
123
144
  Suggestions? Please get in touch!
124
145
 
@@ -136,7 +157,6 @@ Disclaimers
136
157
 
137
158
  This project and its authors have no affiliation with Rally Software Development Corp. or the Cucumber project.
138
159
 
139
-
140
160
  It was written as necessity in a real-world project, and by no means should represent endorsement of either product.
141
161
 
142
162
  Rally (c) 2003-2011 Rally Software Development Corp.
@@ -19,8 +19,9 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_development_dependency 'aruba'
22
+ s.add_development_dependency 'cucumber'
22
23
 
23
- s.add_dependency 'cucumber'
24
+ s.add_dependency 'gherkin'
24
25
  s.add_dependency 'rally_rest_api'
25
26
  s.add_dependency 'highline'
26
27
  s.add_dependency 'activesupport'
@@ -0,0 +1,18 @@
1
+ Feature: Create and Delete Stories
2
+
3
+ In order to manipulate stories without leaving the terminal
4
+ A lazy developer
5
+ Wants to create and delete stories using crab
6
+
7
+ Background:
8
+ Given I am logged in
9
+
10
+ Scenario: Create and Delete a Simple Story
11
+ # When I run `crab create "Dummy Story (created by crab)"`
12
+ # Then the output should match /US\d{4}: Dummy Story (created by crab) (grooming)/
13
+
14
+ # When I run `crab find "Dummy Story"`
15
+ # Then the output should match /US\d{4}: Dummy Story (created by crab) (grooming)/
16
+
17
+ # When I run `crab delete ????`
18
+ # Then the output should match /Story US\d{4} deleted./
@@ -1,21 +1,21 @@
1
1
  Feature: Find Text in Stories
2
-
2
+
3
3
  In order to find the story ID
4
4
  A lazy developer
5
5
  Wants to search for arbitrary bits of text
6
6
 
7
- Background:
7
+ Background:
8
8
  Given I am logged in
9
9
  And I have selected my test project
10
10
 
11
11
  Scenario: Matching Name
12
12
  When I run `crab find Sample Crab`
13
13
  Then the output should contain:
14
- """
15
- US4988: Sample Crab Story (grooming)
16
- US4999: Sample Crab Parent Story (grooming)
17
- US5000: Sample Crab Parent Story (grooming)
18
- """
14
+ """
15
+ US4988: Sample Crab Story (grooming)
16
+ US4999: Sample Crab Parent Story (grooming)
17
+ US5000: Sample Crab Parent Story (grooming)
18
+ """
19
19
 
20
20
  @quick
21
21
  Scenario: Project Must be Specified If Not Set
@@ -26,6 +26,7 @@ US5000: Sample Crab Parent Story (grooming)
26
26
  Scenario: Project Must Exist
27
27
  When I run `crab find --project "foo" pattern`
28
28
  Then the output should contain:
29
- """
30
- Error: Project "foo" not found.
31
- """
29
+ """
30
+ Error: Project "foo" not found.
31
+ """
32
+
@@ -1,5 +1,5 @@
1
1
  Feature: Log In and Out of Rally
2
-
2
+
3
3
  In order to avoid typing his credentials all the time
4
4
  A lazy and security-conscious developer
5
5
  Wants to log in and out of Rally in order to perform operations
@@ -1,27 +1,25 @@
1
1
  Feature: Project Selection
2
-
2
+
3
3
  In order to work with the find, list etc commands more effectively
4
4
  A lazy developer
5
5
  Wants to set the project persistently across all commands
6
6
 
7
- Background:
7
+ Background:
8
8
  Given I am logged in
9
9
 
10
10
  Scenario: Selecting a Project
11
11
  Given no project is selected
12
-
13
12
  When I run `crab project`
14
13
  Then the output should contain "No project currently selected."
15
-
16
14
  When I select my test project
17
15
  Then the exit status should be 0
18
-
19
16
  When I run `crab project`
20
17
  Then the output should be the name of my test project
21
18
 
22
19
  Scenario: Selecting an Invalid Project
23
20
  When I run `crab project "invalid"`
24
21
  Then the output should contain:
25
- """
26
- Error: "invalid" is not a valid project.
27
- """
22
+ """
23
+ Error: "invalid" is not a valid project.
24
+ """
25
+
@@ -1,10 +1,10 @@
1
1
  Feature: Pull From Rally Into Cucumber
2
-
2
+
3
3
  In order to begin development of a story that was written in Rally
4
4
  A developer who doesn't want to open a browser or click things
5
5
  Wants the story converted into the much nicer Cucumber format
6
6
 
7
- Background:
7
+ Background:
8
8
  Given I am logged in
9
9
 
10
10
  Scenario: Pulling a Single Story
@@ -15,11 +15,12 @@ Feature: Pull From Rally Into Cucumber
15
15
  And a directory named "features" should exist
16
16
  And a file named "features/grooming/US4988-sample-crab-story.feature" should exist
17
17
  And the file "features/grooming/US4988-sample-crab-story.feature" should contain exactly:
18
- """
19
- Feature: [US4988] Sample Crab Story
18
+ """
19
+ # language: en
20
+ Feature: [US4988] Sample Crab Story
20
21
 
21
- Sample Description
22
- """
22
+ Sample Description
23
+ """
23
24
 
24
25
  Scenario: Pulling Multiple Stories
25
26
  When I run `crab pull US4988 US5000`
@@ -1,26 +1,32 @@
1
1
  Feature: Show Story From Rally
2
-
2
+
3
3
  In order to see what's in a story
4
4
  A lazy developer
5
5
  Wants to be able to do it from the command line
6
6
 
7
- Background:
7
+ Background:
8
8
  Given I am logged in
9
9
 
10
10
  Scenario: Show Simple Story
11
11
  When I run `crab show US4988`
12
12
  Then the output should contain:
13
- """
14
- Feature: [US4988] Sample Crab Story
13
+ """
14
+ Feature: [US4988] Sample Crab Story
15
15
 
16
- Sample Description
17
- """
16
+ Sample Description
17
+ """
18
18
 
19
19
  Scenario: Show Story With Test Cases
20
20
  When I run `crab show US5000`
21
21
  Then the output should contain "Feature: [US5000] Sample Crab Parent Story"
22
- And the output should contain "@manual @functional"
23
- And the output should contain "Scenario: [TC10388] Sample Testcase"
24
- And the output should contain " Given Rally behaves"
25
- And the output should contain " When I look at the test case steps"
26
- And the output should contain " Then I should be able to export them into Cucumber format"
22
+ And the output should contain "@manual @functional"
23
+ And the output should contain "Scenario: [TC10388] Sample Testcase"
24
+ And the output should contain " Given Rally behaves"
25
+ And the output should contain " When I look at the test case steps"
26
+ And the output should contain " Then I should be able to export them into Cucumber format"
27
+
28
+ Scenario: Story In Different Language
29
+ When I run `crab show US5000 --language pt`
30
+ Then the output should contain "Funcionalidade: "
31
+ And the output should contain "Cenario: "
32
+
@@ -1,6 +1,6 @@
1
1
  @quick
2
2
  Feature: Subcommand Help
3
-
3
+
4
4
  In order to learn how to use crab
5
5
  A newbie developer
6
6
  Wants to see a useful help message when she runs crab with the wrong arguments
@@ -11,46 +11,66 @@ Feature: Subcommand Help
11
11
 
12
12
  Scenario: Help
13
13
  When I run `crab -h`
14
- Then the output should contain " find Find stories by text in name, description or notes"
15
- And the output should contain " login Persistently authenticate user with Rally"
16
- And the output should contain " project Persistently select project to work with in Rally"
17
- And the output should contain " pull Downloads stories (and its test cases) as Cucumber feature files"
18
- And the output should contain " show Show a story (and its test cases) as a Cucumber feature"
19
- And the output should contain " update Update a story (name, estimate, etc)"
14
+ Then the output should contain " create Create a new story in Rally"
15
+ Then the output should contain " delete Delete an existing story in Rally"
16
+ And the output should contain " find Find stories by text in name, description or notes"
17
+ And the output should contain " login Persistently authenticate user with Rally"
18
+ And the output should contain " project Persistently select project to work with in Rally"
19
+ And the output should contain " pull Downloads stories (and its test cases) as Cucumber feature files"
20
+ And the output should contain " show Show a story (and its test cases) as a Cucumber feature"
21
+ And the output should contain " update Update a story (name, estimate, etc)"
20
22
 
21
23
  Scenario: Bogus Subcommand
22
24
  When I run `crab bogus`
23
25
  Then the output should contain:
24
- """
25
- Error: Unknown subcommand "bogus".
26
- """
26
+ """
27
+ Error: Unknown subcommand "bogus".
28
+ """
27
29
 
28
30
  Scenario: Pull Subcommand
29
31
  When I run `crab pull --help`
30
32
  Then the output should contain:
31
- """
32
- crab pull: pulls stories from Rally and writes them out as Cucumber features
33
+ """
34
+ crab pull: pulls stories from Rally and writes them out as Cucumber features
33
35
 
34
- Usage: crab [options] pull story1 [story2 ...]
35
- """
36
+ Usage: crab [options] pull story1 [story2 ...]
37
+ """
36
38
 
37
39
  Scenario: Show Subcommand
38
40
  When I run `crab show --help`
39
41
  Then the output should contain:
40
- """
41
- crab show: displays a story in Rally as a Cucumber feature
42
+ """
43
+ crab show: displays a story in Rally as a Cucumber feature
44
+
45
+ Usage: crab [options] show story
46
+ """
47
+
48
+ Scenario: Create Subcommand
49
+ When I run `crab create --help`
50
+ Then the output should contain:
51
+ """
52
+ crab create: create a new story in Rally
42
53
 
43
- Usage: crab [options] show story
44
- """
54
+ Usage: crab [options] create name [options]
55
+ """
56
+
57
+ Scenario: Delete Subcommand
58
+ When I run `crab delete --help`
59
+ Then the output should contain:
60
+ """
61
+ crab delete: delete an existing story in Rally
62
+
63
+ Usage: crab [options] delete story [options]
64
+ """
45
65
 
46
66
  Scenario: Update Subcommand
47
67
  When I run `crab update --help`
48
68
  Then the output should contain:
49
- """
50
- crab update: update a story in Rally
69
+ """
70
+ crab update: update a story in Rally
51
71
 
52
- Usage: crab [options] update story [options]
53
- """
72
+ Usage: crab [options] update story [options]
73
+ """
54
74
 
55
75
  Scenario: Update Needs a Story Number
56
76
  When I run `crab update`
@@ -63,8 +83,9 @@ Usage: crab [options] update story [options]
63
83
  Scenario: Find Subcommand
64
84
  When I run `crab find --help`
65
85
  Then the output should contain:
66
- """
67
- crab find: find a story in Rally
86
+ """
87
+ crab find: find a story in Rally
88
+
89
+ Usage: crab [options] find [options] [text]
90
+ """
68
91
 
69
- Usage: crab [options] find [options] [text]
70
- """
@@ -1,10 +1,10 @@
1
1
  Feature: Update Story in Rally
2
-
2
+
3
3
  In order to change a story's fields in Rally
4
4
  A developer who doesn't want to open a browser or click on things
5
5
  Wants to update those fields from the command line
6
6
 
7
- Background:
7
+ Background:
8
8
  Given I am logged in
9
9
 
10
10
  Scenario: Update Name
@@ -15,7 +15,6 @@ Feature: Update Story in Rally
15
15
  When I run `crab update US4988 --blocked`
16
16
  Then the output should contain "US4988: Sample Crab Story (grooming)"
17
17
  And the story US4988 should be blocked
18
-
19
18
  When I run `crab update US4988 --unblocked`
20
19
  Then the output should contain "US4988: Sample Crab Story (grooming)"
21
20
  And the story US4988 should be unblocked
@@ -31,3 +30,4 @@ Feature: Update Story in Rally
31
30
  Scenario: Setting Parent
32
31
  When I run `crab update US4988 --parent US5000`
33
32
  Then the story US4988 should have US5000 as its parent
33
+
@@ -11,6 +11,8 @@ require "crab/update"
11
11
  require "crab/show"
12
12
  require "crab/scenario"
13
13
  require "crab/project"
14
+ require "crab/create"
15
+ require "crab/delete"
14
16
  require "crab/cucumber_feature"
15
17
  require "crab/cucumber_scenario"
16
18
 
@@ -2,7 +2,7 @@ require 'trollop'
2
2
 
3
3
  module Crab
4
4
 
5
- SUB_COMMANDS = %w(pull login list update show find project)
5
+ SUB_COMMANDS = %w(pull login list update show find project create delete)
6
6
 
7
7
  class CLI
8
8
  def self.start
@@ -11,6 +11,8 @@ module Crab
11
11
  banner """
12
12
  crab version #{Crab::VERSION}: A Cucumber-Rally bridge
13
13
 
14
+ create Create a new story in Rally
15
+ delete Delete an existing story in Rally
14
16
  find Find stories by text in name, description or notes
15
17
  login Persistently authenticate user with Rally
16
18
  project Persistently select project to work with in Rally
@@ -28,6 +30,7 @@ crab version #{Crab::VERSION}: A Cucumber-Rally bridge
28
30
  banner "crab pull: pulls stories from Rally and writes them out as Cucumber features
29
31
 
30
32
  Usage: crab [options] pull story1 [story2 ...]"
33
+ opt :language, "Language to generate Cucumber features in (ISO code)", :default => "en", :short => "-l"
31
34
  end
32
35
 
33
36
  Crab::Pull.new(global_opts, cmd_opts, ARGV).run
@@ -37,6 +40,7 @@ Usage: crab [options] pull story1 [story2 ...]"
37
40
  banner "crab show: displays a story in Rally as a Cucumber feature
38
41
 
39
42
  Usage: crab [options] show story"
43
+ opt :language, "Language to display Cucumber features in (ISO code)", :default => "en", :short => "-l"
40
44
  end
41
45
 
42
46
  Crab::Show.new(global_opts, cmd_opts, ARGV).run
@@ -88,6 +92,24 @@ Usage: crab [options] project name"
88
92
 
89
93
  Crab::Project.new(global_opts, cmd_opts, ARGV).run
90
94
 
95
+ when "create"
96
+ cmd_opts = Trollop::options do
97
+ banner "crab create: create a new story in Rally
98
+
99
+ Usage: crab [options] create name [options]"
100
+ end
101
+
102
+ Crab::Create.new(global_opts, cmd_opts, ARGV).run
103
+
104
+ when "delete"
105
+ cmd_opts = Trollop::options do
106
+ banner "crab delete: delete an existing story in Rally
107
+
108
+ Usage: crab [options] delete story [options]"
109
+ end
110
+
111
+ Crab::Delete.new(global_opts, cmd_opts, ARGV).run
112
+
91
113
  else
92
114
  if cmd
93
115
  Trollop::die "Unknown subcommand #{cmd.inspect}"
@@ -0,0 +1,23 @@
1
+ module Crab
2
+
3
+ class Create
4
+
5
+ def initialize(global_opts, cmd_opts, args)
6
+ @global_opts = global_opts
7
+ @cmd_opts = cmd_opts
8
+ @args = args
9
+ @rally = Rally.new
10
+ end
11
+
12
+ def run
13
+ name = @args.join(" ")
14
+ Trollop::die "Please specify a name for the story" if name.blank?
15
+
16
+ @rally.connect
17
+ story = @rally.create_story :name => name
18
+
19
+ puts "#{story.formatted_id}: #{story.name} (#{story.state})"
20
+ end
21
+
22
+ end
23
+ end
@@ -1,15 +1,22 @@
1
1
  require 'fileutils'
2
+ require 'gherkin/i18n'
2
3
 
3
4
  module Crab
4
5
 
5
6
  class CucumberFeature
7
+
8
+ def initialize(language)
9
+ @language = Gherkin::I18n.new(language)
10
+ end
11
+
6
12
  def generate_from(story)
7
13
  text = <<-FEATURE
8
- Feature: [#{story.formatted_id}] #{story.name}
14
+ # language: #{@language.iso_code}
15
+ #{@language.keywords('feature').last}: [#{story.formatted_id}] #{story.name}
9
16
 
10
17
  #{story.description}
11
18
 
12
- #{Array(story.scenarios).map {|scenario| CucumberScenario.new.generate_from scenario }}
19
+ #{Array(story.scenarios).map {|scenario| CucumberScenario.new(@language.iso_code).generate_from scenario }}
13
20
  FEATURE
14
21
  text.strip
15
22
  end
@@ -1,11 +1,16 @@
1
1
  module Crab
2
2
  class CucumberScenario
3
+
4
+ def initialize(language)
5
+ @language = Gherkin::I18n.new(language)
6
+ end
7
+
3
8
  def generate_from(scenario)
4
9
  tags = [scenario.method, scenario.test_type]
5
10
  return <<-SCENARIO
6
11
 
7
12
  #{tags.map {|t| " @" + t.strip }.join}
8
- Scenario: [#{scenario.formatted_id}] #{scenario.name}
13
+ #{@language.keywords('scenario').last}: [#{scenario.formatted_id}] #{scenario.name}
9
14
  #{scenario.steps.join("\n ")}
10
15
  SCENARIO
11
16
  end
@@ -0,0 +1,25 @@
1
+ module Crab
2
+
3
+ class Delete
4
+
5
+ def initialize(global_opts, cmd_opts, args)
6
+ @global_opts = global_opts
7
+ @cmd_opts = cmd_opts
8
+ @args = args
9
+ @rally = Rally.new
10
+ end
11
+
12
+ def run
13
+ story_id = @args.join(" ")
14
+ Trollop::die "Story ID must be specified" if story_id.blank?
15
+
16
+ @rally.connect
17
+ story = @rally.find_story_with_id story_id
18
+
19
+ story.delete
20
+
21
+ puts "Story #{story_id} deleted."
22
+ end
23
+
24
+ end
25
+ end
@@ -4,9 +4,9 @@ module Crab
4
4
 
5
5
  class Pull
6
6
 
7
- def initialize(global_options, pull_options, story_numbers)
8
- @global_options = global_options
9
- @pull_options = pull_options
7
+ def initialize(global_opts, cmd_opts, story_numbers)
8
+ @global_opts = global_opts
9
+ @cmd_opts = cmd_opts
10
10
  @story_numbers = story_numbers
11
11
  @rally = Crab::Rally.new
12
12
  end
@@ -24,7 +24,7 @@ module Crab
24
24
  ::FileUtils.touch story.full_file_name
25
25
 
26
26
  File.open(story.full_file_name, "w") do |file|
27
- file.write CucumberFeature.new.generate_from story
27
+ file.write CucumberFeature.new(@cmd_opts[:language]).generate_from story
28
28
  end
29
29
  end
30
30
  end
@@ -48,5 +48,8 @@ module Crab
48
48
  @rally.find(:release) { equal :name, name }.first
49
49
  end
50
50
 
51
+ def create_story(opts)
52
+ Crab::Story.new @rally.create(:hierarchical_requirement, opts)
53
+ end
51
54
  end
52
55
  end
@@ -11,7 +11,7 @@ module Crab
11
11
  @rally.connect
12
12
 
13
13
  story = @rally.find_story_with_id @story_id
14
- puts CucumberFeature.new.generate_from story
14
+ puts CucumberFeature.new(@cmd_opts[:language]).generate_from story
15
15
  end
16
16
  end
17
17
  end
@@ -46,6 +46,10 @@ module Crab
46
46
  @rally_story.update opts
47
47
  end
48
48
 
49
+ def delete
50
+ @rally_story.delete
51
+ end
52
+
49
53
  def rally_object
50
54
  @rally_story
51
55
  end
@@ -1,3 +1,3 @@
1
1
  module Crab
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crab
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 3
10
- version: 0.1.3
9
+ - 4
10
+ version: 0.1.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Carlos Villela
@@ -44,10 +44,10 @@ dependencies:
44
44
  segments:
45
45
  - 0
46
46
  version: "0"
47
- type: :runtime
47
+ type: :development
48
48
  version_requirements: *id002
49
49
  - !ruby/object:Gem::Dependency
50
- name: rally_rest_api
50
+ name: gherkin
51
51
  prerelease: false
52
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
53
  none: false
@@ -61,7 +61,7 @@ dependencies:
61
61
  type: :runtime
62
62
  version_requirements: *id003
63
63
  - !ruby/object:Gem::Dependency
64
- name: highline
64
+ name: rally_rest_api
65
65
  prerelease: false
66
66
  requirement: &id004 !ruby/object:Gem::Requirement
67
67
  none: false
@@ -75,7 +75,7 @@ dependencies:
75
75
  type: :runtime
76
76
  version_requirements: *id004
77
77
  - !ruby/object:Gem::Dependency
78
- name: activesupport
78
+ name: highline
79
79
  prerelease: false
80
80
  requirement: &id005 !ruby/object:Gem::Requirement
81
81
  none: false
@@ -89,7 +89,7 @@ dependencies:
89
89
  type: :runtime
90
90
  version_requirements: *id005
91
91
  - !ruby/object:Gem::Dependency
92
- name: i18n
92
+ name: activesupport
93
93
  prerelease: false
94
94
  requirement: &id006 !ruby/object:Gem::Requirement
95
95
  none: false
@@ -103,7 +103,7 @@ dependencies:
103
103
  type: :runtime
104
104
  version_requirements: *id006
105
105
  - !ruby/object:Gem::Dependency
106
- name: sanitize
106
+ name: i18n
107
107
  prerelease: false
108
108
  requirement: &id007 !ruby/object:Gem::Requirement
109
109
  none: false
@@ -117,7 +117,7 @@ dependencies:
117
117
  type: :runtime
118
118
  version_requirements: *id007
119
119
  - !ruby/object:Gem::Dependency
120
- name: trollop
120
+ name: sanitize
121
121
  prerelease: false
122
122
  requirement: &id008 !ruby/object:Gem::Requirement
123
123
  none: false
@@ -130,6 +130,20 @@ dependencies:
130
130
  version: "0"
131
131
  type: :runtime
132
132
  version_requirements: *id008
133
+ - !ruby/object:Gem::Dependency
134
+ name: trollop
135
+ prerelease: false
136
+ requirement: &id009 !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ type: :runtime
146
+ version_requirements: *id009
133
147
  description: CRaB is a bridge between Cucumber and Rally
134
148
  email:
135
149
  - cvillela@thoughtworks.com
@@ -147,6 +161,7 @@ files:
147
161
  - Rakefile
148
162
  - bin/crab
149
163
  - crab.gemspec
164
+ - features/create-and-delete-story.feature
150
165
  - features/find-text-in-stories.feature
151
166
  - features/login-and-out-of-rally.feature
152
167
  - features/project-selection.feature
@@ -158,8 +173,10 @@ files:
158
173
  - features/update-story-in-rally.feature
159
174
  - lib/crab.rb
160
175
  - lib/crab/cli.rb
176
+ - lib/crab/create.rb
161
177
  - lib/crab/cucumber_feature.rb
162
178
  - lib/crab/cucumber_scenario.rb
179
+ - lib/crab/delete.rb
163
180
  - lib/crab/find.rb
164
181
  - lib/crab/login.rb
165
182
  - lib/crab/project.rb
@@ -171,12 +188,6 @@ files:
171
188
  - lib/crab/update.rb
172
189
  - lib/crab/utilities.rb
173
190
  - lib/crab/version.rb
174
- - old/.gitignore
175
- - old/Gemfile
176
- - old/Gemfile.lock
177
- - old/Rakefile
178
- - old/templates/feature-pt.mustache
179
- - old/templates/feature.mustache
180
191
  has_rdoc: true
181
192
  homepage: http://github.com/cv/crab
182
193
  licenses: []
@@ -1 +0,0 @@
1
- ./crab/Gemfile.lock
@@ -1,14 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem 'rally_rest_api'
4
- gem 'cucumber'
5
- gem 'rake'
6
-
7
- gem 'highline', :require => 'highline/import' # password prompts, etc
8
- gem 'pry' # irb-like awesomeness for debugging
9
- gem 'activesupport', :require => 'active_support/all' # nice additions to Ruby API
10
- gem 'i18n'
11
-
12
- gem 'mustache' # for file templates
13
- gem 'sanitize' # clean up description HTML
14
-
@@ -1,277 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
-
4
- Bundler.require :default
5
-
6
- task :ensure_credentials_are_present do
7
- ENV['RALLY_USERNAME'] ||= ask('Username: ')
8
- ENV['RALLY_PASSWORD'] ||= ask('Password: ') {|q| q.echo = "*" }
9
- end
10
-
11
- task :rally => :ensure_credentials_are_present do
12
- @rally = RallyRestAPI.new :username => ENV['RALLY_USERNAME'], :password => ENV['RALLY_PASSWORD']
13
- puts "Logged in as #{@rally.user.login_name}"
14
- end
15
-
16
- # a few things in this script break / do the wrong thing if you don't have
17
- # the project set up or don't have access to it
18
- task :project => :rally do
19
- project = ENV['RALLY_PROJECT'] ||= ask('Project: ')
20
- @project = @rally.find(:project) { equal :name, ENV['RALLY_PROJECT']}.first
21
- puts "Using project \"#{ENV['RALLY_PROJECT']}\""
22
- end
23
-
24
- task :default => :rally do
25
- binding.pry
26
- end
27
-
28
- class Feature < Mustache
29
-
30
- self.template_path = File.join(File.dirname(__FILE__), 'templates')
31
-
32
- # didn't like what's being done to support multiple languages
33
- # this is going to break horribly when we try to add supoort for generating the
34
- # scenarios from Rally and the whole roundtripping thing
35
- # (as it'll be difficult to use partials, for example)
36
- def initialize(rally_story, language=(ENV['CUCUMBER_LANG'] || ''))
37
- @delegate = rally_story
38
- self.class.template_file = File.join(self.class.template_path, "feature-#{language}.mustache") if language.present?
39
- end
40
-
41
- def name
42
- @delegate.name
43
- end
44
-
45
- def formatted_id
46
- @delegate.formatted_i_d
47
- end
48
-
49
- def state
50
- (@delegate.schedule_state || "unknown").parameterize.underscore
51
- end
52
-
53
- def description
54
- # this could use a lot of rethinking :(
55
- # biggest problem is that Cucumber breaks if text in description looks like something
56
- # it might know how to parse, but doesn't
57
- # our feature descriptions are quite like that, so I was being ultra-conservative
58
- sanitize(@delegate.description || '').gsub(/ +/, "\n").gsub(/\n\n/, "\n").gsub(/\n/, "\n ")
59
- end
60
-
61
- def file_name
62
- "#{formatted_id}_#{name.parameterize.underscore}.feature"
63
- end
64
-
65
- def dir_name
66
- state
67
- end
68
-
69
- end
70
-
71
- task :generate_features => :project do
72
- project = @project
73
- @stories = @rally.find(:hierarchical_requirement, :fetch => true) { equal :project, project }
74
-
75
- @stories.each do |story|
76
- feature = Feature.new(story)
77
-
78
- dir = File.join("features", feature.dir_name)
79
- file = File.join(dir, feature.file_name)
80
-
81
- FileUtils.mkdir_p dir
82
- FileUtils.touch file
83
-
84
- File.open(file, 'w') do |f|
85
- f.write feature.render
86
- end
87
- putc '.'
88
- end
89
- puts
90
-
91
- end
92
- # this whole class should die and we should probably use
93
- # a Cucumber formatter instead of relying directly on the Gherkin
94
- # APIs. With the extra info that Cucumber provides, it should also
95
- # be possible to generate Test Case Runs, which would be pretty sweet
96
- # to track using charts in Rally -- eg "How often do I have to do a manual
97
- # test run of the @manual stuff so it's all good?"
98
- class FeatureProxy
99
- def initialize
100
- @scenarios = []
101
- @steps = ActiveSupport::OrderedHash.new([])
102
- end
103
-
104
- attr_reader :scenarios, :steps
105
-
106
- def name
107
- @feature.name
108
- end
109
-
110
- def description
111
- @feature.description
112
- end
113
-
114
- # the next two methods could be replaced with something like an extension to the cucumber syntax
115
- # in which we annotate foreign IDs -- my proposal is something like what's in here:
116
- #
117
- # Feature: [XXXXX] Lorem Ipsum
118
- #
119
- # Simple regex, fairly hard to get wrong in almost any western keyboard, and should work for any bug/issue/card/story tracker I've seen
120
- #
121
- def rally_id
122
- self.name.match(/^\[([^\]]+)\](.*)$/)
123
- $1
124
- end
125
-
126
- def title_for_rally
127
- self.name.match(/^\[([^\]]+)\](.*)$/)
128
- $2.squish.titleize
129
- end
130
-
131
- # needed by Gherkin
132
- def uri(uri)
133
- end
134
-
135
- def feature(feature)
136
- @feature = feature
137
- end
138
-
139
- def get
140
- @feature
141
- end
142
-
143
- def scenario(scenario)
144
- @scenarios << scenario
145
- end
146
-
147
- def step(step)
148
- @steps[@scenarios.last] += Array(step)
149
- end
150
-
151
- def eof
152
- end
153
-
154
- def has_story_id?
155
- !!self.name.match(/^\[([^\]]+)\](.*)$/)
156
- end
157
- end
158
-
159
- def parse(feature)
160
- updater = FeatureProxy.new
161
- parser = Gherkin::Parser::Parser.new(updater, false, "root", false)
162
- parser.parse File.read(feature), feature, 0
163
- updater
164
- end
165
-
166
- # took a while to figure out that we need to remove the CSS from inside embedded <style> tags!
167
- # Rally uses some crazy rich text editor that I'd be soooooo happy to disable, somehow. Chrome Extension, perhaps?
168
- def sanitize(source)
169
- Sanitize.clean source, :remove_contents => %w{style}
170
- end
171
-
172
- task :update_features => :rally do
173
- Dir['features/**/*.feature'].sort {|a,b| File.mtime(b) <=> File.mtime(a) }.each do |file|
174
- feature = parse file
175
- unless feature.has_story_id?
176
- raise "Incompatible feature name: #{feature.name} in #{file} (needs to begin with a story number in square brackets)"
177
- end
178
-
179
- # TODO inline variables
180
- feature_id = feature.rally_id
181
- feature_name = feature.title_for_rally
182
-
183
- story = @rally.find(:hierarchical_requirement) { equal :formatted_i_d, feature_id }.first
184
-
185
- updates = {} # collect all updates first (room for double-checking and you only get to call Rally over the net once per item)
186
-
187
- if story.name != feature_name
188
- updates[:name] = feature_name
189
- end
190
-
191
- # matching the descriptions sucked -- Rally does some conversion to strip out HTML
192
- rally_description = sanitize(story.description || '').gsub(/\s+/, ' ').strip
193
- cuke_description = sanitize(feature.description || '').gsub(/\s+/, ' ').strip
194
-
195
- if rally_description != cuke_description
196
- formatted_description = (feature.description || '').strip.gsub(/\n/, '<br/>')
197
- updates[:description] = formatted_description
198
- end
199
- #
200
-
201
- if updates.empty?
202
- puts "Nothing to do for #{feature_id} (story already up to date)"
203
- else
204
- story.update updates
205
- puts "Updated #{feature_id}: #{story.name} (#{updates.keys.join(',')})"
206
- end
207
- end
208
- end
209
-
210
- task :update_scenarios => :project do
211
- Dir['features/**/*.feature'].sort {|a,b| File.mtime(b) <=> File.mtime(a) }.each do |file|
212
- feature = parse file
213
- feature.steps.each do |scenario, steps|
214
- create_test_case @project, feature, scenario, steps
215
-
216
- # TODO make the scenarios in the Cucumber file reflect what's in Rally as well
217
- #
218
- # goal is to have roundtrip editing, but if not whatever is in Cucumber is "the truth"
219
- end
220
- end
221
- end
222
-
223
- TYPE_TAGS = %w{acceptance functional non-functional performance regression usability user_interface}
224
- RISK_TAGS = %w{low_risk medium_risk high_risk}
225
- PRIORITY_TAGS = %{useful important critical}
226
- METHOD_TAGS = %{automated manual}
227
-
228
- def create_test_case(project, feature, scenario, steps)
229
- if !scenario.name.match /^\[([^\]]+)\](.*)$/
230
- # uh oh, we have to create this scenario!
231
- tags = scenario.tags.map {|t| t.name.gsub(/^@/, '') }
232
-
233
- # could definitely use some cleaning up, but the defaults seem sensible so far
234
- type_tag = (tags.find {|t| TYPE_TAGS.include? t } || 'acceptance').humanize
235
- risk_tag = (tags.find {|t| RISK_TAGS.include? t } || 'medium_risk').gsub(/_risk/, '').humanize
236
- priority_tag = (tags.find {|t| PRIORITY_TAGS.include? t } || 'important').humanize
237
- method_tag = (tags.find {|t| METHOD_TAGS.include? t } || 'automated').humanize
238
-
239
- story = @rally.find(:hierarchical_requirement) { equal :formatted_i_d, feature.rally_id }.first
240
-
241
- options = {
242
- :name => scenario.name,
243
- :description => "Automatically updated by Cucumber, do not edit",
244
- :type => type_tag,
245
- :risk => risk_tag,
246
- :priority => priority_tag,
247
- :method => method_tag,
248
- :pre_conditions => "N/A", # do scenarios have a header? BeforeScenario?
249
- :post_conditions => "N/A", # ...and/or a footer? AfterScenario, perhaps?
250
- :work_product => story,
251
- :project => @project # linking testcase to story is not enough
252
- }
253
-
254
- test_case = @rally.create(:test_case, options) do |test_case|
255
- steps.each_with_index do |step, i|
256
- test_case_step = @rally.create(:test_case_step, :test_case => test_case, :index => i, :input => "#{step.keyword.strip} #{step.name.strip}")
257
- end
258
- end
259
-
260
- puts "Created test case #{test_case.formatted_i_d} in story #{feature.rally_id}"
261
- else
262
- # TODO update scenario text in feature with ID from Rally
263
- # then update other fields in Rally with our scenario fields / tags
264
- end
265
- end
266
-
267
- desc "DANGER DANGER DANGER"
268
- task :delete_all_test_cases => :project do
269
- story_id = ENV['STORY'] ||= ask("Story #: ")
270
- project = @project
271
- story = @rally.find(:hierarchical_requirement) { equal :formatted_i_d, story_id }.first
272
- test_cases = @rally.find(:test_case) { equal :work_product, story }
273
-
274
- test_cases.each do |tc|
275
- p tc.delete
276
- end
277
- end
@@ -1,5 +0,0 @@
1
- # language: pt
2
- @{{state}}
3
- Funcionalidade: [{{formatted_id}}] {{{name}}}
4
-
5
- {{{description}}}
@@ -1,4 +0,0 @@
1
- @{{state}}
2
- Feature: [{{formatted_id}}] {{{name}}}
3
-
4
- {{{description}}}