cuke_slicer 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/Gemfile +2 -0
- data/History.rdoc +11 -0
- data/README.md +14 -1
- data/Rakefile +53 -49
- data/cuke_slicer.gemspec +1 -1
- data/features/step_definitions/action_steps.rb +12 -11
- data/features/step_definitions/setup_steps.rb +24 -0
- data/features/step_definitions/verification_steps.rb +11 -0
- data/features/test_case_extraction.feature +7 -1
- data/features/validation.feature +6 -0
- data/lib/cuke_slicer.rb +10 -10
- data/lib/cuke_slicer/collections/nested_tag_collection.rb +27 -0
- data/lib/cuke_slicer/collections/path_collection.rb +26 -0
- data/lib/cuke_slicer/collections/tag_collection.rb +28 -0
- data/lib/cuke_slicer/extractors/directory_extractor.rb +17 -0
- data/lib/cuke_slicer/extractors/file_extractor.rb +32 -0
- data/lib/cuke_slicer/filters/filter_set.rb +42 -0
- data/lib/cuke_slicer/helpers/extraction_helpers.rb +40 -0
- data/lib/cuke_slicer/helpers/filter_helpers.rb +60 -0
- data/lib/cuke_slicer/helpers/helpers.rb +21 -0
- data/lib/cuke_slicer/helpers/matching_helpers.rb +47 -0
- data/lib/cuke_slicer/slicer.rb +18 -194
- data/lib/cuke_slicer/version.rb +1 -1
- data/spec/slicer_integration_spec.rb +114 -43
- data/spec/slicer_unit_spec.rb +3 -3
- metadata +17 -10
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/History.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
=== Version 2.0.0 / 2015-07-08
|
2
|
+
|
3
|
+
* When slicing a directory, the test cases returned can now be provided as model objects in addition to the
|
4
|
+
previous 'file_path:line_number' format.
|
5
|
+
|
6
|
+
* Major performance increase
|
7
|
+
|
8
|
+
|
9
|
+
=== Version 1.0.0 / 2015-04-01
|
10
|
+
|
11
|
+
* Initial release
|
data/README.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# CukeSlicer
|
2
2
|
|
3
|
+
<a href="http://badge.fury.io/rb/cuke_slicer"><img src="https://badge.fury.io/rb/cuke_slicer.svg" alt="Gem Version"></a>
|
4
|
+
|
5
|
+
<a href='https://gemnasium.com/grange-insurance/cuke_slicer'><img src="https://gemnasium.com/grange-insurance/cuke_slicer.svg" alt="Dependency Status" /></a>
|
6
|
+
|
7
|
+
<a href='https://github.com/grange-insurance/cuke_slicer/blob/master/LICENSE.txt'><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Project License" /></a>
|
8
|
+
|
9
|
+
<a href="https://travis-ci.org/grange-insurance/cuke_slicer"><img src="https://travis-ci.org/grange-insurance/cuke_slicer.svg" alt="Build Status"></a>
|
10
|
+
|
11
|
+
<a href='https://coveralls.io/r/grange-insurance/cuke_slicer'><img src='https://coveralls.io/repos/grange-insurance/cuke_slicer/badge.svg' alt='Coverage Status' /></a>
|
12
|
+
|
13
|
+
<a href="https://codeclimate.com/github/grange-insurance/cuke_slicer"><img src="https://codeclimate.com/github/grange-insurance/cuke_slicer/badges/gpa.svg" alt="Code Quality" /></a>
|
14
|
+
|
15
|
+
|
3
16
|
The cuke_slicer gem provides an easy and programmatic way to divide a Cucumber test suite into granular test
|
4
17
|
cases that can then be dealt with on an individual basis. Often this means handing them off to a distributed
|
5
18
|
testing system in order to parallelize test execution.
|
@@ -33,7 +46,7 @@ Or install it yourself as:
|
|
33
46
|
included_paths: [/test_directory/]}
|
34
47
|
|
35
48
|
# Use the slicer to find all tests matching those filters
|
36
|
-
found_tests = CukeSlicer::Slicer.new.slice(test_directory, filters)
|
49
|
+
found_tests = CukeSlicer::Slicer.new.slice(test_directory, filters, :file_line)
|
37
50
|
|
38
51
|
|
39
52
|
# Arrange the sliced pieces to suit your particular needs. In this case, we will dump them
|
data/Rakefile
CHANGED
@@ -1,49 +1,53 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
2
|
-
require 'cucumber/rake/task'
|
3
|
-
require 'rspec/core/rake_task'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
RSpec
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
Rake::Task['cuke_slicer:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'cucumber/rake/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'coveralls/rake/task'
|
5
|
+
|
6
|
+
|
7
|
+
def set_cucumber_options(options)
|
8
|
+
ENV['CUCUMBER_OPTS'] = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def combine_options(set_1, set_2)
|
12
|
+
set_2 ? "#{set_1} #{set_2}" : set_1
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
namespace 'cuke_slicer' do
|
17
|
+
|
18
|
+
task :clear_coverage do
|
19
|
+
puts 'Clearing old code coverage results...'
|
20
|
+
|
21
|
+
# Remove previous coverage results so that they don't get merged in the new results
|
22
|
+
code_coverage_directory = File.join(File.dirname(__FILE__), 'coverage')
|
23
|
+
FileUtils.remove_dir(code_coverage_directory, true) if File.exists?(code_coverage_directory)
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace 'cucumber' do
|
27
|
+
desc 'Run all Cucumber tests for the gem'
|
28
|
+
task :tests, [:command_options] do |t, args|
|
29
|
+
set_cucumber_options(combine_options('-t ~@wip -t ~@off', args[:command_options]))
|
30
|
+
end
|
31
|
+
Cucumber::Rake::Task.new(:tests)
|
32
|
+
end
|
33
|
+
|
34
|
+
namespace 'rspec' do
|
35
|
+
desc 'Run all RSpec tests for the gem'
|
36
|
+
RSpec::Core::RakeTask.new(:specs, :command_options) do |t, args|
|
37
|
+
t.rspec_opts = combine_options('--tag ~wip', args[:command_options])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Run all tests for the gem'
|
42
|
+
task :test_everything, [:command_options] => :clear_coverage do |t, args|
|
43
|
+
Rake::Task['cuke_slicer:rspec:specs'].invoke(args[:command_options])
|
44
|
+
Rake::Task['cuke_slicer:cucumber:tests'].invoke(args[:command_options])
|
45
|
+
end
|
46
|
+
|
47
|
+
Coveralls::RakeTask.new
|
48
|
+
task :ci_build => [:clear_coverage, :test_everything, 'coveralls:push']
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
task :default => 'cuke_slicer:test_everything'
|
data/cuke_slicer.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["morrow748@gmail.com"]
|
11
11
|
spec.summary = %q{A gem for extracting test cases from a Cucumber test suite.}
|
12
12
|
spec.description = spec.summary
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/grange-insurance/cuke_slicer"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -7,16 +7,17 @@ When(/^test cases are extracted from "([^"]*)"$/) do |target|
|
|
7
7
|
filters[:excluded_paths] = @excluded_path_filters if @excluded_path_filters
|
8
8
|
filters[:included_paths] = @included_path_filters if @included_path_filters
|
9
9
|
|
10
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters, &@custom_filter)
|
10
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters, :file_line, &@custom_filter)
|
11
11
|
end
|
12
12
|
|
13
13
|
When(/^test cases are extracted from it$/) do
|
14
14
|
@output ||= {}
|
15
15
|
filters = {}
|
16
16
|
target = @targets.first
|
17
|
+
@output_type ||= :file_line
|
17
18
|
|
18
19
|
begin
|
19
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters)
|
20
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters, @output_type)
|
20
21
|
rescue ArgumentError => e
|
21
22
|
@error_raised = e
|
22
23
|
end
|
@@ -27,7 +28,7 @@ When(/^test cases are extracted from them$/) do
|
|
27
28
|
filters = {}
|
28
29
|
|
29
30
|
@targets.each do |target|
|
30
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters)
|
31
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", filters, :file_line)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
@@ -37,7 +38,7 @@ When(/^test cases are extracted from "([^"]*)" using the following exclusive tag
|
|
37
38
|
|
38
39
|
options[:excluded_tags] = filters.raw.flatten.collect { |filter| process_filter(filter) }
|
39
40
|
|
40
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options)
|
41
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options, :file_line)
|
41
42
|
end
|
42
43
|
|
43
44
|
When(/^test cases are extracted from "([^"]*)" using the following inclusive tag filters:$/) do |target, filters|
|
@@ -46,7 +47,7 @@ When(/^test cases are extracted from "([^"]*)" using the following inclusive tag
|
|
46
47
|
|
47
48
|
options[:included_tags] = filters.raw.flatten.collect { |filter| process_filter(filter) }
|
48
49
|
|
49
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options)
|
50
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options, :file_line)
|
50
51
|
end
|
51
52
|
|
52
53
|
When(/^test cases are extracted from "([^"]*)" using the following inclusive path filters:$/) do |target, filters|
|
@@ -55,7 +56,7 @@ When(/^test cases are extracted from "([^"]*)" using the following inclusive pat
|
|
55
56
|
|
56
57
|
options[:included_paths] = filters.raw.flatten.collect { |filter| process_filter(filter) }
|
57
58
|
|
58
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options)
|
59
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options, :file_line)
|
59
60
|
end
|
60
61
|
|
61
62
|
When(/^test cases are extracted from "([^"]*)" using the following exclusive path filters:$/) do |target, filters|
|
@@ -64,7 +65,7 @@ When(/^test cases are extracted from "([^"]*)" using the following exclusive pat
|
|
64
65
|
|
65
66
|
options[:excluded_paths] = filters.raw.flatten.collect { |filter| process_filter(filter) }
|
66
67
|
|
67
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options)
|
68
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options, :file_line)
|
68
69
|
end
|
69
70
|
|
70
71
|
When(/^test cases are extracted from "([^"]*)" using the following custom filter:$/) do |target, filter_block|
|
@@ -72,7 +73,7 @@ When(/^test cases are extracted from "([^"]*)" using the following custom filter
|
|
72
73
|
|
73
74
|
custom_filter = eval("Proc.new #{filter_block}")
|
74
75
|
|
75
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", &custom_filter)
|
76
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", :file_line, &custom_filter)
|
76
77
|
end
|
77
78
|
|
78
79
|
When(/^test cases are extracted from "([^"]*)" using "([^"]*)"$/) do |target, included_tag_filters|
|
@@ -81,7 +82,7 @@ When(/^test cases are extracted from "([^"]*)" using "([^"]*)"$/) do |target, in
|
|
81
82
|
|
82
83
|
options[:included_tags] = eval("[#{included_tag_filters}]")
|
83
84
|
|
84
|
-
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options)
|
85
|
+
@output[target] = CukeSlicer::Slicer.new.slice("#{@default_file_directory}/#{target}", options, :file_line)
|
85
86
|
end
|
86
87
|
|
87
88
|
def process_filter(filter)
|
@@ -91,7 +92,7 @@ end
|
|
91
92
|
|
92
93
|
When(/^it tries to extract test cases using an unknown filter type$/) do
|
93
94
|
begin
|
94
|
-
@slicer.slice(@default_file_directory, {unknown_filter: 'foo'})
|
95
|
+
@slicer.slice(@default_file_directory, {unknown_filter: 'foo'}, :file_line)
|
95
96
|
rescue ArgumentError => e
|
96
97
|
@error_raised = e
|
97
98
|
end
|
@@ -99,7 +100,7 @@ end
|
|
99
100
|
|
100
101
|
When(/^it tries to extract test cases using an invalid filter$/) do
|
101
102
|
begin
|
102
|
-
@slicer.slice(@default_file_directory, {included_tags: 7})
|
103
|
+
@slicer.slice(@default_file_directory, {included_tags: 7}, :file_line)
|
103
104
|
rescue ArgumentError => e
|
104
105
|
@error_raised = e
|
105
106
|
end
|
@@ -77,3 +77,27 @@ end
|
|
77
77
|
Given(/^a slicer$/) do
|
78
78
|
@slicer = CukeSlicer::Slicer.new
|
79
79
|
end
|
80
|
+
|
81
|
+
And(/^the test cases are to be extracted as objects$/) do
|
82
|
+
@output_type = :test_object
|
83
|
+
end
|
84
|
+
|
85
|
+
And(/^an invalid output option$/) do
|
86
|
+
@output_type = :invalid_option
|
87
|
+
end
|
88
|
+
|
89
|
+
Given(/^a test suite to extract from$/) do
|
90
|
+
@test_directory ||= @default_file_directory
|
91
|
+
|
92
|
+
@targets ||= []
|
93
|
+
@targets << @default_feature_file_name
|
94
|
+
|
95
|
+
|
96
|
+
test_suite = "Feature: Test feature
|
97
|
+
|
98
|
+
@tag
|
99
|
+
Scenario: Test scenario
|
100
|
+
* some step"
|
101
|
+
|
102
|
+
File.write("#{@test_directory}/#{@default_feature_file_name}", test_suite)
|
103
|
+
end
|
@@ -45,3 +45,14 @@ Then(/^an error indicating that the filter is invalid will be triggered$/) do
|
|
45
45
|
expect(@error_raised).to_not be_nil
|
46
46
|
expect(@error_raised.message).to match(/invalid filter/i)
|
47
47
|
end
|
48
|
+
|
49
|
+
Then(/^the test cases are provided as objects$/) do
|
50
|
+
@output.values.flatten.each do |output|
|
51
|
+
expect(output).to be_a(CukeModeler::Scenario).or be_a(CukeModeler::Row)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Then(/^an error indicating that the output type is invalid will be triggered$/) do
|
56
|
+
expect(@error_raised).to_not be_nil
|
57
|
+
expect(@error_raised.message).to match(/Invalid Output Format/i)
|
58
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Feature: Test case extraction
|
2
2
|
|
3
|
-
Test cases can be extracted from a source file or directory as a collection of 'file:line' items that
|
3
|
+
Test cases can be extracted from a source file or directory as a collection of 'file:line' items or objects that
|
4
4
|
can then be conveniently arranged for consumption by some other tool (e.g. Cucumber).
|
5
5
|
|
6
6
|
|
@@ -76,3 +76,9 @@ Feature: Test case extraction
|
|
76
76
|
And the directory "test_directory/empty_directory"
|
77
77
|
When test cases are extracted from "test_directory"
|
78
78
|
Then no test cases are found
|
79
|
+
|
80
|
+
Scenario: Extracting objects
|
81
|
+
Given a test suite to extract from
|
82
|
+
And the test cases are to be extracted as objects
|
83
|
+
When test cases are extracted from it
|
84
|
+
Then the test cases are provided as objects
|
data/features/validation.feature
CHANGED
@@ -54,3 +54,9 @@ Feature: Validation
|
|
54
54
|
When test cases are extracted from "test_directory"
|
55
55
|
Then the following test cases are found
|
56
56
|
| path/to/test_directory/a_test.feature:3 |
|
57
|
+
|
58
|
+
Scenario: Invalid output type are not allowed
|
59
|
+
Given a test suite to extract from
|
60
|
+
And an invalid output option
|
61
|
+
When test cases are extracted from it
|
62
|
+
Then an error indicating that the output type is invalid will be triggered
|
data/lib/cuke_slicer.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'cuke_modeler'
|
2
|
-
|
3
|
-
require "cuke_slicer/version"
|
4
|
-
require "cuke_slicer/slicer"
|
5
|
-
|
6
|
-
|
7
|
-
# Top level module under which the gem code lives.
|
8
|
-
module CukeSlicer
|
9
|
-
# Your code goes here...
|
10
|
-
end
|
1
|
+
require 'cuke_modeler'
|
2
|
+
|
3
|
+
require "cuke_slicer/version"
|
4
|
+
require "cuke_slicer/slicer"
|
5
|
+
|
6
|
+
|
7
|
+
# Top level module under which the gem code lives.
|
8
|
+
module CukeSlicer
|
9
|
+
# Your code goes here...
|
10
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "cuke_slicer/helpers/helpers"
|
2
|
+
|
3
|
+
|
4
|
+
module CukeSlicer
|
5
|
+
class NestedTagCollection
|
6
|
+
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
|
10
|
+
def initialize collection
|
11
|
+
self.nested_collection = collection
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
nested_collection.each do |element|
|
16
|
+
raise(ArgumentError, "Tag filters cannot be nested more than one level deep.") if element.is_a?(Array)
|
17
|
+
raise(ArgumentError, "Filter '#{element}' must be a String or Regexp. Got #{element.class}") unless str_regex?(element)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_accessor :nested_collection
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "cuke_slicer/helpers/helpers"
|
2
|
+
|
3
|
+
|
4
|
+
module CukeSlicer
|
5
|
+
class PathCollection
|
6
|
+
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
|
10
|
+
def initialize filter_values
|
11
|
+
self.filter_values = filter_values
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
filter_values.each do |val|
|
16
|
+
raise(ArgumentError, "Filter '#{val}' must be a String or Regexp. Got #{val.class}") unless str_regex?(val)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_accessor :filter_values
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "cuke_slicer/helpers/helpers"
|
2
|
+
require "cuke_slicer/collections/nested_tag_collection"
|
3
|
+
|
4
|
+
|
5
|
+
module CukeSlicer
|
6
|
+
class TagCollection
|
7
|
+
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
|
11
|
+
def initialize parameters
|
12
|
+
self.filter_values = parameters
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate
|
16
|
+
filter_values.each do |val|
|
17
|
+
raise(ArgumentError, "Filter '#{val}' must be a String, Regexp, or Array. Got #{val.class}") unless str_regex_arr?(val)
|
18
|
+
NestedTagCollection.new(val).validate if val.is_a?(Array)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_accessor :filter_values
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CukeSlicer
|
2
|
+
class DirectoryExtractor
|
3
|
+
|
4
|
+
def extract(target, filters, format, &block)
|
5
|
+
Array.new.tap do |test_cases|
|
6
|
+
target.feature_files.each do |feature_file|
|
7
|
+
test_cases.concat(FileExtractor.new.extract(feature_file, filters, format, &block))
|
8
|
+
end
|
9
|
+
|
10
|
+
target.directories.each do |directory|
|
11
|
+
test_cases.concat(extract(directory, filters, format, &block))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|