pdk 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1336 -1329
  3. data/LICENSE +201 -201
  4. data/README.md +163 -163
  5. data/exe/pdk +10 -10
  6. data/lib/pdk/analytics/client/google_analytics.rb +143 -143
  7. data/lib/pdk/analytics/client/noop.rb +25 -25
  8. data/lib/pdk/analytics/util.rb +19 -19
  9. data/lib/pdk/analytics.rb +30 -30
  10. data/lib/pdk/answer_file.rb +12 -12
  11. data/lib/pdk/bolt.rb +19 -19
  12. data/lib/pdk/cli/build.rb +82 -82
  13. data/lib/pdk/cli/bundle.rb +48 -48
  14. data/lib/pdk/cli/config/get.rb +26 -26
  15. data/lib/pdk/cli/config.rb +22 -22
  16. data/lib/pdk/cli/console.rb +148 -148
  17. data/lib/pdk/cli/convert.rb +52 -52
  18. data/lib/pdk/cli/env.rb +52 -52
  19. data/lib/pdk/cli/errors.rb +25 -25
  20. data/lib/pdk/cli/exec/command.rb +293 -293
  21. data/lib/pdk/cli/exec/interactive_command.rb +114 -114
  22. data/lib/pdk/cli/exec.rb +84 -84
  23. data/lib/pdk/cli/exec_group.rb +104 -104
  24. data/lib/pdk/cli/get/config.rb +24 -24
  25. data/lib/pdk/cli/get.rb +20 -20
  26. data/lib/pdk/cli/module/build.rb +12 -12
  27. data/lib/pdk/cli/module/generate.rb +47 -47
  28. data/lib/pdk/cli/module.rb +14 -14
  29. data/lib/pdk/cli/new/class.rb +32 -32
  30. data/lib/pdk/cli/new/defined_type.rb +32 -32
  31. data/lib/pdk/cli/new/fact.rb +29 -29
  32. data/lib/pdk/cli/new/function.rb +29 -29
  33. data/lib/pdk/cli/new/module.rb +53 -53
  34. data/lib/pdk/cli/new/provider.rb +29 -29
  35. data/lib/pdk/cli/new/task.rb +34 -34
  36. data/lib/pdk/cli/new/test.rb +52 -52
  37. data/lib/pdk/cli/new/transport.rb +27 -27
  38. data/lib/pdk/cli/new.rb +21 -21
  39. data/lib/pdk/cli/release/prep.rb +39 -39
  40. data/lib/pdk/cli/release/publish.rb +50 -50
  41. data/lib/pdk/cli/release.rb +194 -194
  42. data/lib/pdk/cli/remove/config.rb +80 -80
  43. data/lib/pdk/cli/remove.rb +20 -20
  44. data/lib/pdk/cli/set/config.rb +119 -119
  45. data/lib/pdk/cli/set.rb +20 -20
  46. data/lib/pdk/cli/test/unit.rb +90 -90
  47. data/lib/pdk/cli/test.rb +11 -11
  48. data/lib/pdk/cli/update.rb +64 -64
  49. data/lib/pdk/cli/util/command_redirector.rb +27 -27
  50. data/lib/pdk/cli/util/interview.rb +72 -72
  51. data/lib/pdk/cli/util/option_normalizer.rb +55 -55
  52. data/lib/pdk/cli/util/option_validator.rb +68 -68
  53. data/lib/pdk/cli/util/spinner.rb +13 -13
  54. data/lib/pdk/cli/util/update_manager_printer.rb +82 -82
  55. data/lib/pdk/cli/util.rb +305 -305
  56. data/lib/pdk/cli/validate.rb +116 -116
  57. data/lib/pdk/cli.rb +175 -175
  58. data/lib/pdk/config/analytics_schema.json +26 -26
  59. data/lib/pdk/config/errors.rb +5 -5
  60. data/lib/pdk/config/ini_file.rb +183 -183
  61. data/lib/pdk/config/ini_file_setting.rb +39 -39
  62. data/lib/pdk/config/json.rb +34 -34
  63. data/lib/pdk/config/json_schema_namespace.rb +142 -142
  64. data/lib/pdk/config/json_schema_setting.rb +53 -53
  65. data/lib/pdk/config/json_with_schema.rb +49 -49
  66. data/lib/pdk/config/namespace.rb +354 -354
  67. data/lib/pdk/config/setting.rb +135 -135
  68. data/lib/pdk/config/validator.rb +31 -31
  69. data/lib/pdk/config/yaml.rb +46 -46
  70. data/lib/pdk/config/yaml_with_schema.rb +59 -59
  71. data/lib/pdk/config.rb +390 -390
  72. data/lib/pdk/context/control_repo.rb +60 -60
  73. data/lib/pdk/context/module.rb +28 -28
  74. data/lib/pdk/context/none.rb +22 -22
  75. data/lib/pdk/context.rb +99 -99
  76. data/lib/pdk/control_repo.rb +90 -90
  77. data/lib/pdk/generate/defined_type.rb +43 -43
  78. data/lib/pdk/generate/fact.rb +25 -25
  79. data/lib/pdk/generate/function.rb +48 -48
  80. data/lib/pdk/generate/module.rb +352 -352
  81. data/lib/pdk/generate/provider.rb +28 -28
  82. data/lib/pdk/generate/puppet_class.rb +43 -43
  83. data/lib/pdk/generate/puppet_object.rb +232 -232
  84. data/lib/pdk/generate/task.rb +68 -68
  85. data/lib/pdk/generate/transport.rb +33 -33
  86. data/lib/pdk/generate.rb +24 -24
  87. data/lib/pdk/i18n.rb +4 -4
  88. data/lib/pdk/logger.rb +45 -45
  89. data/lib/pdk/module/build.rb +322 -322
  90. data/lib/pdk/module/convert.rb +296 -296
  91. data/lib/pdk/module/metadata.rb +202 -202
  92. data/lib/pdk/module/release.rb +260 -260
  93. data/lib/pdk/module/update.rb +131 -131
  94. data/lib/pdk/module/update_manager.rb +227 -227
  95. data/lib/pdk/module.rb +30 -30
  96. data/lib/pdk/report/event.rb +370 -370
  97. data/lib/pdk/report.rb +121 -121
  98. data/lib/pdk/template/fetcher/git.rb +85 -85
  99. data/lib/pdk/template/fetcher/local.rb +28 -28
  100. data/lib/pdk/template/fetcher.rb +98 -98
  101. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +116 -116
  102. data/lib/pdk/template/renderer/v1/renderer.rb +132 -132
  103. data/lib/pdk/template/renderer/v1/template_file.rb +102 -102
  104. data/lib/pdk/template/renderer/v1.rb +25 -25
  105. data/lib/pdk/template/renderer.rb +96 -96
  106. data/lib/pdk/template/template_dir.rb +67 -67
  107. data/lib/pdk/template.rb +59 -59
  108. data/lib/pdk/tests/unit.rb +252 -252
  109. data/lib/pdk/util/bundler.rb +259 -259
  110. data/lib/pdk/util/changelog_generator.rb +137 -137
  111. data/lib/pdk/util/env.rb +47 -47
  112. data/lib/pdk/util/filesystem.rb +138 -138
  113. data/lib/pdk/util/git.rb +179 -179
  114. data/lib/pdk/util/json_finder.rb +85 -85
  115. data/lib/pdk/util/puppet_strings.rb +125 -125
  116. data/lib/pdk/util/puppet_version.rb +266 -266
  117. data/lib/pdk/util/ruby_version.rb +179 -179
  118. data/lib/pdk/util/template_uri.rb +295 -295
  119. data/lib/pdk/util/vendored_file.rb +93 -93
  120. data/lib/pdk/util/version.rb +43 -43
  121. data/lib/pdk/util/windows/api_types.rb +82 -82
  122. data/lib/pdk/util/windows/file.rb +36 -36
  123. data/lib/pdk/util/windows/process.rb +79 -79
  124. data/lib/pdk/util/windows/string.rb +16 -16
  125. data/lib/pdk/util/windows.rb +15 -15
  126. data/lib/pdk/util.rb +278 -278
  127. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -23
  128. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -98
  129. data/lib/pdk/validate/external_command_validator.rb +208 -208
  130. data/lib/pdk/validate/internal_ruby_validator.rb +100 -100
  131. data/lib/pdk/validate/invokable_validator.rb +228 -228
  132. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -86
  133. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -78
  134. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -20
  135. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -133
  136. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -66
  137. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -137
  138. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -21
  139. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -80
  140. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -19
  141. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -88
  142. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -50
  143. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -20
  144. data/lib/pdk/validate/validator.rb +118 -118
  145. data/lib/pdk/validate/validator_group.rb +104 -104
  146. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -95
  147. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -19
  148. data/lib/pdk/validate.rb +94 -94
  149. data/lib/pdk/version.rb +4 -4
  150. data/lib/pdk.rb +76 -76
  151. data/locales/config.yaml +21 -21
  152. data/locales/pdk.pot +2111 -2094
  153. metadata +3 -3
@@ -1,370 +1,370 @@
1
- require 'pdk'
2
-
3
- module PDK
4
- class Report
5
- class Event
6
- # @return [String] The path to the file that the event is in reference
7
- # to.
8
- attr_reader :file
9
-
10
- # @return [Integer] The line number in the file that the event is in
11
- # reference to.
12
- attr_reader :line
13
-
14
- # @return [Integer] The column number in the line of the file that the
15
- # event is in reference to.
16
- attr_reader :column
17
-
18
- # @return [String] The name of the source of the event (usually the name
19
- # of the validation or testing tool that generated the event).
20
- attr_reader :source
21
-
22
- # @return [String] A freeform String containing a human readable message
23
- # describing the event.
24
- attr_reader :message
25
-
26
- # @return [String] The severity of the event as reported by the
27
- # underlying tool.
28
- attr_reader :severity
29
-
30
- # @return [String] The name of the test that generated the event.
31
- attr_reader :test
32
-
33
- # @return [Symbol] The state of the event. :passed, :failure, :error, or
34
- # :skipped.
35
- attr_reader :state
36
-
37
- # @return [Array] Array of full stack trace lines associated with event
38
- attr_reader :trace
39
-
40
- # Initailises a new PDK::Report::Event object.
41
- #
42
- # @param data [Hash{Symbol=>Object}
43
- # @option data [String] :file (see #file)
44
- # @option data [Integer] :line (see #line)
45
- # @option data [Integer] :column (see #column)
46
- # @option data [String] :source (see #source)
47
- # @option data [String] :message (see #message)
48
- # @option data [String] :severity (see #severity)
49
- # @option data [String] :test (see #test)
50
- # @option data [Symbol] :state (see #state)
51
- # @option data [Array] :trace (see #trace)
52
- #
53
- # @raise [ArgumentError] (see #sanitise_data)
54
- def initialize(data)
55
- sanitise_data(data).each do |key, value|
56
- instance_variable_set("@#{key}", value)
57
- end
58
- end
59
-
60
- # Checks if the event is the result of a passing test.
61
- #
62
- # @return [Boolean] true if the test passed, otherwise false.
63
- def pass?
64
- state == :passed
65
- end
66
-
67
- # Checks if the event is the result of a test that could not complete due
68
- # to an error.
69
- #
70
- # @return [Boolean] true if the test did not complete, otherwise false.
71
- def error?
72
- state == :error
73
- end
74
-
75
- # Checks if the event is the result of a failing test.
76
- #
77
- # @return [Boolean] true if the test failed, otherwise false.
78
- def failure?
79
- state == :failure
80
- end
81
-
82
- # Checks if the event is the result of test that was not run.
83
- # This includes pending tests (that are run but have an expected failure result).
84
- #
85
- # @return [Boolean] true if the test was skipped, otherwise false.
86
- def skipped?
87
- state == :skipped
88
- end
89
-
90
- # Checks if the event stores the result of an rspec-puppet coverage
91
- # check.
92
- #
93
- # Due to the implementation details of this check, the `file` value for
94
- # this event will always point to the coverage.rb file in rspec-puppet,
95
- # making it easy to filter out.
96
- #
97
- # @return [Boolean] true if the event contains rspec-puppet coverage
98
- # results.
99
- def rspec_puppet_coverage?
100
- @rspec_puppet_coverage_pattern ||= File.join('**', 'lib', 'rspec-puppet', 'coverage.rb')
101
- source == 'rspec' && PDK::Util::Filesystem.fnmatch?(@rspec_puppet_coverage_pattern, PDK::Util::Filesystem.expand_path(file))
102
- end
103
-
104
- # Renders the event in a clang style text format.
105
- #
106
- # @return [String] The rendered event.
107
- def to_text
108
- return message if rspec_puppet_coverage?
109
-
110
- location = [file, line, column].compact.join(':')
111
- location = nil if location.empty?
112
-
113
- # TODO: maybe add trace
114
- if source == 'rspec'
115
- header = [severity, source, location, message].compact.join(': ')
116
- result = [header, " #{test}"]
117
- context = context_lines
118
- unless context.nil?
119
- result << ' Failure/Error:'
120
- result.concat(context)
121
- result << "\n"
122
- end
123
-
124
- result.compact.join("\n")
125
- else
126
- output = ['pdk']
127
- output << "(#{severity.upcase}):" unless severity.nil?
128
- output << "#{source}:" unless source.nil?
129
- output << message unless message.nil?
130
- output << "(#{location})" unless location.nil?
131
-
132
- output.join(' ')
133
- end
134
- end
135
-
136
- # Renders the event as a JUnit XML testcase.
137
- #
138
- # @return [REXML::Element] The rendered event.
139
- def to_junit
140
- require 'rexml/document'
141
-
142
- testcase = REXML::Element.new('testcase')
143
- testcase.attributes['classname'] = [source, test].compact.join('.')
144
- testcase.attributes['name'] = [file, line, column].compact.join(':')
145
- testcase.attributes['time'] = 0
146
-
147
- if failure?
148
- failure = REXML::Element.new('failure')
149
- failure.attributes['type'] = severity
150
- failure.attributes['message'] = message
151
- failure.text = to_text
152
- testcase.elements << failure
153
- elsif skipped?
154
- testcase.add_element('skipped')
155
- end
156
-
157
- testcase
158
- end
159
-
160
- private
161
-
162
- # Processes the data hash used to initialise the event, validating and
163
- # munging the values as necessary.
164
- #
165
- # @param data [Hash{Symbol => Object}] (see #initialize)
166
- #
167
- # @return [Hash{Symbol => String}] A copy of the data hash passed to the
168
- # method with sanitised values.
169
- #
170
- # @raise [ArgumentError] (see #sanitise_file)
171
- # @raise [ArgumentError] (see #sanitise_state)
172
- # @raise [ArgumentError] (see #sanitise_source)
173
- def sanitise_data(data)
174
- result = data.dup
175
- data.each do |key, value|
176
- key = key.to_sym unless key.is_a?(Symbol)
177
- method = "sanitise_#{key}"
178
- result[key] = send(method, value) if respond_to?(method, true)
179
- end
180
-
181
- result
182
- end
183
-
184
- # Munges and validates the file path used to instantiate the event.
185
- #
186
- # If the path is an absolute path, it will be rewritten so that it is
187
- # relative to the module root instead.
188
- #
189
- # @param value [String] The path to the file that the event is
190
- # describing.
191
- #
192
- # @return [String] The path to the file, relative to the module root.
193
- #
194
- # @raise [ArgumentError] if the value is nil, an empty String, or not
195
- # a String.
196
- def sanitise_file(value)
197
- if value.nil? || (value.is_a?(String) && value.empty?)
198
- raise ArgumentError, _('File not specified.')
199
- end
200
-
201
- unless value.is_a?(String)
202
- raise ArgumentError, _('File must be a String.')
203
- end
204
-
205
- require 'pathname'
206
- require 'pdk/util'
207
-
208
- path = Pathname.new(value)
209
-
210
- if path.absolute?
211
- module_root = Pathname.new(PDK::Util.module_root)
212
- path = path.relative_path_from(module_root).to_path
213
- path << '/' if path == '.'
214
- path
215
- else
216
- path.to_path
217
- end
218
- end
219
-
220
- # Munges and validates the state of the event.
221
- #
222
- # The valid event states are:
223
- # :passed - The event represents a passing test.
224
- # :error - The event represents a test that could not be completed due
225
- # to an unexpected error.
226
- # :failure - The event represents a failing test.
227
- # :skipped - The event represents a test that was skipped.
228
- #
229
- # @param value [Symbol, String] The state of the event. If passed as
230
- # a String, it will be turned into a Symbol before validation.
231
- #
232
- # @return [Symbol] The sanitised state type.
233
- #
234
- # @raise [ArgumentError] if the value is nil, an empty String, or not
235
- # a String or Symbol representation of a valid state.
236
- def sanitise_state(value)
237
- if value.nil? || (value.is_a?(String) && value.empty?)
238
- raise ArgumentError, _('State not specified.')
239
- end
240
-
241
- value = value.to_sym if value.is_a?(String)
242
- unless value.is_a?(Symbol)
243
- raise ArgumentError, _('State must be a Symbol, not %{type}') % { type: value.class }
244
- end
245
-
246
- valid_states = [:passed, :error, :failure, :skipped]
247
- unless valid_states.include?(value)
248
- raise ArgumentError, _('Invalid state %{state}. Valid states are: %{valid}.') % {
249
- state: value.inspect,
250
- valid: valid_states.map(&:inspect).join(', '),
251
- }
252
- end
253
-
254
- value
255
- end
256
-
257
- # Validates the source of the event.
258
- #
259
- # @param value [String, Symbol] The name of the source of the event.
260
- #
261
- # @return [String] the value passed to the event, converted to a String
262
- # if necessary.
263
- #
264
- # @raise [ArgumentError] if the value is nil or an empty String.
265
- def sanitise_source(value)
266
- if value.nil? || (value.is_a?(String) && value.empty?)
267
- raise ArgumentError, _('Source not specified.')
268
- end
269
-
270
- value.to_s
271
- end
272
-
273
- # Munges the line number of the event into an Integer.
274
- #
275
- # @param value [Integer, String, Fixnum] The line number.
276
- #
277
- # @return [Integer] the provided value, converted into an Integer if
278
- # necessary.
279
- def sanitise_line(value)
280
- return if value.nil?
281
-
282
- valid_types = [String, Integer]
283
- if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
284
- valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
285
- end
286
-
287
- unless valid_types.include?(value.class)
288
- raise ArgumentError, _('Line must be an Integer or a String representation of an Integer.')
289
- end
290
-
291
- if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
292
- raise ArgumentError, _('The line number can contain only the digits 0-9.')
293
- end
294
-
295
- value.to_i
296
- end
297
-
298
- # Munges the column number of the event into an Integer.
299
- #
300
- # @param value [Integer, String, Fixnum] The column number.
301
- #
302
- # @return [Integer] the provided value, converted into an Integer if
303
- # necessary.
304
- def sanitise_column(value)
305
- return if value.nil?
306
-
307
- valid_types = [String, Integer]
308
- if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
309
- valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
310
- end
311
-
312
- unless valid_types.include?(value.class)
313
- raise ArgumentError, _('Column must be an Integer or a String representation of an Integer.')
314
- end
315
-
316
- if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
317
- raise ArgumentError, _('The column number can contain only the digits 0-9.')
318
- end
319
-
320
- value.to_i
321
- end
322
-
323
- # Cleans up provided stack trace by removing entries that are inside gems
324
- # or the rspec binstub.
325
- #
326
- # @param value [Array] Array of stack trace lines
327
- #
328
- # @return [Array] Array of stack trace lines with less relevant lines excluded
329
- def sanitise_trace(value)
330
- return if value.nil?
331
-
332
- valid_types = [Array]
333
-
334
- unless valid_types.include?(value.class)
335
- raise ArgumentError, _('Trace must be an Array of stack trace lines.')
336
- end
337
-
338
- # Drop any stacktrace lines that include '/gems/' in the path or
339
- # are the original rspec binstub lines
340
- value.reject do |line|
341
- (line =~ %r{/gems/}) || (line =~ %r{bin/rspec:})
342
- end
343
- end
344
-
345
- # Extract contextual information for the event from the file that it
346
- # references.
347
- #
348
- # @param max_num_lines [Integer] The maximum number of lines to return.
349
- #
350
- # @return [Array] Array of lines from the file, centred on the line
351
- # number of the event.
352
- def context_lines(max_num_lines = 5)
353
- return if file.nil? || line.nil?
354
-
355
- file_path = [file, File.join(PDK::Util.module_root, file)].find do |path|
356
- PDK::Util::Filesystem.file?(path)
357
- end
358
-
359
- return if file_path.nil?
360
-
361
- file_content = PDK::Util::Filesystem.read_file(file_path).split("\n")
362
- delta = (max_num_lines - 1) / 2
363
- min = [0, (line - 1) - delta].max
364
- max = [(line - 1) + delta, file_content.length].min
365
-
366
- file_content[min..max].map { |r| " #{r}" }
367
- end
368
- end
369
- end
370
- end
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Report
5
+ class Event
6
+ # @return [String] The path to the file that the event is in reference
7
+ # to.
8
+ attr_reader :file
9
+
10
+ # @return [Integer] The line number in the file that the event is in
11
+ # reference to.
12
+ attr_reader :line
13
+
14
+ # @return [Integer] The column number in the line of the file that the
15
+ # event is in reference to.
16
+ attr_reader :column
17
+
18
+ # @return [String] The name of the source of the event (usually the name
19
+ # of the validation or testing tool that generated the event).
20
+ attr_reader :source
21
+
22
+ # @return [String] A freeform String containing a human readable message
23
+ # describing the event.
24
+ attr_reader :message
25
+
26
+ # @return [String] The severity of the event as reported by the
27
+ # underlying tool.
28
+ attr_reader :severity
29
+
30
+ # @return [String] The name of the test that generated the event.
31
+ attr_reader :test
32
+
33
+ # @return [Symbol] The state of the event. :passed, :failure, :error, or
34
+ # :skipped.
35
+ attr_reader :state
36
+
37
+ # @return [Array] Array of full stack trace lines associated with event
38
+ attr_reader :trace
39
+
40
+ # Initailises a new PDK::Report::Event object.
41
+ #
42
+ # @param data [Hash{Symbol=>Object}
43
+ # @option data [String] :file (see #file)
44
+ # @option data [Integer] :line (see #line)
45
+ # @option data [Integer] :column (see #column)
46
+ # @option data [String] :source (see #source)
47
+ # @option data [String] :message (see #message)
48
+ # @option data [String] :severity (see #severity)
49
+ # @option data [String] :test (see #test)
50
+ # @option data [Symbol] :state (see #state)
51
+ # @option data [Array] :trace (see #trace)
52
+ #
53
+ # @raise [ArgumentError] (see #sanitise_data)
54
+ def initialize(data)
55
+ sanitise_data(data).each do |key, value|
56
+ instance_variable_set("@#{key}", value)
57
+ end
58
+ end
59
+
60
+ # Checks if the event is the result of a passing test.
61
+ #
62
+ # @return [Boolean] true if the test passed, otherwise false.
63
+ def pass?
64
+ state == :passed
65
+ end
66
+
67
+ # Checks if the event is the result of a test that could not complete due
68
+ # to an error.
69
+ #
70
+ # @return [Boolean] true if the test did not complete, otherwise false.
71
+ def error?
72
+ state == :error
73
+ end
74
+
75
+ # Checks if the event is the result of a failing test.
76
+ #
77
+ # @return [Boolean] true if the test failed, otherwise false.
78
+ def failure?
79
+ state == :failure
80
+ end
81
+
82
+ # Checks if the event is the result of test that was not run.
83
+ # This includes pending tests (that are run but have an expected failure result).
84
+ #
85
+ # @return [Boolean] true if the test was skipped, otherwise false.
86
+ def skipped?
87
+ state == :skipped
88
+ end
89
+
90
+ # Checks if the event stores the result of an rspec-puppet coverage
91
+ # check.
92
+ #
93
+ # Due to the implementation details of this check, the `file` value for
94
+ # this event will always point to the coverage.rb file in rspec-puppet,
95
+ # making it easy to filter out.
96
+ #
97
+ # @return [Boolean] true if the event contains rspec-puppet coverage
98
+ # results.
99
+ def rspec_puppet_coverage?
100
+ @rspec_puppet_coverage_pattern ||= File.join('**', 'lib', 'rspec-puppet', 'coverage.rb')
101
+ source == 'rspec' && PDK::Util::Filesystem.fnmatch?(@rspec_puppet_coverage_pattern, PDK::Util::Filesystem.expand_path(file))
102
+ end
103
+
104
+ # Renders the event in a clang style text format.
105
+ #
106
+ # @return [String] The rendered event.
107
+ def to_text
108
+ return message if rspec_puppet_coverage?
109
+
110
+ location = [file, line, column].compact.join(':')
111
+ location = nil if location.empty?
112
+
113
+ # TODO: maybe add trace
114
+ if source == 'rspec'
115
+ header = [severity, source, location, message].compact.join(': ')
116
+ result = [header, " #{test}"]
117
+ context = context_lines
118
+ unless context.nil?
119
+ result << ' Failure/Error:'
120
+ result.concat(context)
121
+ result << "\n"
122
+ end
123
+
124
+ result.compact.join("\n")
125
+ else
126
+ output = ['pdk']
127
+ output << "(#{severity.upcase}):" unless severity.nil?
128
+ output << "#{source}:" unless source.nil?
129
+ output << message unless message.nil?
130
+ output << "(#{location})" unless location.nil?
131
+
132
+ output.join(' ')
133
+ end
134
+ end
135
+
136
+ # Renders the event as a JUnit XML testcase.
137
+ #
138
+ # @return [REXML::Element] The rendered event.
139
+ def to_junit
140
+ require 'rexml/document'
141
+
142
+ testcase = REXML::Element.new('testcase')
143
+ testcase.attributes['classname'] = [source, test].compact.join('.')
144
+ testcase.attributes['name'] = [file, line, column].compact.join(':')
145
+ testcase.attributes['time'] = 0
146
+
147
+ if failure?
148
+ failure = REXML::Element.new('failure')
149
+ failure.attributes['type'] = severity
150
+ failure.attributes['message'] = message
151
+ failure.text = to_text
152
+ testcase.elements << failure
153
+ elsif skipped?
154
+ testcase.add_element('skipped')
155
+ end
156
+
157
+ testcase
158
+ end
159
+
160
+ private
161
+
162
+ # Processes the data hash used to initialise the event, validating and
163
+ # munging the values as necessary.
164
+ #
165
+ # @param data [Hash{Symbol => Object}] (see #initialize)
166
+ #
167
+ # @return [Hash{Symbol => String}] A copy of the data hash passed to the
168
+ # method with sanitised values.
169
+ #
170
+ # @raise [ArgumentError] (see #sanitise_file)
171
+ # @raise [ArgumentError] (see #sanitise_state)
172
+ # @raise [ArgumentError] (see #sanitise_source)
173
+ def sanitise_data(data)
174
+ result = data.dup
175
+ data.each do |key, value|
176
+ key = key.to_sym unless key.is_a?(Symbol)
177
+ method = "sanitise_#{key}"
178
+ result[key] = send(method, value) if respond_to?(method, true)
179
+ end
180
+
181
+ result
182
+ end
183
+
184
+ # Munges and validates the file path used to instantiate the event.
185
+ #
186
+ # If the path is an absolute path, it will be rewritten so that it is
187
+ # relative to the module root instead.
188
+ #
189
+ # @param value [String] The path to the file that the event is
190
+ # describing.
191
+ #
192
+ # @return [String] The path to the file, relative to the module root.
193
+ #
194
+ # @raise [ArgumentError] if the value is nil, an empty String, or not
195
+ # a String.
196
+ def sanitise_file(value)
197
+ if value.nil? || (value.is_a?(String) && value.empty?)
198
+ raise ArgumentError, _('File not specified.')
199
+ end
200
+
201
+ unless value.is_a?(String)
202
+ raise ArgumentError, _('File must be a String.')
203
+ end
204
+
205
+ require 'pathname'
206
+ require 'pdk/util'
207
+
208
+ path = Pathname.new(value)
209
+
210
+ if path.absolute?
211
+ module_root = Pathname.new(PDK::Util.module_root)
212
+ path = path.relative_path_from(module_root).to_path
213
+ path << '/' if path == '.'
214
+ path
215
+ else
216
+ path.to_path
217
+ end
218
+ end
219
+
220
+ # Munges and validates the state of the event.
221
+ #
222
+ # The valid event states are:
223
+ # :passed - The event represents a passing test.
224
+ # :error - The event represents a test that could not be completed due
225
+ # to an unexpected error.
226
+ # :failure - The event represents a failing test.
227
+ # :skipped - The event represents a test that was skipped.
228
+ #
229
+ # @param value [Symbol, String] The state of the event. If passed as
230
+ # a String, it will be turned into a Symbol before validation.
231
+ #
232
+ # @return [Symbol] The sanitised state type.
233
+ #
234
+ # @raise [ArgumentError] if the value is nil, an empty String, or not
235
+ # a String or Symbol representation of a valid state.
236
+ def sanitise_state(value)
237
+ if value.nil? || (value.is_a?(String) && value.empty?)
238
+ raise ArgumentError, _('State not specified.')
239
+ end
240
+
241
+ value = value.to_sym if value.is_a?(String)
242
+ unless value.is_a?(Symbol)
243
+ raise ArgumentError, _('State must be a Symbol, not %{type}') % { type: value.class }
244
+ end
245
+
246
+ valid_states = [:passed, :error, :failure, :skipped]
247
+ unless valid_states.include?(value)
248
+ raise ArgumentError, _('Invalid state %{state}. Valid states are: %{valid}.') % {
249
+ state: value.inspect,
250
+ valid: valid_states.map(&:inspect).join(', '),
251
+ }
252
+ end
253
+
254
+ value
255
+ end
256
+
257
+ # Validates the source of the event.
258
+ #
259
+ # @param value [String, Symbol] The name of the source of the event.
260
+ #
261
+ # @return [String] the value passed to the event, converted to a String
262
+ # if necessary.
263
+ #
264
+ # @raise [ArgumentError] if the value is nil or an empty String.
265
+ def sanitise_source(value)
266
+ if value.nil? || (value.is_a?(String) && value.empty?)
267
+ raise ArgumentError, _('Source not specified.')
268
+ end
269
+
270
+ value.to_s
271
+ end
272
+
273
+ # Munges the line number of the event into an Integer.
274
+ #
275
+ # @param value [Integer, String, Fixnum] The line number.
276
+ #
277
+ # @return [Integer] the provided value, converted into an Integer if
278
+ # necessary.
279
+ def sanitise_line(value)
280
+ return if value.nil?
281
+
282
+ valid_types = [String, Integer]
283
+ if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
284
+ valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
285
+ end
286
+
287
+ unless valid_types.include?(value.class)
288
+ raise ArgumentError, _('Line must be an Integer or a String representation of an Integer.')
289
+ end
290
+
291
+ if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
292
+ raise ArgumentError, _('The line number can contain only the digits 0-9.')
293
+ end
294
+
295
+ value.to_i
296
+ end
297
+
298
+ # Munges the column number of the event into an Integer.
299
+ #
300
+ # @param value [Integer, String, Fixnum] The column number.
301
+ #
302
+ # @return [Integer] the provided value, converted into an Integer if
303
+ # necessary.
304
+ def sanitise_column(value)
305
+ return if value.nil?
306
+
307
+ valid_types = [String, Integer]
308
+ if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
309
+ valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
310
+ end
311
+
312
+ unless valid_types.include?(value.class)
313
+ raise ArgumentError, _('Column must be an Integer or a String representation of an Integer.')
314
+ end
315
+
316
+ if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
317
+ raise ArgumentError, _('The column number can contain only the digits 0-9.')
318
+ end
319
+
320
+ value.to_i
321
+ end
322
+
323
+ # Cleans up provided stack trace by removing entries that are inside gems
324
+ # or the rspec binstub.
325
+ #
326
+ # @param value [Array] Array of stack trace lines
327
+ #
328
+ # @return [Array] Array of stack trace lines with less relevant lines excluded
329
+ def sanitise_trace(value)
330
+ return if value.nil?
331
+
332
+ valid_types = [Array]
333
+
334
+ unless valid_types.include?(value.class)
335
+ raise ArgumentError, _('Trace must be an Array of stack trace lines.')
336
+ end
337
+
338
+ # Drop any stacktrace lines that include '/gems/' in the path or
339
+ # are the original rspec binstub lines
340
+ value.reject do |line|
341
+ (line =~ %r{/gems/}) || (line =~ %r{bin/rspec:})
342
+ end
343
+ end
344
+
345
+ # Extract contextual information for the event from the file that it
346
+ # references.
347
+ #
348
+ # @param max_num_lines [Integer] The maximum number of lines to return.
349
+ #
350
+ # @return [Array] Array of lines from the file, centred on the line
351
+ # number of the event.
352
+ def context_lines(max_num_lines = 5)
353
+ return if file.nil? || line.nil?
354
+
355
+ file_path = [file, File.join(PDK::Util.module_root, file)].find do |path|
356
+ PDK::Util::Filesystem.file?(path)
357
+ end
358
+
359
+ return if file_path.nil?
360
+
361
+ file_content = PDK::Util::Filesystem.read_file(file_path).split("\n")
362
+ delta = (max_num_lines - 1) / 2
363
+ min = [0, (line - 1) - delta].max
364
+ max = [(line - 1) + delta, file_content.length].min
365
+
366
+ file_content[min..max].map { |r| " #{r}" }
367
+ end
368
+ end
369
+ end
370
+ end