cucumber_fm-core 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/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
|