diff options
Diffstat (limited to 'spec/ruby/language/predefined_spec.rb')
| -rw-r--r-- | spec/ruby/language/predefined_spec.rb | 1574 |
1 files changed, 1574 insertions, 0 deletions
diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb new file mode 100644 index 0000000000..fc1667a38f --- /dev/null +++ b/spec/ruby/language/predefined_spec.rb @@ -0,0 +1,1574 @@ +require_relative '../spec_helper' +require_relative '../core/exception/shared/set_backtrace' +require 'stringio' + +# The following tables are excerpted from Programming Ruby: The Pragmatic Programmer's Guide' +# Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 319-22. +# +# Entries marked [r/o] are read-only and an error will be raised of the program attempts to +# modify them. Entries marked [thread] are thread local. + +# Exception Information +# --------------------------------------------------------------------------------------------------- +# +# $! Exception The exception object passed to raise. [thread] +# $@ Array The stack backtrace generated by the last exception. [thread] + +# Pattern Matching Variables +# --------------------------------------------------------------------------------------------------- +# +# These variables are set to nil after an unsuccessful pattern match. +# +# $& String The string matched (following a successful pattern match). This variable is +# local to the current scope. [r/o, thread] +# $+ String The contents of the highest-numbered group matched following a successful +# pattern match. Thus, in "cat" =~/(c|a)(t|z)/, $+ will be set to “t”. This +# variable is local to the current scope. [r/o, thread] +# $` String The string preceding the match in a successful pattern match. This variable +# is local to the current scope. [r/o, thread] +# $' String The string following the match in a successful pattern match. This variable +# is local to the current scope. [r/o, thread] +# $1 to $<N> String The contents of successive groups matched in a successful pattern match. In +# "cat" =~/(c|a)(t|z)/, $1 will be set to “a” and $2 to “t”. This variable +# is local to the current scope. [r/o, thread] +# $~ MatchData An object that encapsulates the results of a successful pattern match. The +# variables $&, $`, $', and $1 to $<N> are all derived from $~. Assigning to $~ +# changes the values of these derived variables. This variable is local to the +# current scope. [thread] + + +describe "Predefined global $~" do + it "is set to contain the MatchData object of the last match if successful" do + md = /foo/.match 'foo' + $~.should be_kind_of(MatchData) + $~.should equal md + + /bar/ =~ 'bar' + $~.should be_kind_of(MatchData) + $~.should_not equal md + end + + it "is set to nil if the last match was unsuccessful" do + /foo/ =~ 'foo' + $~.should_not.nil? + + /foo/ =~ 'bar' + $~.should.nil? + end + + it "is set at the method-scoped level rather than block-scoped" do + obj = Object.new + def obj.foo; yield; end + def obj.foo2(&proc); proc.call; end + + match2 = nil + match3 = nil + match4 = nil + + match1 = /foo/.match "foo" + + obj.foo { match2 = /bar/.match("bar") } + + match2.should_not == nil + $~.should == match2 + + match3 = /baz/.match("baz") + + match3.should_not == nil + $~.should == match3 + + obj.foo2 { match4 = /qux/.match("qux") } + + match4.should_not == nil + $~.should == match4 + end + + it "raises an error if assigned an object not nil or instanceof MatchData" do + $~ = nil + $~.should == nil + $~ = /foo/.match("foo") + $~.should be_an_instance_of(MatchData) + + -> { $~ = Object.new }.should raise_error(TypeError, 'wrong argument type Object (expected MatchData)') + -> { $~ = 1 }.should raise_error(TypeError, 'wrong argument type Integer (expected MatchData)') + end + + it "changes the value of derived capture globals when assigned" do + "foo" =~ /(f)oo/ + foo_match = $~ + "bar" =~ /(b)ar/ + $~ = foo_match + $1.should == "f" + end + + it "changes the value of the derived preceding match global" do + "foo hello" =~ /hello/ + foo_match = $~ + "bar" =~ /(bar)/ + $~ = foo_match + $`.should == "foo " + end + + it "changes the value of the derived following match global" do + "foo hello" =~ /foo/ + foo_match = $~ + "bar" =~ /(bar)/ + $~ = foo_match + $'.should == " hello" + end + + it "changes the value of the derived full match global" do + "foo hello" =~ /foo/ + foo_match = $~ + "bar" =~ /(bar)/ + $~ = foo_match + $&.should == "foo" + end +end + +describe "Predefined global $&" do + it "is equivalent to MatchData#[0] on the last match $~" do + /foo/ =~ 'barfoobaz' + $&.should == $~[0] + $&.should == 'foo' + end + + it "sets the encoding to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $&.encoding.should equal(Encoding::EUC_JP) + end + + it "is read-only" do + -> { + eval %q{$& = ""} + }.should raise_error(SyntaxError, /Can't set variable \$&/) + end + + it "is read-only when aliased" do + alias $predefined_spec_ampersand $& + -> { + $predefined_spec_ampersand = "" + }.should raise_error(NameError, '$predefined_spec_ampersand is a read-only variable') + end +end + +describe "Predefined global $`" do + it "is equivalent to MatchData#pre_match on the last match $~" do + /foo/ =~ 'barfoobaz' + $`.should == $~.pre_match + $`.should == 'bar' + end + + it "sets the encoding to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $`.encoding.should equal(Encoding::EUC_JP) + end + + it "sets an empty result to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /a/ + $`.encoding.should equal(Encoding::ISO_8859_1) + end + + it "is read-only" do + -> { + eval %q{$` = ""} + }.should raise_error(SyntaxError, /Can't set variable \$`/) + end + + it "is read-only when aliased" do + alias $predefined_spec_backquote $` + -> { + $predefined_spec_backquote = "" + }.should raise_error(NameError, '$predefined_spec_backquote is a read-only variable') + end +end + +describe "Predefined global $'" do + it "is equivalent to MatchData#post_match on the last match $~" do + /foo/ =~ 'barfoobaz' + $'.should == $~.post_match + $'.should == 'baz' + end + + it "sets the encoding to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /b/ + $'.encoding.should equal(Encoding::EUC_JP) + end + + it "sets an empty result to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::ISO_8859_1) =~ /c/ + $'.encoding.should equal(Encoding::ISO_8859_1) + end + + it "is read-only" do + -> { + eval %q{$' = ""} + }.should raise_error(SyntaxError, /Can't set variable \$'/) + end + + it "is read-only when aliased" do + alias $predefined_spec_single_quote $' + -> { + $predefined_spec_single_quote = "" + }.should raise_error(NameError, '$predefined_spec_single_quote is a read-only variable') + end +end + +describe "Predefined global $+" do + it "is equivalent to $~.captures.last" do + /(f(o)o)/ =~ 'barfoobaz' + $+.should == $~.captures.last + $+.should == 'o' + end + + it "captures the last non nil capture" do + /(a)|(b)/ =~ 'a' + $+.should == 'a' + end + + it "sets the encoding to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ + $+.encoding.should equal(Encoding::EUC_JP) + end + + it "is read-only" do + -> { + eval %q{$+ = ""} + }.should raise_error(SyntaxError, /Can't set variable \$\+/) + end + + it "is read-only when aliased" do + alias $predefined_spec_plus $+ + -> { + $predefined_spec_plus = "" + }.should raise_error(NameError, '$predefined_spec_plus is a read-only variable') + end +end + +describe "Predefined globals $1..N" do + it "are equivalent to $~[N]" do + /(f)(o)(o)/ =~ 'foo' + $1.should == $~[1] + $2.should == $~[2] + $3.should == $~[3] + $4.should == $~[4] + + [$1, $2, $3, $4].should == ['f', 'o', 'o', nil] + end + + it "are nil unless a match group occurs" do + def test(arg) + case arg + when /-(.)?/ + $1 + end + end + test("-").should == nil + end + + it "sets the encoding to the encoding of the source String" do + "abc".dup.force_encoding(Encoding::EUC_JP) =~ /(b)/ + $1.encoding.should equal(Encoding::EUC_JP) + end +end + +describe "Predefined global $stdout" do + before :each do + @old_stdout = $stdout + end + + after :each do + $stdout = @old_stdout + end + + it "raises TypeError error if assigned to nil" do + -> { $stdout = nil }.should raise_error(TypeError, '$stdout must have write method, NilClass given') + end + + it "raises TypeError error if assigned to object that doesn't respond to #write" do + obj = mock('object') + -> { $stdout = obj }.should raise_error(TypeError) + + obj.stub!(:write) + $stdout = obj + $stdout.should equal(obj) + end +end + +describe "Predefined global $!" do + it "is Fiber-local" do + Fiber.new do + raise "hi" + rescue + Fiber.yield + end.resume + + $!.should == nil + end + + it "is read-only" do + -> { + $! = [] + }.should raise_error(NameError, '$! is a read-only variable') + end + + # See http://jira.codehaus.org/browse/JRUBY-5550 + it "remains nil after a failed core class \"checked\" coercion against a class that defines method_missing" do + $!.should == nil + + obj = Class.new do + def method_missing(*args) + super + end + end.new + + [obj, 'foo'].join + + $!.should == nil + end + + it "should be set to the value of $! before the begin after a successful rescue" do + outer = StandardError.new 'outer' + inner = StandardError.new 'inner' + + begin + raise outer + rescue + $!.should == outer + + # nested rescue + begin + $!.should == outer + raise inner + rescue + $!.should == inner + ensure + $!.should == outer + end + $!.should == outer + end + $!.should == nil + end + + it "should be set to the value of $! before the begin after a rescue which returns" do + def foo + outer = StandardError.new 'outer' + inner = StandardError.new 'inner' + + begin + raise outer + rescue + $!.should == outer + + # nested rescue + begin + $!.should == outer + raise inner + rescue + $!.should == inner + return + ensure + $!.should == outer + end + $!.should == outer + end + $!.should == nil + end + foo + end + + it "should be set to the value of $! before the begin after a successful rescue within an ensure" do + outer = StandardError.new 'outer' + inner = StandardError.new 'inner' + + begin + begin + raise outer + ensure + $!.should == outer + + # nested rescue + begin + $!.should == outer + raise inner + rescue + $!.should == inner + ensure + $!.should == outer + end + $!.should == outer + end + flunk "outer should be raised after the ensure" + rescue + $!.should == outer + end + $!.should == nil + end + + it "should be set to the new exception after a throwing rescue" do + outer = StandardError.new 'outer' + inner = StandardError.new 'inner' + + begin + raise outer + rescue + $!.should == outer + + begin + # nested rescue + begin + $!.should == outer + raise inner + rescue # the throwing rescue + $!.should == inner + raise inner + ensure + $!.should == inner + end + rescue # do not make the exception fail the example + $!.should == inner + end + $!.should == outer + end + $!.should == nil + end + + describe "in bodies without ensure" do + it "should be cleared when an exception is rescued" do + e = StandardError.new 'foo' + begin + raise e + rescue + $!.should == e + end + $!.should == nil + end + + it "should be cleared when an exception is rescued even when a non-local return is present" do + def foo(e) + $!.should == e + yield + end + def bar + e = StandardError.new 'foo' + begin + raise e + rescue + $!.should == e + foo(e) { return } + end + end + + bar + $!.should == nil + end + + it "should be cleared when an exception is rescued even when a non-local return from block" do + def foo + [ 1 ].each do + begin + raise StandardError.new('err') + rescue => e + $!.should == e + return + end + end + end + + foo + $!.should == nil + end + + it "should not be cleared when an exception is not rescued" do + e = StandardError.new + begin + begin + begin + raise e + rescue TypeError + flunk + end + ensure + $!.should == e + end + rescue + $!.should == e + end + $!.should == nil + end + + it "should not be cleared when an exception is rescued and rethrown" do + e = StandardError.new 'foo' + begin + begin + begin + raise e + rescue => e + $!.should == e + raise e + end + ensure + $!.should == e + end + rescue + $!.should == e + end + $!.should == nil + end + end + + describe "in ensure-protected bodies" do + it "should be cleared when an exception is rescued" do + e = StandardError.new 'foo' + begin + raise e + rescue + $!.should == e + ensure + $!.should == nil + end + $!.should == nil + end + + it "should not be cleared when an exception is not rescued" do + e = StandardError.new + begin + begin + begin + raise e + rescue TypeError + flunk + ensure + $!.should == e + end + ensure + $!.should == e + end + rescue + $!.should == e + end + end + + it "should not be cleared when an exception is rescued and rethrown" do + e = StandardError.new + begin + begin + begin + raise e + rescue => e + $!.should == e + raise e + ensure + $!.should == e + end + ensure + $!.should == e + end + rescue + $!.should == e + end + end + end +end + +describe "Predefined global $@" do + it "is Fiber-local" do + Fiber.new do + raise "hi" + rescue + Fiber.yield + end.resume + + $@.should == nil + end + + it "is set to a backtrace of a rescued exception" do + begin + raise + rescue + $@.should be_an_instance_of(Array) + $@.should == $!.backtrace + end + end + + it "is cleared when an exception is rescued" do + begin + raise + rescue + end + + $@.should == nil + end + + it "is not set when there is no current exception" do + $@.should == nil + end + + it "is set to a backtrace of a rescued exception" do + begin + raise + rescue + $@.should be_an_instance_of(Array) + $@.should == $!.backtrace + end + end + + it "is not read-only" do + begin + raise + rescue + $@ = [] + $@.should == [] + end + end + + it_behaves_like :exception_set_backtrace, -> backtrace { + exception = nil + begin + raise + rescue + $@ = backtrace + exception = $! + end + exception + } + + it "cannot be assigned when there is no a rescued exception" do + -> { + $@ = [] + }.should raise_error(ArgumentError, '$! not set') + end +end + +# Input/Output Variables +# --------------------------------------------------------------------------------------------------- +# +# $/ String The input record separator (newline by default). This is the value that rou- +# tines such as Kernel#gets use to determine record boundaries. If set to +# nil, gets will read the entire file. +# $-0 String Synonym for $/. +# $\ String The string appended to the output of every call to methods such as +# Kernel#print and IO#write. The default value is nil. +# $, String The separator string output between the parameters to methods such as +# Kernel#print and Array#join. Defaults to nil, which adds no text. +# $. Integer The number of the last line read from the current input file. +# $; String The default separator pattern used by String#split. May be set from the +# command line using the -F flag. +# $< Object An object that provides access to the concatenation of the contents of all +# the files given as command-line arguments or $stdin (in the case where +# there are no arguments). $< supports methods similar to a File object: +# binmode, close, closed?, each, each_byte, each_line, eof, eof?, +# file, filename, fileno, getc, gets, lineno, lineno=, path, pos, pos=, +# read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, +# to_i, to_io, to_s, along with the methods in Enumerable. The method +# file returns a File object for the file currently being read. This may change +# as $< reads through the files on the command line. [r/o] +# $> IO The destination of output for Kernel#print and Kernel#printf. The +# default value is $stdout. +# $_ String The last line read by Kernel#gets or Kernel#readline. Many string- +# related functions in the Kernel module operate on $_ by default. The vari- +# able is local to the current scope. [thread] +# $-F String Synonym for $;. +# $stderr IO The current standard error output. +# $stdin IO The current standard input. +# $stdout IO The current standard output. Assignment to $stdout is deprecated: use +# $stdout.reopen instead. + +describe "Predefined global $/" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_slash = $/ + @dollar_dash_zero = $-0 + end + + after :each do + $/ = @dollar_slash + $-0 = @dollar_dash_zero + $VERBOSE = @verbose + end + + ruby_version_is ""..."4.0" do + it "can be assigned a String" do + str = +"abc" + $/ = str + $/.should equal(str) + end + end + + ruby_version_is "4.0" do + it "makes a new frozen String from the assigned String" do + string_subclass = Class.new(String) + str = string_subclass.new("abc") + str.instance_variable_set(:@ivar, 1) + $/ = str + $/.should.frozen? + $/.should be_an_instance_of(String) + $/.should_not.instance_variable_defined?(:@ivar) + $/.should == str + end + + it "makes a new frozen String if it's not frozen" do + str = +"abc" + $/ = str + $/.should.frozen? + $/.should == str + end + + it "assigns the given String if it's frozen and has no instance variables" do + str = "abc".freeze + $/ = str + $/.should equal(str) + end + end + it "can be assigned nil" do + $/ = nil + $/.should be_nil + end + + it "returns the value assigned" do + ($/ = "xyz").should == "xyz" + end + + it "changes $-0" do + $/ = "xyz" + $-0.should equal($/) + end + + it "does not call #to_str to convert the object to a String" do + obj = mock("$/ value") + obj.should_not_receive(:to_str) + + -> { $/ = obj }.should raise_error(TypeError, 'value of $/ must be String') + end + + it "raises a TypeError if assigned an Integer" do + -> { $/ = 1 }.should raise_error(TypeError, 'value of $/ must be String') + end + + it "raises a TypeError if assigned a boolean" do + -> { $/ = true }.should raise_error(TypeError, 'value of $/ must be String') + end + + it "warns if assigned non-nil" do + -> { $/ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\/' is deprecated/) + end +end + +describe "Predefined global $-0" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_slash = $/ + @dollar_dash_zero = $-0 + end + + after :each do + $/ = @dollar_slash + $-0 = @dollar_dash_zero + $VERBOSE = @verbose + end + + ruby_version_is ""..."4.0" do + it "can be assigned a String" do + str = +"abc" + $-0 = str + $-0.should equal(str) + end + end + + ruby_version_is "4.0" do + it "makes a new frozen String from the assigned String" do + string_subclass = Class.new(String) + str = string_subclass.new("abc") + str.instance_variable_set(:@ivar, 1) + $-0 = str + $-0.should.frozen? + $-0.should be_an_instance_of(String) + $-0.should_not.instance_variable_defined?(:@ivar) + $-0.should == str + end + + it "makes a new frozen String if it's not frozen" do + str = +"abc" + $-0 = str + $-0.should.frozen? + $-0.should == str + end + + it "assigns the given String if it's frozen and has no instance variables" do + str = "abc".freeze + $-0 = str + $-0.should equal(str) + end + end + + it "can be assigned nil" do + $-0 = nil + $-0.should be_nil + end + + it "returns the value assigned" do + ($-0 = "xyz").should == "xyz" + end + + it "changes $/" do + $-0 = "xyz" + $/.should equal($-0) + end + + it "does not call #to_str to convert the object to a String" do + obj = mock("$-0 value") + obj.should_not_receive(:to_str) + + -> { $-0 = obj }.should raise_error(TypeError, 'value of $-0 must be String') + end + + it "raises a TypeError if assigned an Integer" do + -> { $-0 = 1 }.should raise_error(TypeError, 'value of $-0 must be String') + end + + it "raises a TypeError if assigned a boolean" do + -> { $-0 = true }.should raise_error(TypeError, 'value of $-0 must be String') + end + + it "warns if assigned non-nil" do + -> { $-0 = "_" }.should complain(/warning: (?:non-nil )?[`']\$-0' is deprecated/) + end +end + +describe "Predefined global $\\" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_backslash = $\ + end + + after :each do + $\ = @dollar_backslash + $VERBOSE = @verbose + end + + it "can be assigned a String" do + str = "abc" + $\ = str + $\.should equal(str) + end + + it "can be assigned nil" do + $\ = nil + $\.should be_nil + end + + it "returns the value assigned" do + ($\ = "xyz").should == "xyz" + end + + it "does not call #to_str to convert the object to a String" do + obj = mock("$\\ value") + obj.should_not_receive(:to_str) + + -> { $\ = obj }.should raise_error(TypeError, 'value of $\ must be String') + end + + it "raises a TypeError if assigned not String" do + -> { $\ = 1 }.should raise_error(TypeError, 'value of $\ must be String') + -> { $\ = true }.should raise_error(TypeError, 'value of $\ must be String') + end + + it "warns if assigned non-nil" do + -> { $\ = "_" }.should complain(/warning: (?:non-nil )?[`']\$\\' is deprecated/) + end +end + +describe "Predefined global $," do + after :each do + $, = nil + end + + it "defaults to nil" do + $,.should be_nil + end + + it "raises TypeError if assigned a non-String" do + -> { $, = Object.new }.should raise_error(TypeError, 'value of $, must be String') + end + + it "warns if assigned non-nil" do + -> { $, = "_" }.should complain(/warning: (?:non-nil )?[`']\$,' is deprecated/) + end +end + +describe "Predefined global $." do + it "can be assigned an Integer" do + $. = 123 + $..should == 123 + end + + it "can be assigned a Float" do + $. = 123.5 + $..should == 123 + end + + it "should call #to_int to convert the object to an Integer" do + obj = mock("good-value") + obj.should_receive(:to_int).and_return(321) + + $. = obj + $..should == 321 + end + + it "raises TypeError if object can't be converted to an Integer" do + obj = mock("bad-value") + obj.should_receive(:to_int).and_return('abc') + + -> { $. = obj }.should raise_error(TypeError) + end +end + +describe "Predefined global $;" do + after :each do + $; = nil + end + + it "warns if assigned non-nil" do + -> { $; = "_" }.should complain(/warning: (?:non-nil )?[`']\$;' is deprecated/) + end +end + +describe "Predefined global $_" do + it "is set to the last line read by e.g. StringIO#gets" do + stdin = StringIO.new("foo\nbar\n", "r") + + read = stdin.gets + read.should == "foo\n" + $_.should == read + + read = stdin.gets + read.should == "bar\n" + $_.should == read + + read = stdin.gets + read.should == nil + $_.should == read + end + + it "is set at the method-scoped level rather than block-scoped" do + obj = Object.new + def obj.foo; yield; end + def obj.foo2; yield; end + + stdin = StringIO.new("foo\nbar\nbaz\nqux\n", "r") + match = stdin.gets + + obj.foo { match = stdin.gets } + + match.should == "bar\n" + $_.should == match + + match = stdin.gets + + match.should == "baz\n" + $_.should == match + + obj.foo2 { match = stdin.gets } + + match.should == "qux\n" + $_.should == match + end + + it "is Thread-local" do + $_ = nil + running = false + + thr = Thread.new do + $_ = "last line" + running = true + end + + Thread.pass until running + $_.should be_nil + + thr.join + end + + it "can be assigned any value" do + $_ = nil + $_.should == nil + $_ = "foo" + $_.should == "foo" + o = Object.new + $_ = o + $_.should == o + $_ = 1 + $_.should == 1 + end +end + +# Execution Environment Variables +# --------------------------------------------------------------------------------------------------- +# +# $0 String The name of the top-level Ruby program being executed. Typically this will +# be the program’s filename. On some operating systems, assigning to this +# variable will change the name of the process reported (for example) by the +# ps(1) command. +# $* Array An array of strings containing the command-line options from the invoca- +# tion of the program. Options used by the Ruby interpreter will have been +# removed. [r/o] +# $" Array An array containing the filenames of modules loaded by require. [r/o] +# $$ Integer The process number of the program being executed. [r/o] +# $? Process::Status The exit status of the last child process to terminate. [r/o, thread] +# $: Array An array of strings, where each string specifies a directory to be searched for +# Ruby scripts and binary extensions used by the load and require methods. +# The initial value is the value of the arguments passed via the -I command- +# line option, followed by an installation-defined standard library location, fol- +# lowed by the current directory (“.”). This variable may be set from within a +# program to alter the default search path; typically, programs use $: << dir +# to append dir to the path. [r/o] +# $-a Object True if the -a option is specified on the command line. [r/o] +# $-d Object Synonym for $DEBUG. +# $DEBUG Object Set to true if the -d command-line option is specified. +# __FILE__ String The name of the current source file. [r/o] +# $F Array The array that receives the split input line if the -a command-line option is +# used. +# $FILENAME String The name of the current input file. Equivalent to $<.filename. [r/o] +# $-i String If in-place edit mode is enabled (perhaps using the -i command-line +# option), $-i holds the extension used when creating the backup file. If you +# set a value into $-i, enables in-place edit mode. +# $-I Array Synonym for $:. [r/o] +# $-K String Sets the multibyte coding system for strings and regular expressions. Equiv- +# alent to the -K command-line option. +# $-l Object Set to true if the -l option (which enables line-end processing) is present +# on the command line. [r/o] +# __LINE__ String The current line number in the source file. [r/o] +# $LOAD_PATH Array A synonym for $:. [r/o] +# $-p Object Set to true if the -p option (which puts an implicit while gets . . . end +# loop around your program) is present on the command line. [r/o] +# $VERBOSE Object Set to true if the -v, --version, -W, or -w option is specified on the com- +# mand line. Set to false if no option, or -W1 is given. Set to nil if -W0 +# was specified. Setting this option to true causes the interpreter and some +# library routines to report additional information. Setting to nil suppresses +# all warnings (including the output of Kernel.warn). +# $-v Object Synonym for $VERBOSE. +# $-w Object Synonym for $VERBOSE. +describe "Execution variable $:" do + it "is initialized to an array of strings" do + $:.is_a?(Array).should == true + ($:.length > 0).should == true + end + + it "does not include the current directory" do + $:.should_not include(".") + end + + it "is the same object as $LOAD_PATH and $-I" do + $:.__id__.should == $LOAD_PATH.__id__ + $:.__id__.should == $-I.__id__ + end + + it "can be changed via <<" do + $: << "foo" + $:.should include("foo") + ensure + $:.delete("foo") + end + + it "is read-only" do + -> { + $: = [] + }.should raise_error(NameError, '$: is a read-only variable') + + -> { + $LOAD_PATH = [] + }.should raise_error(NameError, '$LOAD_PATH is a read-only variable') + + -> { + $-I = [] + }.should raise_error(NameError, '$-I is a read-only variable') + end + + it "default $LOAD_PATH entries until sitelibdir included have @gem_prelude_index set" do + skip "no sense in ruby itself" if MSpecScript.instance_variable_defined?(:@testing_ruby) + + if platform_is :windows + # See https://github.com/ruby/setup-ruby/pull/762#issuecomment-2917460440 + $:.should.find { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + idx = $:.index { |e| File.realdirpath(e) == RbConfig::CONFIG['sitelibdir'] } + else + $:.should.include?(RbConfig::CONFIG['sitelibdir']) + idx = $:.index(RbConfig::CONFIG['sitelibdir']) + end + + $:[idx..-1].all? { |p| p.instance_variable_defined?(:@gem_prelude_index) }.should be_true + $:[0...idx].all? { |p| !p.instance_variable_defined?(:@gem_prelude_index) }.should be_true + end +end + +describe "Global variable $\"" do + it "is an alias for $LOADED_FEATURES" do + $".should equal $LOADED_FEATURES + end + + it "is read-only" do + -> { + $" = [] + }.should raise_error(NameError, '$" is a read-only variable') + + -> { + $LOADED_FEATURES = [] + }.should raise_error(NameError, '$LOADED_FEATURES is a read-only variable') + end +end + +describe "Global variable $<" do + it "is read-only" do + -> { + $< = nil + }.should raise_error(NameError, '$< is a read-only variable') + end +end + +describe "Global variable $FILENAME" do + it "is read-only" do + -> { + $FILENAME = "-" + }.should raise_error(NameError, '$FILENAME is a read-only variable') + end +end + +describe "Global variable $?" do + it "is read-only" do + -> { + $? = nil + }.should raise_error(NameError, '$? is a read-only variable') + end + + it "is thread-local" do + system(ruby_cmd('exit 0')) + Thread.new { $?.should be_nil }.join + end +end + +describe "Global variable $-a" do + it "is read-only" do + -> { $-a = true }.should raise_error(NameError, '$-a is a read-only variable') + end +end + +describe "Global variable $-l" do + it "is read-only" do + -> { $-l = true }.should raise_error(NameError, '$-l is a read-only variable') + end +end + +describe "Global variable $-p" do + it "is read-only" do + -> { $-p = true }.should raise_error(NameError, '$-p is a read-only variable') + end +end + +describe "Global variable $-d" do + before :each do + @debug = $DEBUG + end + + after :each do + $DEBUG = @debug + end + + it "is an alias of $DEBUG" do + $DEBUG = true + $-d.should be_true + $-d = false + $DEBUG.should be_false + end +end + +describe "Global variable $VERBOSE" do + before :each do + @verbose = $VERBOSE + end + + after :each do + $VERBOSE = @verbose + end + + it "is false by default" do + $VERBOSE.should be_false + end + + it "converts truthy values to true" do + [true, 1, 0, [], ""].each do |true_value| + $VERBOSE = true_value + $VERBOSE.should be_true + end + end + + it "allows false" do + $VERBOSE = false + $VERBOSE.should be_false + end + + it "allows nil without coercing to false" do + $VERBOSE = nil + $VERBOSE.should be_nil + end +end + +describe :verbose_global_alias, shared: true do + before :each do + @verbose = $VERBOSE + end + + after :each do + $VERBOSE = @verbose + end + + it "is an alias of $VERBOSE" do + $VERBOSE = true + eval(@method).should be_true + eval("#{@method} = false") + $VERBOSE.should be_false + end +end + +describe "Global variable $-v" do + it_behaves_like :verbose_global_alias, '$-v' +end + +describe "Global variable $-w" do + it_behaves_like :verbose_global_alias, '$-w' +end + +describe "Global variable $0" do + before :each do + @orig_program_name = $0 + end + + after :each do + $0 = @orig_program_name + end + + it "is the path given as the main script and the same as __FILE__" do + script = "fixtures/dollar_zero.rb" + Dir.chdir(__dir__) do + ruby_exe(script).should == "#{script}\n#{script}\nOK" + end + end + + it "returns the program name" do + $0 = "rbx" + $0.should == "rbx" + end + + platform_is :linux, :darwin do + it "actually sets the program name" do + title = "rubyspec-dollar0-test" + $0 = title + `ps -ocommand= -p#{$$}`.should include(title) + end + end + + it "returns the given value when set" do + ($0 = "rbx").should == "rbx" + end + + it "raises a TypeError when not given an object that can be coerced to a String" do + -> { $0 = nil }.should raise_error(TypeError) + end +end + +# Standard Objects +# --------------------------------------------------------------------------------------------------- +# +# ARGF Object A synonym for $<. +# ARGV Array A synonym for $*. +# ENV Object A hash-like object containing the program’s environment variables. An +# instance of class Object, ENV implements the full set of Hash methods. Used +# to query and set the value of an environment variable, as in ENV["PATH"] +# and ENV["term"]="ansi". +# false FalseClass Singleton instance of class FalseClass. [r/o] +# nil NilClass The singleton instance of class NilClass. The value of uninitialized +# instance and global variables. [r/o] +# self Object The receiver (object) of the current method. [r/o] +# true TrueClass Singleton instance of class TrueClass. [r/o] + +describe "The predefined standard objects" do + it "includes ARGF" do + Object.const_defined?(:ARGF).should == true + end + + it "includes ARGV" do + Object.const_defined?(:ARGV).should == true + end + + it "includes a hash-like object ENV" do + Object.const_defined?(:ENV).should == true + ENV.respond_to?(:[]).should == true + end +end + +describe "The predefined standard object nil" do + it "is an instance of NilClass" do + nil.should be_kind_of(NilClass) + end + + it "raises a SyntaxError if assigned to" do + -> { eval("nil = true") }.should raise_error(SyntaxError, /Can't assign to nil/) + end +end + +describe "The predefined standard object true" do + it "is an instance of TrueClass" do + true.should be_kind_of(TrueClass) + end + + it "raises a SyntaxError if assigned to" do + -> { eval("true = false") }.should raise_error(SyntaxError, /Can't assign to true/) + end +end + +describe "The predefined standard object false" do + it "is an instance of FalseClass" do + false.should be_kind_of(FalseClass) + end + + it "raises a SyntaxError if assigned to" do + -> { eval("false = nil") }.should raise_error(SyntaxError, /Can't assign to false/) + end +end + +describe "The self pseudo-variable" do + it "raises a SyntaxError if assigned to" do + -> { eval("self = 1") }.should raise_error(SyntaxError, /Can't change the value of self/) + end +end + +# Global Constants +# --------------------------------------------------------------------------------------------------- +# +# The following constants are defined by the Ruby interpreter. +# +# DATA IO If the main program file contains the directive __END__, then +# the constant DATA will be initialized so that reading from it will +# return lines following __END__ from the source file. +# FALSE FalseClass Synonym for false (deprecated, removed in Ruby 3). +# NIL NilClass Synonym for nil (deprecated, removed in Ruby 3). +# RUBY_PLATFORM String The identifier of the platform running this program. This string +# is in the same form as the platform identifier used by the GNU +# configure utility (which is not a coincidence). +# RUBY_RELEASE_DATE String The date of this release. +# RUBY_VERSION String The version number of the interpreter. +# STDERR IO The actual standard error stream for the program. The initial +# value of $stderr. +# STDIN IO The actual standard input stream for the program. The initial +# value of $stdin. +# STDOUT IO The actual standard output stream for the program. The initial +# value of $stdout. +# SCRIPT_LINES__ Hash If a constant SCRIPT_LINES__ is defined and references a Hash, +# Ruby will store an entry containing the contents of each file it +# parses, with the file’s name as the key and an array of strings as +# the value. +# TOPLEVEL_BINDING Binding A Binding object representing the binding at Ruby’s top level— +# the level where programs are initially executed. +# TRUE TrueClass Synonym for true (deprecated, removed in Ruby 3). + +describe "The predefined global constants" do + describe "TRUE" do + it "is no longer defined" do + Object.const_defined?(:TRUE).should == false + end + end + + describe "FALSE" do + it "is no longer defined" do + Object.const_defined?(:FALSE).should == false + end + end + + describe "NIL" do + it "is no longer defined" do + Object.const_defined?(:NIL).should == false + end + end + + it "includes STDIN" do + Object.const_defined?(:STDIN).should == true + end + + it "includes STDOUT" do + Object.const_defined?(:STDOUT).should == true + end + + it "includes STDERR" do + Object.const_defined?(:STDERR).should == true + end + + it "includes RUBY_VERSION" do + Object.const_defined?(:RUBY_VERSION).should == true + end + + it "includes RUBY_RELEASE_DATE" do + Object.const_defined?(:RUBY_RELEASE_DATE).should == true + end + + it "includes RUBY_PLATFORM" do + Object.const_defined?(:RUBY_PLATFORM).should == true + end + + it "includes TOPLEVEL_BINDING" do + Object.const_defined?(:TOPLEVEL_BINDING).should == true + end +end + +describe "The predefined global constant" do + before :each do + @external = Encoding.default_external + @internal = Encoding.default_internal + end + + after :each do + Encoding.default_external = @external + Encoding.default_internal = @internal + end + + describe "STDIN" do + platform_is_not :windows do + it "has the same external encoding as Encoding.default_external" do + STDIN.external_encoding.should equal(Encoding.default_external) + end + + it "has the same external encoding as Encoding.default_external when that encoding is changed" do + Encoding.default_external = Encoding::ISO_8859_16 + STDIN.external_encoding.should equal(Encoding::ISO_8859_16) + end + + it "has nil for the internal encoding" do + STDIN.internal_encoding.should be_nil + end + + it "has nil for the internal encoding despite Encoding.default_internal being changed" do + Encoding.default_internal = Encoding::IBM437 + STDIN.internal_encoding.should be_nil + end + end + + it "has the encodings set by #set_encoding" do + code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \ + "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]" + ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]} + end + + it "retains the encoding set by #set_encoding when Encoding.default_external is changed" do + code = "STDIN.set_encoding Encoding::IBM775, Encoding::IBM866; " \ + "Encoding.default_external = Encoding::ISO_8859_16;" \ + "p [STDIN.external_encoding.name, STDIN.internal_encoding.name]" + ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]} + end + end + + describe "STDOUT" do + it "has nil for the external encoding" do + STDOUT.external_encoding.should be_nil + end + + it "has nil for the external encoding despite Encoding.default_external being changed" do + Encoding.default_external = Encoding::ISO_8859_1 + STDOUT.external_encoding.should be_nil + end + + it "has the encodings set by #set_encoding" do + code = "STDOUT.set_encoding Encoding::IBM775, Encoding::IBM866; " \ + "p [STDOUT.external_encoding.name, STDOUT.internal_encoding.name]" + ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]} + end + + it "has nil for the internal encoding" do + STDOUT.internal_encoding.should be_nil + end + + it "has nil for the internal encoding despite Encoding.default_internal being changed" do + Encoding.default_internal = Encoding::IBM437 + STDOUT.internal_encoding.should be_nil + end + end + + describe "STDERR" do + it "has nil for the external encoding" do + STDERR.external_encoding.should be_nil + end + + it "has nil for the external encoding despite Encoding.default_external being changed" do + Encoding.default_external = Encoding::ISO_8859_1 + STDERR.external_encoding.should be_nil + end + + it "has the encodings set by #set_encoding" do + code = "STDERR.set_encoding Encoding::IBM775, Encoding::IBM866; " \ + "p [STDERR.external_encoding.name, STDERR.internal_encoding.name]" + ruby_exe(code).chomp.should == %{["IBM775", "IBM866"]} + end + + it "has nil for the internal encoding" do + STDERR.internal_encoding.should be_nil + end + + it "has nil for the internal encoding despite Encoding.default_internal being changed" do + Encoding.default_internal = Encoding::IBM437 + STDERR.internal_encoding.should be_nil + end + end + + describe "ARGV" do + it "contains Strings encoded in locale Encoding" do + code = fixture __FILE__, "argv_encoding.rb" + result = ruby_exe(code, args: "a b") + encoding = Encoding.default_external + result.chomp.should == %{["#{encoding}", "#{encoding}"]} + end + end +end + +describe "$LOAD_PATH.resolve_feature_path" do + it "returns what will be loaded without actual loading, .rb file" do + extension, path = $LOAD_PATH.resolve_feature_path('pp') + extension.should == :rb + path.should.end_with?('/pp.rb') + end + + it "returns what will be loaded without actual loading, .so file" do + require 'rbconfig' + skip "no dynamically loadable standard extension" if RbConfig::CONFIG["EXTSTATIC"] == "static" + + extension, path = $LOAD_PATH.resolve_feature_path('etc') + extension.should == :so + path.should.end_with?("/etc.#{RbConfig::CONFIG['DLEXT']}") + end + + it "return nil if feature cannot be found" do + $LOAD_PATH.resolve_feature_path('noop').should be_nil + end +end + +# Some other pre-defined global variables + +describe "Predefined global $=" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @dollar_assign = $= + end + + after :each do + $= = @dollar_assign + $VERBOSE = @verbose + end + + it "warns when accessed" do + -> { a = $= }.should complain(/is no longer effective/) + end + + it "warns when assigned" do + -> { $= = "_" }.should complain(/is no longer effective/) + end + + it "returns the value assigned" do + ($= = "xyz").should == "xyz" + end +end |
