cuke_linter 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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