Skip to content
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
43 changes: 43 additions & 0 deletions core/kernel.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,49 @@ module Kernel : BasicObject
def self?.system: (String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)
| (Hash[string, string?] env, String command, *String args, ?unsetenv_others: boolish, ?pgroup: true | Integer, ?umask: Integer, ?in: redirect_fd, ?out: redirect_fd, ?err: redirect_fd, ?close_others: boolish, ?chdir: String, ?exception: bool) -> (NilClass | FalseClass | TrueClass)

# An interface used with `trace_var` (and `untrace_var`) for custom command types.
interface _Tracer
# Called whenever the global variable that's being traced changes; the argument is the new value.
def call: (untyped argument) -> void
end

# <!--
# rdoc-file=eval.c
# - trace_var(symbol, cmd ) -> nil
# - trace_var(symbol) {|val| block } -> nil
# -->
# Controls tracing of assignments to global variables. The parameter `symbol`
# identifies the variable (as either a string name or a symbol identifier).
# *cmd* (which may be a string or a `Proc` object) or block is executed whenever
# the variable is assigned. The block or `Proc` object receives the variable's
# new value as a parameter. Also see #untrace_var.
#
# trace_var :$_, proc {|v| puts "$_ is now '#{v}'" }
# $_ = "hello"
# $_ = ' there'
#
# *produces:*
#
# $_ is now 'hello'
# $_ is now ' there'
#
def self?.trace_var: (interned name, String | _Tracer cmd) -> nil
| (interned name) { (untyped value) -> void } -> nil
| (interned name, nil) -> Array[String | _Tracer]?

# <!--
# rdoc-file=eval.c
# - untrace_var(symbol [, cmd] ) -> array or nil
# -->
# Removes tracing for the specified command on the given global variable and
# returns `nil`. If no command is specified, removes all tracing for that
# variable and returns an array containing the commands actually removed.
#
def self?.untrace_var: (interned name, ?nil) -> Array[String | _Tracer]
| (interned name, String cmd) -> [String]?
| [T < _Tracer] (interned name, T cmd) -> [T]?
| (interned name, untyped cmd) -> nil

# <!--
# rdoc-file=object.c
# - obj !~ other -> true or false
Expand Down
75 changes: 75 additions & 0 deletions test/stdlib/Kernel_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,81 @@ def test_rand
assert_send_type "(Range[Float]) -> Float", Kernel, :rand, 0.0...10.0
assert_send_type "(Range[Float]) -> nil", Kernel, :rand, 0.0...0.0
end

def test_trace_var
tracer = BlankSlate.new
def tracer.call(new) nil end

with_interned '$__TEST_TRACE_VAR' do |name|
assert_send_type '(interned, String) -> nil',
Kernel, :trace_var, name, '1'
assert_send_type '(interned, ::Kernel::_Tracer) -> nil',
Kernel, :trace_var, name, tracer
assert_send_type '(interned) { (any) -> void } -> nil',
Kernel, :trace_var, name do |x| 0 end

# `Kernel.trace_var` doesn't actually check the type of its second argument,
# but instead defers until the global is actually assigned. To ensure that
# our signatures are correct, we assign the global here (which, if our
# signatures are incorrect, will raise an exception)
$__TEST_TRACE_VAR = 1

# Acts the same as `untrace_var`, so this performs the untracing for us.
assert_send_type '(interned, nil) -> Array[String | ::Kernel::_Tracer]',
Kernel, :trace_var, name, nil
end
ensure
# Just in case an exception stopped it, we don't want to continue tracing.
# We do `defined?` as `untrace_var :$some_undefined_global` fails
untrace_var :$__TEST_TRACE_VAR if defined? $__TEST_TRACE_VAR
end

def test_untrace_var
tracer = BlankSlate.new
def tracer.call(new) nil end

with_interned '$__TEST_UNTRACE_VAR' do |name|
# No argument yields all traces
trace_var :$__TEST_UNTRACE_VAR, '"string"'
trace_var :$__TEST_UNTRACE_VAR do "proc" end
trace_var :$__TEST_UNTRACE_VAR, tracer
assert_send_type '(interned) -> Array[String | ::Kernel::_Tracer]',
Kernel, :untrace_var, name

# `nil` also yields all traces
trace_var :$__TEST_UNTRACE_VAR, '"string"'
trace_var :$__TEST_UNTRACE_VAR do "proc" end
trace_var :$__TEST_UNTRACE_VAR, tracer
assert_send_type '(interned, nil) -> Array[String | ::Kernel::_Tracer]',
Kernel, :untrace_var, name, nil

# Passing a String in yields the string if they're the same, or `nil`
string = '"string"'
trace_var :$__TEST_UNTRACE_VAR, string
assert_send_type '(interned, String) -> [String]',
Kernel, :untrace_var, name, string
assert_send_type '(interned, String) -> nil',
Kernel, :untrace_var, name, 'not a trace'

# Passing a `tracer` yields the tracer if it's set, or `nil` otherwise
trace_var :$__TEST_UNTRACE_VAR, tracer
assert_send_type '[T < ::Kernel::_Tracer] (interned, T) -> [T]',
Kernel, :untrace_var, name, tracer
assert_send_type '[T < ::Kernel::_Tracer] (interned, T) -> nil',
Kernel, :untrace_var, name, tracer

# Anything else is `nil`
with_untyped do |trace|
next if nil == trace
assert_send_type '(interned, untyped) -> nil',
Kernel, :untrace_var, name, trace
end
end
ensure
# Just in case an exception stopped it, we don't want to continue tracing.
# We do `defined?` as `untrace_var :$some_undefined_global` fails
untrace_var :$__TEST_UNTRACE_VAR if defined? $__TEST_UNTRACE_VAR
end
end

class KernelInstanceTest < Test::Unit::TestCase
Expand Down