cuke_linter 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.simplecov +8 -0
- data/.travis.yml +16 -14
- data/CHANGELOG.md +11 -2
- data/Gemfile +6 -6
- data/README.md +160 -148
- data/Rakefile +48 -23
- data/cuke_linter.gemspec +3 -0
- data/environments/cucumber_env.rb +10 -0
- data/environments/rspec_env.rb +3 -0
- data/lib/cuke_linter/formatters/pretty_formatter.rb +4 -0
- data/lib/cuke_linter/linters/example_without_name_linter.rb +24 -0
- data/lib/cuke_linter/linters/feature_without_scenarios_linter.rb +6 -4
- data/lib/cuke_linter/linters/outline_with_single_example_row_linter.rb +27 -0
- data/lib/cuke_linter/linters/test_with_too_many_steps_linter.rb +26 -0
- data/lib/cuke_linter/version.rb +4 -3
- data/lib/cuke_linter.rb +14 -1
- data/testing/cucumber/features/linters/default_linters.feature +8 -1
- data/testing/cucumber/features/linters/example_without_name.feature +27 -0
- data/testing/cucumber/features/linters/feature_without_scenarios.feature +7 -2
- data/testing/cucumber/features/linters/outline_with_single_example_row.feature +23 -0
- data/testing/cucumber/features/linters/test_with_too_many_steps.feature +33 -0
- data/testing/cucumber/step_definitions/action_steps.rb +5 -1
- data/testing/cucumber/step_definitions/setup_steps.rb +12 -0
- data/testing/cucumber/step_definitions/verification_steps.rb +3 -2
- data/testing/model_factory.rb +36 -0
- data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +6 -0
- data/testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb +8 -0
- data/testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb +8 -0
- data/testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb +8 -0
- data/testing/rspec/spec/unit/formatters/pretty_formatter_unit_spec.rb +5 -1
- data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +83 -0
- data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +52 -2
- data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +231 -0
- data/testing/rspec/spec/unit/linters/test_with_too_many_steps_linter_unit_spec.rb +156 -0
- metadata +57 -2
data/lib/cuke_linter.rb
CHANGED
@@ -2,29 +2,42 @@ require 'cuke_modeler'
|
|
2
2
|
|
3
3
|
require "cuke_linter/version"
|
4
4
|
require 'cuke_linter/formatters/pretty_formatter'
|
5
|
+
require 'cuke_linter/linters/example_without_name_linter'
|
5
6
|
require 'cuke_linter/linters/feature_without_scenarios_linter'
|
7
|
+
require 'cuke_linter/linters/outline_with_single_example_row_linter'
|
8
|
+
require 'cuke_linter/linters/test_with_too_many_steps_linter'
|
6
9
|
|
7
10
|
|
11
|
+
# The top level namespace used by this gem
|
12
|
+
|
8
13
|
module CukeLinter
|
9
14
|
|
10
|
-
@registered_linters = { 'FeatureWithoutScenariosLinter'
|
15
|
+
@registered_linters = { 'FeatureWithoutScenariosLinter' => FeatureWithoutScenariosLinter.new,
|
16
|
+
'ExampleWithoutNameLinter' => ExampleWithoutNameLinter.new,
|
17
|
+
'OutlineWithSingleExampleRowLinter' => OutlineWithSingleExampleRowLinter.new,
|
18
|
+
'TestWithTooManyStepsLinter' => TestWithTooManyStepsLinter.new }
|
11
19
|
|
20
|
+
# Registers for linting use the given linter object, tracked by the given name
|
12
21
|
def self.register_linter(linter:, name:)
|
13
22
|
@registered_linters[name] = linter
|
14
23
|
end
|
15
24
|
|
25
|
+
# Unregisters the linter object tracked by the given name so that it is not used for linting
|
16
26
|
def self.unregister_linter(name)
|
17
27
|
@registered_linters.delete(name)
|
18
28
|
end
|
19
29
|
|
30
|
+
# Lists the names of the currently registered linting objects
|
20
31
|
def self.registered_linters
|
21
32
|
@registered_linters
|
22
33
|
end
|
23
34
|
|
35
|
+
# Unregisters all currently registered linting objects
|
24
36
|
def self.clear_registered_linters
|
25
37
|
@registered_linters.clear
|
26
38
|
end
|
27
39
|
|
40
|
+
# Lints the tree of model objects rooted at the given model using the given linting objects and formatting the results with the given formatters and their respective output locations
|
28
41
|
def self.lint(model_tree: CukeModeler::Directory.new(Dir.pwd), linters: @registered_linters.values, formatters: [[CukeLinter::PrettyFormatter.new]])
|
29
42
|
# puts "model tree: #{model_tree}"
|
30
43
|
# puts "linters: #{linters}"
|
@@ -1,7 +1,14 @@
|
|
1
1
|
Feature: Default Linters
|
2
2
|
|
3
|
+
As a user of cuke_linter
|
4
|
+
I want a default set of linters to be used
|
5
|
+
So that I don't have to specifically include every linter
|
6
|
+
|
3
7
|
|
4
8
|
Scenario: Using the default linters
|
5
9
|
Given no other linters have been registered
|
6
10
|
Then the following linters are registered by default
|
7
|
-
|
|
11
|
+
| ExampleWithoutNameLinter |
|
12
|
+
| FeatureWithoutScenariosLinter |
|
13
|
+
| OutlineWithSingleExampleRowLinter |
|
14
|
+
| TestWithTooManyStepsLinter |
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Example without name linter
|
2
|
+
|
3
|
+
As a reader of documentation
|
4
|
+
I want every example to have a name
|
5
|
+
So that I can understand the significance of the example grouping
|
6
|
+
|
7
|
+
|
8
|
+
Scenario: Linting
|
9
|
+
Given a linter for examples without names
|
10
|
+
And the following feature:
|
11
|
+
"""
|
12
|
+
Feature:
|
13
|
+
|
14
|
+
Scenario Outline:
|
15
|
+
* a step
|
16
|
+
Examples:
|
17
|
+
| param |
|
18
|
+
| value |
|
19
|
+
Examples:
|
20
|
+
| param |
|
21
|
+
| value |
|
22
|
+
"""
|
23
|
+
When it is linted
|
24
|
+
Then an error is reported
|
25
|
+
| linter | problem | location |
|
26
|
+
| ExampleWithoutNameLinter | Example has no name | <path_to_file>:5 |
|
27
|
+
| ExampleWithoutNameLinter | Example has no name | <path_to_file>:8 |
|
@@ -1,5 +1,10 @@
|
|
1
1
|
Feature: Feature without scenarios linter
|
2
2
|
|
3
|
+
As a writer of documentation
|
4
|
+
I want features to have at least one use case
|
5
|
+
So that I do not have incomplete documentation
|
6
|
+
|
7
|
+
|
3
8
|
Scenario: Linting
|
4
9
|
Given a linter for features without scenarios
|
5
10
|
And the following feature:
|
@@ -8,5 +13,5 @@ Feature: Feature without scenarios linter
|
|
8
13
|
"""
|
9
14
|
When it is linted
|
10
15
|
Then an error is reported
|
11
|
-
| problem | location |
|
12
|
-
| Feature has no scenarios | <path_to_file>:1 |
|
16
|
+
| linter | problem | location |
|
17
|
+
| FeatureWithoutScenariosLinter | Feature has no scenarios | <path_to_file>:1 |
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: Outline with single example row linter
|
2
|
+
|
3
|
+
As a writer of documentation
|
4
|
+
I want outlines to have at least two example rows
|
5
|
+
So that I am not needlessly using an outline instead of a scenario
|
6
|
+
|
7
|
+
|
8
|
+
Scenario: Linting
|
9
|
+
Given a linter for outlines with only one example row
|
10
|
+
And the following feature:
|
11
|
+
"""
|
12
|
+
Feature:
|
13
|
+
|
14
|
+
Scenario Outline:
|
15
|
+
* a step
|
16
|
+
Examples:
|
17
|
+
| param |
|
18
|
+
| value |
|
19
|
+
"""
|
20
|
+
When it is linted
|
21
|
+
Then an error is reported
|
22
|
+
| linter | problem | location |
|
23
|
+
| OutlineWithSingleExampleRowLinter | Outline has only one example row | <path_to_file>:3 |
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Test with too many steps linter
|
2
|
+
|
3
|
+
As a reader of documentation
|
4
|
+
I want scenarios and outlines to not have an excessive number of steps
|
5
|
+
So that I can fit it all in my head at once
|
6
|
+
|
7
|
+
|
8
|
+
Scenario: Linting
|
9
|
+
|
10
|
+
Note: Also works on outlines
|
11
|
+
|
12
|
+
Given a linter for tests with too many steps
|
13
|
+
And the following feature:
|
14
|
+
"""
|
15
|
+
Feature:
|
16
|
+
|
17
|
+
Scenario:
|
18
|
+
* step 1
|
19
|
+
* step 2
|
20
|
+
* step 3
|
21
|
+
* step 4
|
22
|
+
* step 5
|
23
|
+
* step 6
|
24
|
+
* step 7
|
25
|
+
* step 8
|
26
|
+
* step 9
|
27
|
+
* step 10
|
28
|
+
* step one too many...
|
29
|
+
"""
|
30
|
+
When it is linted
|
31
|
+
Then an error is reported
|
32
|
+
| linter | problem | location |
|
33
|
+
| TestWithTooManyStepsLinter | Test has too many steps. 11 steps found (max 10) | <path_to_file>:3 |
|
@@ -9,5 +9,9 @@ When(/^it is formatted by the "([^"]*)" formatter$/) do |linter_name|
|
|
9
9
|
end
|
10
10
|
|
11
11
|
When(/^it is linted$/) do
|
12
|
-
|
12
|
+
options = { model_tree: @model,
|
13
|
+
linters: [@linter],
|
14
|
+
formatters: [[CukeLinter::FormatterFactory.generate_fake_formatter, "#{CukeLinter::FileHelper::create_directory}/junk_output_file.txt"]] }
|
15
|
+
|
16
|
+
@results = CukeLinter.lint(options)
|
13
17
|
end
|
@@ -28,3 +28,15 @@ end
|
|
28
28
|
Given(/^no other linters have been registered$/) do
|
29
29
|
# There is no way to 'reset' the linters, so just assume that no changes have been made
|
30
30
|
end
|
31
|
+
|
32
|
+
Given(/^a linter for examples without names$/) do
|
33
|
+
@linter = CukeLinter::ExampleWithoutNameLinter.new
|
34
|
+
end
|
35
|
+
|
36
|
+
Given(/^a linter for outlines with only one example row$/) do
|
37
|
+
@linter = CukeLinter::OutlineWithSingleExampleRowLinter.new
|
38
|
+
end
|
39
|
+
|
40
|
+
Given(/^a linter for tests with too many steps$/) do
|
41
|
+
@linter = CukeLinter::TestWithTooManyStepsLinter.new
|
42
|
+
end
|
@@ -8,11 +8,12 @@ end
|
|
8
8
|
|
9
9
|
Then(/^an error is reported$/) do |table|
|
10
10
|
table.hashes.each do |error_record|
|
11
|
-
expect(@results).to include({
|
11
|
+
expect(@results).to include({ linter: error_record['linter'],
|
12
|
+
problem: error_record['problem'],
|
12
13
|
location: error_record['location'].sub('<path_to_file>', @model.get_ancestor(:feature_file).path) })
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
Then(/^the following linters are registered by default$/) do |linter_names|
|
17
|
-
expect(CukeLinter.registered_linters.keys).to
|
18
|
+
expect(CukeLinter.registered_linters.keys).to match_array(linter_names.raw.flatten)
|
18
19
|
end
|
data/testing/model_factory.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module CukeLinter
|
2
|
+
|
3
|
+
# A simple model used for testing
|
4
|
+
|
2
5
|
class TestModel < CukeModeler::Model
|
3
6
|
|
4
7
|
include CukeModeler::Sourceable
|
@@ -8,6 +11,9 @@ end
|
|
8
11
|
|
9
12
|
|
10
13
|
module CukeLinter
|
14
|
+
|
15
|
+
# A helper module that generates various models for use in testing
|
16
|
+
|
11
17
|
module ModelFactory
|
12
18
|
|
13
19
|
def self.generate_feature_model(source_text: 'Feature:', parent_file_path: 'path_to_file')
|
@@ -20,6 +26,36 @@ module CukeLinter
|
|
20
26
|
model
|
21
27
|
end
|
22
28
|
|
29
|
+
def self.generate_example_model(source_text: 'Examples:', parent_file_path: 'path_to_file')
|
30
|
+
fake_file_model = CukeModeler::FeatureFile.new
|
31
|
+
fake_file_model.path = parent_file_path
|
32
|
+
|
33
|
+
model = CukeModeler::Example.new(source_text)
|
34
|
+
model.parent_model = fake_file_model
|
35
|
+
|
36
|
+
model
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.generate_outline_model(source_text: "Scenario Outline:\n*a step\nExamples:\n|param|", parent_file_path: 'path_to_file')
|
40
|
+
fake_file_model = CukeModeler::FeatureFile.new
|
41
|
+
fake_file_model.path = parent_file_path
|
42
|
+
|
43
|
+
model = CukeModeler::Outline.new(source_text)
|
44
|
+
model.parent_model = fake_file_model
|
45
|
+
|
46
|
+
model
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.generate_scenario_model(source_text: 'Scenario:', parent_file_path: 'path_to_file')
|
50
|
+
fake_file_model = CukeModeler::FeatureFile.new
|
51
|
+
fake_file_model.path = parent_file_path
|
52
|
+
|
53
|
+
model = CukeModeler::Scenario.new(source_text)
|
54
|
+
model.parent_model = fake_file_model
|
55
|
+
|
56
|
+
model
|
57
|
+
end
|
58
|
+
|
23
59
|
def self.generate_lintable_model(parent_file_path: 'path_to_file', source_line: '1', children: [])
|
24
60
|
fake_file_model = CukeModeler::FeatureFile.new
|
25
61
|
fake_file_model.path = parent_file_path
|
@@ -107,6 +107,12 @@ RSpec.describe CukeLinter do
|
|
107
107
|
it 'has a default set of registered linters' do
|
108
108
|
expect(subject.registered_linters.keys).to include('FeatureWithoutScenariosLinter')
|
109
109
|
expect(subject.registered_linters['FeatureWithoutScenariosLinter']).to be_a(CukeLinter::FeatureWithoutScenariosLinter)
|
110
|
+
expect(subject.registered_linters.keys).to include('ExampleWithoutNameLinter')
|
111
|
+
expect(subject.registered_linters['ExampleWithoutNameLinter']).to be_a(CukeLinter::ExampleWithoutNameLinter)
|
112
|
+
expect(subject.registered_linters.keys).to include('OutlineWithSingleExampleRowLinter')
|
113
|
+
expect(subject.registered_linters['OutlineWithSingleExampleRowLinter']).to be_a(CukeLinter::OutlineWithSingleExampleRowLinter)
|
114
|
+
expect(subject.registered_linters.keys).to include('TestWithTooManyStepsLinter')
|
115
|
+
expect(subject.registered_linters['TestWithTooManyStepsLinter']).to be_a(CukeLinter::TestWithTooManyStepsLinter)
|
110
116
|
end
|
111
117
|
|
112
118
|
end
|
@@ -57,6 +57,9 @@ RSpec.describe CukeLinter::PrettyFormatter do
|
|
57
57
|
{ linter: 'SomeLinter',
|
58
58
|
problem: 'Some problem',
|
59
59
|
location: 'path/to/the_file:3' },
|
60
|
+
{ linter: 'SomeLinter',
|
61
|
+
problem: 'Some problem',
|
62
|
+
location: 'path/to/the_file:3' },
|
60
63
|
{ linter: 'SomeLinter',
|
61
64
|
problem: 'Some problem',
|
62
65
|
location: 'path/to/the_file:1' }]
|
@@ -68,8 +71,9 @@ RSpec.describe CukeLinter::PrettyFormatter do
|
|
68
71
|
' path/to/the_file:1',
|
69
72
|
' path/to/the_file:2',
|
70
73
|
' path/to/the_file:3',
|
74
|
+
' path/to/the_file:3',
|
71
75
|
'',
|
72
|
-
'
|
76
|
+
'4 issues found'].join("\n"))
|
73
77
|
end
|
74
78
|
|
75
79
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative '../../../../../environments/rspec_env'
|
2
|
+
|
3
|
+
|
4
|
+
RSpec.describe CukeLinter::ExampleWithoutNameLinter do
|
5
|
+
|
6
|
+
let(:good_data) do
|
7
|
+
model = CukeLinter::ModelFactory.generate_example_model
|
8
|
+
model.name = 'foo'
|
9
|
+
|
10
|
+
model
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:bad_data) do
|
14
|
+
model = CukeLinter::ModelFactory.generate_example_model
|
15
|
+
model.name = ''
|
16
|
+
|
17
|
+
model
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
it_should_behave_like 'a linter at the unit level'
|
22
|
+
|
23
|
+
|
24
|
+
it 'has a name' do
|
25
|
+
expect(subject.name).to eq('ExampleWithoutNameLinter')
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'linting' do
|
29
|
+
|
30
|
+
context 'an example with no name' do
|
31
|
+
|
32
|
+
let(:test_model_with_nil_name) do
|
33
|
+
model = CukeLinter::ModelFactory.generate_example_model(parent_file_path: 'path_to_file')
|
34
|
+
model.name = nil
|
35
|
+
|
36
|
+
model
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:test_model_with_blank_name) do
|
40
|
+
model = CukeLinter::ModelFactory.generate_example_model(parent_file_path: 'path_to_file')
|
41
|
+
model.name = ''
|
42
|
+
|
43
|
+
model
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'records a problem' do
|
47
|
+
results = subject.lint(test_model_with_nil_name)
|
48
|
+
|
49
|
+
expect(results.first[:problem]).to eq('Example has no name')
|
50
|
+
|
51
|
+
results = subject.lint(test_model_with_blank_name)
|
52
|
+
|
53
|
+
expect(results.first[:problem]).to eq('Example has no name')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'records the location of the problem' do
|
57
|
+
model_1 = CukeLinter::ModelFactory.generate_example_model(parent_file_path: 'path_to_file')
|
58
|
+
model_1.name = nil
|
59
|
+
model_1.source_line = 1
|
60
|
+
model_2 = CukeLinter::ModelFactory.generate_example_model(parent_file_path: 'path_to_file')
|
61
|
+
model_2.name = nil
|
62
|
+
model_2.source_line = 3
|
63
|
+
|
64
|
+
results = subject.lint(model_1)
|
65
|
+
expect(results.first[:location]).to eq('path_to_file:1')
|
66
|
+
|
67
|
+
results = subject.lint(model_2)
|
68
|
+
expect(results.first[:location]).to eq('path_to_file:3')
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'a non-example model' do
|
74
|
+
|
75
|
+
it 'returns an empty set of results' do
|
76
|
+
results = subject.lint(CukeModeler::Model.new)
|
77
|
+
|
78
|
+
expect(results).to eq([])
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -29,15 +29,26 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
|
|
29
29
|
|
30
30
|
context 'a feature with no scenarios' do
|
31
31
|
|
32
|
-
let(:
|
32
|
+
let(:test_model_with_empty_scenarios) do
|
33
33
|
model = CukeLinter::ModelFactory.generate_feature_model(parent_file_path: 'path_to_file')
|
34
34
|
model.tests = []
|
35
35
|
|
36
36
|
model
|
37
37
|
end
|
38
38
|
|
39
|
+
let(:test_model_with_nil_scenarios) do
|
40
|
+
model = CukeLinter::ModelFactory.generate_feature_model(parent_file_path: 'path_to_file')
|
41
|
+
model.tests = nil
|
42
|
+
|
43
|
+
model
|
44
|
+
end
|
45
|
+
|
39
46
|
it 'records a problem' do
|
40
|
-
results = subject.lint(
|
47
|
+
results = subject.lint(test_model_with_empty_scenarios)
|
48
|
+
|
49
|
+
expect(results.first[:problem]).to eq('Feature has no scenarios')
|
50
|
+
|
51
|
+
results = subject.lint(test_model_with_nil_scenarios)
|
41
52
|
|
42
53
|
expect(results.first[:problem]).to eq('Feature has no scenarios')
|
43
54
|
end
|
@@ -59,6 +70,45 @@ RSpec.describe CukeLinter::FeatureWithoutScenariosLinter do
|
|
59
70
|
|
60
71
|
end
|
61
72
|
|
73
|
+
context 'a feature with scenarios' do
|
74
|
+
|
75
|
+
context 'with a scenario' do
|
76
|
+
|
77
|
+
let(:test_model) do
|
78
|
+
gherkin = 'Feature:
|
79
|
+
|
80
|
+
Scenario:'
|
81
|
+
|
82
|
+
CukeLinter::ModelFactory.generate_feature_model(source_text: gherkin)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'does not record a problem' do
|
86
|
+
expect(subject.lint(test_model)).to eq([])
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with an outline' do
|
92
|
+
|
93
|
+
let(:test_model) do
|
94
|
+
gherkin = 'Feature:
|
95
|
+
|
96
|
+
Scenario Outline:
|
97
|
+
* a step
|
98
|
+
Examples:
|
99
|
+
| param |'
|
100
|
+
|
101
|
+
CukeLinter::ModelFactory.generate_feature_model(source_text: gherkin)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'does not record a problem' do
|
105
|
+
expect(subject.lint(test_model)).to eq([])
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
62
112
|
context 'a non-feature model' do
|
63
113
|
|
64
114
|
it 'returns an empty set of results' do
|