cuke_linter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +8 -0
  3. data/.travis.yml +16 -14
  4. data/CHANGELOG.md +11 -2
  5. data/Gemfile +6 -6
  6. data/README.md +160 -148
  7. data/Rakefile +48 -23
  8. data/cuke_linter.gemspec +3 -0
  9. data/environments/cucumber_env.rb +10 -0
  10. data/environments/rspec_env.rb +3 -0
  11. data/lib/cuke_linter/formatters/pretty_formatter.rb +4 -0
  12. data/lib/cuke_linter/linters/example_without_name_linter.rb +24 -0
  13. data/lib/cuke_linter/linters/feature_without_scenarios_linter.rb +6 -4
  14. data/lib/cuke_linter/linters/outline_with_single_example_row_linter.rb +27 -0
  15. data/lib/cuke_linter/linters/test_with_too_many_steps_linter.rb +26 -0
  16. data/lib/cuke_linter/version.rb +4 -3
  17. data/lib/cuke_linter.rb +14 -1
  18. data/testing/cucumber/features/linters/default_linters.feature +8 -1
  19. data/testing/cucumber/features/linters/example_without_name.feature +27 -0
  20. data/testing/cucumber/features/linters/feature_without_scenarios.feature +7 -2
  21. data/testing/cucumber/features/linters/outline_with_single_example_row.feature +23 -0
  22. data/testing/cucumber/features/linters/test_with_too_many_steps.feature +33 -0
  23. data/testing/cucumber/step_definitions/action_steps.rb +5 -1
  24. data/testing/cucumber/step_definitions/setup_steps.rb +12 -0
  25. data/testing/cucumber/step_definitions/verification_steps.rb +3 -2
  26. data/testing/model_factory.rb +36 -0
  27. data/testing/rspec/spec/integration/cuke_linter_integration_spec.rb +6 -0
  28. data/testing/rspec/spec/integration/linters/example_without_name_linter_integration_spec.rb +8 -0
  29. data/testing/rspec/spec/integration/linters/outline_with_single_example_row_linter_integration_spec.rb +8 -0
  30. data/testing/rspec/spec/integration/linters/test_with_too_many_steps_linter_integration_spec.rb +8 -0
  31. data/testing/rspec/spec/unit/formatters/pretty_formatter_unit_spec.rb +5 -1
  32. data/testing/rspec/spec/unit/linters/example_without_name_linter_unit_spec.rb +83 -0
  33. data/testing/rspec/spec/unit/linters/feature_without_scenarios_linter_unit_spec.rb +52 -2
  34. data/testing/rspec/spec/unit/linters/outline_with_single_example_row_linter_unit_spec.rb +231 -0
  35. data/testing/rspec/spec/unit/linters/test_with_too_many_steps_linter_unit_spec.rb +156 -0
  36. 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' => FeatureWithoutScenariosLinter.new }
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
- | FeatureWithoutScenariosLinter |
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
- @results = @linter.lint(@model)
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({ problem: error_record['problem'],
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 eq(linter_names.raw.flatten)
18
+ expect(CukeLinter.registered_linters.keys).to match_array(linter_names.raw.flatten)
18
19
  end
@@ -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
@@ -0,0 +1,8 @@
1
+ require_relative '../../../../../environments/rspec_env'
2
+
3
+
4
+ RSpec.describe CukeLinter::ExampleWithoutNameLinter do
5
+
6
+ it_should_behave_like 'a linter at the integration level'
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../../../../../environments/rspec_env'
2
+
3
+
4
+ RSpec.describe CukeLinter::OutlineWithSingleExampleRowLinter do
5
+
6
+ it_should_behave_like 'a linter at the integration level'
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../../../../../environments/rspec_env'
2
+
3
+
4
+ RSpec.describe CukeLinter::TestWithTooManyStepsLinter do
5
+
6
+ it_should_behave_like 'a linter at the integration level'
7
+
8
+ 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
- '3 issues found'].join("\n"))
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(:test_model) do
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(test_model)
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