From 1a822a51ada564de4a90326a3a4e2a3fae02864c Mon Sep 17 00:00:00 2001 From: David Siaw Date: Wed, 22 May 2024 09:18:46 +0900 Subject: [PATCH] cmdrunner tests --- exe/kaiser | 1 - lib/kaiser/cmds/up.rb | 6 +- lib/kaiser/command_runner.rb | 20 ++++--- spec/command_runner_spec.rb | 109 +++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 spec/command_runner_spec.rb diff --git a/exe/kaiser b/exe/kaiser index aa7918b2..ea383e2b 100755 --- a/exe/kaiser +++ b/exe/kaiser @@ -5,7 +5,6 @@ require 'optimist' require 'fileutils' require 'yaml' require 'json' -require 'pty' require 'erb' require 'kaiser' diff --git a/lib/kaiser/cmds/up.rb b/lib/kaiser/cmds/up.rb index cee834f7..b4eab52c 100644 --- a/lib/kaiser/cmds/up.rb +++ b/lib/kaiser/cmds/up.rb @@ -34,7 +34,6 @@ def build_cmd build_args = docker_build_args.map { |k, v| "--build-arg #{k}=#{v}" } [ 'docker build', - "--progress=plain", "-t kaiser:#{envname}-#{current_branch}", "-f #{tmp_dockerfile_name} #{Config.work_dir}", platform_args, @@ -46,7 +45,10 @@ def setup_app Config.info_out.puts 'Setting up application' File.write(tmp_dockerfile_name, docker_file_contents) - CommandRunner.run! Config.out, build_cmd.join("\n\t") + CommandRunner.run! Config.out, build_cmd.join("\n\t"), env_vars: { + 'DOCKER_BUILDKIT' => '1', + 'BUILDKIT_PROGRESS' => 'plain' + } FileUtils.rm(tmp_dockerfile_name) end end diff --git a/lib/kaiser/command_runner.rb b/lib/kaiser/command_runner.rb index 0d18601d..593c86a4 100644 --- a/lib/kaiser/command_runner.rb +++ b/lib/kaiser/command_runner.rb @@ -1,24 +1,28 @@ # frozen_string_literal: true +require 'pty' require 'English' -# This is the command runner module Kaiser - # Make running easy + # This is the command runner + # it abstracts away the complicated syntax required to deal with + # PTY and to pass the lines programmatically to the host application + # as well as to capture the return code at the end. class CommandRunner - def self.run(out, cmd, &block) + def self.run(out, cmd, env_vars:, &block) out.puts "> #{cmd}" - CommandRunner.new(out, cmd).run_command(&block) + CommandRunner.new(out, cmd, env_vars).run_command(&block) end - def self.run!(out, cmd, &block) - status = run(out, cmd, &block) + def self.run!(out, cmd, env_vars: {}, &block) + status = run(out, cmd, env_vars: env_vars, &block) raise Kaiser::CmdError.new(cmd, status) if status.to_s != '0' end - def initialize(out, cmd) + def initialize(out, cmd, env_vars) @out = out @cmd = cmd.tr "\n", ' ' + @env_vars = env_vars end def print_and_return_status(status = 0) @@ -38,7 +42,7 @@ def print_lines(lines) end def run_command(&block) - PTY.spawn("#{@cmd} 2>&1") do |stdout, _stdin, pid| + PTY.spawn(@env_vars, "#{@cmd} 2>&1") do |stdout, _stdin, pid| print_lines(stdout, &block) Process.wait(pid) end diff --git a/spec/command_runner_spec.rb b/spec/command_runner_spec.rb new file mode 100644 index 00000000..feb689ab --- /dev/null +++ b/spec/command_runner_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'kaiser/command_runner' + +RSpec.describe Kaiser::CommandRunner do + # this is the core function of this class + describe '#run_command' do + it 'runs a simple command' do + # out can also be $stderr or $stdout + out = StringIO.new + described_class.new(out, 'echo hello', {}).run_command + expect(out.string).to eq <<~OUTPUT + hello\r + $? = 0 + OUTPUT + end + + it 'can capture lines' do + lines = [] + out = StringIO.new + described_class.new(out, 'echo hello', {}).run_command do |line| + lines << line + end + + expect(lines).to eq ['hello'] + end + + it 'returns the 0 status if the command succeeds' do + out = StringIO.new + retval = described_class.new(out, 'true', {}).run_command + expect(retval).to eq 0 + end + + it 'returns the 1 status if the command returns 1' do + out = StringIO.new + retval = described_class.new(out, 'false', {}).run_command + expect(retval).to eq 1 + end + + it 'allows application of environment variables' do + lines = [] + out = StringIO.new + described_class.new(out, 'echo $HELLO', { 'HELLO' => 'WORLD' }).run_command do |line| + lines << line + end + + expect(lines).to eq ['WORLD'] + end + + it 'can capture lines' do + lines = [] + out = StringIO.new + described_class.new(out, 'echo hello', {}).run_command do |line| + lines << line + end + + expect(lines).to eq ['hello'] + end + end + + describe '.run' do + it 'adds the appropriate lines to output' do + out = StringIO.new + described_class.run(out, 'echo hello', env_vars: {}) + + expect(out.string).to eq <<~OUTPUT + > echo hello + hello\r + $? = 0 + OUTPUT + end + + it 'yields lines approprately' do + lines = [] + out = StringIO.new + described_class.run(out, 'echo hello', env_vars: {}) do |line| + lines << line + end + + expect(lines).to eq ['hello'] + end + + it 'handles env vars' do + lines = [] + out = StringIO.new + described_class.run(out, 'echo $HELLO', env_vars: { 'HELLO' => 'world' }) do |line| + lines << line + end + + expect(lines).to eq ['world'] + end + end + + describe '.run!' do + out = StringIO.new + it 'does whatever run does' do + expect(described_class).to receive(:run).with(out, 'meow', env_vars: { 'FOO' => 'bar' }).and_return('0') + + described_class.run!(out, 'meow', env_vars: { 'FOO' => 'bar' }) + end + + it 'throws when the return code is not 0' do + allow(described_class).to receive(:run).with(out, 'woof', env_vars: { 'FOO' => 'bar' }).and_return('1') + + expect { described_class.run!(out, 'woof', env_vars: { 'FOO' => 'bar' }) } + .to raise_error Kaiser::CmdError + end + end +end