cuke_sniffer 0.0.3 → 0.0.5

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.
@@ -1,22 +1,34 @@
1
- module CukeSniffer
2
- module Constants
3
- FILE_IGNORE_LIST = %w(. .. .svn)
4
- DATE_REGEX = /(?<date>\d{2}\/\d{2}\/\d{4})/
5
- COMMENT_REGEX = /#?\s*/
6
-
7
- TAG_REGEX = /(?<tag>@\S*)/
8
-
9
- SCENARIO_TITLE_STYLES = /(?<type>Background|Scenario|Scenario Outline|Scenario Template):\s*/
10
-
11
- STEP_STYLES = /(?<style>Given|When|Then|And|Or|But|Transform|\*)\s/
12
- STEP_REGEX = /^#{COMMENT_REGEX}#{STEP_STYLES}(?<step_string>.*)/
13
- STEP_DEFINITION_REGEX = /^#{STEP_STYLES}\/(?<step>.+)\/\sdo\s?(\|(?<parameters>.*)\|)?$/
14
-
15
- THRESHOLDS = {
16
- "Project" => 1000,
17
- "Feature" => 30,
18
- "Scenario" => 30,
19
- "StepDefinition" => 20
20
- }
21
- end
22
- end
1
+ module CukeSniffer
2
+ # Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
3
+ # Copyright:: Copyright (C) 2013 Robert Cochran
4
+ # License:: Distributes under the MIT License
5
+ # A collection of constants that are used throughout the gem
6
+ module Constants
7
+
8
+ FILE_IGNORE_LIST = %w(. .. .svn) # :nodoc:
9
+ DATE_REGEX = /(?<date>\d{2}\/\d{2}\/\d{4})/ # :nodoc:
10
+ COMMENT_REGEX = /#?\s*/ # :nodoc:
11
+ TAG_REGEX = /(^|\s+)(?<tag>@\S*)/ # :nodoc:
12
+ SCENARIO_TITLE_STYLES = /^\s*\#*\s*(?<type>Background|Scenario|Scenario Outline|Scenario Template):\s*/ # :nodoc:
13
+ STEP_STYLES = /(?<style>Given|When|Then|And|Or|But|Transform|\*)\s+/ # :nodoc:
14
+ STEP_REGEX = /^#{COMMENT_REGEX}#{STEP_STYLES}(?<step_string>.*)/ # :nodoc:
15
+ STEP_DEFINITION_REGEX = /^#{STEP_STYLES}\/(?<step>.+)\/\sdo\s?(\|(?<parameters>.*)\|)?$/ # :nodoc:
16
+ HOOK_STYLES = /(?<type>Before|After|AfterConfiguration|at_exit|Around|AfterStep)/ # :nodoc:
17
+ HOOK_REGEX = /^#{HOOK_STYLES}(\((?<tags>.*)\)\sdo|\s+do)(\s\|(?<parameters>.*)\|)?/
18
+
19
+ # hash: Stores scores to compare against for determining if an object is good
20
+ # * Key: String of the object name
21
+ # Project, Feature, Scenario, StepDefinition
22
+ # * Value: Integer of the highest acceptable value an object can have
23
+ # Customizable for a projects level of acceptable score
24
+ # CukeSniffer::Constants::THRESHOLDS["Project"] = 95000
25
+ THRESHOLDS = {
26
+ "Project" => 1000,
27
+ "Feature" => 30,
28
+ "Scenario" => 30,
29
+ "StepDefinition" => 20,
30
+ "Hook" => 20
31
+ }
32
+
33
+ end
34
+ end
@@ -1,30 +1,52 @@
1
1
  module CukeSniffer
2
+
3
+ # Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
4
+ # Copyright:: Copyright (C) 2013 Robert Cochran
5
+ # License:: Distributes under the MIT License
6
+ # Handles feature files and disassembles and evaluates
7
+ # its components.
8
+ # Extends CukeSniffer::FeatureRulesEvaluator
2
9
  class Feature < FeatureRulesEvaluator
3
- include CukeSniffer::Constants
4
- include CukeSniffer::RuleConfig
5
- include ROXML
6
10
 
7
11
  xml_accessor :scenarios, :as => [CukeSniffer::FeatureRulesEvaluator], :in => "scenarios"
8
12
 
9
- SCENARIO_TITLE_REGEX = /#{COMMENT_REGEX}#{SCENARIO_TITLE_STYLES}(?<name>.*)/
13
+ SCENARIO_TITLE_REGEX = /#{COMMENT_REGEX}#{SCENARIO_TITLE_STYLES}(?<name>.*)/ # :nodoc:
14
+
15
+ # Scenario: The background of a Feature, created as a Scenario object
16
+ attr_accessor :background
17
+
18
+ # Scenario array: A list of all scenarios contained in a feature file
19
+ attr_accessor :scenarios
10
20
 
11
- attr_accessor :background, :scenarios, :scenarios_score,:total_score
21
+ # int: Total score from all of the scenarios contained in the feature
22
+ attr_accessor :scenarios_score
12
23
 
24
+ # int: Total score of the feature and its scenarios
25
+ attr_accessor :total_score
26
+
27
+ # file_name must be in the format of "file_path\file_name.feature"
13
28
  def initialize(file_name)
14
29
  super(file_name)
15
30
  @scenarios = []
16
- @RULES_hash = {}
17
31
  @scenarios_score = 0
18
32
  @total_score = 0
19
33
  feature_lines = extract_feature_from_file(file_name)
20
34
  if feature_lines == []
21
- store_rule(RULES[:empty_feature])
35
+ rule = RULES[:empty_feature]
36
+ store_rule(rule)
22
37
  else
23
38
  split_feature(file_name, feature_lines)
24
39
  evaluate_score
25
40
  end
26
41
  end
27
42
 
43
+ def ==(comparison_object) # :nodoc:
44
+ super(comparison_object) &&
45
+ comparison_object.scenarios == scenarios
46
+ end
47
+
48
+ private
49
+
28
50
  def extract_feature_from_file(file_name)
29
51
  feature_lines = []
30
52
  feature_file = File.open(file_name)
@@ -49,10 +71,20 @@ module CukeSniffer
49
71
  index_of_title = nil
50
72
  code_block = []
51
73
  until index >= feature_lines.length
52
- if scenario_title_found and (feature_lines[index].match TAG_REGEX or feature_lines[index].match SCENARIO_TITLE_REGEX)
53
- add_scenario_to_feature(code_block, index_of_title)
74
+ if scenario_title_found and feature_lines[index].match SCENARIO_TITLE_REGEX
75
+ not_our_code = []
76
+ code_block.reverse.each do |line|
77
+ break if line =~ /#{SCENARIO_TITLE_STYLES}|#{STEP_STYLES}|^\|.*\||Examples:/
78
+ not_our_code << line
79
+ end
80
+
81
+ if not_our_code.empty?
82
+ add_scenario_to_feature(code_block, index_of_title)
83
+ else
84
+ add_scenario_to_feature(code_block[0...(-1 * not_our_code.length)], index_of_title)
85
+ end
54
86
  scenario_title_found = false
55
- code_block = []
87
+ code_block = not_our_code.reverse
56
88
  end
57
89
  code_block << feature_lines[index].strip
58
90
  if feature_lines[index].match SCENARIO_TITLE_REGEX
@@ -67,6 +99,7 @@ module CukeSniffer
67
99
 
68
100
  def add_scenario_to_feature(code_block, index_of_title)
69
101
  scenario = CukeSniffer::Scenario.new(index_of_title, code_block)
102
+ feature_applied_scenario_rules(scenario)
70
103
  if scenario.type == "Background"
71
104
  @background = scenario
72
105
  else
@@ -74,30 +107,51 @@ module CukeSniffer
74
107
  end
75
108
  end
76
109
 
77
- def ==(comparison_object)
78
- super(comparison_object)
79
- comparison_object.scenarios == scenarios
80
- end
81
-
82
110
  def evaluate_score
83
111
  super
84
112
  rule_no_scenarios
85
113
  rule_too_many_scenarios
86
114
  rule_background_with_no_scenarios
87
115
  rule_background_with_one_scenario
116
+ rule_scenario_same_tag
88
117
  get_scenarios_score
89
118
  @total_score = score + @scenarios_score
90
119
  end
91
120
 
121
+ def feature_applied_scenario_rules(scenario)
122
+ rule_feature_same_tags(scenario)
123
+ end
124
+
125
+ def rule_feature_same_tags(scenario)
126
+ rule = RULES[:feature_same_tag]
127
+ tags.each do |tag|
128
+ scenario.store_rule(rule, rule[:phrase]) if scenario.tags.include?(tag)
129
+ end
130
+ end
131
+
132
+ def rule_scenario_same_tag
133
+ rule = RULES[:scenario_same_tag]
134
+ unless scenarios.empty?
135
+ base_tag_list = scenarios.first.tags.clone
136
+ scenarios.each do |scenario|
137
+ base_tag_list.each do |tag|
138
+ base_tag_list.delete(tag) unless scenario.tags.include?(tag)
139
+ end
140
+ end
141
+ base_tag_list.count.times do
142
+ store_rule(rule, rule[:phrase])
143
+ end
144
+ end
145
+ end
146
+
92
147
  def get_scenarios_score
93
148
  @scenarios_score += @background.score unless @background.nil?
94
- @scenarios.each do |scenario|
95
- @scenarios_score += scenario.score
96
- end
149
+ @scenarios.each { |scenario| @scenarios_score += scenario.score }
97
150
  end
98
151
 
99
152
  def rule_no_scenarios
100
- store_rule(RULES[:no_scenarios]) if @scenarios.empty?
153
+ rule = RULES[:no_scenarios]
154
+ store_rule(rule) if @scenarios.empty?
101
155
  end
102
156
 
103
157
  def rule_too_many_scenarios
@@ -106,12 +160,13 @@ module CukeSniffer
106
160
  end
107
161
 
108
162
  def rule_background_with_no_scenarios
109
- store_rule( RULES[:background_with_no_scenarios]) if @scenarios.empty? and !@background.nil?
163
+ rule = RULES[:background_with_no_scenarios]
164
+ store_rule(rule) if @scenarios.empty? and !@background.nil?
110
165
  end
111
166
 
112
167
  def rule_background_with_one_scenario
113
- store_rule(RULES[:background_with_one_scenario]) if @scenarios.size == 1 and !@background.nil?
168
+ rule = RULES[:background_with_one_scenario]
169
+ store_rule(rule) if @scenarios.size == 1 and !@background.nil?
114
170
  end
115
-
116
171
  end
117
172
  end
@@ -1,69 +1,115 @@
1
- require 'cuke_sniffer/constants'
2
- require 'cuke_sniffer/rule_config'
3
- require 'cuke_sniffer/rules_evaluator'
4
-
5
- module CukeSniffer
6
- class FeatureRulesEvaluator < RulesEvaluator
7
- include CukeSniffer::Constants
8
- include CukeSniffer::RuleConfig
9
-
10
- attr_accessor :tags, :name
11
-
12
- def initialize(location)
13
- @name = ""
14
- @tags = []
15
- super(location)
16
- end
17
-
18
- def create_name(line, filter)
19
- line.gsub!(/#{COMMENT_REGEX}#{filter}/, "")
20
- line.strip!
21
- @name += " " unless @name.empty? or line.empty?
22
- @name += line
23
- end
24
-
25
- def update_tag_list(line)
26
- if TAG_REGEX.match(line) && !is_comment?(line)
27
- line.scan(TAG_REGEX).each { |tag| @tags << tag[0] }
28
- else
29
- @tags << line.strip unless line.empty?
30
- end
31
- end
32
-
33
- def evaluate_score
34
- super
35
- cls_name = self.class.to_s.gsub('CukeSniffer::', '')
36
- rule_too_many_tags(cls_name)
37
- rule_no_description(cls_name)
38
- rule_numbers_in_name(cls_name)
39
- rule_long_name(cls_name)
40
- end
41
-
42
- def rule_too_many_tags(type)
43
- rule = RULES[:too_many_tags]
44
- store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if tags.size >= rule[:max]
45
- end
46
-
47
- def rule_no_description(type)
48
- rule = RULES[:no_description]
49
- store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name.empty?
50
- end
51
-
52
- def rule_numbers_in_name(type)
53
- rule = RULES[:numbers_in_description]
54
- store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name =~ /\d/
55
- end
56
-
57
- def rule_long_name(type)
58
- rule = RULES[:long_name]
59
- store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name.size >= rule[:max]
60
- end
61
-
62
- def == (comparison_object)
63
- super(comparison_object)
64
- comparison_object.name == name
65
- comparison_object.tags == tags
66
- end
67
-
68
- end
69
- end
1
+ require 'cuke_sniffer/constants'
2
+ require 'cuke_sniffer/rule_config'
3
+ require 'cuke_sniffer/rules_evaluator'
4
+
5
+ module CukeSniffer
6
+
7
+ # Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
8
+ # Copyright:: Copyright (C) 2013 Robert Cochran
9
+ # License:: Distributes under the MIT License
10
+ # Parent class for Feature and Scenario objects
11
+ # holds shared attributes and rules.
12
+ # Extends CukeSniffer::RulesEvaluator
13
+ class FeatureRulesEvaluator < RulesEvaluator
14
+
15
+ # string array: Contains all tags attached to a Feature or Scenario
16
+ attr_accessor :tags
17
+
18
+ # string: Name of the Feature or Scenario
19
+ attr_accessor :name
20
+
21
+ # Location must be in the format of "file_path\file_name.rb:line_number"
22
+ def initialize(location)
23
+ @name = ""
24
+ @tags = []
25
+ super(location)
26
+ end
27
+
28
+ def == (comparison_object) # :nodoc:
29
+ super(comparison_object) &&
30
+ comparison_object.name == name &&
31
+ comparison_object.tags == tags
32
+ end
33
+
34
+ private
35
+
36
+ def create_name(line, filter)
37
+ line.gsub!(/#{COMMENT_REGEX}#{filter}/, "")
38
+ line.strip!
39
+ @name += " " unless @name.empty? or line.empty?
40
+ @name += line
41
+ end
42
+
43
+ def update_tag_list(line)
44
+ comment_start = (line =~ /([^@\w]#)|(^#)/)
45
+
46
+ if comment_start
47
+ line[0...comment_start].split.each { |single_tag| @tags << single_tag }
48
+ @tags << line[comment_start..line.length].strip
49
+ else
50
+ line.split.each { |single_tag| @tags << single_tag }
51
+ end
52
+ end
53
+
54
+ def evaluate_score
55
+ cls_name = self.class.to_s.gsub('CukeSniffer::', '')
56
+ rule_too_many_tags(cls_name)
57
+ rule_no_description(cls_name)
58
+ rule_numbers_in_name(cls_name)
59
+ rule_long_name(cls_name)
60
+ rule_commas_in_description(cls_name)
61
+ rule_comment_after_tag(cls_name)
62
+ rule_commented_tag(cls_name)
63
+ end
64
+
65
+ def rule_too_many_tags(type)
66
+ rule = RULES[:too_many_tags]
67
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
68
+ store_rule(rule, rule_phrase) if tags.size >= rule[:max]
69
+ end
70
+
71
+ def rule_no_description(type)
72
+ rule = RULES[:no_description]
73
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
74
+ store_rule(rule, rule_phrase) if name.empty?
75
+ end
76
+
77
+ def rule_numbers_in_name(type)
78
+ rule = RULES[:numbers_in_description]
79
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
80
+ store_rule(rule, rule_phrase) if name =~ /\d/
81
+ end
82
+
83
+ def rule_long_name(type)
84
+ rule = RULES[:long_name]
85
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
86
+ store_rule(rule, rule_phrase) if name.size >= rule[:max]
87
+ end
88
+
89
+ def rule_commas_in_description(type)
90
+ rule = RULES[:commas_in_description]
91
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
92
+ store_rule(rule, rule_phrase) if name.include?(',')
93
+ end
94
+
95
+ def rule_comment_after_tag(type)
96
+ rule = RULES[:comment_after_tag]
97
+
98
+ last_comment_index = tags.rindex { |single_tag| is_comment?(single_tag) }
99
+ if last_comment_index
100
+ comment_after_tag = tags[0...last_comment_index].any? { |single_tag| !is_comment?(single_tag) }
101
+ rule_phrase = rule[:phrase].gsub(/{.*}/, type)
102
+ store_rule(rule, rule_phrase) if comment_after_tag
103
+ end
104
+ end
105
+
106
+ def rule_commented_tag(type)
107
+ rule = RULES[:commented_tag]
108
+ tags.each do |tag|
109
+ store_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if is_comment?(tag) && tag.match(/@\S*/)
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+
@@ -0,0 +1,160 @@
1
+ require 'roxml'
2
+ module CukeSniffer
3
+
4
+ # Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
5
+ # Copyright:: Copyright (C) 2013 Robert Cochran
6
+ # License:: Distributes under the MIT License
7
+ # Cucumber Hook class used for evaluating rules
8
+ # Extends CukeSniffer::RulesEvaluator
9
+ class Hook < RulesEvaluator
10
+
11
+ xml_accessor :start_line
12
+ xml_accessor :type
13
+ xml_accessor :tags, :as => [], :in => "tags"
14
+ xml_accessor :parameters, :as => [], :in => "parameters"
15
+ xml_accessor :code, :as => [], :in => "code"
16
+
17
+ # The type of the hook: AfterConfiguration, After, AfterStep, Around, Before, at_exit
18
+ attr_accessor :type
19
+
20
+ # The list of tags used as a filter for the hook
21
+ attr_accessor :tags
22
+
23
+ # The parameters that are declared on the hook
24
+ attr_accessor :parameters
25
+
26
+ # Integer of the line in which the hook was found
27
+ attr_accessor :start_line
28
+
29
+ # Array of strings that contain the code kept in the hook
30
+ attr_accessor :code
31
+
32
+
33
+ # location must be in the format of "file_path\file_name.rb:line_number"
34
+ # raw_code is an array of strings that represents the step definition
35
+ # must contain the hook declaration line and the pairing end
36
+ def initialize(location, raw_code)
37
+ super(location)
38
+
39
+ @start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
40
+ @type = nil
41
+ @tags = []
42
+ @parameters = []
43
+
44
+ end_match_index = (raw_code.size - 1) - raw_code.reverse.index("end")
45
+ @code = raw_code[1...end_match_index]
46
+
47
+ raw_code.each do |line|
48
+ if line =~ HOOK_REGEX
49
+ matches = HOOK_REGEX.match(line)
50
+ @type = matches[:type]
51
+ hook_tag_regexp = /["']([^"']*)["']/
52
+ matches[:tags].scan(hook_tag_regexp).each { |tag| @tags << tag[0] } if matches[:tags]
53
+ @parameters = matches[:parameters].split(/,\s*/) if matches[:parameters]
54
+ end
55
+ end
56
+ evaluate_score
57
+ end
58
+
59
+ def ==(comparison_object) # :nodoc:
60
+ super(comparison_object) &&
61
+ comparison_object.type == type &&
62
+ comparison_object.tags == tags &&
63
+ comparison_object.parameters == parameters &&
64
+ comparison_object.code == code
65
+ end
66
+
67
+ private
68
+
69
+ def evaluate_score
70
+ rule_empty_hook
71
+ rule_hook_not_in_hooks_file
72
+ rule_no_debugging
73
+ rule_all_comments
74
+ rule_conflicting_tags
75
+ rule_duplicate_tags
76
+ if @type == "Around"
77
+ rule_around_hook_without_2_parameters
78
+ rule_around_hook_no_block_call
79
+ end
80
+ end
81
+
82
+ def rule_empty_hook
83
+ rule = RULES[:empty_hook]
84
+ store_rule(rule) if @code == []
85
+ end
86
+
87
+ def rule_hook_not_in_hooks_file
88
+ rule = RULES[:hook_not_in_hooks_file]
89
+ store_rule(rule) unless @location.include?(rule[:file])
90
+ end
91
+
92
+ def rule_around_hook_without_2_parameters
93
+ rule = RULES[:around_hook_without_2_parameters]
94
+ store_rule(rule) unless @parameters.count == 2
95
+ end
96
+
97
+ def rule_around_hook_no_block_call
98
+ return if rule_stored?(:around_hook_without_2_parameters)
99
+ rule = RULES[:around_hook_no_block_call]
100
+ block_call = "#{@parameters[1]}.call"
101
+ @code.each do |line|
102
+ return if line.include?(block_call)
103
+ end
104
+ store_rule(rule)
105
+ end
106
+
107
+ def rule_stored?(rule_symbol)
108
+ @rules_hash.keys.include?(RULES[rule_symbol][:phrase])
109
+ end
110
+
111
+ def rule_no_debugging
112
+ return if rule_stored?(:empty_hook)
113
+ rule = RULES[:hook_no_debugging]
114
+ begin_found = false
115
+ rescue_found = false
116
+ @code.each do |line|
117
+ begin_found = true if line.include?("begin")
118
+ rescue_found = true if line.include?("rescue")
119
+ break if begin_found and rescue_found
120
+ end
121
+ store_rule(rule) unless begin_found and rescue_found
122
+ end
123
+
124
+ def rule_all_comments
125
+ rule = RULES[:hook_all_comments]
126
+ @code.each do |line|
127
+ return unless is_comment?(line)
128
+ end
129
+ store_rule(rule)
130
+ end
131
+
132
+ def rule_conflicting_tags
133
+ rule = RULES[:hook_conflicting_tags]
134
+ all_tags = flatten_tags
135
+
136
+ all_tags.each do |single_tag|
137
+ tag = single_tag.gsub("~", "")
138
+ if all_tags.include?(tag) and all_tags.include?("~#{tag}")
139
+ store_rule(rule)
140
+ return
141
+ end
142
+ end
143
+ end
144
+
145
+ def flatten_tags
146
+ all_tags = []
147
+ @tags.each { |single_tag| all_tags << single_tag.split(',') }
148
+ all_tags.flatten
149
+ end
150
+
151
+ def rule_duplicate_tags
152
+ rule = RULES[:hook_duplicate_tags]
153
+ all_tags = flatten_tags
154
+ unique_tags = all_tags.uniq
155
+
156
+ store_rule(rule) unless all_tags == unique_tags
157
+ end
158
+
159
+ end
160
+ end