diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/lib/dslkeywords/agent_test.rb | 773 | ||||
| -rw-r--r-- | test/lib/dslkeywords/file_test.rb | 136 | ||||
| -rw-r--r-- | test/support/mock_agent.rb | 46 |
3 files changed, 939 insertions, 16 deletions
diff --git a/test/lib/dslkeywords/agent_test.rb b/test/lib/dslkeywords/agent_test.rb index d3fc49f..39d997e 100644 --- a/test/lib/dslkeywords/agent_test.rb +++ b/test/lib/dslkeywords/agent_test.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -# rubocop:disable Metrics/ClassLength, Metrics/MethodLength +# rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize require 'minitest/autorun' +require 'json' require 'fileutils' require 'rbconfig' require 'shellwords' @@ -15,10 +16,13 @@ class RCMAgentTest < Minitest::Test def setup @dir_path = Dir.mktmpdir('.agent_test.rcmtmp.') @original_argv = ARGV.dup + @original_xdg_cache_home = ENV['XDG_CACHE_HOME'] + ENV['XDG_CACHE_HOME'] = path('cache') end def teardown ARGV.replace(@original_argv) if @original_argv + ENV['XDG_CACHE_HOME'] = @original_xdg_cache_home FileUtils.rm_rf(@dir_path) if @dir_path end @@ -39,6 +43,14 @@ class RCMAgentTest < Minitest::Test [Shellwords.escape(parts.shift), Shellwords.escape(parts.shift), Shellwords.escape(parts.shift), *parts].join(' ') end + def agent_cache_files + Dir.glob(File.join(ENV.fetch('XDG_CACHE_HOME'), 'rcm', 'agents', '*.json')) + end + + def counter_value(counter_path) + File.file?(counter_path) ? File.read(counter_path).to_i : 0 + end + def test_duplicate_agent_definition assert_raises(RCM::DSL::DuplicateDefinition) do configure_from_scratch do @@ -67,6 +79,22 @@ class RCMAgentTest < Minitest::Test end end + def test_duplicate_command_definition + template = mock_agent_command(:join_args, 'spell') + + assert_raises(RCM::DSL::DuplicateDefinition) do + configure_from_scratch do + command spell do + template + end + + command spell do + template + end + end + end + end + def test_agent_processes_file_using_stdin_and_names_with_spaces file_path = path('process.txt') command = mock_agent_command(:upcase_prompt, 'PROMPT') @@ -111,28 +139,141 @@ class RCMAgentTest < Minitest::Test assert_equal 'HELLO WORLD|Fix grammar', File.read(file_path) end + def test_agent_processes_file_using_prompt_appended_command_output + file_path = path('process-appended-command.txt') + agent_command = mock_agent_command(:upcase_prompt, 'PROMPT') + spell_command = mock_agent_command(:join_args, 'spell', 'FILE_PATH') + File.write(file_path, 'hello world') + + configure_from_scratch do + agent mock do + agent_command + end + + command spell output do + spell_command + end + + prompt fix english do + append from command spell output + 'Fix grammar' + end + + file file_path do + agent mock fix english + end + end + + assert_equal "HELLO WORLD|Fix grammar\nspell|#{file_path}", File.read(file_path) + end + + def test_agent_processes_file_using_prompt_prepended_command_output + file_path = path('process-prepended-command.txt') + agent_command = mock_agent_command(:upcase_prompt, 'PROMPT') + spell_command = mock_agent_command(:join_args, 'spell', 'FILE_PATH') + File.write(file_path, 'hello world') + + configure_from_scratch do + agent mock do + agent_command + end + + command spell output do + spell_command + end + + prompt fix english do + prepend from command spell output + 'Fix grammar' + end + + file file_path do + agent mock fix english + end + end + + assert_equal "HELLO WORLD|spell|#{file_path}\nFix grammar", File.read(file_path) + end + + def test_agent_streams_output_while_capturing_final_content + file_path = path('streamed-output.txt') + command = mock_agent_command(:stream_chunks, 'he', 'llo', '--stderr=oops') + File.write(file_path, 'ignored') + + stdout, stderr = capture_io do + configure_from_scratch do + agent streamer do + retries 0 + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent streamer, 'no op' + end + end + end + + assert_includes stdout, 'hello' + assert_includes stderr, 'oops' + assert_equal 'hello', File.read(file_path) + end + def test_agent_can_use_input_placeholder file_path = path('input.txt') command = mock_agent_command(:reverse_input, 'INPUT') File.write(file_path, 'abc123') configure_from_scratch do - agent 'reverse via file' do + agent reverse via file do command end - prompt 'no op' do + prompt no op do '' end file file_path do - agent 'reverse via file', 'no op' + agent reverse via file no op end end assert_equal '321cba', File.read(file_path) end + def test_agent_spec_raises_when_multiword_split_is_ambiguous + file_path = path('ambiguous.txt') + command = mock_agent_command(:pass_through) + File.write(file_path, 'hello') + + assert_raises(RCM::File::InvalidAgentSpec) do + configure_from_scratch do + agent alpha do + command + end + + agent alpha beta do + command + end + + prompt gamma do + '' + end + + prompt beta gamma do + '' + end + + file file_path do + agent alpha beta gamma + end + end + end + end + def test_agent_can_use_file_path_placeholder file_path = path('placeholder.txt') command = mock_agent_command(:basename, 'FILE_PATH') @@ -203,6 +344,398 @@ class RCMAgentTest < Minitest::Test assert_equal 1, Dir.glob(File.join(backup_dir, 'backup.txt.*')).count end + def test_agent_processing_creates_track_record_and_skips_when_checksum_is_fresh + file_path = path('fresh.txt') + counter_path = path('fresh-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 1, counter_value(counter_path) + assert_equal 1, agent_cache_files.count + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 1, counter_value(counter_path) + assert_equal 1, agent_cache_files.count + record = JSON.parse(File.read(agent_cache_files.first)) + assert_equal Digest::SHA256.hexdigest('same content'), record.fetch('file_checksum') + end + + def test_agent_processing_updates_track_record_immediately_after_file_change + file_path = path('fresh-after-change.txt') + counter_path = path('fresh-after-change-counter.txt') + command = mock_agent_command(:count_upcase, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 'SAME CONTENT', File.read(file_path) + assert_equal 1, counter_value(counter_path) + record = JSON.parse(File.read(agent_cache_files.first)) + assert_equal Digest::SHA256.hexdigest('SAME CONTENT'), record.fetch('file_checksum') + end + + def test_agent_processing_reruns_when_input_checksum_changes + file_path = path('changed.txt') + counter_path = path('changed-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + File.write(file_path, 'changed content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 2, counter_value(counter_path) + end + + def test_agent_processing_reruns_when_agent_definition_changes + file_path = path('agent-definition-change.txt') + counter_path = path('agent-definition-change-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + "#{command} # one" + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + configure_from_scratch do + agent 'count runs' do + "#{command} # two" + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 2, counter_value(counter_path) + end + + def test_agent_processing_reruns_when_prompt_definition_changes + file_path = path('prompt-definition-change.txt') + counter_path = path('prompt-definition-change-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + 'changed prompt' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 2, counter_value(counter_path) + end + + def test_agent_processing_reruns_when_prompt_command_definition_changes + file_path = path('prompt-command-definition-change.txt') + counter_path = path('prompt-command-definition-change-counter.txt') + agent_command = mock_agent_command(:count_pass_through, counter_path) + spell_command = mock_agent_command(:join_args, 'spell', 'FILE_PATH') + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + agent_command + end + + command spell do + "#{spell_command} # one" + end + + prompt 'fix english' do + append from command spell + 'Fix grammar' + end + + file file_path do + agent 'count runs', 'fix english' + end + end + + configure_from_scratch do + agent 'count runs' do + agent_command + end + + command spell do + "#{spell_command} # two" + end + + prompt 'fix english' do + append from command spell + 'Fix grammar' + end + + file file_path do + agent 'count runs', 'fix english' + end + end + + assert_equal 2, counter_value(counter_path) + end + + def test_agent_processing_uses_separate_track_records_for_same_file_and_different_prompts + file_path = path('multiple-prompts.txt') + counter_path = path('multiple-prompts-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt one do + '' + end + + file file_path do + agent 'count runs', 'one' + end + end + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt two do + '' + end + + file file_path do + agent 'count runs', 'two' + end + end + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt one do + '' + end + + file file_path do + agent 'count runs', 'one' + end + end + + assert_equal 2, counter_value(counter_path) + assert_equal 2, agent_cache_files.count + end + + def test_failed_agent_run_does_not_refresh_track_record + file_path = path('failure-does-not-refresh.txt') + counter_path = path('failure-does-not-refresh-counter.txt') + marker_path = path('failure-marker.txt') + command = mock_agent_command(:count_or_fail, counter_path, marker_path, 'boom', '7') + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + File.write(file_path, 'changed content') + File.write(marker_path, 'boom') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + File.delete(marker_path) + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 2, counter_value(counter_path) + end + + def test_corrupt_agent_track_record_is_treated_as_stale + file_path = path('corrupt-record.txt') + counter_path = path('corrupt-record-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'same content') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + File.write(agent_cache_files.first, '{') + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 2, counter_value(counter_path) + end + def test_unknown_agent_raises file_path = path('unknown-agent.txt') File.write(file_path, 'hello') @@ -238,6 +771,29 @@ class RCMAgentTest < Minitest::Test end end + def test_unknown_command_raises + file_path = path('unknown-command.txt') + agent_command = mock_agent_command(:pass_through) + File.write(file_path, 'hello') + + assert_raises(RCM::DSL::NoSuchCommandDefinition) do + configure_from_scratch do + agent mock do + agent_command + end + + prompt fix english do + append from command spell output + 'Fix grammar' + end + + file file_path do + agent mock fix english + end + end + end + end + def test_missing_agent_input_raises file_path = path('missing.txt') command = mock_agent_command(:pass_through) @@ -283,6 +839,60 @@ class RCMAgentTest < Minitest::Test assert_equal 'keep me', File.read(file_path) end + def test_dry_run_stale_agent_does_not_create_track_record + file_path = path('dry-run-no-cache.txt') + counter_path = path('dry-run-no-cache-counter.txt') + command = mock_agent_command(:count_pass_through, counter_path) + File.write(file_path, 'keep me') + ARGV.replace(['--dry']) + + configure_from_scratch do + agent 'count runs' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'count runs', 'no op' + end + end + + assert_equal 0, counter_value(counter_path) + assert_empty agent_cache_files + end + + def test_dry_run_does_not_execute_prompt_command + file_path = path('dry-run-command.txt') + agent_command = mock_agent_command(:fail, 'agent boom', '7') + prompt_command = mock_agent_command(:fail, 'prompt boom', '8') + File.write(file_path, 'keep me') + ARGV.replace(['--dry']) + + configure_from_scratch do + agent 'should not run' do + agent_command + end + + command spell do + prompt_command + end + + prompt fix english do + append from command spell + 'Fix grammar' + end + + file file_path do + agent 'should not run', 'fix english' + end + end + + assert_equal 'keep me', File.read(file_path) + end + def test_dry_run_unknown_agent_raises file_path = path('dry-run-unknown-agent.txt') File.write(file_path, 'keep me') @@ -320,23 +930,160 @@ class RCMAgentTest < Minitest::Test end end - def test_non_zero_exit_raises + def test_non_zero_exit_skips_file_and_continues_after_retries_are_exhausted file_path = path('broken.txt') - command = mock_agent_command(:fail, 'boom', '7') + next_file_path = path('still-runs.txt') + counter_path = path('broken-counter.txt') + command = mock_agent_command(:fail_then_pass, counter_path, '5', 'boom', '7') + File.write(file_path, 'hello') + + configure_from_scratch do + agent 'broken agent' do + retry_delay 0 + retry_backoff 1 + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'broken agent', 'no op' + end + + file next_file_path do + 'still runs' + end + end + + assert_equal 'hello', File.read(file_path) + assert_equal 'still runs', File.read(next_file_path) + assert_equal 3, counter_value(counter_path) + assert_empty agent_cache_files + end + + def test_agent_retries_failed_command_and_eventually_succeeds + file_path = path('retry-success.txt') + counter_path = path('retry-success-counter.txt') + command = mock_agent_command(:fail_then_pass, counter_path, '1', 'boom', '7') + File.write(file_path, 'hello') + + configure_from_scratch do + agent 'flaky agent' do + retries 1 + retry_delay 0 + retry_backoff 1 + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'flaky agent', 'no op' + end + end + + assert_equal 'hello', File.read(file_path) + assert_equal 2, counter_value(counter_path) + end + + def test_agent_skips_file_after_retries_are_exhausted + file_path = path('retry-failure.txt') + counter_path = path('retry-failure-counter.txt') + command = mock_agent_command(:fail_then_pass, counter_path, '5', 'boom', '7') File.write(file_path, 'hello') - error = assert_raises(RCM::File::AgentCommandFailed) do + configure_from_scratch do + agent 'flaky agent' do + retries 2 + retry_delay 0 + retry_backoff 1 + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'flaky agent', 'no op' + end + end + + assert_equal 'hello', File.read(file_path) + assert_equal 3, counter_value(counter_path) + assert_empty agent_cache_files + end + + def test_invalid_agent_retry_settings_raise + assert_raises(RCM::AgentDefinition::InvalidRetrySetting) do configure_from_scratch do - agent 'broken agent' do - command + agent 'broken retries' do + retries(-1) + 'ruby -e "print STDIN.read"' end + end + end - prompt 'no op' do - '' + assert_raises(RCM::AgentDefinition::InvalidRetrySetting) do + configure_from_scratch do + agent 'broken backoff' do + retry_backoff 0.5 + 'ruby -e "print STDIN.read"' + end + end + end + end + + def test_agent_processing_writes_via_temporary_file + file_path = path('temporary-write.txt') + command = mock_agent_command(:upcase) + File.write(file_path, 'hello') + + configure_from_scratch do + agent 'make loud' do + command + end + + prompt 'no op' do + '' + end + + file file_path do + agent 'make loud', 'no op' + end + end + + assert_equal 'HELLO', File.read(file_path) + refute File.exist?("#{file_path}.rcmtmp") + end + + def test_non_zero_prompt_command_exit_raises + file_path = path('broken-prompt-command.txt') + agent_command = mock_agent_command(:pass_through) + prompt_command = mock_agent_command(:fail, 'boom', '7') + File.write(file_path, 'hello') + + error = assert_raises(RCM::PromptDefinition::CommandFailed) do + configure_from_scratch do + agent mock do + agent_command + end + + command spell do + prompt_command + end + + prompt fix english do + append from command spell + 'Fix grammar' end file file_path do - agent 'broken agent', 'no op' + agent mock fix english end end end @@ -346,4 +1093,4 @@ class RCMAgentTest < Minitest::Test end end -# rubocop:enable Metrics/ClassLength, Metrics/MethodLength +# rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize diff --git a/test/lib/dslkeywords/file_test.rb b/test/lib/dslkeywords/file_test.rb index a290627..124a612 100644 --- a/test/lib/dslkeywords/file_test.rb +++ b/test/lib/dslkeywords/file_test.rb @@ -1,11 +1,17 @@ +# frozen_string_literal: true + +# rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize require 'minitest/autorun' require 'fileutils' +require 'rbconfig' +require 'shellwords' require_relative '../../../lib/dsl' class RCMFileTest < Minitest::Test - FILE_PATH = './.file_test.rcmtmp'.freeze - DIR_PATH = './.dir_test.rcmtmp'.freeze + FILE_PATH = './.file_test.rcmtmp' + DIR_PATH = './.dir_test.rcmtmp' + MOCK_COMMAND = File.expand_path('../../support/mock_agent.rb', __dir__).freeze Minitest.after_run do File.unlink(FILE_PATH) if File.file?(FILE_PATH) @@ -14,10 +20,28 @@ class RCMFileTest < Minitest::Test # Clean up shared temp file between tests to prevent order-dependent failures def setup + @original_argv = ARGV.dup File.unlink(FILE_PATH) if File.file?(FILE_PATH) FileUtils.rm_r(DIR_PATH) if File.directory?(DIR_PATH) end + def teardown + ARGV.replace(@original_argv) if @original_argv + end + + def mock_command(mode, *args) + parts = [RbConfig.ruby, MOCK_COMMAND, mode.to_s] + args.each do |arg| + parts << if %w[INPUT PROMPT FILE_PATH].include?(arg) + arg + else + Shellwords.escape(arg.to_s) + end + end + + [Shellwords.escape(parts.shift), Shellwords.escape(parts.shift), Shellwords.escape(parts.shift), *parts].join(' ') + end + def test_create_file_from_string text = 'Hello World!' configure_from_scratch do @@ -104,6 +128,58 @@ class RCMFileTest < Minitest::Test assert_equal 'One plus two is 3!', File.read(FILE_PATH) end + def test_file_appends_command_output + command_template = mock_command(:join_args, 'suffix', 'FILE_PATH') + + configure_from_scratch do + command suffix do + command_template + end + + file FILE_PATH do + append from command suffix + 'Hello World!' + end + end + + assert_equal "Hello World!\nsuffix|#{FILE_PATH}", File.read(FILE_PATH) + end + + def test_file_prepends_command_output + command_template = mock_command(:join_args, 'prefix', 'FILE_PATH') + + configure_from_scratch do + command prefix do + command_template + end + + file FILE_PATH do + prepend from command prefix + 'Hello World!' + end + end + + assert_equal "prefix|#{FILE_PATH}\nHello World!", File.read(FILE_PATH) + end + + def test_file_appends_command_output_to_template_content + command_template = mock_command(:join_args, 'suffix', 'FILE_PATH') + + configure_from_scratch do + command suffix do + command_template + end + + file FILE_PATH do + append from command suffix + from template + 'One plus two is <%= 1 + 2 %>!' + end + end + + assert_equal "One plus two is 3!\nsuffix|#{FILE_PATH}", File.read(FILE_PATH) + end + def test_line File.write(FILE_PATH, "Hey there\n") configure_from_scratch { file(FILE_PATH) { line 'Whats up?' } } @@ -146,7 +222,8 @@ class RCMFileTest < Minitest::Test def test_backup file_path = "#{DIR_PATH}/foo/backup-me.txt" original_content = 'original_content' - backup_path = "#{DIR_PATH}/foo/.rcmbackup/backup-me.txt.d4c3af73588ce06c32ed04d1b79801286109ea265712a2bd3fdc3ed01c82bb86" + backup_path = + "#{DIR_PATH}/foo/.rcmbackup/backup-me.txt.d4c3af73588ce06c32ed04d1b79801286109ea265712a2bd3fdc3ed01c82bb86" configure_from_scratch do file original do @@ -168,4 +245,57 @@ class RCMFileTest < Minitest::Test assert File.file?(file_path) assert_equal :new_content.to_s, File.read(file_path) end + + def test_unknown_command_raises_for_file_content + error = assert_raises(RCM::DSL::NoSuchCommandDefinition) do + configure_from_scratch do + file FILE_PATH do + append from command suffix + 'Hello World!' + end + end + end + + assert_match("No such command 'suffix'", error.message) + end + + def test_non_zero_command_exit_raises_for_file_content + command_template = mock_command(:fail, 'boom', '7') + + error = assert_raises(RCM::File::CommandFailed) do + configure_from_scratch do + command explode do + command_template + end + + file FILE_PATH do + append from command explode + 'Hello World!' + end + end + end + + assert_match('exit 7', error.message) + assert_match('boom', error.message) + end + + def test_dry_run_does_not_execute_file_command + command_template = mock_command(:fail, 'boom', '7') + ARGV.replace(['--dry']) + + configure_from_scratch do + command explode do + command_template + end + + file FILE_PATH do + append from command explode + 'Hello World!' + end + end + + refute File.exist?(FILE_PATH) + end end + +# rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize diff --git a/test/support/mock_agent.rb b/test/support/mock_agent.rb index b86de4f..9c21345 100644 --- a/test/support/mock_agent.rb +++ b/test/support/mock_agent.rb @@ -13,6 +13,52 @@ when 'reverse_input' when 'basename' file_path = ARGV.fetch(0) print File.basename(file_path) +when 'join_args' + print ARGV.join('|') +when 'count_pass_through' + counter_path = ARGV.fetch(0) + count = File.file?(counter_path) ? File.read(counter_path).to_i : 0 + File.write(counter_path, count + 1) + print stdin +when 'count_upcase' + counter_path = ARGV.fetch(0) + count = File.file?(counter_path) ? File.read(counter_path).to_i : 0 + File.write(counter_path, count + 1) + print stdin.upcase +when 'fail_then_pass' + counter_path = ARGV.fetch(0) + failures_before_success = Integer(ARGV.fetch(1, '1')) + message = ARGV.fetch(2, 'boom') + exit_code = Integer(ARGV.fetch(3, '7')) + count = File.file?(counter_path) ? File.read(counter_path).to_i : 0 + count += 1 + File.write(counter_path, count) + if count <= failures_before_success + warn message + exit exit_code + end + print stdin +when 'stream_chunks' + stderr_text = ARGV.pop.sub('--stderr=', '') if ARGV.last&.start_with?('--stderr=') + ARGV.each do |chunk| + $stdout.write(chunk) + $stdout.flush + sleep 0.01 + end + if stderr_text + $stderr.write(stderr_text) + $stderr.flush + end +when 'count_or_fail' + counter_path = ARGV.fetch(0) + marker_path = ARGV.fetch(1) + if File.exist?(marker_path) + warn ARGV.fetch(2, 'boom') + exit Integer(ARGV.fetch(3, '7')) + end + count = File.file?(counter_path) ? File.read(counter_path).to_i : 0 + File.write(counter_path, count + 1) + print stdin when 'pass_through' print stdin when 'upcase' |
