Commit 77bc3c6
bin/test
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$LOAD_PATH.unshift(File.expand_path(File.join(__dir__, '../lib')))
+
+require 'jive'
+
+exit Jive.run([%w[bundle exec rspec]])
exe/jive
lib/jive.rb
@@ -1,6 +1,144 @@
+# frozen_string_literal: true
+
+require "fileutils"
require "jive/version"
+require "open3"
module Jive
class Error < StandardError; end
- # Your code goes here...
+
+ def self.run(tasks)
+ Jive::BatchRunner.new.run(tasks)
+ end
+
+ module Popen
+ Result = Struct.new(:command, :stdout, :stderr, :status, :duration)
+
+ def self.popen(command, path = nil, env = {}, &block)
+ result = popen_with_detail(command, path, env, &block)
+
+ ["#{result.stdout}#{result.stderr}", result.status&.exitstatus]
+ end
+
+ def self.popen_with_detail(command, path = Dir.pwd, env = {})
+ FileUtils.mkdir_p(path) unless File.directory?(path)
+
+ captured_stdout = ''
+ captured_stderr = ''
+ exit_status = nil
+ start = Time.now
+
+ Open3.popen3(env.merge('PWD' => path), *Array(command), { chdir: path }) do |stdin, stdout, stderr, wait_thr|
+ out_reader = Thread.new { stdout.read }
+ err_reader = Thread.new { stderr.read }
+
+ yield(stdin) if block_given?
+
+ stdin.close
+ captured_stdout = out_reader.value
+ captured_stderr = err_reader.value
+ exit_status = wait_thr.value
+ end
+ Result.new(command, captured_stdout, captured_stderr, exit_status, Time.now - start)
+ end
+ end
+
+ class BatchRunner
+ attr_reader :runner, :stdout
+
+ def initialize(runner: Runner.new, stdout: STDOUT)
+ @runner = runner
+ @stdout = stdout
+ end
+
+ def run(tasks, verbose: true)
+ runner.run(tasks) do |command, &run|
+ stdout.puts
+ stdout.puts "$ #{command.join(' ')}"
+ result = run.call
+ stdout.print result.stdout if verbose
+ stdout.print result.stderr if verbose
+ stdout.puts "==> Finished in #{result.duration} seconds"
+ stdout.puts
+ end
+ stdout.puts '==================================================='
+ if runner.all_success_and_clean?
+ stdout.puts 'Passed successfully.'
+ return 0
+ elsif runner.all_success?
+ stdout.puts 'Passed successfully, but we have warnings:'
+ stdout.puts
+ emit_warnings
+ return 2
+ else
+ stdout.puts 'Something failed:'
+ emit_warnings
+ emit_errors
+ return 1
+ end
+ end
+
+ private
+
+ def emit_warnings
+ runner.warned_results.each do |result|
+ stdout.puts
+ stdout.puts "**** #{result.command.join(' ')} had the following warning(s):"
+ stdout.puts
+ stdout.puts result.stderr
+ stdout.puts
+ end
+ end
+
+ def emit_errors
+ runner.failed_results.each do |result|
+ stdout.puts
+ stdout.puts "**** #{result.command.join(' ')} failed with the following error(s):"
+ stdout.puts
+ stdout.puts result.stdout
+ stdout.puts result.stderr
+ stdout.puts
+ end
+ end
+ end
+
+ class Runner
+ attr_reader :results
+
+ def initialize
+ @results = []
+ end
+
+ def run(commands, &block)
+ commands.each do |command|
+ block.call(command) do
+ cmd_result = Popen.popen_with_detail(command)
+ results << cmd_result
+ cmd_result
+ end
+ end
+ end
+
+ def all_success_and_clean?
+ all_success? && all_stderr_empty?
+ end
+
+ def all_success?
+ results.all? { |result| result.status.success? }
+ end
+
+ def all_stderr_empty?
+ results.all? { |result| result.stderr.empty? }
+ end
+
+ def failed_results
+ results.reject { |result| result.status.success? }
+ end
+
+ def warned_results
+ results.select do |result|
+ result.status.success? && !result.stderr.empty?
+ end
+ end
+ end
end