cucumber_fm-core 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE +20 -0
- data/lib/cucumber_f_m/aggregator.rb +65 -0
- data/lib/cucumber_f_m/comment_module/comment.rb +7 -0
- data/lib/cucumber_f_m/config.rb +37 -0
- data/lib/cucumber_f_m/cvs/git.rb +7 -0
- data/lib/cucumber_f_m/feature.rb +141 -0
- data/lib/cucumber_f_m/feature_element/background.rb +10 -0
- data/lib/cucumber_f_m/feature_element/comment.rb +13 -0
- data/lib/cucumber_f_m/feature_element/component/comments.rb +21 -0
- data/lib/cucumber_f_m/feature_element/component/information/component.rb +16 -0
- data/lib/cucumber_f_m/feature_element/component/tags.rb +108 -0
- data/lib/cucumber_f_m/feature_element/component/title.rb +26 -0
- data/lib/cucumber_f_m/feature_element/component/total_estimation.rb +42 -0
- data/lib/cucumber_f_m/feature_element/example.rb +7 -0
- data/lib/cucumber_f_m/feature_element/info.rb +13 -0
- data/lib/cucumber_f_m/feature_element/narrative.rb +7 -0
- data/lib/cucumber_f_m/feature_element/scenario.rb +15 -0
- data/lib/cucumber_f_m/feature_element/scenario_outline.rb +23 -0
- data/lib/cucumber_f_m/feature_element/step.rb +7 -0
- data/lib/cucumber_f_m/statistic.rb +31 -0
- data/lib/cucumber_f_m/tag_filter.rb +85 -0
- data/lib/cucumber_feature_manager.rb +164 -0
- data/lib/grit/lib/grit.rb +75 -0
- data/lib/grit/lib/grit/actor.rb +36 -0
- data/lib/grit/lib/grit/blame.rb +61 -0
- data/lib/grit/lib/grit/blob.rb +126 -0
- data/lib/grit/lib/grit/commit.rb +247 -0
- data/lib/grit/lib/grit/commit_stats.rb +128 -0
- data/lib/grit/lib/grit/config.rb +44 -0
- data/lib/grit/lib/grit/diff.rb +70 -0
- data/lib/grit/lib/grit/errors.rb +7 -0
- data/lib/grit/lib/grit/git-ruby.rb +267 -0
- data/lib/grit/lib/grit/git-ruby/commit_db.rb +52 -0
- data/lib/grit/lib/grit/git-ruby/file_index.rb +193 -0
- data/lib/grit/lib/grit/git-ruby/git_object.rb +350 -0
- data/lib/grit/lib/grit/git-ruby/internal/file_window.rb +58 -0
- data/lib/grit/lib/grit/git-ruby/internal/loose.rb +137 -0
- data/lib/grit/lib/grit/git-ruby/internal/pack.rb +384 -0
- data/lib/grit/lib/grit/git-ruby/internal/raw_object.rb +37 -0
- data/lib/grit/lib/grit/git-ruby/object.rb +325 -0
- data/lib/grit/lib/grit/git-ruby/repository.rb +767 -0
- data/lib/grit/lib/grit/git.rb +323 -0
- data/lib/grit/lib/grit/index.rb +122 -0
- data/lib/grit/lib/grit/lazy.rb +33 -0
- data/lib/grit/lib/grit/merge.rb +45 -0
- data/lib/grit/lib/grit/ref.rb +74 -0
- data/lib/grit/lib/grit/repo.rb +482 -0
- data/lib/grit/lib/grit/ruby1.9.rb +7 -0
- data/lib/grit/lib/grit/status.rb +151 -0
- data/lib/grit/lib/grit/submodule.rb +88 -0
- data/lib/grit/lib/grit/tag.rb +16 -0
- data/lib/grit/lib/grit/tree.rb +123 -0
- data/lib/grit/lib/open3_detach.rb +46 -0
- data/spec/cucumber_f_m/aggregator_spec.rb +210 -0
- data/spec/cucumber_f_m/comment_module/comment_spec.rb +4 -0
- data/spec/cucumber_f_m/config_spec.rb +27 -0
- data/spec/cucumber_f_m/cvs/git_spec.rb +40 -0
- data/spec/cucumber_f_m/feature_all_tags_to_scenario_spec.rb +7 -0
- data/spec/cucumber_f_m/feature_file_manipulation_spec.rb +29 -0
- data/spec/cucumber_f_m/feature_module/background_spec.rb +30 -0
- data/spec/cucumber_f_m/feature_module/comment_spec.rb +23 -0
- data/spec/cucumber_f_m/feature_module/example_spec.rb +5 -0
- data/spec/cucumber_f_m/feature_module/info_spec.rb +30 -0
- data/spec/cucumber_f_m/feature_module/narrative_spec.rb +7 -0
- data/spec/cucumber_f_m/feature_module/scenario_outline_spec.rb +39 -0
- data/spec/cucumber_f_m/feature_module/scenario_spec.rb +33 -0
- data/spec/cucumber_f_m/feature_module/step_spec.rb +7 -0
- data/spec/cucumber_f_m/feature_module/tag_spec.rb +96 -0
- data/spec/cucumber_f_m/feature_spec.rb +229 -0
- data/spec/cucumber_f_m/tag_filter_spec.rb +191 -0
- data/spec/cucumber_feature_manager_spec.rb +59 -0
- data/spec/data/feature_manager/some_ruby_file.rb +0 -0
- data/spec/spec_helper.rb +1 -0
- metadata +141 -0
data/LICENCE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010..2011 Michał Czyż @cs3b <michalczyz@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
class Aggregator
|
3
|
+
def initialize(cfm, aggregator, multiple = false)
|
4
|
+
if multiple
|
5
|
+
@collection = Collection.nested_hash(0)
|
6
|
+
multiple.each do |tag|
|
7
|
+
cfm.scenarios.each do |scenario|
|
8
|
+
@collection[tag].push scenario if scenario.tags.include?(tag)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
else
|
12
|
+
@collection = Collection.nested_hash(aggregator.size)
|
13
|
+
if aggregator.size == 2
|
14
|
+
cfm.scenarios.each do |scenario|
|
15
|
+
@collection[label(aggregator.first, scenario.tags_without_technical)][label(aggregator.last, scenario.tags_without_technical)][scenario.feature].push scenario
|
16
|
+
end
|
17
|
+
else
|
18
|
+
@collection = Collection.nested_hash(1)
|
19
|
+
cfm.scenarios.each do |scenario|
|
20
|
+
@collection[label(aggregator.first, scenario.tags_without_technical)][scenario.feature].push scenario
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def collection
|
27
|
+
@collection
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def label(aggregate, tags)
|
33
|
+
tags.find { |tag| tag =~ aggregate } || '_undefined_'
|
34
|
+
end
|
35
|
+
|
36
|
+
class Collection < Hash
|
37
|
+
|
38
|
+
include CucumberFM::FeatureElement::Component::TotalEstimation
|
39
|
+
|
40
|
+
def features
|
41
|
+
keys.collect { |key|
|
42
|
+
self[key].is_a?(Array) ? key : self[key].features
|
43
|
+
}.flatten.uniq
|
44
|
+
end
|
45
|
+
|
46
|
+
def scenarios
|
47
|
+
values.collect { |value|
|
48
|
+
value.is_a?(Array) ? value : value.scenarios
|
49
|
+
}.flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
def Collection.nested_hash (level=1)
|
53
|
+
new do |hash, key|
|
54
|
+
hash[key]= (level > 0 ? nested_hash(level-1) : ScenarioCollection.new)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class ScenarioCollection < Array
|
59
|
+
|
60
|
+
include CucumberFM::FeatureElement::Component::TotalEstimation
|
61
|
+
|
62
|
+
alias_method :scenarios, :entries
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
class Config
|
3
|
+
attr_accessor :dir, :tags, :aggregate, :sort, :display_as, :cvs_commit, :cvs_push
|
4
|
+
|
5
|
+
def initialize(params={})
|
6
|
+
set_default_values
|
7
|
+
update(params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def aggregate_options
|
11
|
+
[''] + CucumberFM::FeatureElement::Component::Tags::PATTERN.keys.map(& :to_s).sort
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def set_default_values
|
17
|
+
update(
|
18
|
+
{
|
19
|
+
:dir => '',
|
20
|
+
:tags => '',
|
21
|
+
:aggregate => [],
|
22
|
+
:sort => '',
|
23
|
+
:display_as => 'list',
|
24
|
+
:cvs_commit => '0',
|
25
|
+
:cvs_push => '0'
|
26
|
+
})
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(params)
|
31
|
+
params.each_pair do |attribute, value|
|
32
|
+
setter_method_name = "#{attribute}="
|
33
|
+
send(setter_method_name, value) if respond_to? setter_method_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
class Feature < Struct.new(:path, :cfm)
|
3
|
+
|
4
|
+
include FeatureElement::Component::TotalEstimation
|
5
|
+
|
6
|
+
def id
|
7
|
+
Base64.encode64(relative_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def relative_path
|
11
|
+
path.gsub(/^#{cfm.path}\//, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def raw
|
15
|
+
@raw ||= read_content_from_file
|
16
|
+
end
|
17
|
+
|
18
|
+
def raw= content
|
19
|
+
@raw = content
|
20
|
+
end
|
21
|
+
|
22
|
+
def info
|
23
|
+
@info ||= FeatureElement::Info.new(self, scan_for_feature_info_from_raw)
|
24
|
+
end
|
25
|
+
|
26
|
+
def background
|
27
|
+
@background ||= FeatureElement::Background.new(self, scan_for_background_from_raw)
|
28
|
+
end
|
29
|
+
|
30
|
+
def scenarios
|
31
|
+
@scenarios ||= fetch_scenarios
|
32
|
+
end
|
33
|
+
|
34
|
+
def tags
|
35
|
+
info.tags
|
36
|
+
end
|
37
|
+
|
38
|
+
def tags_all
|
39
|
+
scenarios.collect{|scenario| scenario.tags }.flatten.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def save
|
43
|
+
write_content_to_file
|
44
|
+
commit
|
45
|
+
push
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def destroy
|
50
|
+
File.delete(path)
|
51
|
+
remove_file_from_repo
|
52
|
+
push
|
53
|
+
end
|
54
|
+
|
55
|
+
def filename
|
56
|
+
File.basename(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def filename_without_extension
|
60
|
+
File.basename(path, '.feature')
|
61
|
+
end
|
62
|
+
|
63
|
+
def <=>(_f)
|
64
|
+
info.title <=> _f.info.title
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def read_content_from_file
|
70
|
+
File.open(path, 'r') { |stream| stream.read }
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_content_to_file
|
74
|
+
File.open(path, 'w') { |stream| stream.write raw }
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# TODO we need to detect it in more clever way
|
79
|
+
def commit
|
80
|
+
cfm.commit_change_on(self) if do_commit?
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO we need to detect it in more clever way
|
84
|
+
def push
|
85
|
+
cfm.send_to_remote if do_push?
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_file_from_repo
|
89
|
+
cfm.remove_file_from_repo(relative_path) if do_commit?
|
90
|
+
end
|
91
|
+
|
92
|
+
def do_push?
|
93
|
+
cfm && cfm.respond_to?(:send_to_remote) && cfm.config.cvs_commit=='1' && cfm.config.cvs_push=='1'
|
94
|
+
end
|
95
|
+
|
96
|
+
def do_commit?
|
97
|
+
cfm && cfm.respond_to?(:commit_change_on) && cfm.config.cvs_commit=='1'
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch_scenarios
|
101
|
+
scenarios = []
|
102
|
+
text = raw
|
103
|
+
while match = scan_for_scenarios_and_scenario_outline_from(text)
|
104
|
+
scenario = case match[0]
|
105
|
+
when FeatureElement::Scenario::PATTERN
|
106
|
+
FeatureElement::Scenario.new(self, match[0])
|
107
|
+
when FeatureElement::ScenarioOutline::PATTERN
|
108
|
+
FeatureElement::ScenarioOutline.new(self, match[0])
|
109
|
+
end
|
110
|
+
scenarios.push(scenario) if cfm.filter.pass?(scenario.tags)
|
111
|
+
text = match.post_match
|
112
|
+
end
|
113
|
+
scenarios
|
114
|
+
end
|
115
|
+
|
116
|
+
def scan_for_feature_info_from_raw
|
117
|
+
if match = FeatureElement::Info::PATTERN.match(raw)
|
118
|
+
match[0]
|
119
|
+
else
|
120
|
+
''
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def scan_for_background_from_raw
|
125
|
+
if match = FeatureElement::Background::PATTERN.match(raw)
|
126
|
+
match[0]
|
127
|
+
else
|
128
|
+
''
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def scan_for_scenarios_and_scenario_outline_from(string)
|
133
|
+
scenario_or_scenario_outline.match(string)
|
134
|
+
end
|
135
|
+
|
136
|
+
def scenario_or_scenario_outline
|
137
|
+
Regexp.union(FeatureElement::Scenario::PATTERN,
|
138
|
+
FeatureElement::ScenarioOutline::PATTERN)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
module FeatureElement
|
3
|
+
class Background < Struct.new(:feature, :raw)
|
4
|
+
PATTERN = /((^.*#+.*\n)+\n?)?^[ \t]*Background:.*\n?(^.*\S+.*\n?)*/
|
5
|
+
|
6
|
+
include CucumberFM::FeatureElement::Component::Title
|
7
|
+
include CucumberFM::FeatureElement::Component::Comments
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
module FeatureElement
|
3
|
+
module Component
|
4
|
+
module Comments
|
5
|
+
LINE_PATTERN = /^\s*#+.*$/
|
6
|
+
|
7
|
+
def comments
|
8
|
+
@comments ||= fetch_comments
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def fetch_comments
|
14
|
+
raw.scan(LINE_PATTERN).collect do |comment_raw|
|
15
|
+
CucumberFM::FeatureElement::Comment.new(self, comment_raw)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module CucumberFM
|
2
|
+
module FeatureElement
|
3
|
+
module Component
|
4
|
+
module Tags
|
5
|
+
LINE_PATTERN = /^\s*@\S.*$/
|
6
|
+
TAG_PATTERN = /@[^,\s]+/
|
7
|
+
|
8
|
+
PATTERN = {
|
9
|
+
:component => /@[a-z]\S{3,}\z/,
|
10
|
+
:milestone => /@m\d.?\z/,
|
11
|
+
:iteration => /@i\d+\z/,
|
12
|
+
:priority => /@p\d+\z/,
|
13
|
+
:status => /@_[a-z]\S+\z/,
|
14
|
+
:developer => /@[a-z]{2,3}\z/,
|
15
|
+
:bucket => /@__[^\s\d]+/,
|
16
|
+
:effort => /@\d/,
|
17
|
+
:benefit => /@_\d/,
|
18
|
+
:something_todo => /@:::[a-z]{2,3}\z/,
|
19
|
+
:role => /@\$_[a-z_]+/
|
20
|
+
}
|
21
|
+
|
22
|
+
TECHNICAL = [
|
23
|
+
'@javascript',
|
24
|
+
'@selenium',
|
25
|
+
'@sellenium',
|
26
|
+
'@celerity',
|
27
|
+
'@culerity',
|
28
|
+
'@mongo',
|
29
|
+
'@allow-rescue',
|
30
|
+
'@needs_wireframe',
|
31
|
+
'@tested_elsewhere',
|
32
|
+
'@added',
|
33
|
+
'@nontestable',
|
34
|
+
'@additional-test'
|
35
|
+
]
|
36
|
+
|
37
|
+
STATUS_COMPLETE = %w(@_done @_qa @_tested @_accepted)
|
38
|
+
|
39
|
+
def tags
|
40
|
+
@tags ||= fetch_tags
|
41
|
+
end
|
42
|
+
|
43
|
+
def tags= tags
|
44
|
+
@tags = tags
|
45
|
+
end
|
46
|
+
|
47
|
+
def tags_without_technical
|
48
|
+
tags - TECHNICAL
|
49
|
+
end
|
50
|
+
|
51
|
+
def done?
|
52
|
+
STATUS_COMPLETE.include?(status)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def estimation
|
57
|
+
effort ? effort.gsub('@', '').to_f : 0.0
|
58
|
+
end
|
59
|
+
|
60
|
+
def value
|
61
|
+
benefit ? benefit.gsub('@_', '').to_i : 0
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def fetch_tags
|
67
|
+
this_tags + parent_tags_without_duplicates
|
68
|
+
end
|
69
|
+
|
70
|
+
def this_tags
|
71
|
+
if tag_line = LINE_PATTERN.match(raw)
|
72
|
+
tag_line[0].scan(TAG_PATTERN)
|
73
|
+
else
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def parent_tags
|
79
|
+
respond_to?(:second_tags_source) ? second_tags_source.tags : []
|
80
|
+
end
|
81
|
+
|
82
|
+
def parent_tags_without_duplicates
|
83
|
+
parent_tags.collect { |p_tag| (type = detect_type(p_tag) and find(type, this_tags)) ? nil : p_tag }.compact
|
84
|
+
end
|
85
|
+
|
86
|
+
def find type, collection = tags
|
87
|
+
collection.detect do |tag|
|
88
|
+
!TECHNICAL.include?(tag) and tag =~ PATTERN[type]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def detect_type tag
|
93
|
+
PATTERN.invert.each_pair do |pattern, type|
|
94
|
+
return(type) if tag =~ pattern
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def method_missing(m, *args, &block)
|
99
|
+
if PATTERN.has_key?(m.to_sym)
|
100
|
+
find(m.to_sym)
|
101
|
+
else
|
102
|
+
super
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|