pdk 2.3.0 → 2.4.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.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1329 -1321
  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 -277
  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 +2094 -2094
  153. metadata +5 -6
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