cuke_linter 0.5.0 → 0.6.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.
@@ -38,9 +38,3 @@ Feature: Using a configuration
38
38
  CukeLinter.load_configuration
39
39
  """
40
40
  Then the linter "SomeLinter" is no longer registered
41
-
42
- @wip
43
- Scenario: Configuring from the command line
44
-
45
- @wip
46
- Scenario: Using the default configuration file from the command line
@@ -1,5 +1,7 @@
1
1
  Feature: Pretty formatter
2
2
 
3
+ The 'pretty' formatter is a basic text formatter that organizes problems into something simple and human readable.
4
+
3
5
  Scenario: Formatting linter data
4
6
  Given the following linter data:
5
7
  | linter name | problem | location |
@@ -1,7 +1,8 @@
1
1
  When(/^the following command is executed:$/) do |command|
2
- command = "bundle exec ruby #{@executable_directory}/#{command}"
2
+ command = "bundle exec ruby #{@executable_directory || "#{PROJECT_ROOT}/exe"}/#{command}"
3
+ command.gsub!('<path_to>', @root_test_directory)
3
4
 
4
- @output = `#{command}`
5
+ @results = @output = `#{command}`
5
6
  end
6
7
 
7
8
  When(/^it is formatted by the "([^"]*)" formatter$/) do |linter_name|
@@ -9,8 +10,8 @@ When(/^it is formatted by the "([^"]*)" formatter$/) do |linter_name|
9
10
  end
10
11
 
11
12
  When(/^(?:the feature|the model|it) is linted$/) do
12
- options = { model_tree: @model,
13
- formatters: [[CukeLinter::FormatterFactory.generate_fake_formatter, "#{CukeLinter::FileHelper::create_directory}/junk_output_file.txt"]] }
13
+ options = { model_trees: [@model],
14
+ formatters: [[CukeLinter::FormatterFactory.generate_fake_formatter, "#{CukeLinter::FileHelper::create_directory}/junk_output_file.txt"]] }
14
15
  options[:linters] = [@linter] if @linter
15
16
 
16
17
  @results = CukeLinter.lint(options)
@@ -117,3 +117,10 @@ end
117
117
  Given(/^the following custom linter class:$/) do |code|
118
118
  eval(code)
119
119
  end
120
+
121
+ Given(/^the following(?: feature)? file "([^"]*)":$/) do |file_path, text|
122
+ path, extension = file_path.split('.')
123
+
124
+ @created_files ||= []
125
+ @created_files << CukeLinter::FileHelper.create_file(directory: @root_test_directory, name: path, extension: ".#{extension}", text: text)
126
+ end
@@ -3,7 +3,15 @@ Then(/^a linting report will be made for all features$/) do
3
3
  end
4
4
 
5
5
  Then(/^the resulting output is the following:$/) do |text|
6
- expect(@results).to eq(text)
6
+ text.gsub!('<path_to>', @root_test_directory)
7
+
8
+ expect(@results.strip).to eq(text)
9
+ end
10
+
11
+ Then(/^the resulting output will include the following:$/) do |text|
12
+ text.gsub!('<path_to>', @root_test_directory)
13
+
14
+ expect(@results.chomp).to include(text)
7
15
  end
8
16
 
9
17
  Then(/^an error is reported$/) do |table|
@@ -25,3 +33,28 @@ end
25
33
  Then(/^the linter "([^"]*)" is no longer registered$/) do |linter_name|
26
34
  expect(CukeLinter.registered_linters).to_not have_key(linter_name)
27
35
  end
36
+
37
+ Then(/^the following help is displayed:$/) do |text|
38
+ expect(@output.chomp).to eq(text)
39
+ end
40
+
41
+ Then(/^the version of the tool is displayed:$/) do |text|
42
+ major_number, minor_number, patch_number = CukeLinter::VERSION.split('.')
43
+ text.sub!('<major>', major_number)
44
+ text.sub!('<minor>', minor_number)
45
+ text.sub!('<patch>', patch_number)
46
+
47
+ expect(@output.chomp).to eq(text)
48
+ end
49
+
50
+ Then(/^the linting report will be output to "([^"]*)"$/) do |file_path|
51
+ file_path.gsub!('<path_to>', @root_test_directory)
52
+
53
+ expect(File.read(file_path)).to match(/\d+ issues found/)
54
+ end
55
+
56
+ And(/^the file "([^"]*)" contains:$/) do |file_path, text|
57
+ file_path.gsub!('<path_to>', @root_test_directory)
58
+
59
+ expect(File.read(file_path)).to eq(text)
60
+ end
@@ -29,6 +29,7 @@ module CukeLinter
29
29
  options[:directory] ||= create_directory
30
30
 
31
31
  file_path = "#{options[:directory]}/#{options[:name]}#{options[:extension]}"
32
+ FileUtils.mkdir_p(File.dirname(file_path)) # Ensuring that the target directory already exists
32
33
  File.write(file_path, options[:text])
33
34
 
34
35
  file_path
@@ -0,0 +1,501 @@
1
+ require_relative '../../../../environments/rspec_env'
2
+ require 'open3'
3
+
4
+
5
+ RSpec.describe 'the Command Line Interface' do
6
+
7
+ # A minimal fake test suite that should be available for every spec
8
+ let!(:test_directory) { CukeLinter::FileHelper.create_directory }
9
+ let!(:linted_file) { CukeLinter::FileHelper.create_file(directory: test_directory,
10
+ name: 'lacking_a_description',
11
+ extension: '.feature',
12
+ text: 'Feature:
13
+ Scenario: A scenario
14
+ * a step') }
15
+
16
+ # Stuff that is not always needed and so can be lazy instantiated
17
+ let(:executable_directory) { "#{PROJECT_ROOT}/exe" }
18
+ let(:executable_name) { 'cuke_linter' }
19
+ let(:executable_path) { "#{executable_directory}/#{executable_name}" }
20
+ let(:results) { std_out, std_err, status = [nil, nil, nil]
21
+
22
+ Dir.chdir(test_directory) do
23
+ std_out, std_err, status = Open3.capture3(command)
24
+ end
25
+
26
+ { std_out: std_out, std_err: std_err, status: status } }
27
+ let(:expected_help_text) { ['Usage: cuke_linter [options]',
28
+ ' -p, --path PATH The file path that should be linted. Can be a file or directory.',
29
+ ' This option can be specified multiple times in order to lint',
30
+ ' multiple, unconnected locations.',
31
+ ' -f, --formatter FORMATTER The formatter used for generating linting output. This option',
32
+ ' can be specified multiple times in order to use more than one',
33
+ ' formatter. Formatters must be specified using their fully',
34
+ ' qualified class name (e.g CukeLinter::PrettyFormatter). Uses',
35
+ ' the default formatter if none are specified.',
36
+ ' -o, --out OUT The file path to which linting results are output. Can be specified',
37
+ ' multiple times. Specified files are matched to formatters in the',
38
+ ' same order that the formatters are specified. Any formatter without',
39
+ ' a corresponding file path will output to STDOUT instead.',
40
+ ' -r, --require FILEPATH A file that will be required before further processing. Likely',
41
+ ' needed when using custom linters or formatters in order to ensure',
42
+ ' that the specified classes have been read into memory. This option',
43
+ ' can be specified multiple times in order to load more than one file.',
44
+ ' -c, --config FILEPATH The configuration file that will be used. Will use the default',
45
+ ' configuration file (if present) if this option is not specified.',
46
+ ' -h, --help Display the help that you are reading now.',
47
+ ' -v, --version Display the version of the gem being used.',
48
+ ''].join("\n") }
49
+
50
+ context 'with no additional arguments' do
51
+
52
+ let(:command) { "bundle exec ruby #{executable_path}" }
53
+
54
+ it 'can run cleanly by default' do
55
+ expect(results[:status].exitstatus).to eq(0)
56
+ end
57
+
58
+ context 'when the default configuration file exists' do
59
+
60
+ before(:each) do
61
+ CukeLinter::FileHelper.create_file(directory: test_directory,
62
+ name: '.cuke_linter',
63
+ extension: '',
64
+ text: 'FeatureWithoutDescriptionLinter:
65
+ Enabled: false')
66
+ end
67
+
68
+ it 'uses the default configuration file by default' do
69
+ expect(results[:std_out]).to include("0 issues found\n")
70
+ end
71
+
72
+ end
73
+
74
+ context 'when the default configuration file does not exist' do
75
+
76
+ before(:each) do
77
+ Dir.chdir(test_directory) do
78
+ FileUtils.rm('./.cuke_linter') if File.exist?('./.cuke_linter')
79
+ end
80
+ end
81
+
82
+ it 'can still run cleanly' do
83
+ expect(results[:status].exitstatus).to eq(0)
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ describe 'option flags' do
91
+
92
+ context 'with a path flag' do
93
+ ['-p', '--path'].each do |path_flag|
94
+
95
+ context "using the '#{path_flag}' form" do
96
+
97
+ let(:flag) { path_flag }
98
+
99
+ context 'with path arguments' do
100
+ let(:file_1) { CukeLinter::FileHelper.create_file(directory: test_directory,
101
+ name: 'some',
102
+ extension: '.feature',
103
+ text: 'Feature:
104
+ Scenario: A scenario
105
+ * a step') }
106
+ let(:file_2) { CukeLinter::FileHelper.create_file(directory: test_directory,
107
+ name: 'a_directory/with_a',
108
+ extension: '.feature',
109
+ text: 'Feature:
110
+ Scenario: A scenario
111
+ * a step') }
112
+ let(:file_1_path) { file_1 }
113
+ let(:file_2_directory) { File.dirname(file_2) }
114
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{file_1_path} #{flag} #{file_2_directory}" }
115
+
116
+
117
+ it "lints that locations specified by '#{path_flag}'" do
118
+ expect(results[:std_out]).to eq(['FeatureWithoutDescriptionLinter',
119
+ ' Feature has no description',
120
+ ' <path_to>/a_directory/with_a.feature:1',
121
+ ' <path_to>/some.feature:1',
122
+ '',
123
+ '2 issues found',
124
+ ''].join("\n").gsub('<path_to>', test_directory))
125
+ end
126
+
127
+ end
128
+
129
+ context 'without path arguments' do
130
+
131
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
132
+
133
+
134
+ it 'complains about the missing argument' do
135
+ expect(results[:std_out]).to include("missing argument: #{flag}")
136
+ end
137
+
138
+ it 'displays the help text' do
139
+ expect(results[:std_out]).to include(expected_help_text)
140
+ end
141
+
142
+ it 'exits with an error' do
143
+ expect(results[:status].exitstatus).to eq(1)
144
+ end
145
+
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+
152
+ context 'with a formatter flag' do
153
+ ['-f', '--formatter'].each do |formatter_flag|
154
+
155
+ context "using the '#{formatter_flag}' form" do
156
+
157
+ let(:flag) { formatter_flag }
158
+
159
+ context 'with formatter arguments' do
160
+ let(:linted_file) { CukeLinter::FileHelper.create_file(name: 'some',
161
+ extension: '.feature',
162
+ text: 'Feature:
163
+ Scenario: A scenario
164
+ * a step') }
165
+ let(:formatter_class) { 'AFakeFormatter' }
166
+ let(:formatter_class_in_module) { 'CukeLinter::AnotherFakeFormatter' }
167
+ let(:formatter_class_file) { CukeLinter::FileHelper.create_file(extension: '.rb',
168
+ text: 'class AFakeFormatter
169
+ def format(data)
170
+ data.reduce("#{self.class}: ") { |final, lint_error| final << "#{lint_error[:problem]}: #{lint_error[:location]}\n" }
171
+ end
172
+ end') }
173
+ let(:formatter_class_in_module_file) { CukeLinter::FileHelper.create_file(extension: '.rb',
174
+ text: 'module CukeLinter
175
+ class AnotherFakeFormatter
176
+ def format(data)
177
+ data.reduce("#{self.class}: ") { |final, lint_error| final << "#{lint_error[:problem]}: #{lint_error[:location]}\n" }
178
+ end
179
+ end
180
+ end') }
181
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{formatter_class} #{flag} #{formatter_class_in_module} -p #{linted_file} -r #{formatter_class_file} -r #{formatter_class_in_module_file}" }
182
+
183
+
184
+ it "uses the formatters specified by '#{formatter_flag}'" do
185
+ expect(results[:std_out]).to eq(['AFakeFormatter: Feature has no description: <path_to_file>:1',
186
+ 'CukeLinter::AnotherFakeFormatter: Feature has no description: <path_to_file>:1',
187
+ ''].join("\n").gsub('<path_to_file>', linted_file))
188
+ end
189
+
190
+ end
191
+
192
+ context 'without formatter arguments' do
193
+
194
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
195
+
196
+
197
+ it 'complains about the missing argument' do
198
+ expect(results[:std_out]).to include("missing argument: #{flag}")
199
+ end
200
+
201
+ it 'displays the help text' do
202
+ expect(results[:std_out]).to include(expected_help_text)
203
+ end
204
+
205
+ it 'exits with an error' do
206
+ expect(results[:status].exitstatus).to eq(1)
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+ end
213
+ end
214
+
215
+ context 'with an output flag' do
216
+ ['-o', '--out'].each do |output_flag|
217
+
218
+ context "using the '#{output_flag}' form" do
219
+
220
+ let(:flag) { output_flag }
221
+
222
+ context 'with output arguments' do
223
+ let(:output_location) { "#{CukeLinter::FileHelper.create_directory}/output.txt" }
224
+ let(:other_output_location) { "#{CukeLinter::FileHelper.create_directory}/other_output.txt" }
225
+ let(:linted_file) { CukeLinter::FileHelper.create_file(name: 'some',
226
+ extension: '.feature',
227
+ text: 'Feature:
228
+ Scenario: A scenario
229
+ * a step') }
230
+ let(:formatter_class_1) { 'AFakeFormatter' }
231
+ let(:formatter_class_2) { 'AnotherFakeFormatter' }
232
+ let(:formatter_class_file) { CukeLinter::FileHelper.create_file(extension: '.rb',
233
+ text: 'class AFakeFormatter
234
+ def format(data)
235
+ "Formatting done by #{self.class}"
236
+ end
237
+ end
238
+
239
+ class AnotherFakeFormatter
240
+ def format(data)
241
+ "Formatting done by #{self.class}"
242
+ end
243
+ end') }
244
+ let(:command) { "bundle exec ruby #{executable_path} -f #{formatter_class_1} -f #{formatter_class_2} #{flag} #{output_location} #{flag} #{other_output_location} -p #{linted_file} -r #{formatter_class_file}" }
245
+
246
+
247
+ it 'matches output locations to formatters in the same order that they are specified' do
248
+ # Have to trigger the command
249
+ results
250
+
251
+ expect(File.read(output_location)).to eq('Formatting done by AFakeFormatter')
252
+ expect(File.read(other_output_location)).to eq('Formatting done by AnotherFakeFormatter')
253
+ end
254
+
255
+
256
+ context 'with unmatched output arguments' do
257
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{output_location} -p #{linted_file}" }
258
+
259
+
260
+ it "outputs to the location specified by '#{output_flag}'" do
261
+ # Have to trigger the command
262
+ results
263
+
264
+ expect(File.read(output_location)).to eq(['FeatureWithoutDescriptionLinter',
265
+ ' Feature has no description',
266
+ ' <path_to_file>:1',
267
+ '',
268
+ '1 issues found'].join("\n").gsub('<path_to_file>', linted_file))
269
+ end
270
+
271
+ it 'does not output to STDOUT' do
272
+ expect(results[:std_out]).to eq('')
273
+ end
274
+
275
+ it 'uses the default formatter' do
276
+ # Have to trigger the command
277
+ results
278
+
279
+ expect(File.read(output_location)).to eq(['FeatureWithoutDescriptionLinter',
280
+ ' Feature has no description',
281
+ ' <path_to_file>:1',
282
+ '',
283
+ '1 issues found'].join("\n").gsub('<path_to_file>', linted_file))
284
+ end
285
+
286
+ end
287
+
288
+ context 'with unmatched formatter arguments' do
289
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{output_location} -f #{formatter_class_1} -f #{formatter_class_2} -p #{linted_file} -r #{formatter_class_file}" }
290
+
291
+
292
+ it "outputs to the location specified by '#{output_flag}' for the matched formatters" do
293
+ # Have to trigger the command
294
+ results
295
+
296
+ expect(File.read(output_location)).to eq("Formatting done by #{formatter_class_1}")
297
+ end
298
+
299
+ it 'outputs to STDOUT for the unmatched formatters' do
300
+ expect(results[:std_out]).to eq("Formatting done by #{formatter_class_2}\n")
301
+ end
302
+
303
+ end
304
+
305
+ end
306
+
307
+
308
+ context 'without output arguments' do
309
+
310
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
311
+
312
+
313
+ it 'complains about the missing argument' do
314
+ expect(results[:std_out]).to include("missing argument: #{flag}")
315
+ end
316
+
317
+ it 'displays the help text' do
318
+ expect(results[:std_out]).to include(expected_help_text)
319
+ end
320
+
321
+ it 'exits with an error' do
322
+ expect(results[:status].exitstatus).to eq(1)
323
+ end
324
+
325
+ end
326
+
327
+ end
328
+ end
329
+ end
330
+
331
+ context 'with a require flag' do
332
+ ['-r', '--require'].each do |require_flag|
333
+
334
+ context "using the '#{require_flag}' form" do
335
+
336
+ let(:flag) { require_flag }
337
+
338
+ context 'with require arguments' do
339
+ let(:file_1) { CukeLinter::FileHelper.create_file(extension: '.rb',
340
+ text: "puts 'This file was loaded'") }
341
+ let(:file_1_path) { file_1 }
342
+ let(:file_2) { CukeLinter::FileHelper.create_file(extension: '.rb',
343
+ text: "puts 'This file was also loaded'") }
344
+ let(:file_2_path) { file_2 }
345
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{file_1_path} #{flag} #{file_2_path}" }
346
+
347
+
348
+ it "require the files specified by '#{require_flag}' before linting" do
349
+ expect(results[:std_out]).to include('This file was loaded')
350
+ expect(results[:std_out]).to include('This file was also loaded')
351
+ end
352
+
353
+ end
354
+
355
+ context 'without require arguments' do
356
+
357
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
358
+
359
+
360
+ it 'complains about the missing argument' do
361
+ expect(results[:std_out]).to include("missing argument: #{flag}")
362
+ end
363
+
364
+ it 'displays the help text' do
365
+ expect(results[:std_out]).to include(expected_help_text)
366
+ end
367
+
368
+ it 'exits with an error' do
369
+ expect(results[:status].exitstatus).to eq(1)
370
+ end
371
+
372
+ end
373
+
374
+ end
375
+ end
376
+ end
377
+
378
+ context 'with a config flag' do
379
+ ['-c', '--config'].each do |config_flag|
380
+
381
+ context "using the '#{config_flag}' form" do
382
+
383
+ let(:flag) { config_flag }
384
+
385
+ context 'with a single config argument' do
386
+
387
+ let(:config_file) { CukeLinter::FileHelper.create_file(name: 'my_config_file',
388
+ extension: '.yml',
389
+ text: 'FeatureWithoutDescriptionLinter:
390
+ Enabled: false') }
391
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} #{config_file}" }
392
+
393
+ it "uses the configuration file specified by '#{config_flag}'" do
394
+ expect(results[:std_out]).to include("0 issues found\n")
395
+ end
396
+
397
+ end
398
+
399
+ context 'with multiple config arguments' do
400
+
401
+ let(:command) { "bundle exec ruby #{executable_path} #{flag} foo #{flag} bar" }
402
+
403
+ it 'complains that more than one configuration file is specified' do
404
+ expect(results[:std_out]).to eq("Cannot specify more than one configuration file!\n")
405
+ end
406
+
407
+ it 'exits with an error' do
408
+ expect(results[:status].exitstatus).to eq(1)
409
+ end
410
+
411
+ end
412
+
413
+ context 'without config arguments' do
414
+
415
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
416
+
417
+
418
+ it 'complains about the missing argument' do
419
+ expect(results[:std_out]).to include("missing argument: #{flag}")
420
+ end
421
+
422
+ it 'displays the help text' do
423
+ expect(results[:std_out]).to include(expected_help_text)
424
+ end
425
+
426
+ it 'exits with an error' do
427
+ expect(results[:status].exitstatus).to eq(1)
428
+ end
429
+
430
+ end
431
+
432
+ end
433
+ end
434
+ end
435
+
436
+ context 'with a help flag' do
437
+
438
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
439
+
440
+ ['-h', '--help'].each do |help_flag|
441
+
442
+ context "using the '#{help_flag}' form" do
443
+
444
+ let(:flag) { help_flag }
445
+
446
+ it "'#{help_flag}' displays the help text" do
447
+ expect(results[:std_out]).to eq(expected_help_text)
448
+ end
449
+
450
+ it 'exits cleanly' do
451
+ expect(results[:status].exitstatus).to eq(0)
452
+ end
453
+
454
+ end
455
+ end
456
+ end
457
+
458
+ context 'with a version flag' do
459
+
460
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
461
+
462
+ ['-v', '--version'].each do |version_flag|
463
+
464
+ context "using the '#{version_flag}' form" do
465
+
466
+ let(:flag) { version_flag }
467
+
468
+ it "'#{version_flag}' displays the version being used" do
469
+ expect(results[:std_out]).to eq("#{CukeLinter::VERSION}\n")
470
+ end
471
+
472
+ it 'exits cleanly' do
473
+ expect(results[:status].exitstatus).to eq(0)
474
+ end
475
+
476
+ end
477
+ end
478
+ end
479
+
480
+ context 'with an invalid flag' do
481
+
482
+ let(:command) { "bundle exec ruby #{executable_path} #{flag}" }
483
+ let(:flag) { '--not_a_real_flag' }
484
+
485
+ it 'complains about the invalid flag' do
486
+ expect(results[:std_out]).to include("invalid option: #{flag}")
487
+ end
488
+
489
+ it 'displays the help text' do
490
+ expect(results[:std_out]).to include(expected_help_text)
491
+ end
492
+
493
+ it 'exits with an error' do
494
+ expect(results[:status].exitstatus).to eq(1)
495
+ end
496
+
497
+ end
498
+
499
+ end
500
+
501
+ end