pdk 0.2.0 → 0.3.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/CHANGELOG.md +19 -0
- data/README.md +49 -3
- data/lib/pdk/cli.rb +1 -0
- data/lib/pdk/cli/exec.rb +46 -9
- data/lib/pdk/cli/new/class.rb +2 -1
- data/lib/pdk/cli/test/unit.rb +35 -16
- data/lib/pdk/cli/util.rb +15 -0
- data/lib/pdk/cli/util/interview.rb +52 -0
- data/lib/pdk/cli/util/option_normalizer.rb +1 -1
- data/lib/pdk/cli/validate.rb +12 -2
- data/lib/pdk/generators/module.rb +109 -61
- data/lib/pdk/report.rb +20 -3
- data/lib/pdk/report/event.rb +29 -0
- data/lib/pdk/tests/unit.rb +114 -9
- data/lib/pdk/util/bundler.rb +24 -46
- data/lib/pdk/validators/base_validator.rb +7 -14
- data/lib/pdk/validators/metadata.rb +36 -14
- data/lib/pdk/validators/puppet/puppet_lint.rb +23 -3
- data/lib/pdk/validators/puppet/puppet_syntax.rb +82 -0
- data/lib/pdk/validators/puppet_validator.rb +2 -2
- data/lib/pdk/validators/ruby/rubocop.rb +13 -3
- data/lib/pdk/version.rb +1 -1
- data/locales/de/pdk.po +235 -66
- data/locales/pdk.pot +202 -54
- metadata +22 -7
- data/lib/pdk/cli/input.rb +0 -28
- data/lib/pdk/validators/puppet/puppet_parser.rb +0 -34
data/lib/pdk/report.rb
CHANGED
@@ -12,7 +12,7 @@ module PDK
|
|
12
12
|
|
13
13
|
# @return [Symbol] the method name of the default report format.
|
14
14
|
def self.default_format
|
15
|
-
:
|
15
|
+
:write_text
|
16
16
|
end
|
17
17
|
|
18
18
|
# @return [#write] the default target to write the report to.
|
@@ -47,7 +47,13 @@ module PDK
|
|
47
47
|
#
|
48
48
|
# @param target [#write] an IO object that the report will be written to.
|
49
49
|
# Defaults to PDK::Report.default_target.
|
50
|
-
def
|
50
|
+
def write_junit(target = self.class.default_target)
|
51
|
+
# Extra defaulting here, b/c the Class.send method will pass in nil
|
52
|
+
target ||= self.class.default_target
|
53
|
+
|
54
|
+
# Open a File Object for IO if target is a string containing a filename or path
|
55
|
+
target = File.open(target, 'w') if target.is_a? String
|
56
|
+
|
51
57
|
document = REXML::Document.new
|
52
58
|
document << REXML::XMLDecl.new
|
53
59
|
testsuites = REXML::Element.new('testsuites')
|
@@ -59,6 +65,7 @@ module PDK
|
|
59
65
|
testsuite.attributes['tests'] = testcases.length
|
60
66
|
testsuite.attributes['errors'] = testcases.select(&:error?).length
|
61
67
|
testsuite.attributes['failures'] = testcases.select(&:failure?).length
|
68
|
+
testsuite.attributes['skipped'] = testcases.select(&:skipped?).length
|
62
69
|
testsuite.attributes['time'] = 0
|
63
70
|
testsuite.attributes['timestamp'] = Time.now.strftime('%Y-%m-%dT%H:%M:%S')
|
64
71
|
testsuite.attributes['hostname'] = Socket.gethostname
|
@@ -75,6 +82,8 @@ module PDK
|
|
75
82
|
|
76
83
|
document.elements << testsuites
|
77
84
|
document.write(target, 2)
|
85
|
+
ensure
|
86
|
+
target.close if target.is_a? File
|
78
87
|
end
|
79
88
|
|
80
89
|
# Renders the report as plain text.
|
@@ -84,12 +93,20 @@ module PDK
|
|
84
93
|
#
|
85
94
|
# @param target [#write] an IO object that the report will be written to.
|
86
95
|
# Defaults to PDK::Report.default_target.
|
87
|
-
def
|
96
|
+
def write_text(target = self.class.default_target)
|
97
|
+
# Extra defaulting here, b/c the Class.send method will pass in nil
|
98
|
+
target ||= self.class.default_target
|
99
|
+
|
100
|
+
# Open a File Object for IO if target is a string containing a filename or path
|
101
|
+
target = File.open(target, 'w') if target.is_a? String
|
102
|
+
|
88
103
|
events.each do |_tool, tool_events|
|
89
104
|
tool_events.each do |event|
|
90
105
|
target.puts(event.to_text) unless event.pass?
|
91
106
|
end
|
92
107
|
end
|
108
|
+
ensure
|
109
|
+
target.close if target.is_a? File
|
93
110
|
end
|
94
111
|
end
|
95
112
|
end
|
data/lib/pdk/report/event.rb
CHANGED
@@ -35,6 +35,9 @@ module PDK
|
|
35
35
|
# :skipped.
|
36
36
|
attr_reader :state
|
37
37
|
|
38
|
+
# @return [Array] Array of full stack trace lines associated with event
|
39
|
+
attr_reader :trace
|
40
|
+
|
38
41
|
# Initailises a new PDK::Report::Event object.
|
39
42
|
#
|
40
43
|
# @param data [Hash{Symbol=>Object}
|
@@ -46,6 +49,7 @@ module PDK
|
|
46
49
|
# @option data [String] :severity (see #severity)
|
47
50
|
# @option data [String] :test (see #test)
|
48
51
|
# @option data [Symbol] :state (see #state)
|
52
|
+
# @option data [Array] :trace (see #trace)
|
49
53
|
#
|
50
54
|
# @raise [ArgumentError] (see #sanitise_data)
|
51
55
|
def initialize(data)
|
@@ -77,6 +81,7 @@ module PDK
|
|
77
81
|
end
|
78
82
|
|
79
83
|
# Checks if the event is the result of test that was not run.
|
84
|
+
# This includes pending tests (that are run but have an expected failure result).
|
80
85
|
#
|
81
86
|
# @return [Boolean] true if the test was skipped, otherwise false.
|
82
87
|
def skipped?
|
@@ -88,7 +93,9 @@ module PDK
|
|
88
93
|
# @return [String] The rendered event.
|
89
94
|
def to_text
|
90
95
|
location = [file, line, column].compact.join(':')
|
96
|
+
location = nil if location.empty?
|
91
97
|
|
98
|
+
# TODO: maybe add trace
|
92
99
|
[location, severity, message].compact.join(': ')
|
93
100
|
end
|
94
101
|
|
@@ -271,6 +278,28 @@ module PDK
|
|
271
278
|
|
272
279
|
value.to_i
|
273
280
|
end
|
281
|
+
|
282
|
+
# Cleans up provided stack trace by removing entries that are inside gems
|
283
|
+
# or the rspec binstub.
|
284
|
+
#
|
285
|
+
# @param value [Array] Array of stack trace lines
|
286
|
+
#
|
287
|
+
# @return [Array] Array of stack trace lines with less relevant lines excluded
|
288
|
+
def sanitise_trace(value)
|
289
|
+
return nil if value.nil?
|
290
|
+
|
291
|
+
valid_types = [Array]
|
292
|
+
|
293
|
+
unless valid_types.include?(value.class)
|
294
|
+
raise ArgumentError, _('trace must be an Array of stack trace lines')
|
295
|
+
end
|
296
|
+
|
297
|
+
# Drop any stacktrace lines that include '/gems/' in the path or
|
298
|
+
# are the original rspec binstub lines
|
299
|
+
value.reject do |line|
|
300
|
+
(line =~ %r{/gems/}) || (line =~ %r{bin/rspec:})
|
301
|
+
end
|
302
|
+
end
|
274
303
|
end
|
275
304
|
end
|
276
305
|
end
|
data/lib/pdk/tests/unit.rb
CHANGED
@@ -1,25 +1,130 @@
|
|
1
1
|
require 'pdk'
|
2
2
|
require 'pdk/cli/exec'
|
3
3
|
require 'pdk/util/bundler'
|
4
|
+
require 'json'
|
4
5
|
|
5
6
|
module PDK
|
6
7
|
module Test
|
7
8
|
class Unit
|
8
9
|
def self.cmd(_tests)
|
9
|
-
# TODO:
|
10
|
-
|
11
|
-
# cmd += " #{tests}" if tests
|
12
|
-
cmd = 'pwd'
|
13
|
-
cmd
|
10
|
+
# TODO: test selection
|
11
|
+
[File.join(PDK::Util.module_root, 'bin', 'rake'), 'spec']
|
14
12
|
end
|
15
13
|
|
16
|
-
def self.invoke(
|
14
|
+
def self.invoke(report, options = {})
|
17
15
|
PDK::Util::Bundler.ensure_bundle!
|
16
|
+
PDK::Util::Bundler.ensure_binstubs!('rake')
|
18
17
|
|
19
|
-
|
18
|
+
tests = options.fetch(:tests)
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
cmd_argv = cmd(tests)
|
21
|
+
cmd_argv.unshift('ruby') if Gem.win_platform?
|
22
|
+
|
23
|
+
command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
|
24
|
+
c.context = :module
|
25
|
+
c.add_spinner('Running unit tests')
|
26
|
+
c.environment['CI_SPEC_OPTIONS'] = '--format j'
|
27
|
+
end
|
28
|
+
|
29
|
+
PDK.logger.debug(_('Running %{cmd}') % { cmd: command.argv.join(' ') })
|
30
|
+
|
31
|
+
result = command.execute!
|
32
|
+
|
33
|
+
# TODO: cleanup rspec and/or beaker output
|
34
|
+
# Iterate through possible JSON documents until we find one that is valid.
|
35
|
+
json_result = nil
|
36
|
+
|
37
|
+
result[:stdout].scan(%r{\{(?:[^{}]|(?:\g<0>))*\}}x) do |str|
|
38
|
+
begin
|
39
|
+
json_result = JSON.parse(str)
|
40
|
+
break
|
41
|
+
rescue JSON::ParserError
|
42
|
+
next
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise PDK::CLI::FatalError, _('Unit test output did not contain a valid JSON result: %{output}') % { output: result[:stdout] } unless json_result
|
47
|
+
|
48
|
+
parse_output(report, json_result)
|
49
|
+
|
50
|
+
result[:exit_code]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_output(report, json_data)
|
54
|
+
# Output messages to stderr.
|
55
|
+
json_data['messages'] && json_data['messages'].each { |msg| $stderr.puts msg }
|
56
|
+
|
57
|
+
example_results = {
|
58
|
+
# Only possibilities are passed, failed, pending:
|
59
|
+
# https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/example.rb#L548
|
60
|
+
'passed' => [],
|
61
|
+
'failed' => [],
|
62
|
+
'pending' => [],
|
63
|
+
}
|
64
|
+
|
65
|
+
json_data['examples'] && json_data['examples'].each do |ex|
|
66
|
+
example_results[ex['status']] << ex if example_results.key?(ex['status'])
|
67
|
+
end
|
68
|
+
|
69
|
+
example_results.each do |result, examples|
|
70
|
+
# Translate rspec example results to JUnit XML testcase results
|
71
|
+
state = case result
|
72
|
+
when 'passed' then :passed
|
73
|
+
when 'failed' then :failure
|
74
|
+
when 'pending' then :skipped
|
75
|
+
end
|
76
|
+
|
77
|
+
examples.each do |ex|
|
78
|
+
report.add_event(
|
79
|
+
source: 'rspec',
|
80
|
+
state: state,
|
81
|
+
file: ex['file_path'],
|
82
|
+
line: ex['line_number'],
|
83
|
+
test: ex['full_description'],
|
84
|
+
severity: ex['status'],
|
85
|
+
message: ex['pending_message'] || (ex['exception'] && ex['exception']['message']) || nil,
|
86
|
+
trace: (ex['exception'] && ex['exception']['backtrace']) || nil,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
return unless json_data['summary']
|
92
|
+
|
93
|
+
# TODO: standardize summary output
|
94
|
+
$stderr.puts ' ' << _('Evaluated %{total} tests in %{duration} seconds: %{failures} failures, %{pending} pending') % {
|
95
|
+
total: json_data['summary']['example_count'],
|
96
|
+
duration: json_data['summary']['duration'],
|
97
|
+
failures: json_data['summary']['failure_count'],
|
98
|
+
pending: json_data['summary']['pending_count'],
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return array of { :id, :full_description }
|
103
|
+
def self.list
|
104
|
+
PDK::Util::Bundler.ensure_bundle!
|
105
|
+
PDK::Util::Bundler.ensure_binstubs!('rspec-core')
|
106
|
+
|
107
|
+
command_argv = [File.join(PDK::Util.module_root, 'bin', 'rspec'), '--dry-run', '--format', 'json']
|
108
|
+
command_argv.unshift('ruby') if Gem.win_platform?
|
109
|
+
list_command = PDK::CLI::Exec::Command.new(*command_argv)
|
110
|
+
list_command.context = :module
|
111
|
+
output = list_command.execute!
|
112
|
+
|
113
|
+
rspec_json_output = JSON.parse(output[:stdout])
|
114
|
+
if rspec_json_output['examples'].empty?
|
115
|
+
rspec_message = rspec_json_output['messages'][0]
|
116
|
+
return [] if rspec_message == 'No examples found.'
|
117
|
+
|
118
|
+
raise PDK::CLI::FatalError, _('Unable to enumerate examples. rspec reported: %{message}' % { message: rspec_message })
|
119
|
+
else
|
120
|
+
examples = []
|
121
|
+
rspec_json_output['examples'].each do |example|
|
122
|
+
examples << { id: example['id'], full_description: example['full_description'] }
|
123
|
+
end
|
124
|
+
examples
|
125
|
+
end
|
126
|
+
rescue JSON::ParserError => e
|
127
|
+
raise PDK::CLI::FatalError, _('Failed to parse output from rspec: %{message}' % { message: e.message })
|
23
128
|
end
|
24
129
|
end
|
25
130
|
end
|
data/lib/pdk/util/bundler.rb
CHANGED
@@ -62,24 +62,28 @@ module PDK
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def installed?
|
65
|
-
|
65
|
+
command = bundle_command('check', "--gemfile=#{gemfile}", "--path=#{bundle_cachedir}").tap do |c|
|
66
|
+
c.add_spinner(_('Checking for missing Gemfile dependencies'))
|
67
|
+
end
|
66
68
|
|
67
|
-
result =
|
69
|
+
result = command.execute!
|
68
70
|
|
69
|
-
|
71
|
+
unless result[:exit_code].zero?
|
72
|
+
$stderr.puts result[:stdout]
|
73
|
+
$stderr.puts result[:stderr]
|
74
|
+
end
|
70
75
|
|
71
76
|
result[:exit_code].zero?
|
72
77
|
end
|
73
78
|
|
74
79
|
def lock!
|
75
|
-
|
80
|
+
command = bundle_command('lock').tap do |c|
|
81
|
+
c.add_spinner(_('Resolving Gemfile dependencies'))
|
82
|
+
end
|
76
83
|
|
77
|
-
result =
|
84
|
+
result = command.execute!
|
78
85
|
|
79
|
-
|
80
|
-
output_end(:success)
|
81
|
-
else
|
82
|
-
output_end(:failure)
|
86
|
+
unless result[:exit_code].zero?
|
83
87
|
$stderr.puts result[:stdout]
|
84
88
|
$stderr.puts result[:stderr]
|
85
89
|
end
|
@@ -88,14 +92,13 @@ module PDK
|
|
88
92
|
end
|
89
93
|
|
90
94
|
def install!
|
91
|
-
|
95
|
+
command = bundle_command('install', "--gemfile=#{gemfile}", "--path=#{bundle_cachedir}").tap do |c|
|
96
|
+
c.add_spinner(_('Installing missing Gemfile dependencies'))
|
97
|
+
end
|
92
98
|
|
93
|
-
result =
|
99
|
+
result = command.execute!
|
94
100
|
|
95
|
-
|
96
|
-
output_end(:success)
|
97
|
-
else
|
98
|
-
output_end(:failure)
|
101
|
+
unless result[:exit_code].zero?
|
99
102
|
$stderr.puts result[:stdout]
|
100
103
|
$stderr.puts result[:stderr]
|
101
104
|
end
|
@@ -104,10 +107,12 @@ module PDK
|
|
104
107
|
end
|
105
108
|
|
106
109
|
def binstubs!(gems)
|
107
|
-
|
108
|
-
|
110
|
+
command = bundle_command('binstubs', gems.join(' '), '--force')
|
111
|
+
|
112
|
+
result = command.execute!
|
109
113
|
|
110
114
|
unless result[:exit_code].zero?
|
115
|
+
PDK.logger.error(_('Failed to generate binstubs for %{gems}') % { gems: gems.join(' ') })
|
111
116
|
$stderr.puts result[:stdout]
|
112
117
|
$stderr.puts result[:stderr]
|
113
118
|
end
|
@@ -121,13 +126,10 @@ module PDK
|
|
121
126
|
|
122
127
|
private
|
123
128
|
|
124
|
-
def
|
125
|
-
|
126
|
-
command = PDK::CLI::Exec::Command.new(bundle_bin, *args).tap do |c|
|
129
|
+
def bundle_command(*args)
|
130
|
+
PDK::CLI::Exec::Command.new(PDK::CLI::Exec.bundle_bin, *args).tap do |c|
|
127
131
|
c.context = :module
|
128
132
|
end
|
129
|
-
|
130
|
-
command.execute!
|
131
133
|
end
|
132
134
|
|
133
135
|
def gemfile_lock
|
@@ -137,30 +139,6 @@ module PDK
|
|
137
139
|
def bundle_cachedir
|
138
140
|
@bundle_cachedir ||= File.join(PDK::Util.cachedir, 'bundler')
|
139
141
|
end
|
140
|
-
|
141
|
-
# These two output_* methods are just a way to not try to do the spinner stuff on Windows for now.
|
142
|
-
def output_start(message)
|
143
|
-
if Gem.win_platform?
|
144
|
-
$stderr.print "#{message}... "
|
145
|
-
else
|
146
|
-
@spinner = TTY::Spinner.new("[:spinner] #{message}")
|
147
|
-
@spinner.auto_spin
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def output_end(state)
|
152
|
-
if Gem.win_platform?
|
153
|
-
$stderr.print((state == :success) ? _("done.\n") : _("FAILURE!\n"))
|
154
|
-
else
|
155
|
-
if state == :success
|
156
|
-
@spinner.success
|
157
|
-
else
|
158
|
-
@spinner.error
|
159
|
-
end
|
160
|
-
|
161
|
-
remove_instance_variable(:@spinner)
|
162
|
-
end
|
163
|
-
end
|
164
142
|
end
|
165
143
|
end
|
166
144
|
end
|
@@ -20,8 +20,7 @@ module PDK
|
|
20
20
|
targets.map { |target|
|
21
21
|
if respond_to?(:pattern)
|
22
22
|
if File.directory?(target)
|
23
|
-
|
24
|
-
files_glob.flatten.empty? ? target : files_glob
|
23
|
+
Array[pattern].flatten.map { |p| Dir.glob(File.join(target, p)) }
|
25
24
|
else
|
26
25
|
target
|
27
26
|
end
|
@@ -40,13 +39,13 @@ module PDK
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def self.invoke(report, options = {})
|
43
|
-
PDK::Util::Bundler.ensure_binstubs!(cmd)
|
44
|
-
|
45
42
|
targets = parse_targets(options)
|
46
|
-
cmd_argv = parse_options(options, targets).unshift(cmd_path)
|
47
|
-
cmd_argv.unshift('ruby') if Gem.win_platform?
|
48
43
|
|
49
|
-
|
44
|
+
return 0 if targets.empty?
|
45
|
+
|
46
|
+
PDK::Util::Bundler.ensure_binstubs!(cmd)
|
47
|
+
cmd_argv = parse_options(options, targets).unshift(cmd_path)
|
48
|
+
cmd_argv.unshift('ruby', '-W0') if Gem.win_platform?
|
50
49
|
|
51
50
|
command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
|
52
51
|
c.context = :module
|
@@ -55,13 +54,7 @@ module PDK
|
|
55
54
|
|
56
55
|
result = command.execute!
|
57
56
|
|
58
|
-
|
59
|
-
json_data = JSON.parse(result[:stdout])
|
60
|
-
rescue JSON::ParserError
|
61
|
-
json_data = []
|
62
|
-
end
|
63
|
-
|
64
|
-
parse_output(report, json_data)
|
57
|
+
parse_output(report, result, targets)
|
65
58
|
|
66
59
|
result[:exit_code]
|
67
60
|
end
|
@@ -14,6 +14,10 @@ module PDK
|
|
14
14
|
'metadata-json-lint'
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.spinner_text
|
18
|
+
_('Checking metadata.json')
|
19
|
+
end
|
20
|
+
|
17
21
|
def self.parse_targets(_options)
|
18
22
|
[File.join(PDK::Util.module_root, 'metadata.json')]
|
19
23
|
end
|
@@ -24,20 +28,38 @@ module PDK
|
|
24
28
|
cmd_options.concat(targets)
|
25
29
|
end
|
26
30
|
|
27
|
-
def self.parse_output(report,
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
31
|
+
def self.parse_output(report, result, _targets)
|
32
|
+
begin
|
33
|
+
json_data = JSON.parse(result[:stdout])
|
34
|
+
rescue JSON::ParserError
|
35
|
+
json_data = []
|
36
|
+
end
|
37
|
+
|
38
|
+
if json_data.empty?
|
39
|
+
report.add_event(
|
40
|
+
file: 'metadata.json',
|
41
|
+
source: cmd,
|
42
|
+
state: :passed,
|
43
|
+
severity: :ok,
|
44
|
+
)
|
45
|
+
else
|
46
|
+
json_data.delete('result')
|
47
|
+
json_data.keys.each do |type|
|
48
|
+
json_data[type].each do |offense|
|
49
|
+
# metadata-json-lint groups the offenses by type, so the type ends
|
50
|
+
# up being `warnings` or `errors`. We want to convert that to the
|
51
|
+
# singular noun for the event.
|
52
|
+
event_type = type[%r{\A(.+?)s?\Z}, 1]
|
53
|
+
|
54
|
+
report.add_event(
|
55
|
+
file: 'metadata.json',
|
56
|
+
source: cmd,
|
57
|
+
message: offense['msg'],
|
58
|
+
test: offense['check'],
|
59
|
+
severity: event_type,
|
60
|
+
state: :failure,
|
61
|
+
)
|
62
|
+
end
|
41
63
|
end
|
42
64
|
end
|
43
65
|
end
|