cuke_sniffer 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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