Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Process::Status#description #15468

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions spec/std/process/exit_reason_spec.cr
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions spec/std/process/status_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
32 changes: 2 additions & 30 deletions src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
50 changes: 50 additions & 0 deletions src/process/status.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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)"
Comment on lines +411 to +412
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That IMO would make it clearer:

Suggested change
# process.wait.description
# # => "Process received and didn't handle signal TERM (15)"
# 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
Expand Down
Loading