makit 0.0.138 → 0.0.140
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/README.md +41 -41
- data/exe/makit +5 -5
- data/lib/makit/apache.rb +28 -28
- data/lib/makit/auto.rb +48 -48
- data/lib/makit/cli/build_commands.rb +500 -500
- data/lib/makit/cli/generators/base_generator.rb +74 -74
- data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
- data/lib/makit/cli/generators/generator_factory.rb +49 -49
- data/lib/makit/cli/generators/node_generator.rb +50 -50
- data/lib/makit/cli/generators/ruby_generator.rb +77 -77
- data/lib/makit/cli/generators/rust_generator.rb +50 -50
- data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
- data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
- data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
- data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -40
- data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
- data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
- data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
- data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
- data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
- data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
- data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
- data/lib/makit/cli/main.rb +69 -67
- data/lib/makit/cli/project_commands.rb +868 -868
- data/lib/makit/cli/repository_commands.rb +661 -661
- data/lib/makit/cli/strategy_commands.rb +51 -0
- data/lib/makit/cli/utility_commands.rb +521 -521
- data/lib/makit/commands/factory.rb +359 -359
- data/lib/makit/commands/middleware/base.rb +73 -73
- data/lib/makit/commands/middleware/cache.rb +248 -248
- data/lib/makit/commands/middleware/command_logger.rb +312 -312
- data/lib/makit/commands/middleware/validator.rb +269 -269
- data/lib/makit/commands/request.rb +316 -316
- data/lib/makit/commands/result.rb +323 -323
- data/lib/makit/commands/runner.rb +385 -385
- data/lib/makit/commands/strategies/base.rb +171 -171
- data/lib/makit/commands/strategies/child_process.rb +1 -1
- data/lib/makit/commands/strategies/synchronous.rb +139 -139
- data/lib/makit/commands.rb +50 -50
- data/lib/makit/configuration/dotnet_project.rb +12 -12
- data/lib/makit/configuration/gitlab_helper.rb +58 -58
- data/lib/makit/configuration/project.rb +168 -168
- data/lib/makit/configuration/rakefile_helper.rb +43 -43
- data/lib/makit/configuration/step.rb +34 -34
- data/lib/makit/configuration/timeout.rb +74 -0
- data/lib/makit/configuration.rb +15 -14
- data/lib/makit/content/default_gitignore.rb +7 -7
- data/lib/makit/content/default_gitignore.txt +225 -225
- data/lib/makit/content/default_rakefile.rb +13 -13
- data/lib/makit/content/gem_rakefile.rb +16 -16
- data/lib/makit/context.rb +1 -1
- data/lib/makit/data.rb +49 -49
- data/lib/makit/directories.rb +140 -140
- data/lib/makit/directory.rb +262 -262
- data/lib/makit/docs/files.rb +89 -89
- data/lib/makit/docs/rake.rb +102 -102
- data/lib/makit/dotnet/cli.rb +69 -69
- data/lib/makit/dotnet/project.rb +217 -217
- data/lib/makit/dotnet/solution.rb +38 -38
- data/lib/makit/dotnet/solution_classlib.rb +239 -239
- data/lib/makit/dotnet/solution_console.rb +264 -264
- data/lib/makit/dotnet/solution_maui.rb +354 -354
- data/lib/makit/dotnet/solution_wasm.rb +275 -275
- data/lib/makit/dotnet/solution_wpf.rb +304 -304
- data/lib/makit/dotnet.rb +102 -102
- data/lib/makit/email.rb +90 -90
- data/lib/makit/environment.rb +142 -142
- data/lib/makit/examples/runner.rb +370 -370
- data/lib/makit/exceptions.rb +45 -45
- data/lib/makit/fileinfo.rb +24 -24
- data/lib/makit/files.rb +43 -43
- data/lib/makit/gems.rb +40 -40
- data/lib/makit/git/cli.rb +54 -54
- data/lib/makit/git/repository.rb +90 -90
- data/lib/makit/git.rb +98 -98
- data/lib/makit/gitlab_runner.rb +59 -59
- data/lib/makit/humanize.rb +137 -137
- data/lib/makit/indexer.rb +47 -47
- data/lib/makit/logging/configuration.rb +308 -308
- data/lib/makit/logging/format_registry.rb +84 -84
- data/lib/makit/logging/formatters/base.rb +39 -39
- data/lib/makit/logging/formatters/console_formatter.rb +140 -140
- data/lib/makit/logging/formatters/json_formatter.rb +65 -65
- data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
- data/lib/makit/logging/formatters/text_formatter.rb +64 -64
- data/lib/makit/logging/log_request.rb +119 -119
- data/lib/makit/logging/logger.rb +199 -199
- data/lib/makit/logging/sinks/base.rb +91 -91
- data/lib/makit/logging/sinks/console.rb +72 -72
- data/lib/makit/logging/sinks/file_sink.rb +92 -92
- data/lib/makit/logging/sinks/structured.rb +123 -123
- data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
- data/lib/makit/logging.rb +565 -565
- data/lib/makit/markdown.rb +75 -75
- data/lib/makit/mp/basic_object_mp.rb +17 -17
- data/lib/makit/mp/command_mp.rb +13 -13
- data/lib/makit/mp/command_request.mp.rb +17 -17
- data/lib/makit/mp/project_mp.rb +199 -199
- data/lib/makit/mp/string_mp.rb +191 -191
- data/lib/makit/nuget.rb +74 -74
- data/lib/makit/port.rb +32 -32
- data/lib/makit/process.rb +163 -163
- data/lib/makit/protoc.rb +107 -107
- data/lib/makit/rake/cli.rb +196 -196
- data/lib/makit/rake.rb +80 -80
- data/lib/makit/ruby/cli.rb +185 -185
- data/lib/makit/ruby.rb +25 -25
- data/lib/makit/secrets.rb +51 -51
- data/lib/makit/serializer.rb +130 -130
- data/lib/makit/services/builder.rb +186 -186
- data/lib/makit/services/error_handler.rb +226 -226
- data/lib/makit/services/repository_manager.rb +231 -231
- data/lib/makit/services/validator.rb +112 -112
- data/lib/makit/setup/classlib.rb +101 -101
- data/lib/makit/setup/gem.rb +268 -268
- data/lib/makit/setup/razorclasslib.rb +101 -101
- data/lib/makit/setup/runner.rb +54 -54
- data/lib/makit/setup.rb +5 -5
- data/lib/makit/show.rb +110 -110
- data/lib/makit/storage.rb +126 -126
- data/lib/makit/symbols.rb +170 -170
- data/lib/makit/task_info.rb +130 -130
- data/lib/makit/tasks/at_exit.rb +15 -15
- data/lib/makit/tasks/build.rb +22 -22
- data/lib/makit/tasks/clean.rb +13 -13
- data/lib/makit/tasks/configure.rb +10 -10
- data/lib/makit/tasks/format.rb +10 -10
- data/lib/makit/tasks/hook_manager.rb +443 -443
- data/lib/makit/tasks/init.rb +49 -49
- data/lib/makit/tasks/integrate.rb +29 -29
- data/lib/makit/tasks/pull_incoming.rb +13 -13
- data/lib/makit/tasks/setup.rb +13 -13
- data/lib/makit/tasks/sync.rb +17 -17
- data/lib/makit/tasks/tag.rb +16 -16
- data/lib/makit/tasks/task_monkey_patch.rb +81 -81
- data/lib/makit/tasks/test.rb +22 -22
- data/lib/makit/tasks/update.rb +18 -18
- data/lib/makit/tasks.rb +20 -20
- data/lib/makit/test_cache.rb +239 -239
- data/lib/makit/tree.rb +37 -37
- data/lib/makit/v1/makit.v1_pb.rb +35 -35
- data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
- data/lib/makit/version.rb +99 -99
- data/lib/makit/version_util.rb +21 -21
- data/lib/makit/wix.rb +95 -95
- data/lib/makit/yaml.rb +29 -29
- data/lib/makit/zip.rb +17 -17
- data/lib/makit copy.rb +44 -44
- data/lib/makit.rb +42 -42
- metadata +3 -2
@@ -1,370 +1,370 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "fileutils"
|
4
|
-
require "parallel"
|
5
|
-
|
6
|
-
module Makit
|
7
|
-
module Examples
|
8
|
-
# Centralized management of example discovery, execution, and verification
|
9
|
-
#
|
10
|
-
# This class provides a clean interface for running all examples in the
|
11
|
-
# examples directory, with support for different execution strategies,
|
12
|
-
# artifact verification, and comprehensive reporting.
|
13
|
-
#
|
14
|
-
# @example Basic usage
|
15
|
-
# runner = Makit::Examples::Runner.new
|
16
|
-
# runner.run_all
|
17
|
-
#
|
18
|
-
# @example Custom configuration
|
19
|
-
# runner = Makit::Examples::Runner.new(
|
20
|
-
# strategy: :parallel,
|
21
|
-
# verify_artifacts: true,
|
22
|
-
# cleanup_after: false
|
23
|
-
# )
|
24
|
-
# runner.run_all
|
25
|
-
class Runner
|
26
|
-
# Configuration options
|
27
|
-
attr_reader :examples_dir, :execution_strategy, :verify_artifacts, :cleanup_after, :filter, :timeout, :verbose
|
28
|
-
|
29
|
-
# Execution results
|
30
|
-
attr_reader :results, :failed_examples, :passed_examples, :examples
|
31
|
-
|
32
|
-
# Initialize the examples runner
|
33
|
-
#
|
34
|
-
# @param options [Hash] configuration options
|
35
|
-
# @option options [String] :examples_dir directory containing examples (default: "examples")
|
36
|
-
# @option options [Symbol] :strategy execution strategy (:sequential, :parallel, :filtered)
|
37
|
-
# @option options [Boolean] :verify_artifacts whether to verify expected artifacts (default: true)
|
38
|
-
# @option options [Boolean] :cleanup_after whether to clean up artifacts after testing (default: true)
|
39
|
-
# @option options [Regexp] :filter pattern to filter examples (default: nil)
|
40
|
-
# @option options [Integer] :timeout timeout per example in seconds (default: 30)
|
41
|
-
# @option options [Boolean] :verbose whether to show detailed output (default: false)
|
42
|
-
def initialize(options = {})
|
43
|
-
@examples_dir = options[:examples_dir] || "examples"
|
44
|
-
@execution_strategy = options[:strategy] || :sequential
|
45
|
-
@verify_artifacts = options[:verify_artifacts] || true
|
46
|
-
@cleanup_after = options[:cleanup_after] || true
|
47
|
-
@filter = options[:filter]
|
48
|
-
@timeout = options[:timeout] || 30
|
49
|
-
@verbose = options[:verbose] || false
|
50
|
-
|
51
|
-
@results = []
|
52
|
-
@failed_examples = []
|
53
|
-
@passed_examples = []
|
54
|
-
@examples = []
|
55
|
-
end
|
56
|
-
|
57
|
-
# Run all discovered examples
|
58
|
-
#
|
59
|
-
# @return [Boolean] true if all examples passed, false otherwise
|
60
|
-
def run_all
|
61
|
-
discover_examples
|
62
|
-
execute_examples
|
63
|
-
verify_results
|
64
|
-
report_results
|
65
|
-
cleanup_if_needed
|
66
|
-
|
67
|
-
@failed_examples.empty?
|
68
|
-
end
|
69
|
-
|
70
|
-
# Run a specific example
|
71
|
-
#
|
72
|
-
# @param example_path [String] path to the example directory
|
73
|
-
# @return [Hash] result hash with success status and details
|
74
|
-
def run_example(example_path)
|
75
|
-
discover_examples if @examples.empty?
|
76
|
-
example = @examples.find { |ex| ex[:name] == example_path }
|
77
|
-
return { success: false, error: "Example not found: #{example_path}" } unless example
|
78
|
-
|
79
|
-
execute_single_example(example)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Discover all examples in the examples directory
|
83
|
-
def discover_examples
|
84
|
-
@examples = Dir.glob("#{@examples_dir}/**/Rakefile").map do |rakefile|
|
85
|
-
example_dir = File.dirname(rakefile)
|
86
|
-
example_name = example_dir.gsub("#{@examples_dir}/", "")
|
87
|
-
|
88
|
-
{
|
89
|
-
path: example_dir,
|
90
|
-
name: example_name,
|
91
|
-
rakefile: rakefile,
|
92
|
-
expected_artifacts: determine_expected_artifacts(example_name),
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
# Apply filter if specified
|
97
|
-
@examples = @examples.select { |ex| ex[:name].match?(@filter) } if @filter
|
98
|
-
|
99
|
-
log "Discovered #{@examples.count} examples" if @verbose
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
|
104
|
-
# Execute all examples based on the configured strategy
|
105
|
-
def execute_examples
|
106
|
-
case @execution_strategy
|
107
|
-
when :sequential
|
108
|
-
execute_sequential
|
109
|
-
when :parallel
|
110
|
-
execute_parallel
|
111
|
-
when :filtered
|
112
|
-
execute_filtered
|
113
|
-
else
|
114
|
-
execute_sequential
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Execute examples sequentially
|
119
|
-
def execute_sequential
|
120
|
-
@examples.each do |example|
|
121
|
-
result = execute_single_example(example)
|
122
|
-
|
123
|
-
# Verify artifacts immediately after execution if enabled
|
124
|
-
verify_example_artifacts(example, result) if @verify_artifacts && result[:success]
|
125
|
-
|
126
|
-
@results << result
|
127
|
-
|
128
|
-
if result[:success]
|
129
|
-
@passed_examples << example
|
130
|
-
log " ✅ #{example[:name]} passed".colorize(:green) if @verbose
|
131
|
-
else
|
132
|
-
@failed_examples << example
|
133
|
-
log " ❌ #{example[:name]} failed: #{result[:error]}".colorize(:red) if @verbose
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# Execute examples in parallel
|
139
|
-
def execute_parallel
|
140
|
-
results = Parallel.map(@examples, in_threads: 4) do |example|
|
141
|
-
result = execute_single_example(example)
|
142
|
-
|
143
|
-
# Verify artifacts immediately after execution if enabled
|
144
|
-
verify_example_artifacts(example, result) if @verify_artifacts && result[:success]
|
145
|
-
|
146
|
-
result
|
147
|
-
end
|
148
|
-
|
149
|
-
results.each_with_index do |result, index|
|
150
|
-
@results << result
|
151
|
-
example = @examples[index]
|
152
|
-
|
153
|
-
if result[:success]
|
154
|
-
@passed_examples << example
|
155
|
-
log " ✅ #{example[:name]} passed".colorize(:green) if @verbose
|
156
|
-
else
|
157
|
-
@failed_examples << example
|
158
|
-
log " ❌ #{example[:name]} failed: #{result[:error]}".colorize(:red) if @verbose
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Execute examples with filtering (same as sequential for now)
|
164
|
-
def execute_filtered
|
165
|
-
execute_sequential
|
166
|
-
end
|
167
|
-
|
168
|
-
# Execute a single example
|
169
|
-
#
|
170
|
-
# @param example [Hash] example configuration
|
171
|
-
# @return [Hash] result hash
|
172
|
-
def execute_single_example(example)
|
173
|
-
# Display separator and example path
|
174
|
-
puts "=" * 80
|
175
|
-
# puts example[:name]
|
176
|
-
puts example[:rakefile]
|
177
|
-
|
178
|
-
log " Testing example: #{example[:name]}" if @verbose
|
179
|
-
|
180
|
-
# Store current directory
|
181
|
-
original_dir = Dir.pwd
|
182
|
-
|
183
|
-
begin
|
184
|
-
Dir.chdir(example[:path]) do
|
185
|
-
# Use the default runner with middleware for consistent behavior
|
186
|
-
runner = Makit::DEFAULT_COMMANDS_RUNNER
|
187
|
-
Makit::Logging::Logger.new(
|
188
|
-
Makit::Logging::Sinks::Console.new,
|
189
|
-
Makit::Logging::Sinks::FileSink.new(log_file: "artifacts/command_examples.log")
|
190
|
-
)
|
191
|
-
|
192
|
-
begin
|
193
|
-
request = Makit::Commands::Request.from_string("rake default")
|
194
|
-
result = runner.execute(request)
|
195
|
-
|
196
|
-
# Log success or error for each example
|
197
|
-
if result.success?
|
198
|
-
Makit::Logging.success(example[:name])
|
199
|
-
else
|
200
|
-
Makit::Logging.error(example[:name])
|
201
|
-
end
|
202
|
-
|
203
|
-
{
|
204
|
-
example: example,
|
205
|
-
success: result.success?,
|
206
|
-
exit_code: result.exit_code,
|
207
|
-
output: result.stdout,
|
208
|
-
error: result.stderr,
|
209
|
-
duration: result.duration,
|
210
|
-
}
|
211
|
-
rescue StandardError => e
|
212
|
-
# Log error for exceptions
|
213
|
-
Makit::Logging.error(example[:name])
|
214
|
-
|
215
|
-
{
|
216
|
-
example: example,
|
217
|
-
success: false,
|
218
|
-
exit_code: -1,
|
219
|
-
output: "",
|
220
|
-
error: e.message,
|
221
|
-
duration: 0,
|
222
|
-
}
|
223
|
-
end
|
224
|
-
end
|
225
|
-
rescue StandardError => e
|
226
|
-
# Log error for directory change failures
|
227
|
-
Makit::Logging.error(example[:name])
|
228
|
-
|
229
|
-
{
|
230
|
-
example: example,
|
231
|
-
success: false,
|
232
|
-
exit_code: -1,
|
233
|
-
output: "",
|
234
|
-
error: "Failed to change directory: #{e.message}",
|
235
|
-
duration: 0,
|
236
|
-
}
|
237
|
-
ensure
|
238
|
-
# Always return to original directory
|
239
|
-
begin
|
240
|
-
Dir.chdir(original_dir)
|
241
|
-
rescue StandardError
|
242
|
-
nil
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# Determine expected artifacts for an example
|
248
|
-
#
|
249
|
-
# @param example_name [String] name of the example
|
250
|
-
# @return [Array<String>] list of expected artifact paths
|
251
|
-
def determine_expected_artifacts(example_name)
|
252
|
-
case example_name
|
253
|
-
when %r{commands/(default_runner|runner)}
|
254
|
-
["artifacts/commands/git_version.log", "artifacts/commands/git_version.txt"]
|
255
|
-
when "protoc"
|
256
|
-
["artifacts/makit.v1_pb.rb"]
|
257
|
-
when "rake_default"
|
258
|
-
[".makit.project.json"]
|
259
|
-
when "run_mp"
|
260
|
-
[] # No expected artifacts
|
261
|
-
when "tasks/simple"
|
262
|
-
[] # No expected artifacts
|
263
|
-
when "rubygem-foo"
|
264
|
-
[] # No expected artifacts
|
265
|
-
else
|
266
|
-
[] # Default to no expected artifacts
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# Verify artifacts for a single example immediately after execution
|
271
|
-
#
|
272
|
-
# @param example [Hash] example configuration
|
273
|
-
# @param result [Hash] execution result
|
274
|
-
def verify_example_artifacts(example, result)
|
275
|
-
return unless @verify_artifacts
|
276
|
-
|
277
|
-
# Check artifacts in the example directory context
|
278
|
-
missing_artifacts = example[:expected_artifacts].reject do |artifact|
|
279
|
-
artifact_path = File.join(example[:path], artifact)
|
280
|
-
File.exist?(artifact_path)
|
281
|
-
end
|
282
|
-
|
283
|
-
return unless missing_artifacts.any?
|
284
|
-
|
285
|
-
result[:success] = false
|
286
|
-
result[:error] = "Missing expected artifacts: #{missing_artifacts.join(", ")}"
|
287
|
-
end
|
288
|
-
|
289
|
-
# Verify that all results meet expectations
|
290
|
-
def verify_results
|
291
|
-
return unless @verify_artifacts
|
292
|
-
|
293
|
-
@results.each do |result|
|
294
|
-
next unless result[:success]
|
295
|
-
|
296
|
-
example = result[:example]
|
297
|
-
|
298
|
-
# Check artifacts in the example directory context
|
299
|
-
missing_artifacts = example[:expected_artifacts].reject do |artifact|
|
300
|
-
artifact_path = File.join(example[:path], artifact)
|
301
|
-
File.exist?(artifact_path)
|
302
|
-
end
|
303
|
-
|
304
|
-
next unless missing_artifacts.any?
|
305
|
-
|
306
|
-
result[:success] = false
|
307
|
-
result[:error] = "Missing expected artifacts: #{missing_artifacts.join(", ")}"
|
308
|
-
|
309
|
-
# Move from passed to failed
|
310
|
-
@passed_examples.delete(example)
|
311
|
-
@failed_examples << example
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
# Clean up artifacts if configured
|
316
|
-
def cleanup_if_needed
|
317
|
-
return unless @cleanup_after
|
318
|
-
|
319
|
-
@examples.each do |example|
|
320
|
-
cleanup_example(example)
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
# Clean up artifacts for a specific example
|
325
|
-
#
|
326
|
-
# @param example [Hash] example configuration
|
327
|
-
def cleanup_example(example)
|
328
|
-
Dir.chdir(example[:path]) do
|
329
|
-
# Remove artifacts directory if it exists
|
330
|
-
FileUtils.rm_rf("artifacts")
|
331
|
-
|
332
|
-
# Remove project file if it exists
|
333
|
-
FileUtils.rm_f(".makit.project.json")
|
334
|
-
end
|
335
|
-
rescue StandardError => e
|
336
|
-
log " Warning: Failed to cleanup #{example[:name]}: #{e.message}" if @verbose
|
337
|
-
end
|
338
|
-
|
339
|
-
# Report the results of example execution
|
340
|
-
def report_results
|
341
|
-
puts "📊 Example Test Results:".colorize(:blue)
|
342
|
-
puts " ✅ Passed: #{@passed_examples.count}".colorize(:green)
|
343
|
-
puts " ❌ Failed: #{@failed_examples.count}".colorize(:red)
|
344
|
-
|
345
|
-
if @failed_examples.any?
|
346
|
-
puts " Failed examples:".colorize(:red)
|
347
|
-
@failed_examples.each do |example|
|
348
|
-
result = @results.find { |r| r[:example] == example }
|
349
|
-
error_msg = result ? result[:error] : "Unknown error"
|
350
|
-
puts " - #{example[:name]}: #{error_msg}".colorize(:red)
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
return unless @passed_examples.any? && @verbose
|
355
|
-
|
356
|
-
puts " Passed examples:".colorize(:green)
|
357
|
-
@passed_examples.each do |example|
|
358
|
-
puts " - #{example[:name]}".colorize(:green)
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
# Log a message if verbose mode is enabled
|
363
|
-
#
|
364
|
-
# @param message [String] message to log
|
365
|
-
def log(message)
|
366
|
-
puts message if @verbose
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "parallel"
|
5
|
+
|
6
|
+
module Makit
|
7
|
+
module Examples
|
8
|
+
# Centralized management of example discovery, execution, and verification
|
9
|
+
#
|
10
|
+
# This class provides a clean interface for running all examples in the
|
11
|
+
# examples directory, with support for different execution strategies,
|
12
|
+
# artifact verification, and comprehensive reporting.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# runner = Makit::Examples::Runner.new
|
16
|
+
# runner.run_all
|
17
|
+
#
|
18
|
+
# @example Custom configuration
|
19
|
+
# runner = Makit::Examples::Runner.new(
|
20
|
+
# strategy: :parallel,
|
21
|
+
# verify_artifacts: true,
|
22
|
+
# cleanup_after: false
|
23
|
+
# )
|
24
|
+
# runner.run_all
|
25
|
+
class Runner
|
26
|
+
# Configuration options
|
27
|
+
attr_reader :examples_dir, :execution_strategy, :verify_artifacts, :cleanup_after, :filter, :timeout, :verbose
|
28
|
+
|
29
|
+
# Execution results
|
30
|
+
attr_reader :results, :failed_examples, :passed_examples, :examples
|
31
|
+
|
32
|
+
# Initialize the examples runner
|
33
|
+
#
|
34
|
+
# @param options [Hash] configuration options
|
35
|
+
# @option options [String] :examples_dir directory containing examples (default: "examples")
|
36
|
+
# @option options [Symbol] :strategy execution strategy (:sequential, :parallel, :filtered)
|
37
|
+
# @option options [Boolean] :verify_artifacts whether to verify expected artifacts (default: true)
|
38
|
+
# @option options [Boolean] :cleanup_after whether to clean up artifacts after testing (default: true)
|
39
|
+
# @option options [Regexp] :filter pattern to filter examples (default: nil)
|
40
|
+
# @option options [Integer] :timeout timeout per example in seconds (default: 30)
|
41
|
+
# @option options [Boolean] :verbose whether to show detailed output (default: false)
|
42
|
+
def initialize(options = {})
|
43
|
+
@examples_dir = options[:examples_dir] || "examples"
|
44
|
+
@execution_strategy = options[:strategy] || :sequential
|
45
|
+
@verify_artifacts = options[:verify_artifacts] || true
|
46
|
+
@cleanup_after = options[:cleanup_after] || true
|
47
|
+
@filter = options[:filter]
|
48
|
+
@timeout = options[:timeout] || 30
|
49
|
+
@verbose = options[:verbose] || false
|
50
|
+
|
51
|
+
@results = []
|
52
|
+
@failed_examples = []
|
53
|
+
@passed_examples = []
|
54
|
+
@examples = []
|
55
|
+
end
|
56
|
+
|
57
|
+
# Run all discovered examples
|
58
|
+
#
|
59
|
+
# @return [Boolean] true if all examples passed, false otherwise
|
60
|
+
def run_all
|
61
|
+
discover_examples
|
62
|
+
execute_examples
|
63
|
+
verify_results
|
64
|
+
report_results
|
65
|
+
cleanup_if_needed
|
66
|
+
|
67
|
+
@failed_examples.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
# Run a specific example
|
71
|
+
#
|
72
|
+
# @param example_path [String] path to the example directory
|
73
|
+
# @return [Hash] result hash with success status and details
|
74
|
+
def run_example(example_path)
|
75
|
+
discover_examples if @examples.empty?
|
76
|
+
example = @examples.find { |ex| ex[:name] == example_path }
|
77
|
+
return { success: false, error: "Example not found: #{example_path}" } unless example
|
78
|
+
|
79
|
+
execute_single_example(example)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Discover all examples in the examples directory
|
83
|
+
def discover_examples
|
84
|
+
@examples = Dir.glob("#{@examples_dir}/**/Rakefile").map do |rakefile|
|
85
|
+
example_dir = File.dirname(rakefile)
|
86
|
+
example_name = example_dir.gsub("#{@examples_dir}/", "")
|
87
|
+
|
88
|
+
{
|
89
|
+
path: example_dir,
|
90
|
+
name: example_name,
|
91
|
+
rakefile: rakefile,
|
92
|
+
expected_artifacts: determine_expected_artifacts(example_name),
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Apply filter if specified
|
97
|
+
@examples = @examples.select { |ex| ex[:name].match?(@filter) } if @filter
|
98
|
+
|
99
|
+
log "Discovered #{@examples.count} examples" if @verbose
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Execute all examples based on the configured strategy
|
105
|
+
def execute_examples
|
106
|
+
case @execution_strategy
|
107
|
+
when :sequential
|
108
|
+
execute_sequential
|
109
|
+
when :parallel
|
110
|
+
execute_parallel
|
111
|
+
when :filtered
|
112
|
+
execute_filtered
|
113
|
+
else
|
114
|
+
execute_sequential
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Execute examples sequentially
|
119
|
+
def execute_sequential
|
120
|
+
@examples.each do |example|
|
121
|
+
result = execute_single_example(example)
|
122
|
+
|
123
|
+
# Verify artifacts immediately after execution if enabled
|
124
|
+
verify_example_artifacts(example, result) if @verify_artifacts && result[:success]
|
125
|
+
|
126
|
+
@results << result
|
127
|
+
|
128
|
+
if result[:success]
|
129
|
+
@passed_examples << example
|
130
|
+
log " ✅ #{example[:name]} passed".colorize(:green) if @verbose
|
131
|
+
else
|
132
|
+
@failed_examples << example
|
133
|
+
log " ❌ #{example[:name]} failed: #{result[:error]}".colorize(:red) if @verbose
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Execute examples in parallel
|
139
|
+
def execute_parallel
|
140
|
+
results = Parallel.map(@examples, in_threads: 4) do |example|
|
141
|
+
result = execute_single_example(example)
|
142
|
+
|
143
|
+
# Verify artifacts immediately after execution if enabled
|
144
|
+
verify_example_artifacts(example, result) if @verify_artifacts && result[:success]
|
145
|
+
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
results.each_with_index do |result, index|
|
150
|
+
@results << result
|
151
|
+
example = @examples[index]
|
152
|
+
|
153
|
+
if result[:success]
|
154
|
+
@passed_examples << example
|
155
|
+
log " ✅ #{example[:name]} passed".colorize(:green) if @verbose
|
156
|
+
else
|
157
|
+
@failed_examples << example
|
158
|
+
log " ❌ #{example[:name]} failed: #{result[:error]}".colorize(:red) if @verbose
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Execute examples with filtering (same as sequential for now)
|
164
|
+
def execute_filtered
|
165
|
+
execute_sequential
|
166
|
+
end
|
167
|
+
|
168
|
+
# Execute a single example
|
169
|
+
#
|
170
|
+
# @param example [Hash] example configuration
|
171
|
+
# @return [Hash] result hash
|
172
|
+
def execute_single_example(example)
|
173
|
+
# Display separator and example path
|
174
|
+
puts "=" * 80
|
175
|
+
# puts example[:name]
|
176
|
+
puts example[:rakefile]
|
177
|
+
|
178
|
+
log " Testing example: #{example[:name]}" if @verbose
|
179
|
+
|
180
|
+
# Store current directory
|
181
|
+
original_dir = Dir.pwd
|
182
|
+
|
183
|
+
begin
|
184
|
+
Dir.chdir(example[:path]) do
|
185
|
+
# Use the default runner with middleware for consistent behavior
|
186
|
+
runner = Makit::DEFAULT_COMMANDS_RUNNER
|
187
|
+
Makit::Logging::Logger.new(
|
188
|
+
Makit::Logging::Sinks::Console.new,
|
189
|
+
Makit::Logging::Sinks::FileSink.new(log_file: "artifacts/command_examples.log")
|
190
|
+
)
|
191
|
+
|
192
|
+
begin
|
193
|
+
request = Makit::Commands::Request.from_string("rake default")
|
194
|
+
result = runner.execute(request)
|
195
|
+
|
196
|
+
# Log success or error for each example
|
197
|
+
if result.success?
|
198
|
+
Makit::Logging.success(example[:name])
|
199
|
+
else
|
200
|
+
Makit::Logging.error(example[:name])
|
201
|
+
end
|
202
|
+
|
203
|
+
{
|
204
|
+
example: example,
|
205
|
+
success: result.success?,
|
206
|
+
exit_code: result.exit_code,
|
207
|
+
output: result.stdout,
|
208
|
+
error: result.stderr,
|
209
|
+
duration: result.duration,
|
210
|
+
}
|
211
|
+
rescue StandardError => e
|
212
|
+
# Log error for exceptions
|
213
|
+
Makit::Logging.error(example[:name])
|
214
|
+
|
215
|
+
{
|
216
|
+
example: example,
|
217
|
+
success: false,
|
218
|
+
exit_code: -1,
|
219
|
+
output: "",
|
220
|
+
error: e.message,
|
221
|
+
duration: 0,
|
222
|
+
}
|
223
|
+
end
|
224
|
+
end
|
225
|
+
rescue StandardError => e
|
226
|
+
# Log error for directory change failures
|
227
|
+
Makit::Logging.error(example[:name])
|
228
|
+
|
229
|
+
{
|
230
|
+
example: example,
|
231
|
+
success: false,
|
232
|
+
exit_code: -1,
|
233
|
+
output: "",
|
234
|
+
error: "Failed to change directory: #{e.message}",
|
235
|
+
duration: 0,
|
236
|
+
}
|
237
|
+
ensure
|
238
|
+
# Always return to original directory
|
239
|
+
begin
|
240
|
+
Dir.chdir(original_dir)
|
241
|
+
rescue StandardError
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Determine expected artifacts for an example
|
248
|
+
#
|
249
|
+
# @param example_name [String] name of the example
|
250
|
+
# @return [Array<String>] list of expected artifact paths
|
251
|
+
def determine_expected_artifacts(example_name)
|
252
|
+
case example_name
|
253
|
+
when %r{commands/(default_runner|runner)}
|
254
|
+
["artifacts/commands/git_version.log", "artifacts/commands/git_version.txt"]
|
255
|
+
when "protoc"
|
256
|
+
["artifacts/makit.v1_pb.rb"]
|
257
|
+
when "rake_default"
|
258
|
+
[".makit.project.json"]
|
259
|
+
when "run_mp"
|
260
|
+
[] # No expected artifacts
|
261
|
+
when "tasks/simple"
|
262
|
+
[] # No expected artifacts
|
263
|
+
when "rubygem-foo"
|
264
|
+
[] # No expected artifacts
|
265
|
+
else
|
266
|
+
[] # Default to no expected artifacts
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Verify artifacts for a single example immediately after execution
|
271
|
+
#
|
272
|
+
# @param example [Hash] example configuration
|
273
|
+
# @param result [Hash] execution result
|
274
|
+
def verify_example_artifacts(example, result)
|
275
|
+
return unless @verify_artifacts
|
276
|
+
|
277
|
+
# Check artifacts in the example directory context
|
278
|
+
missing_artifacts = example[:expected_artifacts].reject do |artifact|
|
279
|
+
artifact_path = File.join(example[:path], artifact)
|
280
|
+
File.exist?(artifact_path)
|
281
|
+
end
|
282
|
+
|
283
|
+
return unless missing_artifacts.any?
|
284
|
+
|
285
|
+
result[:success] = false
|
286
|
+
result[:error] = "Missing expected artifacts: #{missing_artifacts.join(", ")}"
|
287
|
+
end
|
288
|
+
|
289
|
+
# Verify that all results meet expectations
|
290
|
+
def verify_results
|
291
|
+
return unless @verify_artifacts
|
292
|
+
|
293
|
+
@results.each do |result|
|
294
|
+
next unless result[:success]
|
295
|
+
|
296
|
+
example = result[:example]
|
297
|
+
|
298
|
+
# Check artifacts in the example directory context
|
299
|
+
missing_artifacts = example[:expected_artifacts].reject do |artifact|
|
300
|
+
artifact_path = File.join(example[:path], artifact)
|
301
|
+
File.exist?(artifact_path)
|
302
|
+
end
|
303
|
+
|
304
|
+
next unless missing_artifacts.any?
|
305
|
+
|
306
|
+
result[:success] = false
|
307
|
+
result[:error] = "Missing expected artifacts: #{missing_artifacts.join(", ")}"
|
308
|
+
|
309
|
+
# Move from passed to failed
|
310
|
+
@passed_examples.delete(example)
|
311
|
+
@failed_examples << example
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Clean up artifacts if configured
|
316
|
+
def cleanup_if_needed
|
317
|
+
return unless @cleanup_after
|
318
|
+
|
319
|
+
@examples.each do |example|
|
320
|
+
cleanup_example(example)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Clean up artifacts for a specific example
|
325
|
+
#
|
326
|
+
# @param example [Hash] example configuration
|
327
|
+
def cleanup_example(example)
|
328
|
+
Dir.chdir(example[:path]) do
|
329
|
+
# Remove artifacts directory if it exists
|
330
|
+
FileUtils.rm_rf("artifacts")
|
331
|
+
|
332
|
+
# Remove project file if it exists
|
333
|
+
FileUtils.rm_f(".makit.project.json")
|
334
|
+
end
|
335
|
+
rescue StandardError => e
|
336
|
+
log " Warning: Failed to cleanup #{example[:name]}: #{e.message}" if @verbose
|
337
|
+
end
|
338
|
+
|
339
|
+
# Report the results of example execution
|
340
|
+
def report_results
|
341
|
+
puts "📊 Example Test Results:".colorize(:blue)
|
342
|
+
puts " ✅ Passed: #{@passed_examples.count}".colorize(:green)
|
343
|
+
puts " ❌ Failed: #{@failed_examples.count}".colorize(:red)
|
344
|
+
|
345
|
+
if @failed_examples.any?
|
346
|
+
puts " Failed examples:".colorize(:red)
|
347
|
+
@failed_examples.each do |example|
|
348
|
+
result = @results.find { |r| r[:example] == example }
|
349
|
+
error_msg = result ? result[:error] : "Unknown error"
|
350
|
+
puts " - #{example[:name]}: #{error_msg}".colorize(:red)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
return unless @passed_examples.any? && @verbose
|
355
|
+
|
356
|
+
puts " Passed examples:".colorize(:green)
|
357
|
+
@passed_examples.each do |example|
|
358
|
+
puts " - #{example[:name]}".colorize(:green)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Log a message if verbose mode is enabled
|
363
|
+
#
|
364
|
+
# @param message [String] message to log
|
365
|
+
def log(message)
|
366
|
+
puts message if @verbose
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|