diff --git a/ruby/shell/job_control.rb b/ruby/shell/job_control.rb index f116895..156d79f 100644 --- a/ruby/shell/job_control.rb +++ b/ruby/shell/job_control.rb @@ -10,9 +10,10 @@ module Shell attr_reader :logger - def initialize(logger: nil) + def initialize(logger: nil, refresh_line: nil) @jobs_by_pid = {} @logger = logger || Logger.instance + @refresh_line = refresh_line || -> { Readline.refresh_line } end def exec_command(cmd, args, background: false) @@ -67,7 +68,11 @@ module Shell def trap_sigchld # handler for SIGCHLD when a child's state changes Signal.trap("CHLD") do |_signo| - pid = Process.waitpid(-1, Process::WNOHANG) + pid = begin + Process.waitpid(-1, Process::WNOHANG) + rescue Errno::ECHILD + nil + end if pid.nil? # no-op elsif (job = @jobs_by_pid[pid]) @@ -79,7 +84,7 @@ module Shell else warn "\n#{yellow("[WARN]")} No job found for child with PID #{pid}" end - Readline.refresh_line + @refresh_line.call end end diff --git a/ruby/test/shell_test.rb b/ruby/test/shell_test.rb index d53e56c..8a158f2 100644 --- a/ruby/test/shell_test.rb +++ b/ruby/test/shell_test.rb @@ -1,5 +1,9 @@ require "minitest/autorun" require "etc" +require "timeout" +$LOAD_PATH.unshift(File.expand_path("..", __dir__)) +require_relative "../shell/job_control" +require_relative "../shell/logger" class ShellTest < Minitest::Test TRIVIAL_SHELL_SCRIPT = "#!/bin/sh\ntrue".freeze @@ -151,7 +155,21 @@ class ShellTest < Minitest::Test end def test_refreshes_readline_after_bg_execution - skip "unimplemented" + called = false + job_control = Shell::JobControl.new( + logger: Shell::Logger.instance, + refresh_line: -> { called = true } + ) + previous = job_control.trap_sigchld + begin + job_control.exec_command("echo", ["hello"], background: true) + Timeout.timeout(2) do + sleep 0.01 until called + end + assert called + ensure + Signal.trap("CHLD", previous) + end end #########################