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
data/lib/pdk/template.rb CHANGED
@@ -1,59 +1,59 @@
1
- require 'pdk'
2
-
3
- module PDK
4
- module Template
5
- autoload :Fetcher, 'pdk/template/fetcher'
6
- autoload :Renderer, 'pdk/template/renderer'
7
- autoload :TemplateDir, 'pdk/template/template_dir'
8
-
9
- MODULE_TEMPLATE_TYPE = :module_template
10
-
11
- # Creates a TemplateDir object with the path or URL to the template
12
- # and the block of code to run to be run while the template is available.
13
- #
14
- # The template directory is only guaranteed to be available on disk
15
- # within the scope of the block passed to this method.
16
- #
17
- # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the
18
- # template or a URI to a git repository.
19
- #
20
- # @param context [PDK::Context::AbstractContext] The context in which the template will render to
21
- #
22
- # @yieldparam self [PDK::Template::TemplateDir] The initialised object with
23
- # the template available on disk.
24
- #
25
- # @example Using a git repository as a template
26
- # PDK::Template.with('https://github.com/puppetlabs/pdk-templates') do |t|
27
- # t.render_module('module, PDK.context) do |filename, content, status|
28
- # File.open(filename, 'w') do |file|
29
- # ...
30
- # end
31
- # end
32
- # end
33
- #
34
- # @raise [ArgumentError] If no block is given to this method.
35
- # @raise [PDK::CLI::FatalError]
36
- # @raise [ArgumentError]
37
- #
38
- # @api public
39
- def self.with(uri, context)
40
- unless block_given?
41
- raise ArgumentError, _('%{class_name}.with must be passed a block.') % { class_name: name }
42
- end
43
- unless uri.is_a? PDK::Util::TemplateURI
44
- raise ArgumentError, _('%{class_name}.with must be passed a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class, class_name: name }
45
- end
46
-
47
- Fetcher.with(uri) do |fetcher|
48
- template_dir = TemplateDir.instance(uri, fetcher.path, context)
49
- template_dir.metadata = fetcher.metadata
50
-
51
- template_type = uri.default? ? 'default' : 'custom'
52
- PDK.analytics.event('TemplateDir', 'initialize', label: template_type)
53
-
54
- yield template_dir
55
- end
56
- nil
57
- end
58
- end
59
- end
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Template
5
+ autoload :Fetcher, 'pdk/template/fetcher'
6
+ autoload :Renderer, 'pdk/template/renderer'
7
+ autoload :TemplateDir, 'pdk/template/template_dir'
8
+
9
+ MODULE_TEMPLATE_TYPE = :module_template
10
+
11
+ # Creates a TemplateDir object with the path or URL to the template
12
+ # and the block of code to run to be run while the template is available.
13
+ #
14
+ # The template directory is only guaranteed to be available on disk
15
+ # within the scope of the block passed to this method.
16
+ #
17
+ # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the
18
+ # template or a URI to a git repository.
19
+ #
20
+ # @param context [PDK::Context::AbstractContext] The context in which the template will render to
21
+ #
22
+ # @yieldparam self [PDK::Template::TemplateDir] The initialised object with
23
+ # the template available on disk.
24
+ #
25
+ # @example Using a git repository as a template
26
+ # PDK::Template.with('https://github.com/puppetlabs/pdk-templates') do |t|
27
+ # t.render_module('module, PDK.context) do |filename, content, status|
28
+ # File.open(filename, 'w') do |file|
29
+ # ...
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # @raise [ArgumentError] If no block is given to this method.
35
+ # @raise [PDK::CLI::FatalError]
36
+ # @raise [ArgumentError]
37
+ #
38
+ # @api public
39
+ def self.with(uri, context)
40
+ unless block_given?
41
+ raise ArgumentError, _('%{class_name}.with must be passed a block.') % { class_name: name }
42
+ end
43
+ unless uri.is_a? PDK::Util::TemplateURI
44
+ raise ArgumentError, _('%{class_name}.with must be passed a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class, class_name: name }
45
+ end
46
+
47
+ Fetcher.with(uri) do |fetcher|
48
+ template_dir = TemplateDir.instance(uri, fetcher.path, context)
49
+ template_dir.metadata = fetcher.metadata
50
+
51
+ template_type = uri.default? ? 'default' : 'custom'
52
+ PDK.analytics.event('TemplateDir', 'initialize', label: template_type)
53
+
54
+ yield template_dir
55
+ end
56
+ nil
57
+ end
58
+ end
59
+ end
@@ -1,252 +1,252 @@
1
- require 'pdk'
2
-
3
- module PDK
4
- module Test
5
- class Unit
6
- def self.cmd(tests, opts = {})
7
- rake_args = opts[:parallel] ? 'parallel_spec_standalone' : 'spec_standalone'
8
- rake_args += "[#{tests}]" unless tests.nil? || tests.empty?
9
- rake_args
10
- end
11
-
12
- def self.rake_bin
13
- require 'pdk/util'
14
-
15
- @rake ||= File.join(PDK::Util.module_root, 'bin', 'rake')
16
- end
17
-
18
- def self.cmd_with_args(task)
19
- require 'pdk/util/ruby_version'
20
-
21
- argv = [rake_bin, task]
22
- argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe')) if Gem.win_platform?
23
- argv
24
- end
25
-
26
- def self.rake(task, spinner_text, environment = {})
27
- require 'pdk/cli/exec/command'
28
-
29
- command = PDK::CLI::Exec::Command.new(*cmd_with_args(task)).tap do |c|
30
- c.context = :module
31
- c.add_spinner(spinner_text) if spinner_text
32
- c.environment = environment
33
- end
34
-
35
- command.execute!
36
- end
37
-
38
- def self.interactive_rake(task, environment)
39
- require 'pdk/cli/exec/interactive_command'
40
-
41
- command = PDK::CLI::Exec::InteractiveCommand.new(*cmd_with_args(task)).tap do |c|
42
- c.context = :module
43
- c.environment = environment
44
- end
45
-
46
- command.execute!
47
- end
48
-
49
- def self.parallel_with_no_tests?(ran_in_parallel, json_result, result)
50
- ran_in_parallel && json_result.empty? &&
51
- ((!result[:exit_code].zero? && result[:stderr].strip =~ %r{Pass files or folders to run$}) ||
52
- result[:stderr].strip =~ %r{No files for parallel_spec to run against$})
53
- end
54
-
55
- def self.print_failure(result, exception)
56
- $stderr.puts ''
57
- result[:stdout].each_line { |line| $stderr.puts line.rstrip } unless result[:stdout].nil?
58
- result[:stderr].each_line { |line| $stderr.puts line.rstrip } unless result[:stderr].nil?
59
- $stderr.puts ''
60
- raise PDK::CLI::FatalError, exception
61
- end
62
-
63
- def self.tear_down
64
- result = rake('spec_clean', _('Cleaning up after running unit tests.'))
65
-
66
- return if result[:exit_code].zero?
67
-
68
- PDK.logger.error(_('The spec_clean rake task failed with the following error(s):'))
69
- print_failure(result, _('Failed to clean up after running unit tests'))
70
- end
71
-
72
- def self.setup
73
- result = rake('spec_prep', _('Preparing to run the unit tests.'))
74
-
75
- return if result[:exit_code].zero?
76
-
77
- tear_down
78
-
79
- PDK.logger.error(_('The spec_prep rake task failed with the following error(s):'))
80
- print_failure(result, _('Failed to prepare to run the unit tests.'))
81
- end
82
-
83
- def self.invoke(report, options = {})
84
- require 'pdk/util'
85
- require 'pdk/util/bundler'
86
-
87
- PDK::Util::Bundler.ensure_binstubs!('rake', 'rspec-core')
88
-
89
- setup
90
-
91
- tests = options[:tests]
92
- # Due to how rake handles paths in the command line options, any backslashed path (Windows platforms) needs to be converted
93
- # to forward slash. We can't use File.expand_path as the files aren't guaranteed to be on-disk
94
- #
95
- # Ref - https://github.com/puppetlabs/pdk/issues/828
96
- tests = tests.tr('\\', '/') unless tests.nil?
97
-
98
- environment = { 'CI_SPEC_OPTIONS' => '--format j' }
99
- environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
100
- spinner_msg = options[:parallel] ? _('Running unit tests in parallel.') : _('Running unit tests.')
101
-
102
- if options[:interactive]
103
- environment['CI_SPEC_OPTIONS'] = if options[:verbose]
104
- '--format documentation'
105
- else
106
- '--format progress'
107
- end
108
- result = interactive_rake(cmd(tests, options), environment)
109
- return result[:exit_code]
110
- end
111
-
112
- result = rake(cmd(tests, options), spinner_msg, environment)
113
-
114
- json_result = if options[:parallel]
115
- PDK::Util.find_all_json_in(result[:stdout])
116
- else
117
- PDK::Util.find_first_json_in(result[:stdout])
118
- end
119
-
120
- if parallel_with_no_tests?(options[:parallel], json_result, result)
121
- json_result = [{ 'messages' => ['No examples found.'] }]
122
- result[:exit_code] = 0
123
- end
124
-
125
- raise PDK::CLI::FatalError, _('Unit test output did not contain a valid JSON result: %{output}') % { output: result[:stdout] } if json_result.nil? || json_result.empty?
126
-
127
- json_result = merge_json_results(json_result) if options[:parallel]
128
-
129
- parse_output(report, json_result, result[:duration])
130
-
131
- result[:exit_code]
132
- ensure
133
- tear_down if options[:'clean-fixtures']
134
- end
135
-
136
- def self.parse_output(report, json_data, duration)
137
- # Output messages to stderr.
138
- json_data['messages'] && json_data['messages'].each { |msg| $stderr.puts msg }
139
-
140
- example_results = {
141
- # Only possibilities are passed, failed, pending:
142
- # https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/example.rb#L548
143
- 'passed' => [],
144
- 'failed' => [],
145
- 'pending' => [],
146
- }
147
-
148
- json_data['examples'] && json_data['examples'].each do |ex|
149
- example_results[ex['status']] << ex if example_results.key?(ex['status'])
150
- end
151
-
152
- example_results.each do |result, examples|
153
- # Translate rspec example results to JUnit XML testcase results
154
- state = case result
155
- when 'passed' then :passed
156
- when 'failed' then :failure
157
- when 'pending' then :skipped
158
- end
159
-
160
- examples.each do |ex|
161
- report.add_event(
162
- source: 'rspec',
163
- state: state,
164
- file: ex['file_path'],
165
- line: ex['line_number'],
166
- test: ex['full_description'],
167
- severity: ex['status'],
168
- message: ex['pending_message'] || (ex['exception'] && ex['exception']['message']) || nil,
169
- trace: (ex['exception'] && ex['exception']['backtrace']) || nil,
170
- )
171
- end
172
- end
173
-
174
- return unless json_data['summary']
175
-
176
- # TODO: standardize summary output
177
- $stderr.puts ' ' << _('Evaluated %{total} tests in %{duration} seconds: %{failures} failures, %{pending} pending.') % {
178
- total: json_data['summary']['example_count'],
179
- duration: duration,
180
- failures: json_data['summary']['failure_count'],
181
- pending: json_data['summary']['pending_count'],
182
- }
183
- end
184
-
185
- def self.merge_json_results(json_data)
186
- require 'set'
187
-
188
- merged_json_result = {}
189
-
190
- # Merge messages
191
- message_set = Set.new
192
- json_data.each do |json|
193
- next unless json['messages']
194
- message_set |= json['messages']
195
- end
196
- merged_json_result['messages'] = message_set.to_a
197
-
198
- # Merge examples
199
- all_examples = []
200
- json_data.each do |json|
201
- next unless json['examples']
202
- all_examples.concat json['examples']
203
- end
204
- merged_json_result['examples'] = all_examples
205
-
206
- # Merge summaries
207
- summary_hash = {
208
- 'example_count' => 0,
209
- 'failure_count' => 0,
210
- 'pending_count' => 0,
211
- }
212
- json_data.each do |json|
213
- next unless json['summary']
214
- summary_hash['example_count'] += json['summary']['example_count']
215
- summary_hash['failure_count'] += json['summary']['failure_count']
216
- summary_hash['pending_count'] += json['summary']['pending_count']
217
- end
218
- merged_json_result['summary'] = summary_hash
219
-
220
- merged_json_result
221
- end
222
-
223
- # @return array of { :id, :full_description }
224
- def self.list(options = {})
225
- require 'pdk/util'
226
- require 'pdk/util/bundler'
227
-
228
- PDK::Util::Bundler.ensure_binstubs!('rake')
229
-
230
- environment = {}
231
- environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
232
-
233
- output = rake('spec_list_json', _('Finding unit tests.'), environment)
234
-
235
- rspec_json = PDK::Util.find_first_json_in(output[:stdout])
236
- raise PDK::CLI::FatalError, _('Failed to find valid JSON in output from rspec: %{output}' % { output: output[:stdout] }) unless rspec_json
237
- if rspec_json['examples'].empty?
238
- rspec_message = rspec_json['messages'][0]
239
- return [] if rspec_message == 'No examples found.'
240
-
241
- raise PDK::CLI::FatalError, _('Unable to enumerate examples. rspec reported: %{message}' % { message: rspec_message })
242
- else
243
- examples = []
244
- rspec_json['examples'].each do |example|
245
- examples << { file_path: example['file_path'], id: example['id'], full_description: example['full_description'] }
246
- end
247
- examples
248
- end
249
- end
250
- end
251
- end
252
- end
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Test
5
+ class Unit
6
+ def self.cmd(tests, opts = {})
7
+ rake_args = opts[:parallel] ? 'parallel_spec_standalone' : 'spec_standalone'
8
+ rake_args += "[#{tests}]" unless tests.nil? || tests.empty?
9
+ rake_args
10
+ end
11
+
12
+ def self.rake_bin
13
+ require 'pdk/util'
14
+
15
+ @rake ||= File.join(PDK::Util.module_root, 'bin', 'rake')
16
+ end
17
+
18
+ def self.cmd_with_args(task)
19
+ require 'pdk/util/ruby_version'
20
+
21
+ argv = [rake_bin, task]
22
+ argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe')) if Gem.win_platform?
23
+ argv
24
+ end
25
+
26
+ def self.rake(task, spinner_text, environment = {})
27
+ require 'pdk/cli/exec/command'
28
+
29
+ command = PDK::CLI::Exec::Command.new(*cmd_with_args(task)).tap do |c|
30
+ c.context = :module
31
+ c.add_spinner(spinner_text) if spinner_text
32
+ c.environment = environment
33
+ end
34
+
35
+ command.execute!
36
+ end
37
+
38
+ def self.interactive_rake(task, environment)
39
+ require 'pdk/cli/exec/interactive_command'
40
+
41
+ command = PDK::CLI::Exec::InteractiveCommand.new(*cmd_with_args(task)).tap do |c|
42
+ c.context = :module
43
+ c.environment = environment
44
+ end
45
+
46
+ command.execute!
47
+ end
48
+
49
+ def self.parallel_with_no_tests?(ran_in_parallel, json_result, result)
50
+ ran_in_parallel && json_result.empty? &&
51
+ ((!result[:exit_code].zero? && result[:stderr].strip =~ %r{Pass files or folders to run$}) ||
52
+ result[:stderr].strip =~ %r{No files for parallel_spec to run against$})
53
+ end
54
+
55
+ def self.print_failure(result, exception)
56
+ $stderr.puts ''
57
+ result[:stdout].each_line { |line| $stderr.puts line.rstrip } unless result[:stdout].nil?
58
+ result[:stderr].each_line { |line| $stderr.puts line.rstrip } unless result[:stderr].nil?
59
+ $stderr.puts ''
60
+ raise PDK::CLI::FatalError, exception
61
+ end
62
+
63
+ def self.tear_down
64
+ result = rake('spec_clean', _('Cleaning up after running unit tests.'))
65
+
66
+ return if result[:exit_code].zero?
67
+
68
+ PDK.logger.error(_('The spec_clean rake task failed with the following error(s):'))
69
+ print_failure(result, _('Failed to clean up after running unit tests'))
70
+ end
71
+
72
+ def self.setup
73
+ result = rake('spec_prep', _('Preparing to run the unit tests.'))
74
+
75
+ return if result[:exit_code].zero?
76
+
77
+ tear_down
78
+
79
+ PDK.logger.error(_('The spec_prep rake task failed with the following error(s):'))
80
+ print_failure(result, _('Failed to prepare to run the unit tests.'))
81
+ end
82
+
83
+ def self.invoke(report, options = {})
84
+ require 'pdk/util'
85
+ require 'pdk/util/bundler'
86
+
87
+ PDK::Util::Bundler.ensure_binstubs!('rake', 'rspec-core')
88
+
89
+ setup
90
+
91
+ tests = options[:tests]
92
+ # Due to how rake handles paths in the command line options, any backslashed path (Windows platforms) needs to be converted
93
+ # to forward slash. We can't use File.expand_path as the files aren't guaranteed to be on-disk
94
+ #
95
+ # Ref - https://github.com/puppetlabs/pdk/issues/828
96
+ tests = tests.tr('\\', '/') unless tests.nil?
97
+
98
+ environment = { 'CI_SPEC_OPTIONS' => '--format j' }
99
+ environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
100
+ spinner_msg = options[:parallel] ? _('Running unit tests in parallel.') : _('Running unit tests.')
101
+
102
+ if options[:interactive]
103
+ environment['CI_SPEC_OPTIONS'] = if options[:verbose]
104
+ '--format documentation'
105
+ else
106
+ '--format progress'
107
+ end
108
+ result = interactive_rake(cmd(tests, options), environment)
109
+ return result[:exit_code]
110
+ end
111
+
112
+ result = rake(cmd(tests, options), spinner_msg, environment)
113
+
114
+ json_result = if options[:parallel]
115
+ PDK::Util.find_all_json_in(result[:stdout])
116
+ else
117
+ PDK::Util.find_first_json_in(result[:stdout])
118
+ end
119
+
120
+ if parallel_with_no_tests?(options[:parallel], json_result, result)
121
+ json_result = [{ 'messages' => ['No examples found.'] }]
122
+ result[:exit_code] = 0
123
+ end
124
+
125
+ raise PDK::CLI::FatalError, _('Unit test output did not contain a valid JSON result: %{output}') % { output: result[:stdout] } if json_result.nil? || json_result.empty?
126
+
127
+ json_result = merge_json_results(json_result) if options[:parallel]
128
+
129
+ parse_output(report, json_result, result[:duration])
130
+
131
+ result[:exit_code]
132
+ ensure
133
+ tear_down if options[:'clean-fixtures']
134
+ end
135
+
136
+ def self.parse_output(report, json_data, duration)
137
+ # Output messages to stderr.
138
+ json_data['messages'] && json_data['messages'].each { |msg| $stderr.puts msg }
139
+
140
+ example_results = {
141
+ # Only possibilities are passed, failed, pending:
142
+ # https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/example.rb#L548
143
+ 'passed' => [],
144
+ 'failed' => [],
145
+ 'pending' => [],
146
+ }
147
+
148
+ json_data['examples'] && json_data['examples'].each do |ex|
149
+ example_results[ex['status']] << ex if example_results.key?(ex['status'])
150
+ end
151
+
152
+ example_results.each do |result, examples|
153
+ # Translate rspec example results to JUnit XML testcase results
154
+ state = case result
155
+ when 'passed' then :passed
156
+ when 'failed' then :failure
157
+ when 'pending' then :skipped
158
+ end
159
+
160
+ examples.each do |ex|
161
+ report.add_event(
162
+ source: 'rspec',
163
+ state: state,
164
+ file: ex['file_path'],
165
+ line: ex['line_number'],
166
+ test: ex['full_description'],
167
+ severity: ex['status'],
168
+ message: ex['pending_message'] || (ex['exception'] && ex['exception']['message']) || nil,
169
+ trace: (ex['exception'] && ex['exception']['backtrace']) || nil,
170
+ )
171
+ end
172
+ end
173
+
174
+ return unless json_data['summary']
175
+
176
+ # TODO: standardize summary output
177
+ $stderr.puts ' ' << _('Evaluated %{total} tests in %{duration} seconds: %{failures} failures, %{pending} pending.') % {
178
+ total: json_data['summary']['example_count'],
179
+ duration: duration,
180
+ failures: json_data['summary']['failure_count'],
181
+ pending: json_data['summary']['pending_count'],
182
+ }
183
+ end
184
+
185
+ def self.merge_json_results(json_data)
186
+ require 'set'
187
+
188
+ merged_json_result = {}
189
+
190
+ # Merge messages
191
+ message_set = Set.new
192
+ json_data.each do |json|
193
+ next unless json['messages']
194
+ message_set |= json['messages']
195
+ end
196
+ merged_json_result['messages'] = message_set.to_a
197
+
198
+ # Merge examples
199
+ all_examples = []
200
+ json_data.each do |json|
201
+ next unless json['examples']
202
+ all_examples.concat json['examples']
203
+ end
204
+ merged_json_result['examples'] = all_examples
205
+
206
+ # Merge summaries
207
+ summary_hash = {
208
+ 'example_count' => 0,
209
+ 'failure_count' => 0,
210
+ 'pending_count' => 0,
211
+ }
212
+ json_data.each do |json|
213
+ next unless json['summary']
214
+ summary_hash['example_count'] += json['summary']['example_count']
215
+ summary_hash['failure_count'] += json['summary']['failure_count']
216
+ summary_hash['pending_count'] += json['summary']['pending_count']
217
+ end
218
+ merged_json_result['summary'] = summary_hash
219
+
220
+ merged_json_result
221
+ end
222
+
223
+ # @return array of { :id, :full_description }
224
+ def self.list(options = {})
225
+ require 'pdk/util'
226
+ require 'pdk/util/bundler'
227
+
228
+ PDK::Util::Bundler.ensure_binstubs!('rake')
229
+
230
+ environment = {}
231
+ environment['PUPPET_GEM_VERSION'] = options[:puppet] if options[:puppet]
232
+
233
+ output = rake('spec_list_json', _('Finding unit tests.'), environment)
234
+
235
+ rspec_json = PDK::Util.find_first_json_in(output[:stdout])
236
+ raise PDK::CLI::FatalError, _('Failed to find valid JSON in output from rspec: %{output}' % { output: output[:stdout] }) unless rspec_json
237
+ if rspec_json['examples'].empty?
238
+ rspec_message = rspec_json['messages'][0]
239
+ return [] if rspec_message == 'No examples found.'
240
+
241
+ raise PDK::CLI::FatalError, _('Unable to enumerate examples. rspec reported: %{message}' % { message: rspec_message })
242
+ else
243
+ examples = []
244
+ rspec_json['examples'].each do |example|
245
+ examples << { file_path: example['file_path'], id: example['id'], full_description: example['full_description'] }
246
+ end
247
+ examples
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end