diff --git a/spec/std/process/exit_reason_spec.cr b/spec/std/process/exit_reason_spec.cr new file mode 100644 index 000000000000..3a55ea4172d9 --- /dev/null +++ b/spec/std/process/exit_reason_spec.cr @@ -0,0 +1,10 @@ +require "spec" + +describe Process::ExitReason do + describe "#description" do + it "with exit status" do + Process::ExitReason::Normal.description.should eq "Process exited normally" + Process::ExitReason::Unknown.description.should eq "Process terminated abnormally, the cause is unknown" + end + end +end diff --git a/spec/std/process/status_spec.cr b/spec/std/process/status_spec.cr index 6cbabbea5d73..67e53e172d81 100644 --- a/spec/std/process/status_spec.cr +++ b/spec/std/process/status_spec.cr @@ -329,4 +329,28 @@ describe Process::Status do end {% end %} end + + describe "#description" do + it "with exit status" do + Process::Status.new(exit_status(0)).description.should eq "Process exited normally" + Process::Status.new(exit_status(255)).description.should eq "Process exited normally" + end + + it "on interrupt" do + status_for(:interrupted).description.should eq "Process was interrupted" + end + + {% if flag?(:unix) && !flag?(:wasi) %} + it "with exit signal" do + Process::Status.new(Signal::HUP.value).description.should eq "Process terminated abnormally" + Process::Status.new(Signal::KILL.value).description.should eq "Process terminated abnormally" + Process::Status.new(Signal::STOP.value).description.should eq "Process received and didn't handle signal STOP" + last_signal = Signal.values[-1] + Process::Status.new(last_signal.value).description.should eq "Process received and didn't handle signal #{last_signal}" + + unknown_signal = Signal.new(126) + Process::Status.new(unknown_signal.value).description.should eq "Process received and didn't handle signal 126" + end + {% end %} + end end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index cc6f39657f64..a5310f3703b3 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -305,42 +305,14 @@ class Crystal::Command exit exit_code end - if message = exit_message(status) - STDERR.puts message + unless status.exit_reason.normal? + STDERR.puts status.description STDERR.flush end exit 1 end - private def exit_message(status) - case status.exit_reason - when .aborted?, .session_ended?, .terminal_disconnected? - if signal = status.exit_signal? - if signal.kill? - "Program was killed" - else - "Program received and didn't handle signal #{signal} (#{signal.value})" - end - else - "Program exited abnormally" - end - when .breakpoint? - "Program hit a breakpoint and no debugger was attached" - when .access_violation?, .bad_memory_access? - # NOTE: this only happens with the empty prelude, because the stdlib - # runtime catches those exceptions and then exits _normally_ with exit - # code 11 or 1 - "Program exited because of an invalid memory access" - when .bad_instruction? - "Program exited because of an invalid instruction" - when .float_exception? - "Program exited because of a floating-point system exception" - when .unknown? - "Program exited abnormally, the cause is unknown" - end - end - record CompilerConfig, compiler : Compiler, sources : Array(Compiler::Source), diff --git a/src/process/status.cr b/src/process/status.cr index 78cff49f0dc9..b3c04aa3f50a 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -98,6 +98,37 @@ enum Process::ExitReason def abnormal? !normal? end + + # Returns a textual description of this exit reason. + # + # ``` + # Process::ExitReason::Normal.description # => "Process exited normally" + # Process::ExitReason::Aborted.description # => "Process terminated abnormally" + # ``` + # + # `Status#description` provides more detail for a specific process status. + def description + case self + in .normal? + "Process exited normally" + in .aborted?, .session_ended?, .terminal_disconnected? + "Process terminated abnormally" + in .interrupted? + "Process was interrupted" + in .breakpoint? + "Process hit a breakpoint and no debugger was attached" + in .access_violation?, .bad_memory_access? + "Process terminated because of an invalid memory access" + in .bad_instruction? + "Process terminated because of an invalid instruction" + in .float_exception? + "Process terminated because of a floating-point system exception" + in .signal? + "Process terminated because of an unhandled signal" + in .unknown? + "Process terminated abnormally, the cause is unknown" + end + end end # The status of a terminated process. Returned by `Process#wait`. @@ -371,6 +402,25 @@ class Process::Status {% end %} end + # Returns a textual description of this process status. + # + # ``` + # Process::Status.new(0).description # => "Process exited normally" + # process = Process.new("sleep", ["10"]) + # process.terminate + # process.wait.description + # # => "Process received and didn't handle signal TERM (15)" + # ``` + # + # `ExitReason#description` provides the specific messages for non-signal exits. + def description + if exit_reason.signal? && (signal = exit_signal?) + "Process received and didn't handle signal #{signal}" + else + exit_reason.description + end + end + private def stringify_exit_status_windows(io) # On Windows large status codes are typically expressed in hexadecimal if @exit_status >= UInt16::MAX