cucumber_analytics 0.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.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.rdoc +76 -0
  5. data/Rakefile +2 -0
  6. data/cucumber_analytics.gemspec +22 -0
  7. data/features/analysis/feature_collection.feature +39 -0
  8. data/features/analysis/feature_file_collection.feature +36 -0
  9. data/features/analysis/step_collection.feature +137 -0
  10. data/features/analysis/tag_collection.feature +91 -0
  11. data/features/analysis/test_collection.feature +69 -0
  12. data/features/analysis/test_comparison.feature +123 -0
  13. data/features/modeling/background_modeling.feature +147 -0
  14. data/features/modeling/directory_modeling.feature +86 -0
  15. data/features/modeling/feature_file_modeling.feature +37 -0
  16. data/features/modeling/feature_modeling.feature +163 -0
  17. data/features/modeling/outline_modeling.feature +186 -0
  18. data/features/modeling/scenario_modeling.feature +154 -0
  19. data/features/step_definitions/background_steps.rb +55 -0
  20. data/features/step_definitions/directory_steps.rb +20 -0
  21. data/features/step_definitions/feature_steps.rb +62 -0
  22. data/features/step_definitions/file_steps.rb +18 -0
  23. data/features/step_definitions/outline_steps.rb +26 -0
  24. data/features/step_definitions/setup_steps.rb +50 -0
  25. data/features/step_definitions/test_steps.rb +82 -0
  26. data/features/step_definitions/world_steps.rb +158 -0
  27. data/features/support/env.rb +29 -0
  28. data/features/support/transforms.rb +3 -0
  29. data/lib/cucumber_analytics/feature_element.rb +54 -0
  30. data/lib/cucumber_analytics/outline_example.rb +31 -0
  31. data/lib/cucumber_analytics/parsed_background.rb +23 -0
  32. data/lib/cucumber_analytics/parsed_directory.rb +56 -0
  33. data/lib/cucumber_analytics/parsed_feature.rb +70 -0
  34. data/lib/cucumber_analytics/parsed_file.rb +140 -0
  35. data/lib/cucumber_analytics/parsed_scenario.rb +29 -0
  36. data/lib/cucumber_analytics/parsed_scenario_outline.rb +57 -0
  37. data/lib/cucumber_analytics/step.rb +81 -0
  38. data/lib/cucumber_analytics/test_element.rb +93 -0
  39. data/lib/cucumber_analytics/version.rb +3 -0
  40. data/lib/cucumber_analytics/world.rb +182 -0
  41. data/lib/cucumber_analytics.rb +12 -0
  42. metadata +174 -0
@@ -0,0 +1,54 @@
1
+ module CucumberAnalytics
2
+ class FeatureElement
3
+
4
+
5
+ attr_reader :name
6
+ attr_reader :description
7
+
8
+
9
+ # Creates a new FeatureElement object.
10
+ def initialize(source_lines = nil)
11
+ @name = ''
12
+ @description =[]
13
+ end
14
+
15
+
16
+ private
17
+
18
+
19
+ def parse_feature_element(source_lines)
20
+ parse_feature_element_name(source_lines)
21
+ parse_feature_element_description(source_lines)
22
+ end
23
+
24
+ #todo - move this elsewhere
25
+ def parse_feature_element_tags(source_lines)
26
+ source_lines.take_while { |line| !(line =~/^\s*(?:[A-Z a-z])+:/) }.tap do |tag_lines|
27
+ tag_lines.join(' ').delete(' ').split('@').each do |tag|
28
+ @tags << "@#{tag.strip}"
29
+ end
30
+ end
31
+ @tags.shift
32
+
33
+ while source_lines.first =~ /^\s*@/
34
+ source_lines.shift
35
+ end
36
+ end
37
+
38
+ def parse_feature_element_name(source_lines)
39
+ @name.replace source_lines.first.match(/^\s*(?:[A-Z a-z])+:(.*)/)[1].strip
40
+ source_lines.shift
41
+ end
42
+
43
+ def parse_feature_element_description(source_lines)
44
+ until source_lines.first =~ /^\s*(?:(?:Given )|(?:When )|(?:Then )|(?:\* ))/ or
45
+ source_lines.first =~ /^\s*\|/ or
46
+ source_lines.empty?
47
+
48
+ @description << source_lines.first.strip
49
+ source_lines.shift
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ module CucumberAnalytics
2
+ class OutlineExample < FeatureElement
3
+
4
+
5
+ attr_accessor :tags
6
+ attr_accessor :rows
7
+
8
+
9
+ # Creates a new OutlineExample object and, if *source_lines* is provided,
10
+ # populates the object.
11
+ def initialize(source_lines = nil)
12
+ super
13
+
14
+ @tags = []
15
+ @rows = []
16
+
17
+ parse_example(source_lines) if source_lines
18
+ end
19
+
20
+
21
+ private
22
+
23
+
24
+ def parse_example(source_lines)
25
+ parse_feature_element_tags(source_lines)
26
+ parse_feature_element(source_lines)
27
+ rows.concat source_lines.collect { |line| line.strip }
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module CucumberAnalytics
2
+ class ParsedBackground < TestElement
3
+
4
+
5
+ # Creates a new ParsedBackground object and, if *source_lines* is provided,
6
+ # populates the object.
7
+ def initialize(source_lines = nil)
8
+ super
9
+
10
+ parse_background(source_lines) if source_lines
11
+ end
12
+
13
+
14
+ private
15
+
16
+
17
+ def parse_background(source_lines)
18
+ parse_feature_element(source_lines)
19
+ parse_test_element_steps(source_lines)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ module CucumberAnalytics
2
+ class ParsedDirectory
3
+
4
+
5
+ attr_reader :feature_files
6
+ attr_reader :feature_directories
7
+
8
+
9
+ # Creates a new ParsedDirectory object and, if *directory_parsed* is
10
+ # provided, populates the object.
11
+ def initialize(directory_parsed = nil)
12
+ @directory = directory_parsed
13
+
14
+ @feature_files = []
15
+ @feature_directories = []
16
+
17
+ scan_directory if directory_parsed
18
+ end
19
+
20
+ # Returns the name of the directory.
21
+ def name
22
+ File.basename(@directory.gsub('\\', '/'))
23
+ end
24
+
25
+ # Returns the path of the directory.
26
+ def path
27
+ @directory
28
+ end
29
+
30
+ # Returns the number of features files contained in the directory.
31
+ def feature_file_count
32
+ @feature_files.count
33
+ end
34
+
35
+ def contains
36
+ @feature_files + @feature_directories
37
+ end
38
+
39
+
40
+ private
41
+
42
+
43
+ def scan_directory
44
+ entries = Dir.entries(@directory)
45
+ entries.delete '.'
46
+ entries.delete '..'
47
+
48
+ entries.each do |entry|
49
+ entry = @directory + '/' + entry
50
+ @feature_directories << ParsedDirectory.new(entry) if File.directory?(entry)
51
+ @feature_files << ParsedFile.new(entry) if entry =~ /\.feature$/
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,70 @@
1
+ module CucumberAnalytics
2
+ class ParsedFeature < FeatureElement
3
+
4
+
5
+ attr_reader :tags
6
+ attr_accessor :background
7
+ attr_accessor :tests
8
+
9
+
10
+ # Creates a new ParsedFeature object and, if *source_lines* is provided,
11
+ # populates the object.
12
+ def initialize(source_lines = nil)
13
+ super
14
+
15
+ @tags = []
16
+ @tests = []
17
+
18
+ parse_feature(source_lines) if source_lines
19
+ end
20
+
21
+ # Returns true if the feature contains a background, false otherwise.
22
+ def has_background?
23
+ !@background.nil?
24
+ end
25
+
26
+ # Returns the scenarios contained in the feature.
27
+ def scenarios
28
+ @tests.select { |test| test.is_a? ParsedScenario }
29
+ end
30
+
31
+ # Returns the outlines contained in the feature.
32
+ def outlines
33
+ @tests.select { |test| test.is_a? ParsedScenarioOutline }
34
+ end
35
+
36
+ # Returns the number scenarios contained in the feature.
37
+ def scenario_count
38
+ scenarios.count
39
+ end
40
+
41
+ # Returns the number outlines contained in the feature.
42
+ def outline_count
43
+ outlines.count
44
+ end
45
+
46
+ # Returns the number of tests contained in the feature.
47
+ def test_count
48
+ @tests.count
49
+ end
50
+
51
+ # Returns the number of test cases contained in the feature.
52
+ def test_case_count
53
+ scenario_count + outlines.reduce(0) { |sum, outline| sum += outline.examples.count }
54
+ end
55
+
56
+ def contains
57
+ [@background] + @tests
58
+ end
59
+
60
+
61
+ private
62
+
63
+
64
+ def parse_feature(source_lines)
65
+ parse_feature_element_tags(source_lines)
66
+ parse_feature_element(source_lines)
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,140 @@
1
+ module CucumberAnalytics
2
+ class ParsedFile
3
+
4
+
5
+ attr_reader :feature
6
+
7
+
8
+ # Creates a new ParsedFile object and, if *file_parsed* is provided,
9
+ # populates the object.
10
+ def initialize(file_parsed = nil)
11
+ parse_file(file_parsed) if file_parsed
12
+ end
13
+
14
+ # Returns the name of the file.
15
+ def name
16
+ File.basename(@file.gsub('\\', '/'))
17
+ end
18
+
19
+ # Returns the path of the file.
20
+ def path
21
+ @file
22
+ end
23
+
24
+ def contains
25
+ [@feature]
26
+ end
27
+
28
+
29
+ private
30
+
31
+
32
+ def parse_file(file_parsed)
33
+ @file = file_parsed
34
+
35
+ file_lines = []
36
+ feature_lines = []
37
+ background_lines = []
38
+
39
+ File.open(@file, 'r') { |file| file_lines = file.readlines }
40
+
41
+ until file_lines.first =~ /^s*Feature:/
42
+ unless ignored_line?(file_lines.first)
43
+ feature_lines << file_lines.first
44
+ end
45
+ file_lines.shift
46
+ end
47
+
48
+ until file_lines.first =~ /^\s*(?:@|Background:|Scenario:|(?:Scenario Outline:))/ or file_lines.empty?
49
+ unless ignored_line?(file_lines.first)
50
+ feature_lines << file_lines.first
51
+ end
52
+ file_lines.shift
53
+ end
54
+
55
+ until file_lines.first =~ /^\s*(?:@|Scenario:|(?:Scenario Outline:))/ or file_lines.empty?
56
+ if file_lines.first =~ /^\s*"""/
57
+ background_lines.concat(extract_doc_string!(file_lines))
58
+ else
59
+ unless ignored_line?(file_lines.first)
60
+ background_lines << file_lines.first
61
+ end
62
+ file_lines.shift
63
+ end
64
+ end
65
+
66
+ @feature = ParsedFeature.new(feature_lines)
67
+
68
+ unless background_lines.empty?
69
+ @feature.background = ParsedBackground.new(background_lines)
70
+ end
71
+
72
+ parse_tests(file_lines)
73
+ end
74
+
75
+ def parse_tests(lines)
76
+ test_lines = []
77
+
78
+ until lines.empty?
79
+ current_test_line = lines.index { |line| line =~ /^\s*(?:Scenario:|(?:Scenario Outline:))/ }
80
+
81
+ until lines.first =~ /^\s*(?:Scenario:|(?:Scenario Outline:))/
82
+ unless ignored_line?(lines.first)
83
+ test_lines << lines.first
84
+ end
85
+ lines.shift
86
+ end
87
+
88
+ test_lines << lines.first
89
+ lines.shift
90
+
91
+ until (lines.first =~ /^\s*(?:Scenario:|(?:Scenario Outline:))/) or lines.empty?
92
+ if (lines.first =~ /^\s*"""/)
93
+ test_lines.concat(extract_doc_string!(lines))
94
+ else
95
+ unless ignored_line?(lines.first)
96
+ test_lines << lines.first
97
+ end
98
+ lines.shift
99
+ end
100
+ end
101
+
102
+ unless lines.empty?
103
+ while (test_lines.last =~ /^\s*@/)
104
+ lines = [test_lines.pop].concat(lines)
105
+ end
106
+ end
107
+
108
+ if test_lines[current_test_line] =~ /^\s*Scenario Outline:/
109
+ next_test = ParsedScenarioOutline.new(test_lines)
110
+ else
111
+ next_test = ParsedScenario.new(test_lines)
112
+ end
113
+
114
+ @feature.tests << next_test
115
+ end
116
+ end
117
+
118
+ def extract_doc_string!(lines)
119
+ doc_block = []
120
+
121
+ doc_block << lines.first
122
+ lines.shift
123
+
124
+ until lines.first =~ /^\s*"""/
125
+ doc_block << lines.first
126
+ lines.shift
127
+ end
128
+
129
+ doc_block << lines.first
130
+ lines.shift
131
+
132
+ doc_block
133
+ end
134
+
135
+ def ignored_line?(line)
136
+ line =~ /^\s*#/ or !(line =~ /\S/)
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ module CucumberAnalytics
2
+ class ParsedScenario < TestElement
3
+
4
+
5
+ attr_accessor :tags
6
+
7
+
8
+ # Creates a new ParsedScenario object and, if *source_lines* is provided,
9
+ # populates the object.
10
+ def initialize(source_lines = nil)
11
+ super
12
+
13
+ @tags = []
14
+
15
+ parse_scenario(source_lines) if source_lines
16
+ end
17
+
18
+
19
+ private
20
+
21
+
22
+ def parse_scenario(source_lines)
23
+ parse_feature_element_tags(source_lines)
24
+ parse_feature_element(source_lines)
25
+ parse_test_element_steps(source_lines)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ module CucumberAnalytics
2
+ class ParsedScenarioOutline < TestElement
3
+
4
+
5
+ attr_accessor :tags
6
+ attr_accessor :examples
7
+
8
+ # Creates a new ParsedScenarioOutline object and, if *source_lines* is
9
+ # provided, populates the object.
10
+ def initialize(source_lines = nil)
11
+ super
12
+
13
+ @tags = []
14
+ @examples = []
15
+
16
+ parse_outline(source_lines) if source_lines
17
+ end
18
+
19
+ def contains
20
+ @examples
21
+ end
22
+
23
+
24
+ private
25
+
26
+
27
+ def parse_outline(source_lines)
28
+ parse_feature_element_tags(source_lines)
29
+ parse_feature_element(source_lines)
30
+ parse_test_element_steps(source_lines)
31
+ parse_outline_examples(source_lines)
32
+ end
33
+
34
+ def parse_outline_examples(source_lines)
35
+ until source_lines.empty?
36
+ current_example_line = source_lines.index { |line| line =~ /^\s*Examples/ }
37
+
38
+ example_lines = source_lines.slice!(0..current_example_line)
39
+
40
+ next_example_line = source_lines.index { |line| line =~ /^\s*Examples:/ }
41
+
42
+ if next_example_line.nil?
43
+ example_lines.concat(source_lines.slice!(0..source_lines.count))
44
+ else
45
+ while source_lines[next_example_line - 1] =~ /^\s*@/
46
+ next_example_line -= 1
47
+ end
48
+
49
+ example_lines.concat(source_lines.slice!(0...next_example_line))
50
+ end
51
+
52
+ @examples << OutlineExample.new(example_lines)
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,81 @@
1
+ module CucumberAnalytics
2
+ class Step
3
+
4
+
5
+ attr_reader :keyword
6
+ attr_reader :base
7
+ attr_reader :block
8
+
9
+
10
+ # Creates a new Step object based on the passed string. If the optional
11
+ # string array is provided, it becomes the block for the step.
12
+ def initialize(step, block = nil)
13
+ @base = step.sub(/#{World::STEP_KEYWORD_PATTERN}/, '')
14
+ @block = block
15
+ @keyword = step.slice(/#{World::STEP_KEYWORD_PATTERN}/).strip
16
+ end
17
+
18
+ # Returns the text of the step. Options can be set to selectively exclude
19
+ # certain portions of the text. *left_delimiter* and *right_delimiter* are
20
+ # used to determine which parts of the step are arguments.
21
+ #
22
+ # a_step = CucumberAnalytics.new('Given *some* step with a block:', ['block line 1', 'block line 2'])
23
+ #
24
+ # a_step.step_text
25
+ # #=> ['Given *some* step with a block:', 'block line 1', 'block line 2']
26
+ # a_step.step_text(with_keywords: false)
27
+ # #=> ['*some* step with a block:', 'block line 1', 'block line 2']
28
+ # a_step.step_text(with_arguments: false, left_delimiter: '*', right_delimiter: '*')
29
+ # #=> ['Given ** step with a block:']
30
+ # a_step.step_text(with_keywords: false, with_arguments: false, left_delimiter: '-', right_delimiter: '-'))
31
+ # #=> ['*some* step with a block:']
32
+ #
33
+ def step_text(options = {})
34
+ options = {with_keywords: true,
35
+ with_arguments: true,
36
+ left_delimiter: World.left_delimiter,
37
+ right_delimiter: World.right_delimiter}.merge(options)
38
+
39
+ final_step = []
40
+ step_text = ''
41
+
42
+ step_text += "#{@keyword} " if options[:with_keywords]
43
+
44
+ if options[:with_arguments]
45
+ step_text += @base
46
+ final_step << step_text
47
+ final_step.concat @block if @block
48
+ else
49
+ step_text += stripped_step(@base, options[:left_delimiter], options[:right_delimiter])
50
+ final_step << step_text
51
+ end
52
+
53
+ final_step
54
+ end
55
+
56
+
57
+ private
58
+
59
+
60
+ # Returns the step string minus any arguments based on the given delimiters.
61
+ def stripped_step(step, left_delimiter, right_delimiter)
62
+ original_left = left_delimiter
63
+ original_right = right_delimiter
64
+
65
+ begin
66
+ Regexp.new(left_delimiter)
67
+ rescue RegexpError
68
+ left_delimiter = '\\' + left_delimiter
69
+ end
70
+
71
+ begin
72
+ Regexp.new(right_delimiter)
73
+ rescue RegexpError
74
+ right_delimiter = '\\' + right_delimiter
75
+ end
76
+
77
+ step.gsub(Regexp.new("#{left_delimiter}.*?#{right_delimiter}"), original_left + original_right)
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,93 @@
1
+ module CucumberAnalytics
2
+ class TestElement < FeatureElement
3
+
4
+
5
+ attr_reader :steps
6
+
7
+
8
+ # Creates a new TestElement object.
9
+ def initialize(source_lines = nil)
10
+ super
11
+
12
+ @steps = []
13
+ end
14
+
15
+ # Return true if the two elements have the same steps, minus any keywords
16
+ # and arguments, and false otherwise.
17
+ def ==(other_element)
18
+ left_steps = steps.collect { |step| step.step_text(with_keywords: false, with_arguments: false) }.flatten
19
+ right_steps = other_element.steps.collect { |step| step.step_text(with_keywords: false, with_arguments: false) }.flatten
20
+
21
+ left_steps == right_steps
22
+ end
23
+
24
+
25
+ private
26
+
27
+
28
+ def parse_test_element_steps(source_lines)
29
+ until source_lines.empty? or source_lines.first =~ /^\s*(?:@|Examples:)/
30
+ line = source_lines.first
31
+ block = nil
32
+
33
+ case
34
+ when line =~ /^\s*"""/
35
+ block = extract_doc_block(source_lines)
36
+ @steps[@steps.size - 1] = Step.new(@steps.last.keyword + ' ' + @steps.last.base, block)
37
+ when line =~ /^\s*\|/
38
+ block = extract_table_block(source_lines)
39
+ @steps[@steps.size - 1] = Step.new(@steps.last.keyword + ' ' + @steps.last.base, block)
40
+ else
41
+ @steps << Step.new(line.strip)
42
+ source_lines.shift
43
+ end
44
+ end
45
+ end
46
+
47
+ def extract_doc_block(source_lines)
48
+ step_block = []
49
+
50
+ line = source_lines.first
51
+ leading_whitespace = line[/^\s*/]
52
+
53
+ step_block << line.strip
54
+ source_lines.shift
55
+
56
+ line = source_lines.first
57
+ until line =~ /^\s*"""/
58
+
59
+ leading_whitespace.length.times do
60
+ line.slice!(0, 1) if line[0] =~ /\s/
61
+ end
62
+
63
+ step_block << line.chomp
64
+ source_lines.shift
65
+ line = source_lines.first
66
+ end
67
+
68
+ step_block << line.strip
69
+ source_lines.shift
70
+
71
+ step_block
72
+ end
73
+
74
+ def extract_table_block(source_lines)
75
+ step_block = []
76
+
77
+ line = source_lines.first
78
+
79
+ step_block << line.strip
80
+ source_lines.shift
81
+
82
+ line = source_lines.first
83
+ while line =~ /^\s*\|/
84
+ step_block << line.strip
85
+ source_lines.shift
86
+ line = source_lines.first
87
+ end
88
+
89
+ step_block
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ module CucumberAnalytics
2
+ VERSION = "0.0.1"
3
+ end