From 95e8c48dd3348503a8c7db5d0498894a1b676395 Mon Sep 17 00:00:00 2001 From: eregon Date: Sun, 7 May 2017 12:04:49 +0000 Subject: Add in-tree mspec and ruby/spec * For easier modifications of ruby/spec by MRI developers. * .gitignore: track changes under spec. * spec/mspec, spec/rubyspec: add in-tree mspec and ruby/spec. These files can therefore be updated like any other file in MRI. Instructions are provided in spec/README. [Feature #13156] [ruby-core:79246] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58595 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- spec/rubyspec/language/BEGIN_spec.rb | 32 + spec/rubyspec/language/README | 30 + spec/rubyspec/language/alias_spec.rb | 190 +++ spec/rubyspec/language/and_spec.rb | 80 ++ spec/rubyspec/language/array_spec.rb | 155 +++ spec/rubyspec/language/block_spec.rb | 865 +++++++++++++ spec/rubyspec/language/break_spec.rb | 334 +++++ spec/rubyspec/language/case_spec.rb | 382 ++++++ spec/rubyspec/language/class_spec.rb | 332 +++++ spec/rubyspec/language/class_variable_spec.rb | 84 ++ spec/rubyspec/language/constants_spec.rb | 613 ++++++++++ spec/rubyspec/language/def_spec.rb | 714 +++++++++++ spec/rubyspec/language/defined_spec.rb | 1130 +++++++++++++++++ spec/rubyspec/language/encoding_spec.rb | 36 + spec/rubyspec/language/ensure_spec.rb | 126 ++ spec/rubyspec/language/execution_spec.rb | 15 + spec/rubyspec/language/file_spec.rb | 29 + spec/rubyspec/language/fixtures/argv_encoding.rb | 1 + spec/rubyspec/language/fixtures/array.rb | 11 + spec/rubyspec/language/fixtures/block.rb | 57 + spec/rubyspec/language/fixtures/break.rb | 263 ++++ .../language/fixtures/break_lambda_toplevel.rb | 9 + .../fixtures/break_lambda_toplevel_block.rb | 23 + .../fixtures/break_lambda_toplevel_method.rb | 17 + spec/rubyspec/language/fixtures/classes.rb | 31 + spec/rubyspec/language/fixtures/coding_us_ascii.rb | 11 + spec/rubyspec/language/fixtures/coding_utf_8.rb | 11 + .../language/fixtures/constant_visibility.rb | 98 ++ .../rubyspec/language/fixtures/constants_sclass.rb | 54 + spec/rubyspec/language/fixtures/def.rb | 8 + spec/rubyspec/language/fixtures/defined.rb | 296 +++++ spec/rubyspec/language/fixtures/dollar_zero.rb | 6 + spec/rubyspec/language/fixtures/ensure.rb | 72 ++ spec/rubyspec/language/fixtures/file.rb | 1 + .../fixtures/freeze_magic_comment_across_files.rb | 5 + .../freeze_magic_comment_across_files_diff_enc.rb | 5 + ...freeze_magic_comment_across_files_no_comment.rb | 5 + .../fixtures/freeze_magic_comment_one_literal.rb | 4 + .../fixtures/freeze_magic_comment_required.rb | 3 + .../freeze_magic_comment_required_diff_enc.rb | Bin 0 -> 181 bytes .../freeze_magic_comment_required_no_comment.rb | 1 + .../fixtures/freeze_magic_comment_two_literals.rb | 3 + .../language/fixtures/hash_strings_ascii8bit.rb | 7 + .../language/fixtures/hash_strings_usascii.rb | 7 + .../language/fixtures/hash_strings_utf8.rb | 7 + spec/rubyspec/language/fixtures/match_operators.rb | 9 + spec/rubyspec/language/fixtures/metaclass.rb | 34 + spec/rubyspec/language/fixtures/module.rb | 24 + spec/rubyspec/language/fixtures/next.rb | 134 ++ spec/rubyspec/language/fixtures/precedence.rb | 16 + spec/rubyspec/language/fixtures/private.rb | 59 + spec/rubyspec/language/fixtures/rescue.rb | 63 + spec/rubyspec/language/fixtures/return.rb | 139 +++ spec/rubyspec/language/fixtures/send.rb | 140 +++ .../rubyspec/language/fixtures/squiggly_heredoc.rb | 39 + spec/rubyspec/language/fixtures/super.rb | 569 +++++++++ spec/rubyspec/language/fixtures/variables.rb | 85 ++ spec/rubyspec/language/fixtures/yield.rb | 37 + spec/rubyspec/language/for_spec.rb | 171 +++ spec/rubyspec/language/hash_spec.rb | 154 +++ spec/rubyspec/language/heredoc_spec.rb | 87 ++ spec/rubyspec/language/if_spec.rb | 354 ++++++ spec/rubyspec/language/lambda_spec.rb | 573 +++++++++ spec/rubyspec/language/line_spec.rb | 45 + spec/rubyspec/language/loop_spec.rb | 67 + spec/rubyspec/language/magic_comment_spec.rb | 62 + spec/rubyspec/language/match_spec.rb | 74 ++ spec/rubyspec/language/metaclass_spec.rb | 143 +++ spec/rubyspec/language/method_spec.rb | 1290 ++++++++++++++++++++ spec/rubyspec/language/module_spec.rb | 91 ++ spec/rubyspec/language/next_spec.rb | 410 +++++++ spec/rubyspec/language/not_spec.rb | 51 + spec/rubyspec/language/numbers_spec.rb | 97 ++ .../rubyspec/language/optional_assignments_spec.rb | 226 ++++ spec/rubyspec/language/or_spec.rb | 90 ++ spec/rubyspec/language/order_spec.rb | 75 ++ spec/rubyspec/language/precedence_spec.rb | 448 +++++++ spec/rubyspec/language/predefined/data_spec.rb | 29 + .../rubyspec/language/predefined/fixtures/data1.rb | 4 + .../rubyspec/language/predefined/fixtures/data2.rb | 4 + .../rubyspec/language/predefined/fixtures/data3.rb | 7 + .../rubyspec/language/predefined/fixtures/data4.rb | 4 + .../rubyspec/language/predefined/fixtures/data5.rb | 5 + .../language/predefined/fixtures/data_only.rb | 2 + .../language/predefined/fixtures/print_data.rb | 3 + spec/rubyspec/language/predefined_spec.rb | 1221 ++++++++++++++++++ spec/rubyspec/language/private_spec.rb | 67 + spec/rubyspec/language/proc_spec.rb | 220 ++++ spec/rubyspec/language/redo_spec.rb | 66 + spec/rubyspec/language/regexp/anchors_spec.rb | 179 +++ .../language/regexp/back-references_spec.rb | 48 + .../language/regexp/character_classes_spec.rb | 617 ++++++++++ spec/rubyspec/language/regexp/encoding_spec.rb | 103 ++ spec/rubyspec/language/regexp/escapes_spec.rb | 81 ++ spec/rubyspec/language/regexp/grouping_spec.rb | 23 + .../rubyspec/language/regexp/interpolation_spec.rb | 58 + spec/rubyspec/language/regexp/modifiers_spec.rb | 110 ++ spec/rubyspec/language/regexp/repetition_spec.rb | 57 + spec/rubyspec/language/regexp_spec.rb | 150 +++ spec/rubyspec/language/rescue_spec.rb | 293 +++++ spec/rubyspec/language/retry_spec.rb | 52 + spec/rubyspec/language/return_spec.rb | 245 ++++ spec/rubyspec/language/safe_navigator_spec.rb | 101 ++ spec/rubyspec/language/send_spec.rb | 521 ++++++++ spec/rubyspec/language/shared/__FILE__.rb | 23 + spec/rubyspec/language/shared/__LINE__.rb | 15 + spec/rubyspec/language/singleton_class_spec.rb | 293 +++++ spec/rubyspec/language/string_spec.rb | 282 +++++ spec/rubyspec/language/super_spec.rb | 282 +++++ spec/rubyspec/language/symbol_spec.rb | 93 ++ spec/rubyspec/language/throw_spec.rb | 80 ++ spec/rubyspec/language/undef_spec.rb | 39 + spec/rubyspec/language/unless_spec.rb | 43 + spec/rubyspec/language/until_spec.rb | 234 ++++ spec/rubyspec/language/variables_spec.rb | 776 ++++++++++++ spec/rubyspec/language/while_spec.rb | 344 ++++++ spec/rubyspec/language/yield_spec.rb | 179 +++ 117 files changed, 18972 insertions(+) create mode 100644 spec/rubyspec/language/BEGIN_spec.rb create mode 100644 spec/rubyspec/language/README create mode 100644 spec/rubyspec/language/alias_spec.rb create mode 100644 spec/rubyspec/language/and_spec.rb create mode 100644 spec/rubyspec/language/array_spec.rb create mode 100644 spec/rubyspec/language/block_spec.rb create mode 100644 spec/rubyspec/language/break_spec.rb create mode 100644 spec/rubyspec/language/case_spec.rb create mode 100644 spec/rubyspec/language/class_spec.rb create mode 100644 spec/rubyspec/language/class_variable_spec.rb create mode 100644 spec/rubyspec/language/constants_spec.rb create mode 100644 spec/rubyspec/language/def_spec.rb create mode 100644 spec/rubyspec/language/defined_spec.rb create mode 100644 spec/rubyspec/language/encoding_spec.rb create mode 100644 spec/rubyspec/language/ensure_spec.rb create mode 100644 spec/rubyspec/language/execution_spec.rb create mode 100644 spec/rubyspec/language/file_spec.rb create mode 100644 spec/rubyspec/language/fixtures/argv_encoding.rb create mode 100644 spec/rubyspec/language/fixtures/array.rb create mode 100644 spec/rubyspec/language/fixtures/block.rb create mode 100644 spec/rubyspec/language/fixtures/break.rb create mode 100644 spec/rubyspec/language/fixtures/break_lambda_toplevel.rb create mode 100644 spec/rubyspec/language/fixtures/break_lambda_toplevel_block.rb create mode 100644 spec/rubyspec/language/fixtures/break_lambda_toplevel_method.rb create mode 100644 spec/rubyspec/language/fixtures/classes.rb create mode 100644 spec/rubyspec/language/fixtures/coding_us_ascii.rb create mode 100644 spec/rubyspec/language/fixtures/coding_utf_8.rb create mode 100644 spec/rubyspec/language/fixtures/constant_visibility.rb create mode 100644 spec/rubyspec/language/fixtures/constants_sclass.rb create mode 100644 spec/rubyspec/language/fixtures/def.rb create mode 100644 spec/rubyspec/language/fixtures/defined.rb create mode 100644 spec/rubyspec/language/fixtures/dollar_zero.rb create mode 100644 spec/rubyspec/language/fixtures/ensure.rb create mode 100644 spec/rubyspec/language/fixtures/file.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_across_files.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_no_comment.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_one_literal.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_required.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_required_diff_enc.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_required_no_comment.rb create mode 100644 spec/rubyspec/language/fixtures/freeze_magic_comment_two_literals.rb create mode 100644 spec/rubyspec/language/fixtures/hash_strings_ascii8bit.rb create mode 100644 spec/rubyspec/language/fixtures/hash_strings_usascii.rb create mode 100644 spec/rubyspec/language/fixtures/hash_strings_utf8.rb create mode 100644 spec/rubyspec/language/fixtures/match_operators.rb create mode 100644 spec/rubyspec/language/fixtures/metaclass.rb create mode 100644 spec/rubyspec/language/fixtures/module.rb create mode 100644 spec/rubyspec/language/fixtures/next.rb create mode 100644 spec/rubyspec/language/fixtures/precedence.rb create mode 100644 spec/rubyspec/language/fixtures/private.rb create mode 100644 spec/rubyspec/language/fixtures/rescue.rb create mode 100644 spec/rubyspec/language/fixtures/return.rb create mode 100644 spec/rubyspec/language/fixtures/send.rb create mode 100644 spec/rubyspec/language/fixtures/squiggly_heredoc.rb create mode 100644 spec/rubyspec/language/fixtures/super.rb create mode 100644 spec/rubyspec/language/fixtures/variables.rb create mode 100644 spec/rubyspec/language/fixtures/yield.rb create mode 100644 spec/rubyspec/language/for_spec.rb create mode 100644 spec/rubyspec/language/hash_spec.rb create mode 100644 spec/rubyspec/language/heredoc_spec.rb create mode 100644 spec/rubyspec/language/if_spec.rb create mode 100644 spec/rubyspec/language/lambda_spec.rb create mode 100644 spec/rubyspec/language/line_spec.rb create mode 100644 spec/rubyspec/language/loop_spec.rb create mode 100644 spec/rubyspec/language/magic_comment_spec.rb create mode 100644 spec/rubyspec/language/match_spec.rb create mode 100644 spec/rubyspec/language/metaclass_spec.rb create mode 100644 spec/rubyspec/language/method_spec.rb create mode 100644 spec/rubyspec/language/module_spec.rb create mode 100644 spec/rubyspec/language/next_spec.rb create mode 100644 spec/rubyspec/language/not_spec.rb create mode 100644 spec/rubyspec/language/numbers_spec.rb create mode 100644 spec/rubyspec/language/optional_assignments_spec.rb create mode 100644 spec/rubyspec/language/or_spec.rb create mode 100644 spec/rubyspec/language/order_spec.rb create mode 100644 spec/rubyspec/language/precedence_spec.rb create mode 100644 spec/rubyspec/language/predefined/data_spec.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data1.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data2.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data3.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data4.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data5.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/data_only.rb create mode 100644 spec/rubyspec/language/predefined/fixtures/print_data.rb create mode 100644 spec/rubyspec/language/predefined_spec.rb create mode 100644 spec/rubyspec/language/private_spec.rb create mode 100644 spec/rubyspec/language/proc_spec.rb create mode 100644 spec/rubyspec/language/redo_spec.rb create mode 100644 spec/rubyspec/language/regexp/anchors_spec.rb create mode 100644 spec/rubyspec/language/regexp/back-references_spec.rb create mode 100644 spec/rubyspec/language/regexp/character_classes_spec.rb create mode 100644 spec/rubyspec/language/regexp/encoding_spec.rb create mode 100644 spec/rubyspec/language/regexp/escapes_spec.rb create mode 100644 spec/rubyspec/language/regexp/grouping_spec.rb create mode 100644 spec/rubyspec/language/regexp/interpolation_spec.rb create mode 100644 spec/rubyspec/language/regexp/modifiers_spec.rb create mode 100644 spec/rubyspec/language/regexp/repetition_spec.rb create mode 100644 spec/rubyspec/language/regexp_spec.rb create mode 100644 spec/rubyspec/language/rescue_spec.rb create mode 100644 spec/rubyspec/language/retry_spec.rb create mode 100644 spec/rubyspec/language/return_spec.rb create mode 100644 spec/rubyspec/language/safe_navigator_spec.rb create mode 100644 spec/rubyspec/language/send_spec.rb create mode 100644 spec/rubyspec/language/shared/__FILE__.rb create mode 100644 spec/rubyspec/language/shared/__LINE__.rb create mode 100644 spec/rubyspec/language/singleton_class_spec.rb create mode 100644 spec/rubyspec/language/string_spec.rb create mode 100644 spec/rubyspec/language/super_spec.rb create mode 100644 spec/rubyspec/language/symbol_spec.rb create mode 100644 spec/rubyspec/language/throw_spec.rb create mode 100644 spec/rubyspec/language/undef_spec.rb create mode 100644 spec/rubyspec/language/unless_spec.rb create mode 100644 spec/rubyspec/language/until_spec.rb create mode 100644 spec/rubyspec/language/variables_spec.rb create mode 100644 spec/rubyspec/language/while_spec.rb create mode 100644 spec/rubyspec/language/yield_spec.rb (limited to 'spec/rubyspec/language') diff --git a/spec/rubyspec/language/BEGIN_spec.rb b/spec/rubyspec/language/BEGIN_spec.rb new file mode 100644 index 0000000000..826d5f0c89 --- /dev/null +++ b/spec/rubyspec/language/BEGIN_spec.rb @@ -0,0 +1,32 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The BEGIN keyword" do + before :each do + ScratchPad.record [] + end + + it "runs in a shared scope" do + eval("BEGIN { var_in_begin = 'foo' }; var_in_begin").should == "foo" + end + + it "accesses variables outside the eval scope" do + outside_var = 'foo' + eval("BEGIN { var_in_begin = outside_var }; var_in_begin").should == "foo" + end + + it "must appear in a top-level context" do + lambda { eval "1.times { BEGIN { 1 } }" }.should raise_error(SyntaxError) + end + + it "runs first in a given code unit" do + eval "ScratchPad << 'foo'; BEGIN { ScratchPad << 'bar' }" + + ScratchPad.recorded.should == ['bar', 'foo'] + end + + it "runs multiple begins in FIFO order" do + eval "BEGIN { ScratchPad << 'foo' }; BEGIN { ScratchPad << 'bar' }" + + ScratchPad.recorded.should == ['foo', 'bar'] + end +end diff --git a/spec/rubyspec/language/README b/spec/rubyspec/language/README new file mode 100644 index 0000000000..b9d969ba1e --- /dev/null +++ b/spec/rubyspec/language/README @@ -0,0 +1,30 @@ +There are numerous possible way of categorizing the entities and concepts that +make up a programming language. Ruby has a fairly large number of reserved +words. These words significantly describe major elements of the language, +including flow control constructs like 'for' and 'while', conditional +execution like 'if' and 'unless', exceptional execution control like 'rescue', +etc. There are also literals for the basic "types" like String, Regexp, Array +and Fixnum. + +Behavorial specifications describe the behavior of concrete entities. Rather +than using concepts of computation to organize these spec files, we use +entities of the Ruby language. Consider looking at any syntactic element of a +Ruby program. With (almost) no ambiguity, one can identify it as a literal, +reserved word, variable, etc. There is a spec file that corresponds to each +literal construct and most reserved words, with the exceptions noted below. +There are also several files that are more difficult to classify: all +predefined variables, constants, and objects (predefined_spec.rb), the +precedence of all operators (precedence_spec.rb), the behavior of assignment +to variables (variables_spec.rb), the behavior of subprocess execution +(execution_spec.rb), the behavior of the raise method as it impacts the +execution of a Ruby program (raise_spec.rb), and the block entities like +'begin', 'do', ' { ... }' (block_spec.rb). + +Several reserved words and other entities are combined with the primary +reserved word or entity to which they are related: + +false, true, nil, self predefined_spec.rb +in for_spec.rb +then, elsif if_spec.rb +when case_spec.rb +catch throw_spec.rb diff --git a/spec/rubyspec/language/alias_spec.rb b/spec/rubyspec/language/alias_spec.rb new file mode 100644 index 0000000000..626e37a603 --- /dev/null +++ b/spec/rubyspec/language/alias_spec.rb @@ -0,0 +1,190 @@ +require File.expand_path('../../spec_helper', __FILE__) + +class AliasObject + attr :foo + attr_reader :bar + attr_accessor :baz + + def prep; @foo = 3; @bar = 4; end + def value; 5; end + def false_value; 6; end + def self.klass_method; 7; end +end + +describe "The alias keyword" do + before :each do + @obj = AliasObject.new + @meta = class << @obj;self;end + end + + it "creates a new name for an existing method" do + @meta.class_eval do + alias __value value + end + @obj.__value.should == 5 + end + + it "adds the new method to the list of methods" do + original_methods = @obj.methods + @meta.class_eval do + alias __value value + end + (@obj.methods - original_methods).map {|m| m.to_s }.should == ["__value"] + end + + it "adds the new method to the list of public methods" do + original_methods = @obj.public_methods + @meta.class_eval do + alias __value value + end + (@obj.public_methods - original_methods).map {|m| m.to_s }.should == ["__value"] + end + + it "overwrites an existing method with the target name" do + @meta.class_eval do + alias false_value value + end + @obj.false_value.should == 5 + end + + it "is reversible" do + @meta.class_eval do + alias __value value + alias value false_value + end + @obj.value.should == 6 + + @meta.class_eval do + alias value __value + end + @obj.value.should == 5 + end + + it "operates on the object's metaclass when used in instance_eval" do + @obj.instance_eval do + alias __value value + end + + @obj.__value.should == 5 + lambda { AliasObject.new.__value }.should raise_error(NoMethodError) + end + + it "operates on the class/module metaclass when used in instance_eval" do + AliasObject.instance_eval do + alias __klass_method klass_method + end + + AliasObject.__klass_method.should == 7 + lambda { Object.__klass_method }.should raise_error(NoMethodError) + end + + it "operates on the class/module metaclass when used in instance_exec" do + AliasObject.instance_exec do + alias __klass_method2 klass_method + end + + AliasObject.__klass_method2.should == 7 + lambda { Object.__klass_method2 }.should raise_error(NoMethodError) + end + + it "operates on methods defined via attr, attr_reader, and attr_accessor" do + @obj.prep + @obj.instance_eval do + alias afoo foo + alias abar bar + alias abaz baz + end + + @obj.afoo.should == 3 + @obj.abar.should == 4 + @obj.baz = 5 + @obj.abaz.should == 5 + end + + it "operates on methods with splat arguments" do + class AliasObject2;end + AliasObject2.class_eval do + def test(*args) + 4 + end + def test_with_check(*args) + test_without_check(*args) + end + alias test_without_check test + alias test test_with_check + end + AliasObject2.new.test(1,2,3,4,5).should == 4 + end + + it "operates on methods with splat arguments on eigenclasses" do + @meta.class_eval do + def test(*args) + 4 + end + def test_with_check(*args) + test_without_check(*args) + end + alias test_without_check test + alias test test_with_check + end + @obj.test(1,2,3,4,5).should == 4 + end + + it "operates on methods with splat arguments defined in a superclass" do + alias_class = Class.new + alias_class.class_eval do + def test(*args) + 4 + end + def test_with_check(*args) + test_without_check(*args) + end + end + sub = Class.new(alias_class) do + alias test_without_check test + alias test test_with_check + end + sub.new.test(1,2,3,4,5).should == 4 + end + + it "operates on methods with splat arguments defined in a superclass using text block for class eval" do + class Sub < AliasObject;end + AliasObject.class_eval <<-code + def test(*args) + 4 + end + def test_with_check(*args) + test_without_check(*args) + end + alias test_without_check test + alias test test_with_check + code + Sub.new.test("testing").should == 4 + end + + it "is not allowed against Fixnum or String instances" do + lambda do + 1.instance_eval do + alias :foo :to_s + end + end.should raise_error(TypeError) + + lambda do + :blah.instance_eval do + alias :foo :to_s + end + end.should raise_error(TypeError) + end + + it "on top level defines the alias on Object" do + # because it defines on the default definee / current module + ruby_exe("def foo; end; alias bla foo; print method(:bla).owner", escape: true).should == "Object" + end + + it "raises a NameError when passed a missing name" do + lambda { @meta.class_eval { alias undef_method not_exist } }.should raise_error(NameError) { |e| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end +end diff --git a/spec/rubyspec/language/and_spec.rb b/spec/rubyspec/language/and_spec.rb new file mode 100644 index 0000000000..e084fd3cef --- /dev/null +++ b/spec/rubyspec/language/and_spec.rb @@ -0,0 +1,80 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The '&&' statement" do + + it "short-circuits evaluation at the first condition to be false" do + x = nil + true && false && x = 1 + x.should be_nil + end + + it "evaluates to the first condition not to be true" do + value = nil + (value && nil).should == nil + (value && false).should == nil + value = false + (value && nil).should == false + (value && false).should == false + + ("yes" && 1 && nil && true).should == nil + ("yes" && 1 && false && true).should == false + end + + it "evaluates to the last condition if all are true" do + ("yes" && 1).should == 1 + (1 && "yes").should == "yes" + end + + it "evaluates the full set of chained conditions during assignment" do + x, y = nil + x = 1 && y = 2 + # "1 && y = 2" is evaluated and then assigned to x + x.should == 2 + end + + it "treats empty expressions as nil" do + (() && true).should be_nil + (true && ()).should be_nil + (() && ()).should be_nil + end + +end + +describe "The 'and' statement" do + it "short-circuits evaluation at the first condition to be false" do + x = nil + true and false and x = 1 + x.should be_nil + end + + it "evaluates to the first condition not to be true" do + value = nil + (value and nil).should == nil + (value and false).should == nil + value = false + (value and nil).should == false + (value and false).should == false + + ("yes" and 1 and nil and true).should == nil + ("yes" and 1 and false and true).should == false + end + + it "evaluates to the last condition if all are true" do + ("yes" and 1).should == 1 + (1 and "yes").should == "yes" + end + + it "when used in assignment, evaluates and assigns expressions individually" do + x, y = nil + x = 1 and y = 2 + # evaluates (x=1) and (y=2) + x.should == 1 + end + + it "treats empty expressions as nil" do + (() and true).should be_nil + (true and ()).should be_nil + (() and ()).should be_nil + end + +end diff --git a/spec/rubyspec/language/array_spec.rb b/spec/rubyspec/language/array_spec.rb new file mode 100644 index 0000000000..c3ed8c14c5 --- /dev/null +++ b/spec/rubyspec/language/array_spec.rb @@ -0,0 +1,155 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/array', __FILE__) + +describe "Array literals" do + it "[] should return a new array populated with the given elements" do + array = [1, 'a', nil] + array.should be_kind_of(Array) + array[0].should == 1 + array[1].should == 'a' + array[2].should == nil + end + + it "[] treats empty expressions as nil elements" do + array = [0, (), 2, (), 4] + array.should be_kind_of(Array) + array[0].should == 0 + array[1].should == nil + array[2].should == 2 + array[3].should == nil + array[4].should == 4 + end + + it "[] accepts a literal hash without curly braces as its only parameter" do + ["foo" => :bar, baz: 42].should == [{"foo" => :bar, baz: 42}] + end + + it "[] accepts a literal hash without curly braces as its last parameter" do + ["foo", "bar" => :baz].should == ["foo", {"bar" => :baz}] + [1, 2, 3 => 6, 4 => 24].should == [1, 2, {3 => 6, 4 => 24}] + end + + it "[] treats splatted nil as no element" do + [*nil].should == [] + [1, *nil].should == [1] + [1, 2, *nil].should == [1, 2] + [1, *nil, 3].should == [1, 3] + [*nil, *nil, *nil].should == [] + end +end + +describe "Bareword array literal" do + it "%w() transforms unquoted barewords into an array" do + a = 3 + %w(a #{3+a} 3).should == ["a", '#{3+a}', "3"] + end + + it "%W() transforms unquoted barewords into an array, supporting interpolation" do + a = 3 + %W(a #{3+a} 3).should == ["a", '6', "3"] + end + + it "%W() always treats interpolated expressions as a single word" do + a = "hello world" + %W(a b c #{a} d e).should == ["a", "b", "c", "hello world", "d", "e"] + end + + it "treats consecutive whitespace characters the same as one" do + %w(a b c d).should == ["a", "b", "c", "d"] + %W(hello + world).should == ["hello", "world"] + end + + it "treats whitespace as literals characters when escaped by a backslash" do + %w(a b\ c d e).should == ["a", "b c", "d", "e"] + %w(a b\ +c d).should == ["a", "b\nc", "d"] + %W(a\ b\tc).should == ["a ", "b\tc"] + %W(white\ \ \ \ \ space).should == ["white ", " ", " ", " space"] + end +end + +describe "The unpacking splat operator (*)" do + it "when applied to a literal nested array, unpacks its elements into the containing array" do + [1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5] + end + + it "when applied to a nested referenced array, unpacks its elements into the containing array" do + splatted_array = [3, 4, 5] + [1, 2, *splatted_array].should == [1, 2, 3, 4, 5] + end + + it "returns a new array containing the same values when applied to an array inside an empty array" do + splatted_array = [3, 4, 5] + [*splatted_array].should == splatted_array + [*splatted_array].should_not equal(splatted_array) + end + + it "unpacks the start and count arguments in an array slice assignment" do + alphabet_1 = ['a'..'z'].to_a + alphabet_2 = alphabet_1.dup + start_and_count_args = [1, 10] + + alphabet_1[1, 10] = 'a' + alphabet_2[*start_and_count_args] = 'a' + + alphabet_1.should == alphabet_2 + end + + it "unpacks arguments as if they were listed statically" do + static = [1,2,3,4] + receiver = static.dup + args = [0,1] + static[0,1] = [] + static.should == [2,3,4] + receiver[*args] = [] + receiver.should == static + end + + it "unpacks a literal array into arguments in a method call" do + tester = ArraySpec::Splat.new + tester.unpack_3args(*[1, 2, 3]).should == [1, 2, 3] + tester.unpack_4args(1, 2, *[3, 4]).should == [1, 2, 3, 4] + tester.unpack_4args("a", %w(b c), *%w(d e)).should == ["a", ["b", "c"], "d", "e"] + end + + it "unpacks a referenced array into arguments in a method call" do + args = [1, 2, 3] + tester = ArraySpec::Splat.new + tester.unpack_3args(*args).should == [1, 2, 3] + tester.unpack_4args(0, *args).should == [0, 1, 2, 3] + end + + it "when applied to a non-Array value attempts to coerce it to Array if the object respond_to?(:to_a)" do + obj = mock("pseudo-array") + obj.should_receive(:to_a).and_return([2, 3, 4]) + [1, *obj].should == [1, 2, 3, 4] + end + + it "when applied to a non-Array value uses it unchanged if it does not respond_to?(:to_a)" do + obj = Object.new + obj.should_not respond_to(:to_a) + [1, *obj].should == [1, obj] + end + + it "when applied to a BasicObject coerces it to Array if it respond_to?(:to_a)" do + obj = BasicObject.new + def obj.to_a; [2, 3, 4]; end + [1, *obj].should == [1, 2, 3, 4] + end + + it "can be used before other non-splat elements" do + a = [1, 2] + [0, *a, 3].should == [0, 1, 2, 3] + end + + it "can be used multiple times in the same containing array" do + a = [1, 2] + b = [1, 0] + [*a, 3, *a, *b].should == [1, 2, 3, 1, 2, 1, 0] + end +end + +describe "The packing splat operator (*)" do + +end diff --git a/spec/rubyspec/language/block_spec.rb b/spec/rubyspec/language/block_spec.rb new file mode 100644 index 0000000000..0ae2125ade --- /dev/null +++ b/spec/rubyspec/language/block_spec.rb @@ -0,0 +1,865 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/block', __FILE__) + +describe "A block yielded a single" do + before :all do + def m(a) yield a end + end + + context "Array" do + it "assigns the Array to a single argument" do + m([1, 2]) { |a| a }.should == [1, 2] + end + + it "receives the identical Array object" do + ary = [1, 2] + m(ary) { |a| a }.should equal(ary) + end + + it "assigns the Array to a single rest argument" do + m([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]] + end + + it "assigns the first element to a single argument with trailing comma" do + m([1, 2]) { |a, | a }.should == 1 + end + + it "assigns elements to required arguments" do + m([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2] + end + + it "assigns nil to unassigned required arguments" do + m([1, 2]) { |a, *b, c, d| [a, b, c, d] }.should == [1, [], 2, nil] + end + + it "assigns elements to optional arguments" do + m([1, 2]) { |a=5, b=4, c=3| [a, b, c] }.should == [1, 2, 3] + end + + it "assgins elements to post arguments" do + m([1, 2]) { |a=5, b, c, d| [a, b, c, d] }.should == [5, 1, 2, nil] + end + + it "assigns elements to required arguments when a keyword rest argument is present" do + m([1, 2]) { |a, **k| [a, k] }.should == [1, {}] + end + + it "assigns elements to mixed argument types" do + result = m([1, 2, 3, {x: 9}]) { |a, b=5, *c, d, e: 2, **k| [a, b, c, d, e, k] } + result.should == [1, 2, [], 3, 2, {x: 9}] + end + + it "assigns symbol keys from a Hash to keyword arguments" do + result = m(["a" => 1, a: 10]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1}, a: 10] + end + + it "assigns symbol keys from a Hash returned by #to_hash to keyword arguments" do + obj = mock("coerce block keyword arguments") + obj.should_receive(:to_hash).and_return({"a" => 1, b: 2}) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1}, b: 2] + end + + ruby_version_is "2.2.1" do # SEGV on MRI 2.2.0 + it "calls #to_hash on the argument but does not use the result when no keywords are present" do + obj = mock("coerce block keyword arguments") + obj.should_receive(:to_hash).and_return({"a" => 1, "b" => 2}) + + result = m([obj]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 1, "b" => 2}, {}] + end + end + + it "assigns non-symbol keys to non-keyword arguments" do + result = m(["a" => 10, b: 2]) { |a=nil, **b| [a, b] } + result.should == [{"a" => 10}, {b: 2}] + end + + it "does not treat hashes with string keys as keyword arguments" do + result = m(["a" => 10]) { |a = nil, **b| [a, b] } + result.should == [{"a" => 10}, {}] + end + + it "calls #to_hash on the last element if keyword arguments are present" do + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return({x: 9}) + + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2], 3, {x: 9}] + end + + it "assigns the last element to a non-keyword argument if #to_hash returns nil" do + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return(nil) + + result = m([1, 2, 3, obj]) { |a, *b, c, **k| [a, b, c, k] } + result.should == [1, [2, 3], obj, {}] + end + + it "calls #to_hash on the last element when there are more arguments than parameters" do + x = mock("destructure matching block keyword argument") + x.should_receive(:to_hash).and_return({x: 9}) + + result = m([1, 2, 3, {y: 9}, 4, 5, x]) { |a, b=5, c, **k| [a, b, c, k] } + result.should == [1, 2, 3, {x: 9}] + end + + it "raises a TypeError if #to_hash does not return a Hash" do + obj = mock("destructure block keyword arguments") + obj.should_receive(:to_hash).and_return(1) + + lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(TypeError) + end + + it "raises the error raised inside #to_hash" do + obj = mock("destructure block keyword arguments") + error = RuntimeError.new("error while converting to a hash") + obj.should_receive(:to_hash).and_raise(error) + + lambda { m([1, 2, 3, obj]) { |a, *b, c, **k| } }.should raise_error(error) + end + + it "does not call #to_ary on the Array" do + ary = [1, 2] + ary.should_not_receive(:to_ary) + + m(ary) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + end + end + + context "Object" do + it "calls #to_ary on the object when taking multiple arguments" do + obj = mock("destructure block arguments") + obj.should_receive(:to_ary).and_return([1, 2]) + + m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil] + end + + it "does not call #to_ary when not taking any arguments" do + obj = mock("destructure block arguments") + obj.should_not_receive(:to_ary) + + m(obj) { 1 }.should == 1 + end + + it "does not call #to_ary on the object when taking a single argument" do + obj = mock("destructure block arguments") + obj.should_not_receive(:to_ary) + + m(obj) { |a| a }.should == obj + end + + it "does not call #to_ary on the object when taking a single rest argument" do + obj = mock("destructure block arguments") + obj.should_not_receive(:to_ary) + + m(obj) { |*a| a }.should == [obj] + end + + it "receives the object if #to_ary returns nil" do + obj = mock("destructure block arguments") + obj.should_receive(:to_ary).and_return(nil) + + m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("destructure block arguments") + obj.should_receive(:to_ary).and_return(1) + + lambda { m(obj) { |a, b| } }.should raise_error(TypeError) + end + end +end + +# TODO: rewrite +describe "A block" do + before :each do + @y = BlockSpecs::Yielder.new + end + + it "captures locals from the surrounding scope" do + var = 1 + @y.z { var }.should == 1 + end + + it "allows for a leading space before the arguments" do + res = @y.s (:a){ 1 } + res.should == 1 + end + + it "allows to define a block variable with the same name as the enclosing block" do + o = BlockSpecs::OverwriteBlockVariable.new + o.z { 1 }.should == 1 + end + + it "does not capture a local when an argument has the same name" do + var = 1 + @y.s(2) { |var| var }.should == 2 + var.should == 1 + end + + it "does not capture a local when the block argument has the same name" do + var = 1 + proc { |&var| + var.call(2) + }.call { |x| x }.should == 2 + var.should == 1 + end + + describe "taking zero arguments" do + it "does not raise an exception when no values are yielded" do + @y.z { 1 }.should == 1 + end + + it "does not raise an exception when values are yielded" do + @y.s(0) { 1 }.should == 1 + end + end + + describe "taking || arguments" do + it "does not raise an exception when no values are yielded" do + @y.z { || 1 }.should == 1 + end + + it "does not raise an exception when values are yielded" do + @y.s(0) { || 1 }.should == 1 + end + end + + describe "taking |a| arguments" do + it "assigns nil to the argument when no values are yielded" do + @y.z { |a| a }.should be_nil + end + + it "assigns the value yielded to the argument" do + @y.s(1) { |a| a }.should == 1 + end + + it "does not call #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @y.s(obj) { |a| a }.should equal(obj) + end + + it "assigns the first value yielded to the argument" do + @y.m(1, 2) { |a| a }.should == 1 + end + + it "does not destructure a single Array value" do + @y.s([1, 2]) { |a| a }.should == [1, 2] + end + end + + describe "taking |a, b| arguments" do + it "assgins nil to the arguments when no values are yielded" do + @y.z { |a, b| [a, b] }.should == [nil, nil] + end + + it "assigns one value yielded to the first argument" do + @y.s(1) { |a, b| [a, b] }.should == [1, nil] + end + + it "assigns the first two values yielded to the arguments" do + @y.m(1, 2, 3) { |a, b| [a, b] }.should == [1, 2] + end + + it "does not destructure an Array value as one of several values yielded" do + @y.m([1, 2], 3, 4) { |a, b| [a, b] }.should == [[1, 2], 3] + end + + it "assigns 'nil' and 'nil' to the arguments when a single, empty Array is yielded" do + @y.s([]) { |a, b| [a, b] }.should == [nil, nil] + end + + it "assigns the element of a single element Array to the first argument" do + @y.s([1]) { |a, b| [a, b] }.should == [1, nil] + @y.s([nil]) { |a, b| [a, b] }.should == [nil, nil] + @y.s([[]]) { |a, b| [a, b] }.should == [[], nil] + end + + it "destructures a single Array value yielded" do + @y.s([1, 2, 3]) { |a, b| [a, b] }.should == [1, 2] + end + + it "destructures a splatted Array" do + @y.r([[]]) { |a, b| [a, b] }.should == [nil, nil] + @y.r([[1]]) { |a, b| [a, b] }.should == [1, nil] + end + + it "calls #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @y.s(obj) { |a, b| [a, b] }.should == [1, 2] + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |a, b| [a, b] }.should == [1, 2] + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |a, b| [a, b] }.should == [obj, nil] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @y.s(obj) { |a, b| } }.should raise_error(TypeError) + end + + it "raises the original exception if #to_ary raises an exception" do + obj = mock("block yield to_ary raising an exception") + obj.should_receive(:to_ary).and_raise(ZeroDivisionError) + + lambda { @y.s(obj) { |a, b| } }.should raise_error(ZeroDivisionError) + end + + end + + describe "taking |a, *b| arguments" do + it "assigns 'nil' and '[]' to the arguments when no values are yielded" do + @y.z { |a, *b| [a, b] }.should == [nil, []] + end + + it "assigns all yielded values after the first to the rest argument" do + @y.m(1, 2, 3) { |a, *b| [a, b] }.should == [1, [2, 3]] + end + + it "assigns 'nil' and '[]' to the arguments when a single, empty Array is yielded" do + @y.s([]) { |a, *b| [a, b] }.should == [nil, []] + end + + it "assigns the element of a single element Array to the first argument" do + @y.s([1]) { |a, *b| [a, b] }.should == [1, []] + @y.s([nil]) { |a, *b| [a, b] }.should == [nil, []] + @y.s([[]]) { |a, *b| [a, b] }.should == [[], []] + end + + it "destructures a splatted Array" do + @y.r([[]]) { |a, *b| [a, b] }.should == [nil, []] + @y.r([[1]]) { |a, *b| [a, b] }.should == [1, []] + end + + it "destructures a single Array value assigning the remaining values to the rest argument" do + @y.s([1, 2, 3]) { |a, *b| [a, b] }.should == [1, [2, 3]] + end + + it "calls #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]] + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |a, *b| [a, b] }.should == [1, [2]] + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |a, *b| [a, b] }.should == [obj, []] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @y.s(obj) { |a, *b| } }.should raise_error(TypeError) + end + end + + describe "taking |*| arguments" do + it "does not raise an exception when no values are yielded" do + @y.z { |*| 1 }.should == 1 + end + + it "does not raise an exception when values are yielded" do + @y.s(0) { |*| 1 }.should == 1 + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |*| 1 }.should == 1 + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |*| 1 }.should == 1 + end + + it "does not call #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @y.s(obj) { |*| 1 }.should == 1 + end + end + + describe "taking |*a| arguments" do + it "assigns '[]' to the argument when no values are yielded" do + @y.z { |*a| a }.should == [] + end + + it "assigns a single value yielded to the argument as an Array" do + @y.s(1) { |*a| a }.should == [1] + end + + it "assigns all the values passed to the argument as an Array" do + @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3] + end + + it "assigns '[[]]' to the argument when passed an empty Array" do + @y.s([]) { |*a| a }.should == [[]] + end + + it "assigns a single Array value passed to the argument by wrapping it in an Array" do + @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]] + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |*a| a }.should == [[1, 2]] + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |*a| a }.should == [obj] + end + + it "does not call #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @y.s(obj) { |*a| a }.should == [obj] + end + end + + describe "taking |a, | arguments" do + it "assigns nil to the argument when no values are yielded" do + @y.z { |a, | a }.should be_nil + end + + it "assgins the argument a single value yielded" do + @y.s(1) { |a, | a }.should == 1 + end + + it "assigns the argument the first value yielded" do + @y.m(1, 2) { |a, | a }.should == 1 + end + + it "assigns the argument the first of several values yielded when it is an Array" do + @y.m([1, 2], 3) { |a, | a }.should == [1, 2] + end + + it "assigns nil to the argument when passed an empty Array" do + @y.s([]) { |a, | a }.should be_nil + end + + it "assigns the argument the first element of the Array when passed a single Array" do + @y.s([1, 2]) { |a, | a }.should == 1 + end + + it "calls #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @y.s(obj) { |a, | a }.should == 1 + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |a, | a }.should == 1 + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |a, | a }.should == obj + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @y.s(obj) { |a, | } }.should raise_error(TypeError) + end + end + + describe "taking |(a, b)| arguments" do + it "assigns nil to the arguments when yielded no values" do + @y.z { |(a, b)| [a, b] }.should == [nil, nil] + end + + it "destructures a single Array value yielded" do + @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2] + end + + it "destructures a single Array value yielded when shadowing an outer variable" do + a = 9 + @y.s([1, 2]) { |(a, b)| [a, b] }.should == [1, 2] + end + + it "calls #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2] + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |(a, b)| [a, b] }.should == [1, 2] + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |(a, b)| [a, b] }.should == [obj, nil] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @y.s(obj) { |(a, b)| } }.should raise_error(TypeError) + end + end + + describe "taking |(a, b), c| arguments" do + it "assigns nil to the arguments when yielded no values" do + @y.z { |(a, b), c| [a, b, c] }.should == [nil, nil, nil] + end + + it "destructures a single one-level Array value yielded" do + @y.s([1, 2]) { |(a, b), c| [a, b, c] }.should == [1, nil, 2] + end + + it "destructures a single multi-level Array value yielded" do + @y.s([[1, 2, 3], 4]) { |(a, b), c| [a, b, c] }.should == [1, 2, 4] + end + + it "calls #to_ary to convert a single yielded object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2] + end + + it "does not call #to_ary if the single yielded object is an Array" do + obj = [1, 2] + obj.should_not_receive(:to_ary) + + @y.s(obj) { |(a, b), c| [a, b, c] }.should == [1, nil, 2] + end + + it "does not call #to_ary if the object does not respond to #to_ary" do + obj = mock("block yield no to_ary") + + @y.s(obj) { |(a, b), c| [a, b, c] }.should == [obj, nil, nil] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @y.s(obj) { |(a, b), c| } }.should raise_error(TypeError) + end + end + + describe "taking nested |a, (b, (c, d))|" do + it "assigns nil to the arguments when yielded no values" do + @y.m { |a, (b, (c, d))| [a, b, c, d] }.should == [nil, nil, nil, nil] + end + + it "destructures separate yielded values" do + @y.m(1, 2) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, nil, nil] + end + + it "destructures a single multi-level Array value yielded" do + @y.m(1, [2, 3]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, nil] + end + + it "destructures a single multi-level Array value yielded" do + @y.m(1, [2, [3, 4]]) { |a, (b, (c, d))| [a, b, c, d] }.should == [1, 2, 3, 4] + end + end + + describe "taking nested |a, ((b, c), d)|" do + it "assigns nil to the arguments when yielded no values" do + @y.m { |a, ((b, c), d)| [a, b, c, d] }.should == [nil, nil, nil, nil] + end + + it "destructures separate yielded values" do + @y.m(1, 2) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, nil] + end + + it "destructures a single multi-level Array value yielded" do + @y.m(1, [2, 3]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, nil, 3] + end + + it "destructures a single multi-level Array value yielded" do + @y.m(1, [[2, 3], 4]) { |a, ((b, c), d)| [a, b, c, d] }.should == [1, 2, 3, 4] + end + end + + describe "arguments with _" do + it "extracts arguments with _" do + @y.m([[1, 2, 3], 4]) { |(_, a, _), _| a }.should == 2 + @y.m([1, [2, 3, 4]]) { |_, (_, a, _)| a }.should == 3 + end + + it "assigns the first variable named" do + @y.m(1, 2) { |_, _| _ }.should == 1 + end + end + + describe "taking identically-named arguments" do + it "raises a SyntaxError for standard arguments" do + lambda { eval "lambda { |x,x| }" }.should raise_error(SyntaxError) + lambda { eval "->(x,x) {}" }.should raise_error(SyntaxError) + lambda { eval "Proc.new { |x,x| }" }.should raise_error(SyntaxError) + end + + it "accepts unnamed arguments" do + eval("lambda { |_,_| }").should be_an_instance_of(Proc) + eval("->(_,_) {}").should be_an_instance_of(Proc) + eval("Proc.new { |_,_| }").should be_an_instance_of(Proc) + end + end +end + +describe "Block-local variables" do + it "are introduced with a semi-colon in the parameter list" do + [1].map {|one; bl| bl }.should == [nil] + end + + it "can be specified in a comma-separated list after the semi-colon" do + [1].map {|one; bl, bl2| [bl, bl2] }.should == [[nil, nil]] + end + + it "can not have the same name as one of the standard parameters" do + lambda { eval "[1].each {|foo; foo| }" }.should raise_error(SyntaxError) + lambda { eval "[1].each {|foo, bar; glark, bar| }" }.should raise_error(SyntaxError) + end + + it "can not be prefixed with an asterisk" do + lambda { eval "[1].each {|foo; *bar| }" }.should raise_error(SyntaxError) + lambda do + eval "[1].each {|foo, bar; glark, *fnord| }" + end.should raise_error(SyntaxError) + end + + it "can not be prefixed with an ampersand" do + lambda { eval "[1].each {|foo; &bar| }" }.should raise_error(SyntaxError) + lambda do + eval "[1].each {|foo, bar; glark, &fnord| }" + end.should raise_error(SyntaxError) + end + + it "can not be assigned default values" do + lambda { eval "[1].each {|foo; bar=1| }" }.should raise_error(SyntaxError) + lambda do + eval "[1].each {|foo, bar; glark, fnord=:fnord| }" + end.should raise_error(SyntaxError) + end + + it "need not be preceeded by standard parameters" do + [1].map {|; foo| foo }.should == [nil] + [1].map {|; glark, bar| [glark, bar] }.should == [[nil, nil]] + end + + it "only allow a single semi-colon in the parameter list" do + lambda { eval "[1].each {|foo; bar; glark| }" }.should raise_error(SyntaxError) + lambda { eval "[1].each {|; bar; glark| }" }.should raise_error(SyntaxError) + end + + it "override shadowed variables from the outer scope" do + out = :out + [1].each {|; out| out = :in } + out.should == :out + + a = :a + b = :b + c = :c + d = :d + {ant: :bee}.each_pair do |a, b; c, d| + a = :A + b = :B + c = :C + d = :D + end + a.should == :a + b.should == :b + c.should == :c + d.should == :d + end + + it "are not automatically instantiated in the outer scope" do + defined?(glark).should be_nil + [1].each {|;glark| 1} + defined?(glark).should be_nil + end + + it "are automatically instantiated in the block" do + [1].each do |;glark| + glark.should be_nil + end + end + + it "are visible in deeper scopes before initialization" do + [1].each {|;glark| + [1].each { + defined?(glark).should_not be_nil + glark = 1 + } + glark.should == 1 + } + end +end + +describe "Post-args" do + it "appear after a splat" do + proc do |*a, b| + [a, b] + end.call(1, 2, 3).should == [[1, 2], 3] + + proc do |*a, b, c| + [a, b, c] + end.call(1, 2, 3).should == [[1], 2, 3] + + proc do |*a, b, c, d| + [a, b, c, d] + end.call(1, 2, 3).should == [[], 1, 2, 3] + end + + it "are required" do + lambda { + lambda do |*a, b| + [a, b] + end.call + }.should raise_error(ArgumentError) + end + + describe "with required args" do + + it "gathers remaining args in the splat" do + proc do |a, *b, c| + [a, b, c] + end.call(1, 2, 3).should == [1, [2], 3] + end + + it "has an empty splat when there are no remaining args" do + proc do |a, b, *c, d| + [a, b, c, d] + end.call(1, 2, 3).should == [1, 2, [], 3] + + proc do |a, *b, c, d| + [a, b, c, d] + end.call(1, 2, 3).should == [1, [], 2, 3] + end + end + + describe "with optional args" do + + it "gathers remaining args in the splat" do + proc do |a=5, *b, c| + [a, b, c] + end.call(1, 2, 3).should == [1, [2], 3] + end + + it "overrides the optional arg before gathering in the splat" do + proc do |a=5, *b, c| + [a, b, c] + end.call(2, 3).should == [2, [], 3] + + proc do |a=5, b=6, *c, d| + [a, b, c, d] + end.call(1, 2, 3).should == [1, 2, [], 3] + + proc do |a=5, *b, c, d| + [a, b, c, d] + end.call(1, 2, 3).should == [1, [], 2, 3] + end + + it "uses the required arg before the optional and the splat" do + proc do |a=5, *b, c| + [a, b, c] + end.call(3).should == [5, [], 3] + + proc do |a=5, b=6, *c, d| + [a, b, c, d] + end.call(3).should == [5, 6, [], 3] + + proc do |a=5, *b, c, d| + [a, b, c, d] + end.call(2, 3).should == [5, [], 2, 3] + end + + it "overrides the optional args from left to right before gathering the splat" do + proc do |a=5, b=6, *c, d| + [a, b, c, d] + end.call(2, 3).should == [2, 6, [], 3] + end + + describe "with a circular argument reference" do + it "shadows an existing local with the same name as the argument" do + a = 1 + -> { + @proc = eval "proc { |a=a| a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "shadows an existing method with the same name as the argument" do + def a; 1; end + -> { + @proc = eval "proc { |a=a| a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "calls an existing method with the same name as the argument if explicitly using ()" do + def a; 1; end + proc { |a=a()| a }.call.should == 1 + end + end + end + + describe "with pattern matching" do + it "extracts matched blocks with post arguments" do + proc do |(a, *b, c), d, e| + [a, b, c, d, e] + end.call([1, 2, 3, 4], 5, 6).should == [1, [2, 3], 4, 5, 6] + end + + it "allows empty splats" do + proc do |a, (*), b| + [a, b] + end.call([1, 2, 3]).should == [1, 3] + end + end +end diff --git a/spec/rubyspec/language/break_spec.rb b/spec/rubyspec/language/break_spec.rb new file mode 100644 index 0000000000..4758e6ce50 --- /dev/null +++ b/spec/rubyspec/language/break_spec.rb @@ -0,0 +1,334 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/break', __FILE__) + +describe "The break statement in a block" do + before :each do + ScratchPad.record [] + @program = BreakSpecs::Block.new + end + + it "returns nil to method invoking the method yielding to the block when not passed an argument" do + @program.break_nil + ScratchPad.recorded.should == [:a, :aa, :b, nil, :d] + end + + it "returns a value to the method invoking the method yielding to the block" do + @program.break_value + ScratchPad.recorded.should == [:a, :aa, :b, :break, :d] + end + + describe "yielded inside a while" do + it "breaks out of the block" do + value = @program.break_in_block_in_while + ScratchPad.recorded.should == [:aa, :break] + value.should == :value + end + end +end + +describe "The break statement in a captured block" do + before :each do + ScratchPad.record [] + @program = BreakSpecs::Block.new + end + + describe "when the invocation of the scope creating the block is still active" do + it "raises a LocalJumpError when invoking the block from the scope creating the block" do + lambda { @program.break_in_method }.should raise_error(LocalJumpError) + ScratchPad.recorded.should == [:a, :xa, :d, :b] + end + + it "raises a LocalJumpError when invoking the block from a method" do + lambda { @program.break_in_nested_method }.should raise_error(LocalJumpError) + ScratchPad.recorded.should == [:a, :xa, :c, :aa, :b] + end + + it "raises a LocalJumpError when yielding to the block" do + lambda { @program.break_in_yielding_method }.should raise_error(LocalJumpError) + ScratchPad.recorded.should == [:a, :xa, :c, :aa, :b] + end + end + + describe "from a scope that has returned" do + it "raises a LocalJumpError when calling the block from a method" do + lambda { @program.break_in_method_captured }.should raise_error(LocalJumpError) + ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb] + end + + it "raises a LocalJumpError when yielding to the block" do + lambda { @program.break_in_yield_captured }.should raise_error(LocalJumpError) + ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb] + end + end +end + +describe "The break statement in a lambda" do + before :each do + ScratchPad.record [] + @program = BreakSpecs::Lambda.new + end + + it "returns from the lambda" do + l = lambda { + ScratchPad << :before + break :foo + ScratchPad << :after + } + l.call.should == :foo + ScratchPad.recorded.should == [:before] + end + + it "returns from the call site if the lambda is passed as a block" do + def mid(&b) + lambda { + ScratchPad << :before + b.call + ScratchPad << :unreachable1 + }.call + ScratchPad << :unreachable2 + end + + result = [1].each do |e| + mid { + break # This breaks from mid + ScratchPad << :unreachable3 + } + ScratchPad << :after + end + result.should == [1] + ScratchPad.recorded.should == [:before, :after] + end + + describe "when the invocation of the scope creating the lambda is still active" do + it "returns nil when not passed an argument" do + @program.break_in_defining_scope false + ScratchPad.recorded.should == [:a, :b, nil, :d] + end + + it "returns a value to the scope creating and calling the lambda" do + @program.break_in_defining_scope + ScratchPad.recorded.should == [:a, :b, :break, :d] + end + + it "returns a value to the method scope below invoking the lambda" do + @program.break_in_nested_scope + ScratchPad.recorded.should == [:a, :d, :aa, :b, :break, :bb, :e] + end + + it "returns a value to a block scope invoking the lambda in a method below" do + @program.break_in_nested_scope_block + ScratchPad.recorded.should == [:a, :d, :aa, :aaa, :bb, :b, :break, :cc, :bbb, :dd, :e] + end + + it "returns from the lambda" do + @program.break_in_nested_scope_yield + ScratchPad.recorded.should == [:a, :d, :aaa, :b, :bbb, :e] + end + end + + describe "created at the toplevel" do + it "returns a value when invoking from the toplevel" do + code = fixture __FILE__, "break_lambda_toplevel.rb" + ruby_exe(code).chomp.should == "a,b,break,d" + end + + it "returns a value when invoking from a method" do + code = fixture __FILE__, "break_lambda_toplevel_method.rb" + ruby_exe(code).chomp.should == "a,d,b,break,e,f" + end + + it "returns a value when invoking from a block" do + code = fixture __FILE__, "break_lambda_toplevel_block.rb" + ruby_exe(code).chomp.should == "a,d,f,b,break,g,e,h" + end + end + + describe "from a scope that has returned" do + it "returns a value to the method scope invoking the lambda" do + @program.break_in_method + ScratchPad.recorded.should == [:a, :la, :ld, :lb, :break, :b] + end + + it "returns a value to the block scope invoking the lambda in a method" do + @program.break_in_block_in_method + ScratchPad.recorded.should == [:a, :aaa, :b, :la, :ld, :lb, :break, :c, :bbb, :d] + end + + # By passing a lambda as a block argument, the user is requesting to treat + # the lambda as a block, which in this case means breaking to a scope that + # has returned. This is a subtle and confusing semantic where a block pass + # is removing the lambda-ness of a lambda. + it "raises a LocalJumpError when yielding to a lambda passed as a block argument" do + @program.break_in_method_yield + ScratchPad.recorded.should == [:a, :la, :ld, :aaa, :lb, :bbb, :b] + end + end +end + +describe "Break inside a while loop" do + describe "with a value" do + it "exits the loop and returns the value" do + a = while true; break; end; a.should == nil + a = while true; break nil; end; a.should == nil + a = while true; break 1; end; a.should == 1 + a = while true; break []; end; a.should == [] + a = while true; break [1]; end; a.should == [1] + end + + it "passes the value returned by a method with omitted parenthesis and passed block" do + obj = BreakSpecs::Block.new + lambda { break obj.method :value do |x| x end }.call.should == :value + end + end + + describe "with a splat" do + it "exits the loop and makes the splat an Array" do + a = while true; break *[1,2]; end; a.should == [1,2] + end + + it "treats nil as an empty array" do + a = while true; break *nil; end; a.should == [] + end + + it "preserves an array as is" do + a = while true; break *[]; end; a.should == [] + a = while true; break *[1,2]; end; a.should == [1,2] + a = while true; break *[nil]; end; a.should == [nil] + a = while true; break *[[]]; end; a.should == [[]] + end + + it "wraps a non-Array in an Array" do + a = while true; break *1; end; a.should == [1] + end + end + + it "stops a while loop when run" do + i = 0 + while true + break if i == 2 + i+=1 + end + i.should == 2 + end + + it "causes a call with a block to return when run" do + at = 0 + 0.upto(5) do |i| + at = i + break i if i == 2 + end.should == 2 + at.should == 2 + end +end + + +# TODO: Rewrite all the specs from here to the end of the file in the style +# above. +describe "Executing break from within a block" do + + before :each do + ScratchPad.clear + end + + # Discovered in JRuby (see JRUBY-2756) + it "returns from the original invoking method even in case of chained calls" do + class BreakTest + # case #1: yield + def self.meth_with_yield(&b) + yield + fail("break returned from yield to wrong place") + end + def self.invoking_method(&b) + meth_with_yield(&b) + fail("break returned from 'meth_with_yield' method to wrong place") + end + + # case #2: block.call + def self.meth_with_block_call(&b) + b.call + fail("break returned from b.call to wrong place") + end + def self.invoking_method2(&b) + meth_with_block_call(&b) + fail("break returned from 'meth_with_block_call' method to wrong place") + end + end + + # this calls a method that calls another method that yields to the block + BreakTest.invoking_method do + break + fail("break didn't, well, break") + end + + # this calls a method that calls another method that calls the block + BreakTest.invoking_method2 do + break + fail("break didn't, well, break") + end + + res = BreakTest.invoking_method do + break :return_value + fail("break didn't, well, break") + end + res.should == :return_value + + res = BreakTest.invoking_method2 do + break :return_value + fail("break didn't, well, break") + end + res.should == :return_value + + end + + class BreakTest2 + def one + two { yield } + end + + def two + yield + ensure + ScratchPad << :two_ensure + end + + def three + begin + one { break } + ScratchPad << :three_post + ensure + ScratchPad << :three_ensure + end + end + end + + it "runs ensures when continuing upward" do + ScratchPad.record [] + + bt2 = BreakTest2.new + bt2.one { break } + ScratchPad.recorded.should == [:two_ensure] + end + + it "runs ensures when breaking from a loop" do + ScratchPad.record [] + + while true + begin + ScratchPad << :begin + break if true + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "doesn't run ensures in the destination method" do + ScratchPad.record [] + + bt2 = BreakTest2.new + bt2.three + ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure] + end +end diff --git a/spec/rubyspec/language/case_spec.rb b/spec/rubyspec/language/case_spec.rb new file mode 100644 index 0000000000..cb0e8150bf --- /dev/null +++ b/spec/rubyspec/language/case_spec.rb @@ -0,0 +1,382 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The 'case'-construct" do + it "evaluates the body of the when clause matching the case target expression" do + case 1 + when 2; false + when 1; true + end.should == true + end + + it "evaluates the body of the when clause whose array expression includes the case target expression" do + case 2 + when 3, 4; false + when 1, 2; true + end.should == true + end + + it "evaluates the body of the when clause in left-to-right order if it's an array expression" do + @calls = [] + def foo; @calls << :foo; end + def bar; @calls << :bar; end + + case true + when foo, bar; + end + + @calls.should == [:foo, :bar] + end + + it "evaluates the body of the when clause whose range expression includes the case target expression" do + case 5 + when 21..30; false + when 1..20; true + end.should == true + end + + it "returns nil when no 'then'-bodies are given" do + case "a" + when "a" + when "b" + end.should == nil + end + + it "evaluates the 'else'-body when no other expression matches" do + case "c" + when "a"; 'foo' + when "b"; 'bar' + else 'zzz' + end.should == 'zzz' + end + + it "returns nil when no expression matches and 'else'-body is empty" do + case "c" + when "a"; "a" + when "b"; "b" + else + end.should == nil + end + + it "returns 2 when a then body is empty" do + case Object.new + when Numeric then + 1 + when String then + # ok + else + 2 + end.should == 2 + end + + it "returns the statement following 'then'" do + case "a" + when "a" then 'foo' + when "b" then 'bar' + end.should == 'foo' + end + + it "tests classes with case equality" do + case "a" + when String + 'foo' + when Symbol + 'bar' + end.should == 'foo' + end + + it "tests with matching regexps" do + case "hello" + when /abc/; false + when /^hell/; true + end.should == true + end + + it "does not test with equality when given classes" do + case :symbol.class + when Symbol + "bar" + when String + "bar" + else + "foo" + end.should == "foo" + end + + it "takes lists of values" do + case 'z' + when 'a', 'b', 'c', 'd' + "foo" + when 'x', 'y', 'z' + "bar" + end.should == "bar" + + case 'b' + when 'a', 'b', 'c', 'd' + "foo" + when 'x', 'y', 'z' + "bar" + end.should == "foo" + end + + it "expands arrays to lists of values" do + case 'z' + when *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "bar" + end + + it "takes an expanded array in addition to a list of values" do + case 'f' + when 'f', *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + + case 'b' + when 'f', *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + end + + it "takes an expanded array before additional listed values" do + case 'f' + when *['a', 'b', 'c', 'd'], 'f' + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == 'foo' + end + + it "expands arrays from variables before additional listed values" do + a = ['a', 'b', 'c'] + case 'a' + when *a, 'd', 'e' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + it "expands arrays from variables before a single additional listed value" do + a = ['a', 'b', 'c'] + case 'a' + when *a, 'd' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + it "expands multiple arrays from variables before additional listed values" do + a = ['a', 'b', 'c'] + b = ['d', 'e', 'f'] + + case 'f' + when *a, *b, 'g', 'h' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + # MR: critical + it "concats arrays before expanding them" do + a = ['a', 'b', 'c', 'd'] + b = ['f'] + + case 'f' + when 'f', *a|b + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + end + + it "never matches when clauses with no values" do + case nil + when *[] + "foo" + end.should == nil + end + + it "lets you define a method after the case statement" do + case (def foo; 'foo'; end; 'f') + when 'a' + 'foo' + when 'f' + 'bar' + end.should == 'bar' + end + + it "raises a SyntaxError when 'else' is used when no 'when' is given" do + lambda { + eval <<-CODE + case 4 + else + true + end + CODE + }.should raise_error(SyntaxError) + end + + it "raises a SyntaxError when 'else' is used before a 'when' was given" do + lambda { + eval <<-CODE + case 4 + else + true + when 4; false + end + CODE + }.should raise_error(SyntaxError) + end + + it "supports nested case statements" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + end + result.should == true + end + + it "supports nested case statements followed by a when with a splatted array" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + when *[Symbol] + result = false + end + result.should == true + end + + it "supports nested case statements followed by a when with a splatted non-array" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + when *Symbol + result = false + end + result.should == true + end + + it "works even if there's only one when statement" do + case 1 + when 1 + 100 + end.should == 100 + end +end + +describe "The 'case'-construct with no target expression" do + it "evaluates the body of the first clause when at least one of its condition expressions is true" do + case + when true, false; 'foo' + end.should == 'foo' + end + + it "evaluates the body of the first when clause that is not false/nil" do + case + when false; 'foo' + when 2; 'bar' + when 1 == 1; 'baz' + end.should == 'bar' + + case + when false; 'foo' + when nil; 'foo' + when 1 == 1; 'bar' + end.should == 'bar' + end + + it "evaluates the body of the else clause if all when clauses are false/nil" do + case + when false; 'foo' + when nil; 'foo' + when 1 == 2; 'bar' + else 'baz' + end.should == 'baz' + end + + it "evaluates multiple conditional expressions as a boolean disjunction" do + case + when true, false; 'foo' + else 'bar' + end.should == 'foo' + + case + when false, true; 'foo' + else 'bar' + end.should == 'foo' + end + + it "evaluates true as only 'true' when true is the first clause" do + case 1 + when true; "bad" + when Integer; "good" + end.should == "good" + end + + it "evaluates false as only 'false' when false is the first clause" do + case nil + when false; "bad" + when nil; "good" + end.should == "good" + end + + it "treats a literal array as its own when argument, rather than a list of arguments" do + case 'foo' + when ['foo', 'foo']; 'bad' + when 'foo'; 'good' + end.should == 'good' + end + + it "takes multiple expanded arrays" do + a1 = ['f', 'o', 'o'] + a2 = ['b', 'a', 'r'] + + case 'f' + when *a1, *['x', 'y', 'z'] + "foo" + when *a2, *['x', 'y', 'z'] + "bar" + end.should == "foo" + + case 'b' + when *a1, *['x', 'y', 'z'] + "foo" + when *a2, *['x', 'y', 'z'] + "bar" + end.should == "bar" + end + + it "calls === even when private" do + klass = Class.new do + def ===(o) + true + end + private :=== + end + + case 1 + when klass.new + :called + end.should == :called + end +end diff --git a/spec/rubyspec/language/class_spec.rb b/spec/rubyspec/language/class_spec.rb new file mode 100644 index 0000000000..354570e5e9 --- /dev/null +++ b/spec/rubyspec/language/class_spec.rb @@ -0,0 +1,332 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/class', __FILE__) + +ClassSpecsNumber = 12 + +module ClassSpecs + Number = 12 +end + +describe "The class keyword" do + it "creates a new class with semicolon" do + class ClassSpecsKeywordWithSemicolon; end + ClassSpecsKeywordWithSemicolon.should be_an_instance_of(Class) + end + + ruby_version_is "2.3" do + it "does not raise a SyntaxError when opening a class without a semicolon" do + eval "class ClassSpecsKeywordWithoutSemicolon end" + ClassSpecsKeywordWithoutSemicolon.should be_an_instance_of(Class) + end + end +end + +describe "A class definition" do + it "creates a new class" do + ClassSpecs::A.should be_kind_of(Class) + ClassSpecs::A.new.should be_kind_of(ClassSpecs::A) + end + + it "has no class variables" do + ClassSpecs::A.class_variables.should == [] + end + + it "raises TypeError if constant given as class name exists and is not a Module" do + lambda { + class ClassSpecsNumber + end + }.should raise_error(TypeError) + end + + # test case known to be detecting bugs (JRuby, MRI) + it "raises TypeError if the constant qualifying the class is nil" do + lambda { + class nil::Foo + end + }.should raise_error(TypeError) + end + + it "raises TypeError if any constant qualifying the class is not a Module" do + lambda { + class ClassSpecs::Number::MyClass + end + }.should raise_error(TypeError) + + lambda { + class ClassSpecsNumber::MyClass + end + }.should raise_error(TypeError) + end + + it "inherits from Object by default" do + ClassSpecs::A.superclass.should == Object + end + + it "raises an error when trying to change the superclass" do + module ClassSpecs + class SuperclassResetToSubclass < L + end + lambda { + class SuperclassResetToSubclass < M + end + }.should raise_error(TypeError, /superclass mismatch/) + end + end + + it "raises an error when reopening a class with BasicObject as superclass" do + module ClassSpecs + class SuperclassReopenedBasicObject < A + end + SuperclassReopenedBasicObject.superclass.should == A + + lambda { + class SuperclassReopenedBasicObject < BasicObject + end + }.should raise_error(TypeError, /superclass mismatch/) + SuperclassReopenedBasicObject.superclass.should == A + end + end + + # [Bug #12367] [ruby-core:75446] + ruby_version_is "2.4" do # Until backported + it "raises an error when reopening a class with Object as superclass" do + module ClassSpecs + class SuperclassReopenedObject < A + end + SuperclassReopenedObject.superclass.should == A + + lambda { + class SuperclassReopenedObject < Object + end + }.should raise_error(TypeError, /superclass mismatch/) + SuperclassReopenedObject.superclass.should == A + end + end + end + + it "allows reopening a class without specifying the superclass" do + module ClassSpecs + class SuperclassNotGiven < A + end + SuperclassNotGiven.superclass.should == A + + class SuperclassNotGiven + end + SuperclassNotGiven.superclass.should == A + end + end + + it "does not allow to set the superclass even if it was not specified by the first declaration" do + module ClassSpecs + class NoSuperclassSet + end + + lambda { + class NoSuperclassSet < String + end + }.should raise_error(TypeError, /superclass mismatch/) + end + end + + it "allows using self as the superclass if self is a class" do + ClassSpecs::I::J.superclass.should == ClassSpecs::I + + lambda { + class ShouldNotWork < self; end + }.should raise_error(TypeError) + end + + it "first evaluates the superclass before checking if the class already exists" do + module ClassSpecs + class SuperclassEvaluatedFirst + end + a = SuperclassEvaluatedFirst + + class SuperclassEvaluatedFirst < remove_const(:SuperclassEvaluatedFirst) + end + b = SuperclassEvaluatedFirst + b.superclass.should == a + end + end + + it "raises a TypeError if inheriting from a metaclass" do + obj = mock("metaclass super") + meta = obj.singleton_class + lambda { class ClassSpecs::MetaclassSuper < meta; end }.should raise_error(TypeError) + end + + it "allows the declaration of class variables in the body" do + ClassSpecs.string_class_variables(ClassSpecs::B).should == ["@@cvar"] + ClassSpecs::B.send(:class_variable_get, :@@cvar).should == :cvar + end + + it "stores instance variables defined in the class body in the class object" do + ClassSpecs.string_instance_variables(ClassSpecs::B).should include("@ivar") + ClassSpecs::B.instance_variable_get(:@ivar).should == :ivar + end + + it "allows the declaration of class variables in a class method" do + ClassSpecs::C.class_variables.should == [] + ClassSpecs::C.make_class_variable + ClassSpecs.string_class_variables(ClassSpecs::C).should == ["@@cvar"] + ClassSpecs::C.remove_class_variable :@@cvar + end + + it "allows the definition of class-level instance variables in a class method" do + ClassSpecs.string_instance_variables(ClassSpecs::C).should_not include("@civ") + ClassSpecs::C.make_class_instance_variable + ClassSpecs.string_instance_variables(ClassSpecs::C).should include("@civ") + ClassSpecs::C.remove_instance_variable :@civ + end + + it "allows the declaration of class variables in an instance method" do + ClassSpecs::D.class_variables.should == [] + ClassSpecs::D.new.make_class_variable + ClassSpecs.string_class_variables(ClassSpecs::D).should == ["@@cvar"] + ClassSpecs::D.remove_class_variable :@@cvar + end + + it "allows the definition of instance methods" do + ClassSpecs::E.new.meth.should == :meth + end + + it "allows the definition of class methods" do + ClassSpecs::E.cmeth.should == :cmeth + end + + it "allows the definition of class methods using class << self" do + ClassSpecs::E.smeth.should == :smeth + end + + it "allows the definition of Constants" do + Object.const_defined?('CONSTANT').should == false + ClassSpecs::E.const_defined?('CONSTANT').should == true + ClassSpecs::E::CONSTANT.should == :constant! + end + + it "returns the value of the last statement in the body" do + class ClassSpecs::Empty; end.should == nil + class ClassSpecs::Twenty; 20; end.should == 20 + class ClassSpecs::Plus; 10 + 20; end.should == 30 + class ClassSpecs::Singleton; class << self; :singleton; end; end.should == :singleton + end + + describe "within a block creates a new class in the lexical scope" do + it "for named classes at the toplevel" do + klass = Class.new do + class Howdy + end + + def self.get_class_name + Howdy.name + end + end + + Howdy.name.should == 'Howdy' + klass.get_class_name.should == 'Howdy' + end + + it "for named classes in a module" do + klass = ClassSpecs::ANON_CLASS_FOR_NEW.call + + ClassSpecs::NamedInModule.name.should == 'ClassSpecs::NamedInModule' + klass.get_class_name.should == 'ClassSpecs::NamedInModule' + end + + it "for anonymous classes" do + klass = Class.new do + def self.get_class + Class.new do + def self.foo + 'bar' + end + end + end + + def self.get_result + get_class.foo + end + end + + klass.get_result.should == 'bar' + end + + it "for anonymous classes assigned to a constant" do + klass = Class.new do + AnonWithConstant = Class.new + + def self.get_class_name + AnonWithConstant.name + end + end + + AnonWithConstant.name.should == 'AnonWithConstant' + klass.get_class_name.should == 'AnonWithConstant' + end + end +end + +describe "An outer class definition" do + it "contains the inner classes" do + ClassSpecs::Container.constants.should include(:A, :B) + end +end + +describe "A class definition extending an object (sclass)" do + it "allows adding methods" do + ClassSpecs::O.smeth.should == :smeth + end + + it "raises a TypeError when trying to extend numbers" do + lambda { + eval <<-CODE + class << 1 + def xyz + self + end + end + CODE + }.should raise_error(TypeError) + end + + it "allows accessing the block of the original scope" do + ClassSpecs.sclass_with_block { 123 }.should == 123 + end + + it "can use return to cause the enclosing method to return" do + ClassSpecs.sclass_with_return.should == :inner + end +end + +describe "Reopening a class" do + it "extends the previous definitions" do + c = ClassSpecs::F.new + c.meth.should == :meth + c.another.should == :another + end + + it "overwrites existing methods" do + ClassSpecs::G.new.override.should == :override + end + + it "raises a TypeError when superclasses mismatch" do + lambda { class ClassSpecs::A < Array; end }.should raise_error(TypeError) + end + + it "adds new methods to subclasses" do + lambda { ClassSpecs::M.m }.should raise_error(NoMethodError) + class ClassSpecs::L + def self.m + 1 + end + end + ClassSpecs::M.m.should == 1 + ClassSpecs::L.singleton_class.send(:remove_method, :m) + end +end + +describe "class provides hooks" do + it "calls inherited when a class is created" do + ClassSpecs::H.track_inherited.should == [ClassSpecs::K] + end +end diff --git a/spec/rubyspec/language/class_variable_spec.rb b/spec/rubyspec/language/class_variable_spec.rb new file mode 100644 index 0000000000..463f731a93 --- /dev/null +++ b/spec/rubyspec/language/class_variable_spec.rb @@ -0,0 +1,84 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/class_variables', __FILE__) + +describe "A class variable" do + after :each do + ClassVariablesSpec::ClassA.new.cvar_a = :cvar_a + end + + it "can be accessed from a subclass" do + ClassVariablesSpec::ClassB.new.cvar_a.should == :cvar_a + end + + it "is set in the superclass" do + a = ClassVariablesSpec::ClassA.new + b = ClassVariablesSpec::ClassB.new + b.cvar_a = :new_val + + a.cvar_a.should == :new_val + end +end + +describe "A class variable defined in a module" do + after :each do + ClassVariablesSpec::ClassC.cvar_m = :value + ClassVariablesSpec::ClassC.remove_class_variable(:@@cvar) if ClassVariablesSpec::ClassC.cvar_defined? + end + + it "can be accessed from classes that extend the module" do + ClassVariablesSpec::ClassC.cvar_m.should == :value + end + + it "is not defined in these classes" do + ClassVariablesSpec::ClassC.cvar_defined?.should be_false + end + + it "is only updated in the module a method defined in the module is used" do + ClassVariablesSpec::ClassC.cvar_m = "new value" + ClassVariablesSpec::ClassC.cvar_m.should == "new value" + + ClassVariablesSpec::ClassC.cvar_defined?.should be_false + end + + it "is updated in the class when a Method defined in the class is used" do + ClassVariablesSpec::ClassC.cvar_c = "new value" + ClassVariablesSpec::ClassC.cvar_defined?.should be_true + end + + it "can be accessed inside the class using the module methods" do + ClassVariablesSpec::ClassC.cvar_c = "new value" + ClassVariablesSpec::ClassC.cvar_m.should == :value + end + + it "can be accessed from modules that extend the module" do + ClassVariablesSpec::ModuleO.cvar_n.should == :value + end + + it "is defined in the extended module" do + ClassVariablesSpec::ModuleN.class_variable_defined?(:@@cvar_n).should be_true + end + + it "is not defined in the extending module" do + ClassVariablesSpec::ModuleO.class_variable_defined?(:@@cvar_n).should be_false + end +end + +describe 'A class variable definition' do + it "is created in a module if any of the parents do not define it" do + a = Class.new + b = Class.new(a) + c = Class.new(b) + b.class_variable_set(:@@cv, :value) + + lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError) + b.class_variable_get(:@@cv).should == :value + c.class_variable_get(:@@cv).should == :value + + # updates the same variable + c.class_variable_set(:@@cv, :next) + + lambda { a.class_variable_get(:@@cv) }.should raise_error(NameError) + b.class_variable_get(:@@cv).should == :next + c.class_variable_get(:@@cv).should == :next + end +end diff --git a/spec/rubyspec/language/constants_spec.rb b/spec/rubyspec/language/constants_spec.rb new file mode 100644 index 0000000000..e68ba495be --- /dev/null +++ b/spec/rubyspec/language/constants_spec.rb @@ -0,0 +1,613 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/constants', __FILE__) +require File.expand_path('../fixtures/constants_sclass', __FILE__) +require File.expand_path('../fixtures/constant_visibility', __FILE__) + +# Read the documentation in fixtures/constants.rb for the guidelines and +# rationale for the structure and organization of these specs. + +describe "Literal (A::X) constant resolution" do + describe "with statically assigned constants" do + it "searches the immediate class or module scope first" do + ConstantSpecs::ClassA::CS_CONST10.should == :const10_10 + ConstantSpecs::ModuleA::CS_CONST10.should == :const10_1 + ConstantSpecs::ParentA::CS_CONST10.should == :const10_5 + ConstantSpecs::ContainerA::CS_CONST10.should == :const10_2 + ConstantSpecs::ContainerA::ChildA::CS_CONST10.should == :const10_3 + end + + it "searches a module included in the immediate class before the superclass" do + ConstantSpecs::ContainerA::ChildA::CS_CONST15.should == :const15_1 + end + + it "searches the superclass before a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA::CS_CONST11.should == :const11_1 + end + + it "searches a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA::CS_CONST12.should == :const12_1 + end + + it "searches the superclass chain" do + ConstantSpecs::ContainerA::ChildA::CS_CONST13.should == :const13 + end + + it "searches Object if no class or module qualifier is given" do + CS_CONST1.should == :const1 + CS_CONST10.should == :const10_1 + end + + it "searches Object after searching other scopes" do + module ConstantSpecs::SpecAdded1 + CS_CONST10.should == :const10_1 + end + end + + it "searches Object if a toplevel qualifier (::X) is given" do + ::CS_CONST1.should == :const1 + ::CS_CONST10.should == :const10_1 + end + + it "does not search the singleton class of the class or module" do + lambda do + ConstantSpecs::ContainerA::ChildA::CS_CONST14 + end.should raise_error(NameError) + lambda { ConstantSpecs::CS_CONST14 }.should raise_error(NameError) + end + end + + describe "with dynamically assigned constants" do + it "searches the immediate class or module scope first" do + ConstantSpecs::ClassB::CS_CONST101 = :const101_1 + ConstantSpecs::ClassB::CS_CONST101.should == :const101_1 + + ConstantSpecs::ParentB::CS_CONST101 = :const101_2 + ConstantSpecs::ParentB::CS_CONST101.should == :const101_2 + + ConstantSpecs::ContainerB::CS_CONST101 = :const101_3 + ConstantSpecs::ContainerB::CS_CONST101.should == :const101_3 + + ConstantSpecs::ContainerB::ChildB::CS_CONST101 = :const101_4 + ConstantSpecs::ContainerB::ChildB::CS_CONST101.should == :const101_4 + + ConstantSpecs::ModuleA::CS_CONST101 = :const101_5 + ConstantSpecs::ModuleA::CS_CONST101.should == :const101_5 + end + + it "searches a module included in the immediate class before the superclass" do + ConstantSpecs::ParentB::CS_CONST102 = :const102_1 + ConstantSpecs::ModuleF::CS_CONST102 = :const102_2 + ConstantSpecs::ContainerB::ChildB::CS_CONST102.should == :const102_2 + end + + it "searches the superclass before a module included in the superclass" do + ConstantSpecs::ModuleE::CS_CONST103 = :const103_1 + ConstantSpecs::ParentB::CS_CONST103 = :const103_2 + ConstantSpecs::ContainerB::ChildB::CS_CONST103.should == :const103_2 + end + + it "searches a module included in the superclass" do + ConstantSpecs::ModuleA::CS_CONST104 = :const104_1 + ConstantSpecs::ModuleE::CS_CONST104 = :const104_2 + ConstantSpecs::ContainerB::ChildB::CS_CONST104.should == :const104_2 + end + + it "searches the superclass chain" do + ConstantSpecs::ModuleA::CS_CONST105 = :const105 + ConstantSpecs::ContainerB::ChildB::CS_CONST105.should == :const105 + end + + it "searches Object if no class or module qualifier is given" do + CS_CONST106 = :const106 + CS_CONST106.should == :const106 + end + + it "searches Object if a toplevel qualifier (::X) is given" do + ::CS_CONST107 = :const107 + ::CS_CONST107.should == :const107 + end + + it "does not search the singleton class of the class or module" do + class << ConstantSpecs::ContainerB::ChildB + CS_CONST108 = :const108_1 + end + + lambda do + ConstantSpecs::ContainerB::ChildB::CS_CONST108 + end.should raise_error(NameError) + + module ConstantSpecs + class << self + CS_CONST108 = :const108_2 + end + end + + lambda { ConstantSpecs::CS_CONST108 }.should raise_error(NameError) + end + + it "returns the updated value when a constant is reassigned" do + ConstantSpecs::ClassB::CS_CONST109 = :const109_1 + ConstantSpecs::ClassB::CS_CONST109.should == :const109_1 + + -> { + ConstantSpecs::ClassB::CS_CONST109 = :const109_2 + }.should complain(/already initialized constant/) + ConstantSpecs::ClassB::CS_CONST109.should == :const109_2 + end + + it "evaluates the right hand side before evaluating a constant path" do + mod = Module.new + + mod.module_eval <<-EOC + ConstantSpecsRHS::B = begin + module ConstantSpecsRHS; end + + "hello" + end + EOC + + mod::ConstantSpecsRHS::B.should == 'hello' + end + end + + it "raises a NameError if no constant is defined in the search path" do + lambda { ConstantSpecs::ParentA::CS_CONSTX }.should raise_error(NameError) + end + + it "sends #const_missing to the original class or module scope" do + ConstantSpecs::ClassA::CS_CONSTX.should == :CS_CONSTX + end + + it "evaluates the qualifier" do + ConstantSpecs.get_const::CS_CONST2.should == :const2 + end + + it "raises a TypeError if a non-class or non-module qualifier is given" do + lambda { CS_CONST1::CS_CONST }.should raise_error(TypeError) + lambda { 1::CS_CONST }.should raise_error(TypeError) + lambda { "mod"::CS_CONST }.should raise_error(TypeError) + lambda { false::CS_CONST }.should raise_error(TypeError) + end +end + +describe "Constant resolution within methods" do + describe "with statically assigned constants" do + it "searches the immediate class or module scope first" do + ConstantSpecs::ClassA.const10.should == :const10_10 + ConstantSpecs::ParentA.const10.should == :const10_5 + ConstantSpecs::ContainerA.const10.should == :const10_2 + ConstantSpecs::ContainerA::ChildA.const10.should == :const10_3 + + ConstantSpecs::ClassA.new.const10.should == :const10_10 + ConstantSpecs::ParentA.new.const10.should == :const10_5 + ConstantSpecs::ContainerA::ChildA.new.const10.should == :const10_3 + end + + it "searches a module included in the immediate class before the superclass" do + ConstantSpecs::ContainerA::ChildA.const15.should == :const15_1 + ConstantSpecs::ContainerA::ChildA.new.const15.should == :const15_1 + end + + it "searches the superclass before a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const11.should == :const11_1 + ConstantSpecs::ContainerA::ChildA.new.const11.should == :const11_1 + end + + it "searches a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const12.should == :const12_1 + ConstantSpecs::ContainerA::ChildA.new.const12.should == :const12_1 + end + + it "searches the superclass chain" do + ConstantSpecs::ContainerA::ChildA.const13.should == :const13 + ConstantSpecs::ContainerA::ChildA.new.const13.should == :const13 + end + + it "searches the lexical scope of the method not the receiver's immediate class" do + ConstantSpecs::ContainerA::ChildA.const19.should == :const19_1 + end + + it "searches the lexical scope of a singleton method" do + ConstantSpecs::CS_CONST18.const17.should == :const17_1 + end + + it "does not search the lexical scope of the caller" do + lambda { ConstantSpecs::ClassA.const16 }.should raise_error(NameError) + end + + it "searches the lexical scope of a block" do + ConstantSpecs::ClassA.const22.should == :const22_1 + end + + it "searches Object as a lexical scope only if Object is explicitly opened" do + ConstantSpecs::ContainerA::ChildA.const20.should == :const20_1 + ConstantSpecs::ContainerA::ChildA.const21.should == :const21_1 + end + + it "does not search the lexical scope of qualifying modules" do + lambda do + ConstantSpecs::ContainerA::ChildA.const23 + end.should raise_error(NameError) + end + end + + describe "with dynamically assigned constants" do + it "searches the immediate class or module scope first" do + ConstantSpecs::ModuleA::CS_CONST201 = :const201_1 + + class ConstantSpecs::ClassB; CS_CONST201 = :const201_2; end + ConstantSpecs::ParentB::CS_CONST201 = :const201_3 + ConstantSpecs::ContainerB::CS_CONST201 = :const201_4 + ConstantSpecs::ContainerB::ChildB::CS_CONST201 = :const201_5 + + ConstantSpecs::ClassB.const201.should == :const201_2 + ConstantSpecs::ParentB.const201.should == :const201_3 + ConstantSpecs::ContainerB.const201.should == :const201_4 + ConstantSpecs::ContainerB::ChildB.const201.should == :const201_5 + + ConstantSpecs::ClassB.new.const201.should == :const201_2 + ConstantSpecs::ParentB.new.const201.should == :const201_3 + ConstantSpecs::ContainerB::ChildB.new.const201.should == :const201_5 + end + + it "searches a module included in the immediate class before the superclass" do + ConstantSpecs::ParentB::CS_CONST202 = :const202_2 + ConstantSpecs::ContainerB::ChildB::CS_CONST202 = :const202_1 + + ConstantSpecs::ContainerB::ChildB.const202.should == :const202_1 + ConstantSpecs::ContainerB::ChildB.new.const202.should == :const202_1 + end + + it "searches the superclass before a module included in the superclass" do + ConstantSpecs::ParentB::CS_CONST203 = :const203_1 + ConstantSpecs::ModuleE::CS_CONST203 = :const203_2 + + ConstantSpecs::ContainerB::ChildB.const203.should == :const203_1 + ConstantSpecs::ContainerB::ChildB.new.const203.should == :const203_1 + end + + it "searches a module included in the superclass" do + ConstantSpecs::ModuleA::CS_CONST204 = :const204_2 + ConstantSpecs::ModuleE::CS_CONST204 = :const204_1 + + ConstantSpecs::ContainerB::ChildB.const204.should == :const204_1 + ConstantSpecs::ContainerB::ChildB.new.const204.should == :const204_1 + end + + it "searches the superclass chain" do + ConstantSpecs::ModuleA::CS_CONST205 = :const205 + + ConstantSpecs::ContainerB::ChildB.const205.should == :const205 + ConstantSpecs::ContainerB::ChildB.new.const205.should == :const205 + end + + it "searches the lexical scope of the method not the receiver's immediate class" do + ConstantSpecs::ContainerB::ChildB::CS_CONST206 = :const206_2 + class ConstantSpecs::ContainerB::ChildB + class << self + CS_CONST206 = :const206_1 + end + end + + ConstantSpecs::ContainerB::ChildB.const206.should == :const206_1 + end + + it "searches the lexical scope of a singleton method" do + ConstantSpecs::CS_CONST207 = :const207_1 + ConstantSpecs::ClassB::CS_CONST207 = :const207_2 + + ConstantSpecs::CS_CONST208.const207.should == :const207_1 + end + + it "does not search the lexical scope of the caller" do + ConstantSpecs::ClassB::CS_CONST209 = :const209 + + lambda { ConstantSpecs::ClassB.const209 }.should raise_error(NameError) + end + + it "searches the lexical scope of a block" do + ConstantSpecs::ClassB::CS_CONST210 = :const210_1 + ConstantSpecs::ParentB::CS_CONST210 = :const210_2 + + ConstantSpecs::ClassB.const210.should == :const210_1 + end + + it "searches Object as a lexical scope only if Object is explicitly opened" do + Object::CS_CONST211 = :const211_1 + ConstantSpecs::ParentB::CS_CONST211 = :const211_2 + ConstantSpecs::ContainerB::ChildB.const211.should == :const211_1 + + Object::CS_CONST212 = :const212_2 + ConstantSpecs::ParentB::CS_CONST212 = :const212_1 + ConstantSpecs::ContainerB::ChildB.const212.should == :const212_1 + end + + it "returns the updated value when a constant is reassigned" do + ConstantSpecs::ParentB::CS_CONST213 = :const213_1 + ConstantSpecs::ContainerB::ChildB.const213.should == :const213_1 + ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_1 + + -> { + ConstantSpecs::ParentB::CS_CONST213 = :const213_2 + }.should complain(/already initialized constant/) + ConstantSpecs::ContainerB::ChildB.const213.should == :const213_2 + ConstantSpecs::ContainerB::ChildB.new.const213.should == :const213_2 + end + + it "does not search the lexical scope of qualifying modules" do + ConstantSpecs::ContainerB::CS_CONST214 = :const214 + + lambda do + ConstantSpecs::ContainerB::ChildB.const214 + end.should raise_error(NameError) + end + end + + it "raises a NameError if no constant is defined in the search path" do + lambda { ConstantSpecs::ParentA.constx }.should raise_error(NameError) + end + + it "sends #const_missing to the original class or module scope" do + ConstantSpecs::ClassA.constx.should == :CS_CONSTX + ConstantSpecs::ClassA.new.constx.should == :CS_CONSTX + end + + describe "with ||=" do + it "assignes constant if previously undefined" do + ConstantSpecs.should_not have_constant(:OpAssignUndefined) + # Literally opening the module is required to avoid content + # re-assignment error + module ConstantSpecs + OpAssignUndefined ||= 42 + end + ConstantSpecs::OpAssignUndefined.should == 42 + ConstantSpecs.send(:remove_const, :OpAssignUndefined) + end + end +end + +describe "Constant resolution within a singleton class (class << obj)" do + it "works like normal classes or modules" do + ConstantSpecs::CS_SINGLETON1.foo.should == 1 + end + + ruby_version_is "2.3" do + it "uses its own namespace for each object" do + a = ConstantSpecs::CS_SINGLETON2[0].foo + b = ConstantSpecs::CS_SINGLETON2[1].foo + [a, b].should == [1, 2] + end + + it "uses its own namespace for nested modules" do + a = ConstantSpecs::CS_SINGLETON3[0].x + b = ConstantSpecs::CS_SINGLETON3[1].x + a.should_not equal(b) + end + + it "allows nested modules to have proper resolution" do + a = ConstantSpecs::CS_SINGLETON4_CLASSES[0].new + b = ConstantSpecs::CS_SINGLETON4_CLASSES[1].new + [a.foo, b.foo].should == [1, 2] + end + end +end + +describe "Module#private_constant marked constants" do + + it "remain private even when updated" do + mod = Module.new + mod.const_set :Foo, true + mod.send :private_constant, :Foo + -> { + mod.const_set :Foo, false + }.should complain(/already initialized constant/) + + lambda {mod::Foo}.should raise_error(NameError) + end + + describe "in a module" do + it "cannot be accessed from outside the module" do + lambda do + ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE + end.should raise_error(NameError) + end + + it "cannot be reopened as a module from scope where constant would be private" do + lambda do + module ConstantVisibility::ModuleContainer::PrivateModule; end + end.should raise_error(NameError) + end + + it "cannot be reopened as a class from scope where constant would be private" do + lambda do + class ConstantVisibility::ModuleContainer::PrivateClass; end + end.should raise_error(NameError) + end + + it "can be reopened as a module where constant is not private" do + module ::ConstantVisibility::ModuleContainer + module PrivateModule + X = 1 + end + + PrivateModule::X.should == 1 + end + end + + it "can be reopened as a class where constant is not private" do + module ::ConstantVisibility::ModuleContainer + class PrivateClass + X = 1 + end + + PrivateClass::X.should == 1 + end + end + + it "is not defined? with A::B form" do + defined?(ConstantVisibility::PrivConstModule::PRIVATE_CONSTANT_MODULE).should == nil + end + + it "can be accessed from the module itself" do + ConstantVisibility::PrivConstModule.private_constant_from_self.should be_true + end + + it "is defined? from the module itself" do + ConstantVisibility::PrivConstModule.defined_from_self.should == "constant" + end + + it "can be accessed from lexical scope" do + ConstantVisibility::PrivConstModule::Nested.private_constant_from_scope.should be_true + end + + it "is defined? from lexical scope" do + ConstantVisibility::PrivConstModule::Nested.defined_from_scope.should == "constant" + end + + it "can be accessed from classes that include the module" do + ConstantVisibility::PrivConstModuleChild.new.private_constant_from_include.should be_true + end + + it "is defined? from classes that include the module" do + ConstantVisibility::PrivConstModuleChild.new.defined_from_include.should == "constant" + end + end + + describe "in a class" do + it "cannot be accessed from outside the class" do + lambda do + ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS + end.should raise_error(NameError) + end + + it "cannot be reopened as a module" do + lambda do + module ConstantVisibility::ClassContainer::PrivateModule; end + end.should raise_error(NameError) + end + + it "cannot be reopened as a class" do + lambda do + class ConstantVisibility::ClassContainer::PrivateClass; end + end.should raise_error(NameError) + end + + it "can be reopened as a module where constant is not private" do + class ::ConstantVisibility::ClassContainer + module PrivateModule + X = 1 + end + + PrivateModule::X.should == 1 + end + end + + it "can be reopened as a class where constant is not private" do + class ::ConstantVisibility::ClassContainer + class PrivateClass + X = 1 + end + + PrivateClass::X.should == 1 + end + end + + it "is not defined? with A::B form" do + defined?(ConstantVisibility::PrivConstClass::PRIVATE_CONSTANT_CLASS).should == nil + end + + it "can be accessed from the class itself" do + ConstantVisibility::PrivConstClass.private_constant_from_self.should be_true + end + + it "is defined? from the class itself" do + ConstantVisibility::PrivConstClass.defined_from_self.should == "constant" + end + + it "can be accessed from lexical scope" do + ConstantVisibility::PrivConstClass::Nested.private_constant_from_scope.should be_true + end + + it "is defined? from lexical scope" do + ConstantVisibility::PrivConstClass::Nested.defined_from_scope.should == "constant" + end + + it "can be accessed from subclasses" do + ConstantVisibility::PrivConstClassChild.new.private_constant_from_subclass.should be_true + end + + it "is defined? from subclasses" do + ConstantVisibility::PrivConstClassChild.new.defined_from_subclass.should == "constant" + end + end + + describe "in Object" do + it "cannot be accessed using ::Const form" do + lambda do + ::PRIVATE_CONSTANT_IN_OBJECT + end.should raise_error(NameError) + end + + it "is not defined? using ::Const form" do + defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == nil + end + + it "can be accessed through the normal search" do + PRIVATE_CONSTANT_IN_OBJECT.should == true + end + + it "is defined? through the normal search" do + defined?(PRIVATE_CONSTANT_IN_OBJECT).should == "constant" + end + end +end + +describe "Module#public_constant marked constants" do + before :each do + @module = ConstantVisibility::PrivConstModule.dup + end + + describe "in a module" do + it "can be accessed from outside the module" do + @module.send :public_constant, :PRIVATE_CONSTANT_MODULE + @module::PRIVATE_CONSTANT_MODULE.should == true + end + + it "is defined? with A::B form" do + @module.send :public_constant, :PRIVATE_CONSTANT_MODULE + defined?(@module::PRIVATE_CONSTANT_MODULE).should == "constant" + end + end + + describe "in a class" do + before :each do + @class = ConstantVisibility::PrivConstClass.dup + end + + it "can be accessed from outside the class" do + @class.send :public_constant, :PRIVATE_CONSTANT_CLASS + @class::PRIVATE_CONSTANT_CLASS.should == true + end + + it "is defined? with A::B form" do + @class.send :public_constant, :PRIVATE_CONSTANT_CLASS + defined?(@class::PRIVATE_CONSTANT_CLASS).should == "constant" + end + end + + describe "in Object" do + after :each do + ConstantVisibility.reset_private_constants + end + + it "can be accessed using ::Const form" do + Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT + ::PRIVATE_CONSTANT_IN_OBJECT.should == true + end + + it "is defined? using ::Const form" do + Object.send :public_constant, :PRIVATE_CONSTANT_IN_OBJECT + defined?(::PRIVATE_CONSTANT_IN_OBJECT).should == "constant" + end + end +end diff --git a/spec/rubyspec/language/def_spec.rb b/spec/rubyspec/language/def_spec.rb new file mode 100644 index 0000000000..55ee283b90 --- /dev/null +++ b/spec/rubyspec/language/def_spec.rb @@ -0,0 +1,714 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/def', __FILE__) + +# Language-level method behaviour +describe "Redefining a method" do + it "replaces the original method" do + def barfoo; 100; end + barfoo.should == 100 + + def barfoo; 200; end + barfoo.should == 200 + end +end + +describe "Defining a method at the top-level" do + it "defines it on Object with private visibility by default" do + Object.should have_private_instance_method(:some_toplevel_method, false) + end + + it "defines it on Object with public visibility after calling public" do + Object.should have_public_instance_method(:public_toplevel_method, false) + end +end + +describe "Defining an 'initialize' method" do + it "sets the method's visibility to private" do + class DefInitializeSpec + def initialize + end + end + DefInitializeSpec.should have_private_instance_method(:initialize, false) + end +end + +describe "Defining an 'initialize_copy' method" do + it "sets the method's visibility to private" do + class DefInitializeCopySpec + def initialize_copy + end + end + DefInitializeCopySpec.should have_private_instance_method(:initialize_copy, false) + end +end + +describe "Defining an 'initialize_dup' method" do + it "sets the method's visibility to private" do + class DefInitializeDupSpec + def initialize_dup + end + end + DefInitializeDupSpec.should have_private_instance_method(:initialize_dup, false) + end +end + +describe "Defining an 'initialize_clone' method" do + it "sets the method's visibility to private" do + class DefInitializeCloneSpec + def initialize_clone + end + end + DefInitializeCloneSpec.should have_private_instance_method(:initialize_clone, false) + end +end + +describe "Defining a 'respond_to_missing?' method" do + it "sets the method's visibility to private" do + class DefRespondToMissingPSpec + def respond_to_missing? + end + end + DefRespondToMissingPSpec.should have_private_instance_method(:respond_to_missing?, false) + end +end + +describe "Defining a method" do + it "returns a symbol of the method name" do + method_name = def some_method; end + method_name.should == :some_method + end +end + +describe "An instance method definition with a splat" do + it "accepts an unnamed '*' argument" do + def foo(*); end; + + foo.should == nil + foo(1, 2).should == nil + foo(1, 2, 3, 4, :a, :b, 'c', 'd').should == nil + end + + it "accepts a named * argument" do + def foo(*a); a; end; + foo.should == [] + foo(1, 2).should == [1, 2] + foo([:a]).should == [[:a]] + end + + it "accepts non-* arguments before the * argument" do + def foo(a, b, c, d, e, *f); [a, b, c, d, e, f]; end + foo(1, 2, 3, 4, 5, 6, 7, 8).should == [1, 2, 3, 4, 5, [6, 7, 8]] + end + + it "allows only a single * argument" do + lambda { eval 'def foo(a, *b, *c); end' }.should raise_error(SyntaxError) + end + + it "requires the presence of any arguments that precede the *" do + def foo(a, b, *c); end + lambda { foo 1 }.should raise_error(ArgumentError) + end +end + +describe "An instance method with a default argument" do + it "evaluates the default when no arguments are passed" do + def foo(a = 1) + a + end + foo.should == 1 + foo(2).should == 2 + end + + it "evaluates the default empty expression when no arguments are passed" do + def foo(a = ()) + a + end + foo.should == nil + foo(2).should == 2 + end + + it "assigns an empty Array to an unused splat argument" do + def foo(a = 1, *b) + [a,b] + end + foo.should == [1, []] + foo(2).should == [2, []] + end + + it "evaluates the default when required arguments precede it" do + def foo(a, b = 2) + [a,b] + end + lambda { foo }.should raise_error(ArgumentError) + foo(1).should == [1, 2] + end + + it "prefers to assign to a default argument before a splat argument" do + def foo(a, b = 2, *c) + [a,b,c] + end + lambda { foo }.should raise_error(ArgumentError) + foo(1).should == [1,2,[]] + end + + it "prefers to assign to a default argument when there are no required arguments" do + def foo(a = 1, *args) + [a,args] + end + foo(2,2).should == [2,[2]] + end + + it "does not evaluate the default when passed a value and a * argument" do + def foo(a, b = 2, *args) + [a,b,args] + end + foo(2,3,3).should == [2,3,[3]] + end + + it "shadows an existing method with the same name as the local" do + def bar + 1 + end + -> { + eval "def foo(bar = bar) + bar + end" + }.should complain(/circular argument reference/) + foo.should == nil + foo(2).should == 2 + end + + it "calls a method with the same name as the local when explicitly using ()" do + def bar + 1 + end + def foo(bar = bar()) + bar + end + foo.should == 1 + foo(2).should == 2 + end +end + +describe "A singleton method definition" do + it "can be declared for a local variable" do + a = Object.new + def a.foo + 5 + end + a.foo.should == 5 + end + + it "can be declared for an instance variable" do + @a = Object.new + def @a.foo + 6 + end + @a.foo.should == 6 + end + + it "can be declared for a global variable" do + $__a__ = "hi" + def $__a__.foo + 7 + end + $__a__.foo.should == 7 + end + + it "can be declared with an empty method body" do + class DefSpec + def self.foo;end + end + DefSpec.foo.should == nil + end + + it "can be redefined" do + obj = Object.new + def obj.==(other) + 1 + end + (obj==1).should == 1 + def obj.==(other) + 2 + end + (obj==2).should == 2 + end + + it "raises RuntimeError if frozen" do + obj = Object.new + obj.freeze + lambda { def obj.foo; end }.should raise_error(RuntimeError) + end +end + +describe "Redefining a singleton method" do + it "does not inherit a previously set visibility" do + o = Object.new + + class << o; private; def foo; end; end; + + class << o; should have_private_instance_method(:foo); end + + class << o; def foo; end; end; + + class << o; should_not have_private_instance_method(:foo); end + class << o; should have_instance_method(:foo); end + + end +end + +describe "Redefining a singleton method" do + it "does not inherit a previously set visibility" do + o = Object.new + + class << o; private; def foo; end; end; + + class << o; should have_private_instance_method(:foo); end + + class << o; def foo; end; end; + + class << o; should_not have_private_instance_method(:foo); end + class << o; should have_instance_method(:foo); end + + end +end + +describe "A method defined with extreme default arguments" do + it "can redefine itself when the default is evaluated" do + class DefSpecs + def foo(x = (def foo; "hello"; end;1));x;end + end + + d = DefSpecs.new + d.foo(42).should == 42 + d.foo.should == 1 + d.foo.should == 'hello' + end + + it "may use an fcall as a default" do + def bar + 1 + end + def foo(x = bar()) + x + end + foo.should == 1 + foo(2).should == 2 + end + + it "evaluates the defaults in the method's scope" do + def foo(x = ($foo_self = self; nil)); end + foo + $foo_self.should == self + end + + it "may use preceding arguments as defaults" do + def foo(obj, width=obj.length) + width + end + foo('abcde').should == 5 + end + + it "may use a lambda as a default" do + def foo(output = 'a', prc = lambda {|n| output * n}) + prc.call(5) + end + foo.should == 'aaaaa' + end +end + +describe "A singleton method defined with extreme default arguments" do + it "may use a method definition as a default" do + $__a = Object.new + def $__a.foo(x = (def $__a.foo; "hello"; end;1));x;end + + $__a.foo(42).should == 42 + $__a.foo.should == 1 + $__a.foo.should == 'hello' + end + + it "may use an fcall as a default" do + a = Object.new + def a.bar + 1 + end + def a.foo(x = bar()) + x + end + a.foo.should == 1 + a.foo(2).should == 2 + end + + it "evaluates the defaults in the singleton scope" do + a = Object.new + def a.foo(x = ($foo_self = self; nil)); 5 ;end + a.foo + $foo_self.should == a + end + + it "may use preceding arguments as defaults" do + a = Object.new + def a.foo(obj, width=obj.length) + width + end + a.foo('abcde').should == 5 + end + + it "may use a lambda as a default" do + a = Object.new + def a.foo(output = 'a', prc = lambda {|n| output * n}) + prc.call(5) + end + a.foo.should == 'aaaaa' + end +end + +describe "A method definition inside a metaclass scope" do + it "can create a class method" do + class DefSpecSingleton + class << self + def a_class_method;self;end + end + end + + DefSpecSingleton.a_class_method.should == DefSpecSingleton + lambda { Object.a_class_method }.should raise_error(NoMethodError) + end + + it "can create a singleton method" do + obj = Object.new + class << obj + def a_singleton_method;self;end + end + + obj.a_singleton_method.should == obj + lambda { Object.new.a_singleton_method }.should raise_error(NoMethodError) + end + + it "raises RuntimeError if frozen" do + obj = Object.new + obj.freeze + + class << obj + lambda { def foo; end }.should raise_error(RuntimeError) + end + end +end + +describe "A nested method definition" do + it "creates an instance method when evaluated in an instance method" do + class DefSpecNested + def create_instance_method + def an_instance_method;self;end + an_instance_method + end + end + + obj = DefSpecNested.new + obj.create_instance_method.should == obj + obj.an_instance_method.should == obj + + other = DefSpecNested.new + other.an_instance_method.should == other + + DefSpecNested.should have_instance_method(:an_instance_method) + end + + it "creates a class method when evaluated in a class method" do + class DefSpecNested + class << self + # cleanup + remove_method :a_class_method if method_defined? :a_class_method + def create_class_method + def a_class_method;self;end + a_class_method + end + end + end + + lambda { DefSpecNested.a_class_method }.should raise_error(NoMethodError) + DefSpecNested.create_class_method.should == DefSpecNested + DefSpecNested.a_class_method.should == DefSpecNested + lambda { Object.a_class_method }.should raise_error(NoMethodError) + lambda { DefSpecNested.new.a_class_method }.should raise_error(NoMethodError) + end + + it "creates a singleton method when evaluated in the metaclass of an instance" do + class DefSpecNested + def create_singleton_method + class << self + def a_singleton_method;self;end + end + a_singleton_method + end + end + + obj = DefSpecNested.new + obj.create_singleton_method.should == obj + obj.a_singleton_method.should == obj + + other = DefSpecNested.new + lambda { other.a_singleton_method }.should raise_error(NoMethodError) + end + + it "creates a method in the surrounding context when evaluated in a def expr.method" do + class DefSpecNested + TARGET = Object.new + def TARGET.defs_method + def inherited_method;self;end + end + end + + DefSpecNested::TARGET.defs_method + DefSpecNested.should have_instance_method :inherited_method + DefSpecNested::TARGET.should_not have_method :inherited_method + + obj = DefSpecNested.new + obj.inherited_method.should == obj + end + + # See http://yugui.jp/articles/846#label-3 + it "inside an instance_eval creates a singleton method" do + class DefSpecNested + OBJ = Object.new + OBJ.instance_eval do + def create_method_in_instance_eval(a = (def arg_method; end)) + def body_method; end + end + end + end + + obj = DefSpecNested::OBJ + obj.create_method_in_instance_eval + + obj.should have_method :arg_method + obj.should have_method :body_method + + DefSpecNested.should_not have_instance_method :arg_method + DefSpecNested.should_not have_instance_method :body_method + end + + it "defines methods as public by default" do + cls = Class.new do + def do_def + def new_def + 1 + end + end + end + + obj = cls.new + obj.do_def + obj.new_def.should == 1 + end +end + +describe "A method definition inside an instance_eval" do + it "creates a singleton method" do + obj = Object.new + obj.instance_eval do + def an_instance_eval_method;self;end + end + obj.an_instance_eval_method.should == obj + + other = Object.new + lambda { other.an_instance_eval_method }.should raise_error(NoMethodError) + end + + it "creates a singleton method when evaluated inside a metaclass" do + obj = Object.new + obj.instance_eval do + class << self + def a_metaclass_eval_method;self;end + end + end + obj.a_metaclass_eval_method.should == obj + + other = Object.new + lambda { other.a_metaclass_eval_method }.should raise_error(NoMethodError) + end + + it "creates a class method when the receiver is a class" do + DefSpecNested.instance_eval do + def an_instance_eval_class_method;self;end + end + + DefSpecNested.an_instance_eval_class_method.should == DefSpecNested + lambda { Object.an_instance_eval_class_method }.should raise_error(NoMethodError) + end + + it "creates a class method when the receiver is an anonymous class" do + m = Class.new + m.instance_eval do + def klass_method + :test + end + end + + m.klass_method.should == :test + lambda { Object.klass_method }.should raise_error(NoMethodError) + end + + it "creates a class method when instance_eval is within class" do + m = Class.new do + instance_eval do + def klass_method + :test + end + end + end + + m.klass_method.should == :test + lambda { Object.klass_method }.should raise_error(NoMethodError) + end +end + +describe "A method definition inside an instance_exec" do + it "creates a class method when the receiver is a class" do + DefSpecNested.instance_exec(1) do |param| + @stuff = param + + def an_instance_exec_class_method; @stuff; end + end + + DefSpecNested.an_instance_exec_class_method.should == 1 + lambda { Object.an_instance_exec_class_method }.should raise_error(NoMethodError) + end + + it "creates a class method when the receiver is an anonymous class" do + m = Class.new + m.instance_exec(1) do |param| + @stuff = param + + def klass_method + @stuff + end + end + + m.klass_method.should == 1 + lambda { Object.klass_method }.should raise_error(NoMethodError) + end + + it "creates a class method when instance_exec is within class" do + m = Class.new do + instance_exec(2) do |param| + @stuff = param + + def klass_method + @stuff + end + end + end + + m.klass_method.should == 2 + lambda { Object.klass_method }.should raise_error(NoMethodError) + end +end + +describe "A method definition in an eval" do + it "creates an instance method" do + class DefSpecNested + def eval_instance_method + eval "def an_eval_instance_method;self;end", binding + an_eval_instance_method + end + end + + obj = DefSpecNested.new + obj.eval_instance_method.should == obj + obj.an_eval_instance_method.should == obj + + other = DefSpecNested.new + other.an_eval_instance_method.should == other + + lambda { Object.new.an_eval_instance_method }.should raise_error(NoMethodError) + end + + it "creates a class method" do + class DefSpecNestedB + class << self + def eval_class_method + eval "def an_eval_class_method;self;end" #, binding + an_eval_class_method + end + end + end + + DefSpecNestedB.eval_class_method.should == DefSpecNestedB + DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB + + lambda { Object.an_eval_class_method }.should raise_error(NoMethodError) + lambda { DefSpecNestedB.new.an_eval_class_method}.should raise_error(NoMethodError) + end + + it "creates a singleton method" do + class DefSpecNested + def eval_singleton_method + class << self + eval "def an_eval_singleton_method;self;end", binding + end + an_eval_singleton_method + end + end + + obj = DefSpecNested.new + obj.eval_singleton_method.should == obj + obj.an_eval_singleton_method.should == obj + + other = DefSpecNested.new + lambda { other.an_eval_singleton_method }.should raise_error(NoMethodError) + end +end + +describe "a method definition that sets more than one default parameter all to the same value" do + def foo(a=b=c={}) + [a,b,c] + end + it "assigns them all the same object by default" do + foo.should == [{},{},{}] + a, b, c = foo + a.should eql(b) + a.should eql(c) + end + + it "allows the first argument to be given, and sets the rest to null" do + foo(1).should == [1,nil,nil] + end + + it "assigns the parameters different objects across different default calls" do + a, _b, _c = foo + d, _e, _f = foo + a.should_not equal(d) + end + + it "only allows overriding the default value of the first such parameter in each set" do + lambda { foo(1,2) }.should raise_error(ArgumentError) + end + + def bar(a=b=c=1,d=2) + [a,b,c,d] + end + + it "treats the argument after the multi-parameter normally" do + bar.should == [1,1,1,2] + bar(3).should == [3,nil,nil,2] + bar(3,4).should == [3,nil,nil,4] + lambda { bar(3,4,5) }.should raise_error(ArgumentError) + end +end + +describe "The def keyword" do + describe "within a closure" do + it "looks outside the closure for the visibility" do + module DefSpecsLambdaVisibility + private + + lambda { + def some_method; end + }.call + end + + DefSpecsLambdaVisibility.should have_private_instance_method("some_method") + end + end +end diff --git a/spec/rubyspec/language/defined_spec.rb b/spec/rubyspec/language/defined_spec.rb new file mode 100644 index 0000000000..790db64e86 --- /dev/null +++ b/spec/rubyspec/language/defined_spec.rb @@ -0,0 +1,1130 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/defined', __FILE__) + +describe "The defined? keyword for literals" do + it "returns 'self' for self" do + ret = defined?(self) + ret.should == "self" + end + + it "returns 'nil' for nil" do + ret = defined?(nil) + ret.should == "nil" + end + + it "returns 'true' for true" do + ret = defined?(true) + ret.should == "true" + end + + it "returns 'false' for false" do + ret = defined?(false) + ret.should == "false" + end + + describe "for a literal Array" do + + it "returns 'expression' if each element is defined" do + ret = defined?([Object, Array]) + ret.should == "expression" + end + + it "returns nil if one element is not defined" do + ret = defined?([NonExistantConstant, Array]) + ret.should == nil + end + + it "returns nil if all elements are not defined" do + ret = defined?([NonExistantConstant, AnotherNonExistantConstant]) + ret.should == nil + end + + end +end + +describe "The defined? keyword when called with a method name" do + describe "without a receiver" do + it "returns 'method' if the method is defined" do + defined?(puts).should == "method" + end + + it "returns nil if the method is not defined" do + defined?(defined_specs_undefined_method).should be_nil + end + + it "returns 'method' if the method is defined and private" do + obj = DefinedSpecs::Basic.new + obj.private_method_defined.should == "method" + end + + it "returns 'method' if the predicate method is defined and private" do + obj = DefinedSpecs::Basic.new + obj.private_predicate_defined.should == "method" + end + end + + describe "having a module as receiver" do + it "returns 'method' if the method is defined" do + defined?(Kernel.puts).should == "method" + end + + it "returns nil if the method is private" do + defined?(Object.print).should be_nil + end + + it "returns nil if the method is protected" do + defined?(DefinedSpecs::Basic.new.protected_method).should be_nil + end + + it "returns nil if the method is not defined" do + defined?(Kernel.defined_specs_undefined_method).should be_nil + end + + it "returns nil if the class is not defined" do + defined?(DefinedSpecsUndefined.puts).should be_nil + end + + it "returns nil if the subclass is not defined" do + defined?(DefinedSpecs::Undefined.puts).should be_nil + end + end + + describe "having a local variable as receiver" do + it "returns 'method' if the method is defined" do + obj = DefinedSpecs::Basic.new + defined?(obj.a_defined_method).should == "method" + end + + it "returns nil if the method is not defined" do + obj = DefinedSpecs::Basic.new + defined?(obj.an_undefined_method).should be_nil + end + + it "returns nil if the variable does not exist" do + defined?(nonexistent_local_variable.some_method).should be_nil + end + + it "calls #respond_to_missing?" do + obj = mock("respond_to_missing object") + obj.should_receive(:respond_to_missing?).and_return(true) + defined?(obj.something_undefined).should == "method" + end + end + + describe "having an instance variable as receiver" do + it "returns 'method' if the method is defined" do + @defined_specs_obj = DefinedSpecs::Basic.new + defined?(@defined_specs_obj.a_defined_method).should == "method" + end + + it "returns nil if the method is not defined" do + @defined_specs_obj = DefinedSpecs::Basic.new + defined?(@defined_specs_obj.an_undefined_method).should be_nil + end + + it "returns nil if the variable does not exist" do + defined?(@nonexistent_instance_variable.some_method).should be_nil + end + end + + describe "having a global variable as receiver" do + it "returns 'method' if the method is defined" do + $defined_specs_obj = DefinedSpecs::Basic.new + defined?($defined_specs_obj.a_defined_method).should == "method" + end + + it "returns nil if the method is not defined" do + $defined_specs_obj = DefinedSpecs::Basic.new + defined?($defined_specs_obj.an_undefined_method).should be_nil + end + + it "returns nil if the variable does not exist" do + defined?($nonexistent_global_variable.some_method).should be_nil + end + end + + describe "having a method call as a receiver" do + it "returns nil if evaluating the receiver raises an exception" do + defined?(DefinedSpecs.exception_method / 2).should be_nil + ScratchPad.recorded.should == :defined_specs_exception + end + + it "returns nil if the method is not defined on the object the receiver returns" do + defined?(DefinedSpecs.side_effects / 2).should be_nil + ScratchPad.recorded.should == :defined_specs_side_effects + end + + it "returns 'method' if the method is defined on the object the receiver returns" do + defined?(DefinedSpecs.fixnum_method / 2).should == "method" + ScratchPad.recorded.should == :defined_specs_fixnum_method + end + end +end + +describe "The defined? keyword for an expression" do + before :each do + ScratchPad.clear + end + + it "returns 'assignment' for assigning a local variable" do + defined?(x = 2).should == "assignment" + end + + it "returns 'assignment' for assigning an instance variable" do + defined?(@defined_specs_x = 2).should == "assignment" + end + + it "returns 'assignment' for assigning a global variable" do + defined?($defined_specs_x = 2).should == "assignment" + end + + it "returns 'assignment' for assigning a class variable" do + defined?(@@defined_specs_x = 2).should == "assignment" + end + + it "returns 'assignment' for assigning multiple variables" do + defined?((a, b = 1, 2)).should == "assignment" + end + + it "returns 'assignment' for an expression with '%='" do + defined?(x %= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '/='" do + defined?(x /= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '-='" do + defined?(x -= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '+='" do + defined?(x += 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '*='" do + defined?(x *= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '|='" do + defined?(x |= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '&='" do + defined?(x &= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '^='" do + defined?(x ^= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '~='" do + defined?(x = 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '<<='" do + defined?(x <<= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '>>='" do + defined?(x >>= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '||='" do + defined?(x ||= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '&&='" do + defined?(x &&= 2).should == "assignment" + end + + it "returns 'assignment' for an expression with '**='" do + defined?(x **= 2).should == "assignment" + end + + it "returns nil for an expression with == and an undefined method" do + defined?(defined_specs_undefined_method == 2).should be_nil + end + + it "returns nil for an expression with != and an undefined method" do + defined?(defined_specs_undefined_method != 2).should be_nil + end + + it "returns nil for an expression with !~ and an undefined method" do + defined?(defined_specs_undefined_method !~ 2).should be_nil + end + + it "returns 'method' for an expression with '=='" do + x = 42 + defined?(x == 2).should == "method" + end + + it "returns 'method' for an expression with '!='" do + x = 42 + defined?(x != 2).should == "method" + end + + it "returns 'method' for an expression with '!~'" do + x = 42 + defined?(x !~ 2).should == "method" + end + + describe "with logical connectives" do + it "returns nil for an expression with '!' and an undefined method" do + defined?(!defined_specs_undefined_method).should be_nil + end + + it "returns nil for an expression with '!' and an unset class variable" do + -> { + @result = defined?(!@@defined_specs_undefined_class_variable) + }.should complain(/class variable access from toplevel/) + @result.should be_nil + end + + it "returns nil for an expression with 'not' and an undefined method" do + defined?(not defined_specs_undefined_method).should be_nil + end + + it "returns nil for an expression with 'not' and an unset class variable" do + -> { + @result = defined?(not @@defined_specs_undefined_class_variable) + }.should complain(/class variable access from toplevel/) + @result.should be_nil + end + + it "does not propagate an exception raised by a method in a 'not' expression" do + defined?(not DefinedSpecs.exception_method).should be_nil + ScratchPad.recorded.should == :defined_specs_exception + end + + it "returns 'expression' for an expression with '&&/and' and an unset global variable" do + defined?($defined_specs_undefined_global_variable && true).should == "expression" + defined?(true && $defined_specs_undefined_global_variable).should == "expression" + defined?($defined_specs_undefined_global_variable and true).should == "expression" + end + + it "returns 'expression' for an expression with '&&/and' and an unset instance variable" do + defined?(@defined_specs_undefined_instance_variable && true).should == "expression" + defined?(true && @defined_specs_undefined_instance_variable).should == "expression" + defined?(@defined_specs_undefined_instance_variable and true).should == "expression" + end + + it "returns 'expression' for an expression '&&/and' regardless of its truth value" do + defined?(true && false).should == "expression" + defined?(true and false).should == "expression" + end + + it "returns 'expression' for an expression with '||/or' and an unset global variable" do + defined?($defined_specs_undefined_global_variable || true).should == "expression" + defined?(true || $defined_specs_undefined_global_variable).should == "expression" + defined?($defined_specs_undefined_global_variable or true).should == "expression" + end + + it "returns 'expression' for an expression with '||/or' and an unset instance variable" do + defined?(@defined_specs_undefined_instance_variable || true).should == "expression" + defined?(true || @defined_specs_undefined_instance_variable).should == "expression" + defined?(@defined_specs_undefined_instance_variable or true).should == "expression" + end + + it "returns 'expression' for an expression '||/or' regardless of its truth value" do + defined?(true || false).should == "expression" + defined?(true or false).should == "expression" + end + + it "returns nil for an expression with '!' and an unset global variable" do + defined?(!$defined_specs_undefined_global_variable).should be_nil + end + + it "returns nil for an expression with '!' and an unset instance variable" do + defined?(!@defined_specs_undefined_instance_variable).should be_nil + end + + it "returns 'method' for a 'not' expression with a method" do + defined?(not DefinedSpecs.side_effects).should == "method" + end + + it "calls a method in a 'not' expression and returns 'method'" do + defined?(not DefinedSpecs.side_effects).should == "method" + ScratchPad.recorded.should == :defined_specs_side_effects + end + + it "returns nil for an expression with 'not' and an unset global variable" do + defined?(not $defined_specs_undefined_global_variable).should be_nil + end + + it "returns nil for an expression with 'not' and an unset instance variable" do + defined?(not @defined_specs_undefined_instance_variable).should be_nil + end + + it "returns 'expression' for an expression with '&&/and' and an undefined method" do + defined?(defined_specs_undefined_method && true).should == "expression" + defined?(defined_specs_undefined_method and true).should == "expression" + end + + it "returns 'expression' for an expression with '&&/and' and an unset class variable" do + defined?(@@defined_specs_undefined_class_variable && true).should == "expression" + defined?(@@defined_specs_undefined_class_variable and true).should == "expression" + end + + it "does not call a method in an '&&' expression and returns 'expression'" do + defined?(DefinedSpecs.side_effects && true).should == "expression" + ScratchPad.recorded.should be_nil + end + + it "does not call a method in an 'and' expression and returns 'expression'" do + defined?(DefinedSpecs.side_effects and true).should == "expression" + ScratchPad.recorded.should be_nil + end + + it "returns 'expression' for an expression with '||/or' and an undefined method" do + defined?(defined_specs_undefined_method || true).should == "expression" + defined?(defined_specs_undefined_method or true).should == "expression" + end + + it "returns 'expression' for an expression with '||/or' and an unset class variable" do + defined?(@@defined_specs_undefined_class_variable || true).should == "expression" + defined?(@@defined_specs_undefined_class_variable or true).should == "expression" + end + + it "does not call a method in an '||' expression and returns 'expression'" do + defined?(DefinedSpecs.side_effects || true).should == "expression" + ScratchPad.recorded.should be_nil + end + + it "does not call a method in an 'or' expression and returns 'expression'" do + defined?(DefinedSpecs.side_effects or true).should == "expression" + ScratchPad.recorded.should be_nil + end + end + + it "returns 'expression' when passed a String" do + defined?("garble gooble gable").should == "expression" + end + + describe "with a dynamic String" do + it "returns 'expression' when the String contains a literal" do + defined?("garble #{42}").should == "expression" + end + + it "returns 'expression' when the String contains a call to a defined method" do + defined?("garble #{DefinedSpecs.side_effects}").should == "expression" + end + + it "returns 'expression' when the String contains a call to an undefined method" do + defined?("garble #{DefinedSpecs.undefined_method}").should == "expression" + end + + it "does not call the method in the String" do + defined?("garble #{DefinedSpecs.dynamic_string}").should == "expression" + ScratchPad.recorded.should be_nil + end + end + + describe "with a dynamic Regexp" do + it "returns 'expression' when the Regexp contains a literal" do + defined?(/garble #{42}/).should == "expression" + end + + it "returns 'expression' when the Regexp contains a call to a defined method" do + defined?(/garble #{DefinedSpecs.side_effects}/).should == "expression" + end + + it "returns 'expression' when the Regexp contains a call to an undefined method" do + defined?(/garble #{DefinedSpecs.undefined_method}/).should == "expression" + end + + it "does not call the method in the Regexp" do + defined?(/garble #{DefinedSpecs.dynamic_string}/).should == "expression" + ScratchPad.recorded.should be_nil + end + end + + it "returns 'expression' when passed a Fixnum literal" do + defined?(42).should == "expression" + end + + it "returns 'expression' when passed a Bignum literal" do + defined?(0xdead_beef_deed_feed).should == "expression" + end + + it "returns 'expression' when passed a Float literal" do + defined?(1.5).should == "expression" + end + + it "returns 'expression' when passed a Range literal" do + defined?(0..2).should == "expression" + end + + it "returns 'expression' when passed a Regexp literal" do + defined?(/undefined/).should == "expression" + end + + it "returns 'expression' when passed an Array literal" do + defined?([1, 2]).should == "expression" + end + + it "returns 'expression' when passed a Hash literal" do + defined?({a: :b}).should == "expression" + end + + it "returns 'expression' when passed a Symbol literal" do + defined?(:defined_specs).should == "expression" + end +end + +describe "The defined? keyword for variables" do + it "returns 'local-variable' when called with the name of a local variable" do + DefinedSpecs::Basic.new.local_variable_defined.should == "local-variable" + end + + it "returns 'local-variable' when called with the name of a local variable assigned to nil" do + DefinedSpecs::Basic.new.local_variable_defined_nil.should == "local-variable" + end + + it "returns nil for an instance variable that has not been read" do + DefinedSpecs::Basic.new.instance_variable_undefined.should be_nil + end + + it "returns nil for an instance variable that has been read but not assigned to" do + DefinedSpecs::Basic.new.instance_variable_read.should be_nil + end + + it "returns 'instance-variable' for an instance variable that has been assigned" do + DefinedSpecs::Basic.new.instance_variable_defined.should == "instance-variable" + end + + it "returns 'instance-variable' for an instance variable that has been assigned to nil" do + DefinedSpecs::Basic.new.instance_variable_defined_nil.should == "instance-variable" + end + + it "returns nil for a global variable that has not been read" do + DefinedSpecs::Basic.new.global_variable_undefined.should be_nil + end + + it "returns nil for a global variable that has been read but not assigned to" do + DefinedSpecs::Basic.new.global_variable_read.should be_nil + end + + # MRI appears to special case defined? for $! and $~ in that it returns + # 'global-variable' even when they are not set (or they are always "set" + # but the value may be nil). In other words, 'defined?($~)' will return + # 'global-variable' even if no match has been done. + + it "returns 'global-variable' for $!" do + defined?($!).should == "global-variable" + end + + it "returns 'global-variable for $~" do + defined?($~).should == "global-variable" + end + + describe "when a String does not match a Regexp" do + before :each do + "mis-matched" =~ /z(z)z/ + end + + it "returns 'global-variable' for $~" do + defined?($~).should == "global-variable" + end + + it "returns nil for $&" do + defined?($&).should be_nil + end + + it "returns nil for $`" do + defined?($`).should be_nil + end + + it "returns nil for $'" do + defined?($').should be_nil + end + + it "returns nil for $+" do + defined?($+).should be_nil + end + + it "returns nil for $1-$9" do + defined?($1).should be_nil + defined?($2).should be_nil + defined?($3).should be_nil + defined?($4).should be_nil + defined?($5).should be_nil + defined?($6).should be_nil + defined?($7).should be_nil + defined?($8).should be_nil + defined?($9).should be_nil + end + end + + describe "when a String matches a Regexp" do + before :each do + "mis-matched" =~ /s(-)m(.)/ + end + + it "returns 'global-variable' for $~" do + defined?($~).should == "global-variable" + end + + it "returns 'global-variable' for $&" do + defined?($&).should == "global-variable" + end + + it "returns 'global-variable' for $`" do + defined?($`).should == "global-variable" + end + + it "returns 'global-variable' for $'" do + defined?($').should == "global-variable" + end + + it "returns 'global-variable' for $+" do + defined?($+).should == "global-variable" + end + + it "returns 'global-variable' for the capture references" do + defined?($1).should == "global-variable" + defined?($2).should == "global-variable" + end + + it "returns nil for non-captures" do + defined?($3).should be_nil + defined?($4).should be_nil + defined?($5).should be_nil + defined?($6).should be_nil + defined?($7).should be_nil + defined?($8).should be_nil + defined?($9).should be_nil + end + end + + describe "when a Regexp does not match a String" do + before :each do + /z(z)z/ =~ "mis-matched" + end + + it "returns 'global-variable' for $~" do + defined?($~).should == "global-variable" + end + + it "returns nil for $&" do + defined?($&).should be_nil + end + + it "returns nil for $`" do + defined?($`).should be_nil + end + + it "returns nil for $'" do + defined?($').should be_nil + end + + it "returns nil for $+" do + defined?($+).should be_nil + end + + it "returns nil for $1-$9" do + defined?($1).should be_nil + defined?($2).should be_nil + defined?($3).should be_nil + defined?($4).should be_nil + defined?($5).should be_nil + defined?($6).should be_nil + defined?($7).should be_nil + defined?($8).should be_nil + defined?($9).should be_nil + end + end + + describe "when a Regexp matches a String" do + before :each do + /s(-)m(.)/ =~ "mis-matched" + end + + it "returns 'global-variable' for $~" do + defined?($~).should == "global-variable" + end + + it "returns 'global-variable' for $&" do + defined?($&).should == "global-variable" + end + + it "returns 'global-variable' for $`" do + defined?($`).should == "global-variable" + end + + it "returns 'global-variable' for $'" do + defined?($').should == "global-variable" + end + + it "returns 'global-variable' for $+" do + defined?($+).should == "global-variable" + end + + it "returns 'global-variable' for the capture references" do + defined?($1).should == "global-variable" + defined?($2).should == "global-variable" + end + + it "returns nil for non-captures" do + defined?($3).should be_nil + defined?($4).should be_nil + defined?($5).should be_nil + defined?($6).should be_nil + defined?($7).should be_nil + defined?($8).should be_nil + defined?($9).should be_nil + end + end + it "returns 'global-variable' for a global variable that has been assigned" do + DefinedSpecs::Basic.new.global_variable_defined.should == "global-variable" + end + + it "returns nil for a class variable that has not been read" do + DefinedSpecs::Basic.new.class_variable_undefined.should be_nil + end + + # There is no spec for a class variable that is read before being assigned + # to because setting up the code for this raises a NameError before you + # get to the defined? call so it really has nothing to do with 'defined?'. + + it "returns 'class variable' when called with the name of a class variable" do + DefinedSpecs::Basic.new.class_variable_defined.should == "class variable" + end + + it "returns 'local-variable' when called with the name of a block local" do + block = Proc.new { |xxx| defined?(xxx) } + block.call(1).should == "local-variable" + end +end + +describe "The defined? keyword for a simple constant" do + it "returns 'constant' when the constant is defined" do + defined?(DefinedSpecs).should == "constant" + end + + it "returns nil when the constant is not defined" do + defined?(DefinedSpecsUndefined).should be_nil + end + + it "does not call Object.const_missing if the constant is not defined" do + Object.should_not_receive(:const_missing) + defined?(DefinedSpecsUndefined).should be_nil + end + + it "returns 'constant' for an included module" do + DefinedSpecs::Child.module_defined.should == "constant" + end + + it "returns 'constant' for a constant defined in an included module" do + DefinedSpecs::Child.module_constant_defined.should == "constant" + end +end + +describe "The defined? keyword for a top-level constant" do + it "returns 'constant' when passed the name of a top-level constant" do + defined?(::DefinedSpecs).should == "constant" + end + + it "retuns nil if the constant is not defined" do + defined?(::DefinedSpecsUndefined).should be_nil + end + + it "does not call Object.const_missing if the constant is not defined" do + Object.should_not_receive(:const_missing) + defined?(::DefinedSpecsUndefined).should be_nil + end +end + +describe "The defined? keyword for a scoped constant" do + it "returns 'constant' when the scoped constant is defined" do + defined?(DefinedSpecs::Basic).should == "constant" + end + + it "returns nil when the scoped constant is not defined" do + defined?(DefinedSpecs::Undefined).should be_nil + end + + it "does not call .const_missing if the constant is not defined" do + DefinedSpecs.should_not_receive(:const_missing) + defined?(DefinedSpecs::UnknownChild).should be_nil + end + + it "returns nil when an undefined constant is scoped to a defined constant" do + defined?(DefinedSpecs::Child::B).should be_nil + end + + it "returns nil when a constant is scoped to an undefined constant" do + Object.should_not_receive(:const_missing) + defined?(Undefined::Object).should be_nil + end + + it "returns nil when the undefined constant is scoped to an undefined constant" do + defined?(DefinedSpecs::Undefined::Undefined).should be_nil + end + + it "returns nil when a constant is defined on top-level but not on the module" do + defined?(DefinedSpecs::String).should be_nil + end + + it "returns 'constant' when a constant is defined on top-level but not on the class" do + defined?(DefinedSpecs::Basic::String).should == "constant" + end + + it "returns 'constant' if the scoped-scoped constant is defined" do + defined?(DefinedSpecs::Child::A).should == "constant" + end +end + +describe "The defined? keyword for a top-level scoped constant" do + it "returns 'constant' when the scoped constant is defined" do + defined?(::DefinedSpecs::Basic).should == "constant" + end + + it "returns nil when the scoped constant is not defined" do + defined?(::DefinedSpecs::Undefined).should be_nil + end + + it "returns nil when an undefined constant is scoped to a defined constant" do + defined?(::DefinedSpecs::Child::B).should be_nil + end + + it "returns nil when the undefined constant is scoped to an undefined constant" do + defined?(::DefinedSpecs::Undefined::Undefined).should be_nil + end + + it "returns 'constant' if the scoped-scoped constant is defined" do + defined?(::DefinedSpecs::Child::A).should == "constant" + end +end + +describe "The defined? keyword for a self-send method call scoped constant" do + it "returns nil if the constant is not defined in the scope of the method's value" do + defined?(defined_specs_method::Undefined).should be_nil + end + + it "returns 'constant' if the constant is defined in the scope of the method's value" do + defined?(defined_specs_method::Basic).should == "constant" + end + + it "returns nil if the last constant is not defined in the scope chain" do + defined?(defined_specs_method::Basic::Undefined).should be_nil + end + + it "returns nil if the middle constant is not defined in the scope chain" do + defined?(defined_specs_method::Undefined::Undefined).should be_nil + end + + it "returns 'constant' if all the constants in the scope chain are defined" do + defined?(defined_specs_method::Basic::A).should == "constant" + end +end + +describe "The defined? keyword for a receiver method call scoped constant" do + it "returns nil if the constant is not defined in the scope of the method's value" do + defined?(defined_specs_receiver.defined_method::Undefined).should be_nil + end + + it "returns 'constant' if the constant is defined in the scope of the method's value" do + defined?(defined_specs_receiver.defined_method::Basic).should == "constant" + end + + it "returns nil if the last constant is not defined in the scope chain" do + defined?(defined_specs_receiver.defined_method::Basic::Undefined).should be_nil + end + + it "returns nil if the middle constant is not defined in the scope chain" do + defined?(defined_specs_receiver.defined_method::Undefined::Undefined).should be_nil + end + + it "returns 'constant' if all the constants in the scope chain are defined" do + defined?(defined_specs_receiver.defined_method::Basic::A).should == "constant" + end +end + +describe "The defined? keyword for a module method call scoped constant" do + it "returns nil if the constant is not defined in the scope of the method's value" do + defined?(DefinedSpecs.defined_method::Undefined).should be_nil + end + + it "returns 'constant' if the constant scoped by the method's value is defined" do + defined?(DefinedSpecs.defined_method::Basic).should == "constant" + end + + it "returns nil if the last constant in the scope chain is not defined" do + defined?(DefinedSpecs.defined_method::Basic::Undefined).should be_nil + end + + it "returns nil if the middle constant in the scope chain is not defined" do + defined?(DefinedSpecs.defined_method::Undefined::Undefined).should be_nil + end + + it "returns 'constant' if all the constants in the scope chain are defined" do + defined?(DefinedSpecs.defined_method::Basic::A).should == "constant" + end + + it "returns nil if the outer scope constant in the receiver is not defined" do + defined?(Undefined::DefinedSpecs.defined_method::Basic).should be_nil + end + + it "returns nil if the scoped constant in the receiver is not defined" do + defined?(DefinedSpecs::Undefined.defined_method::Basic).should be_nil + end + + it "returns 'constant' if all the constants in the receiver are defined" do + defined?(Object::DefinedSpecs.defined_method::Basic).should == "constant" + end + + it "returns 'constant' if all the constants in the receiver and scope chain are defined" do + defined?(Object::DefinedSpecs.defined_method::Basic::A).should == "constant" + end +end + +describe "The defined? keyword for a variable scoped constant" do + after :all do + Object.__send__(:remove_class_variable, :@@defined_specs_obj) + end + + it "returns nil if the scoped constant is not defined" do + @defined_specs_obj = DefinedSpecs::Basic + defined?(@defined_specs_obj::Undefined).should be_nil + end + + it "returns 'constant' if the constant is defined in the scope of the variable reference" do + @defined_specs_obj = DefinedSpecs::Basic + defined?(@defined_specs_obj::A).should == "constant" + end + + it "returns nil if the scoped constant is not defined" do + $defined_specs_obj = DefinedSpecs::Basic + defined?($defined_specs_obj::Undefined).should be_nil + end + + it "returns 'constant' if the constant is defined in the scope of the variable reference" do + $defined_specs_obj = DefinedSpecs::Basic + defined?($defined_specs_obj::A).should == "constant" + end + + it "returns nil if the scoped constant is not defined" do + -> { + @@defined_specs_obj = DefinedSpecs::Basic + defined?(@@defined_specs_obj::Undefined).should be_nil + }.should complain(/class variable access from toplevel/) + end + + it "returns 'constant' if the constant is defined in the scope of the variable reference" do + -> { + @@defined_specs_obj = DefinedSpecs::Basic + defined?(@@defined_specs_obj::A).should == "constant" + }.should complain(/class variable access from toplevel/) + end + + it "returns nil if the scoped constant is not defined" do + defined_specs_obj = DefinedSpecs::Basic + defined?(defined_specs_obj::Undefined).should be_nil + end + + it "returns 'constant' if the constant is defined in the scope of the variable reference" do + defined_specs_obj = DefinedSpecs::Basic + defined?(defined_specs_obj::A).should == "constant" + end +end + +describe "The defined? keyword for a self:: scoped constant" do + it "returns 'constant' for a constant explicitly scoped to self:: when set" do + defined?(DefinedSpecs::SelfScoped).should == "constant" + end + + it "returns 'constant' for a constant explicitly scoped to self:: in subclass's metaclass" do + DefinedSpecs::Child.parent_constant_defined.should == "constant" + end +end + +describe "The defined? keyword for yield" do + it "returns nil if no block is passed to a method not taking a block parameter" do + DefinedSpecs::Basic.new.no_yield_block.should be_nil + end + + it "returns nil if no block is passed to a method taking a block parameter" do + DefinedSpecs::Basic.new.no_yield_block_parameter.should be_nil + end + + it "returns 'yield' if a block is passed to a method not taking a block parameter" do + DefinedSpecs::Basic.new.yield_block.should == "yield" + end + + it "returns 'yield' if a block is passed to a method taking a block parameter" do + DefinedSpecs::Basic.new.yield_block_parameter.should == "yield" + end +end + +describe "The defined? keyword for super" do + it "returns nil when a superclass undef's the method" do + DefinedSpecs::ClassWithoutMethod.new.test.should be_nil + end + + describe "for a method taking no arguments" do + it "returns nil when no superclass method exists" do + DefinedSpecs::Super.new.no_super_method_no_args.should be_nil + end + + it "returns nil from a block when no superclass method exists" do + DefinedSpecs::Super.new.no_super_method_block_no_args.should be_nil + end + + it "returns nil from a #define_method when no superclass method exists" do + DefinedSpecs::Super.new.no_super_define_method_no_args.should be_nil + end + + it "returns nil from a block in a #define_method when no superclass method exists" do + DefinedSpecs::Super.new.no_super_define_method_block_no_args.should be_nil + end + + it "returns 'super' when a superclass method exists" do + DefinedSpecs::Super.new.method_no_args.should == "super" + end + + it "returns 'super' from a block when a superclass method exists" do + DefinedSpecs::Super.new.method_block_no_args.should == "super" + end + + it "returns 'super' from a #define_method when a superclass method exists" do + DefinedSpecs::Super.new.define_method_no_args.should == "super" + end + + it "returns 'super' from a block in a #define_method when a superclass method exists" do + DefinedSpecs::Super.new.define_method_block_no_args.should == "super" + end + + it "returns 'super' when the method exists in a supermodule" do + DefinedSpecs::SuperWithIntermediateModules.new.method_no_args.should == "super" + end + end + + describe "for a method taking arguments" do + it "returns nil when no superclass method exists" do + DefinedSpecs::Super.new.no_super_method_args.should be_nil + end + + it "returns nil from a block when no superclass method exists" do + DefinedSpecs::Super.new.no_super_method_block_args.should be_nil + end + + it "returns nil from a #define_method when no superclass method exists" do + DefinedSpecs::Super.new.no_super_define_method_args.should be_nil + end + + it "returns nil from a block in a #define_method when no superclass method exists" do + DefinedSpecs::Super.new.no_super_define_method_block_args.should be_nil + end + + it "returns 'super' when a superclass method exists" do + DefinedSpecs::Super.new.method_args.should == "super" + end + + it "returns 'super' from a block when a superclass method exists" do + DefinedSpecs::Super.new.method_block_args.should == "super" + end + + it "returns 'super' from a #define_method when a superclass method exists" do + DefinedSpecs::Super.new.define_method_args.should == "super" + end + + it "returns 'super' from a block in a #define_method when a superclass method exists" do + DefinedSpecs::Super.new.define_method_block_args.should == "super" + end + end + + describe "within an included module's method" do + it "returns 'super' when a superclass method exists in the including hierarchy" do + DefinedSpecs::Child.new.defined_super.should == "super" + end + end +end + + +describe "The defined? keyword for instance variables" do + it "returns 'instance-variable' if assigned" do + @assigned_ivar = "some value" + defined?(@assigned_ivar).should == "instance-variable" + end + + it "returns nil if not assigned" do + defined?(@unassigned_ivar).should be_nil + end +end + +describe "The defined? keyword for pseudo-variables" do + it "returns 'expression' for __FILE__" do + defined?(__FILE__).should == "expression" + end + + it "returns 'expression' for __LINE__" do + defined?(__LINE__).should == "expression" + end + + it "returns 'expression' for __ENCODING__" do + defined?(__ENCODING__).should == "expression" + end +end + +describe "The defined? keyword for conditional expressions" do + it "returns 'expression' for an 'if' conditional" do + defined?(if x then 'x' else '' end).should == "expression" + end + + it "returns 'expression' for an 'unless' conditional" do + defined?(unless x then '' else 'x' end).should == "expression" + end + + it "returns 'expression' for ternary expressions" do + defined?(x ? 'x' : '').should == "expression" + end +end + +describe "The defined? keyword for case expressions" do + it "returns 'expression'" do + defined?(case x; when 'x'; 'y' end).should == "expression" + end +end + +describe "The defined? keyword for loop expressions" do + it "returns 'expression' for a 'for' expression" do + defined?(for n in 1..3 do true end).should == "expression" + end + + it "returns 'expression' for a 'while' expression" do + defined?(while x do y end).should == "expression" + end + + it "returns 'expression' for an 'until' expression" do + defined?(until x do y end).should == "expression" + end + + it "returns 'expression' for a 'break' expression" do + defined?(break).should == "expression" + end + + it "returns 'expression' for a 'next' expression" do + defined?(next).should == "expression" + end + + it "returns 'expression' for a 'redo' expression" do + defined?(redo).should == "expression" + end + + it "returns 'expression' for a 'retry' expression" do + defined?(retry).should == "expression" + end +end + +describe "The defined? keyword for return expressions" do + it "returns 'expression'" do + defined?(return).should == "expression" + end +end + +describe "The defined? keyword for exception expressions" do + it "returns 'expression'" do + defined?(begin 1 end).should == "expression" + end +end diff --git a/spec/rubyspec/language/encoding_spec.rb b/spec/rubyspec/language/encoding_spec.rb new file mode 100644 index 0000000000..070fa52bba --- /dev/null +++ b/spec/rubyspec/language/encoding_spec.rb @@ -0,0 +1,36 @@ +# -*- encoding: us-ascii -*- +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/coding_us_ascii', __FILE__) +require File.expand_path('../fixtures/coding_utf_8', __FILE__) + +describe "The __ENCODING__ pseudo-variable" do + it "is an instance of Encoding" do + __ENCODING__.should be_kind_of(Encoding) + end + + it "is US-ASCII by default" do + __ENCODING__.should == Encoding::US_ASCII + end + + it "is the evaluated strings's one inside an eval" do + eval("__ENCODING__".force_encoding("US-ASCII")).should == Encoding::US_ASCII + eval("__ENCODING__".force_encoding("ASCII-8BIT")).should == Encoding::ASCII_8BIT + end + + it "is the encoding specified by a magic comment inside an eval" do + code = "# encoding: ASCII-8BIT\n__ENCODING__".force_encoding("US-ASCII") + eval(code).should == Encoding::ASCII_8BIT + + code = "# encoding: us-ascii\n__ENCODING__".force_encoding("ASCII-8BIT") + eval(code).should == Encoding::US_ASCII + end + + it "is the encoding specified by a magic comment in the file" do + CodingUS_ASCII.encoding.should == Encoding::US_ASCII + CodingUTF_8.encoding.should == Encoding::UTF_8 + end + + it "raises a SyntaxError if assigned to" do + lambda { eval("__ENCODING__ = 1") }.should raise_error(SyntaxError) + end +end diff --git a/spec/rubyspec/language/ensure_spec.rb b/spec/rubyspec/language/ensure_spec.rb new file mode 100644 index 0000000000..13575fccc5 --- /dev/null +++ b/spec/rubyspec/language/ensure_spec.rb @@ -0,0 +1,126 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/ensure', __FILE__) + +describe "An ensure block inside a begin block" do + before :each do + ScratchPad.record [] + end + + it "is executed when an exception is raised in it's corresponding begin block" do + begin + lambda { + begin + ScratchPad << :begin + raise "An exception occured!" + ensure + ScratchPad << :ensure + end + }.should raise_error(RuntimeError) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + it "is executed when an exception is raised and rescued in it's corresponding begin block" do + begin + begin + ScratchPad << :begin + raise "An exception occured!" + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + + ScratchPad.recorded.should == [:begin, :rescue, :ensure] + end + end + + it "is executed even when a symbol is thrown in it's corresponding begin block" do + begin + catch(:symbol) do + begin + ScratchPad << :begin + throw(:symbol) + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + it "is executed when nothing is raised or thrown in it's corresponding begin block" do + begin + ScratchPad << :begin + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "has no return value" do + begin + :begin + ensure + :ensure + end.should == :begin + end +end + +describe "The value of an ensure expression," do + it "in no-exception scenarios, is the value of the last statement of the protected body" do + begin + v = 1 + eval('x=1') # to prevent opts from triggering + v + ensure + v = 2 + end.should == 1 + end + + it "when an exception is rescued, is the value of the rescuing block" do + begin + raise 'foo' + rescue + v = 3 + ensure + v = 2 + end.should == 3 + end +end + +describe "An ensure block inside a method" do + before :each do + @obj = EnsureSpec::Container.new + end + + it "is executed when an exception is raised in the method" do + lambda { @obj.raise_in_method_with_ensure }.should raise_error(RuntimeError) + @obj.executed.should == [:method, :ensure] + end + + it "is executed when an exception is raised and rescued in the method" do + @obj.raise_and_rescue_in_method_with_ensure + @obj.executed.should == [:method, :rescue, :ensure] + end + + it "is executed even when a symbol is thrown in the method" do + catch(:symbol) { @obj.throw_in_method_with_ensure } + @obj.executed.should == [:method, :ensure] + end + + it "has no impact on the method's implicit return value" do + @obj.implicit_return_in_method_with_ensure.should == :method + end + + it "has an impact on the method's explicit return value" do + @obj.explicit_return_in_method_with_ensure.should == :ensure + end +end diff --git a/spec/rubyspec/language/execution_spec.rb b/spec/rubyspec/language/execution_spec.rb new file mode 100644 index 0000000000..3e6e7ff48c --- /dev/null +++ b/spec/rubyspec/language/execution_spec.rb @@ -0,0 +1,15 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "``" do + it "returns the output of the executed sub-process" do + ip = 'world' + `echo disc #{ip}`.should == "disc world\n" + end +end + +describe "%x" do + it "is the same as ``" do + ip = 'world' + %x(echo disc #{ip}).should == "disc world\n" + end +end diff --git a/spec/rubyspec/language/file_spec.rb b/spec/rubyspec/language/file_spec.rb new file mode 100644 index 0000000000..409400ca83 --- /dev/null +++ b/spec/rubyspec/language/file_spec.rb @@ -0,0 +1,29 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/code_loading', __FILE__) +require File.expand_path('../shared/__FILE__', __FILE__) + +describe "The __FILE__ pseudo-variable" do + it "raises a SyntaxError if assigned to" do + lambda { eval("__FILE__ = 1") }.should raise_error(SyntaxError) + end + + it "equals (eval) inside an eval" do + eval("__FILE__").should == "(eval)" + end +end + +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :require, CodeLoadingSpecs::Method.new +end + +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :require, Kernel +end + +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :load, CodeLoadingSpecs::Method.new +end + +describe "The __FILE__ pseudo-variable" do + it_behaves_like :language___FILE__, :load, Kernel +end diff --git a/spec/rubyspec/language/fixtures/argv_encoding.rb b/spec/rubyspec/language/fixtures/argv_encoding.rb new file mode 100644 index 0000000000..8192b2d9a0 --- /dev/null +++ b/spec/rubyspec/language/fixtures/argv_encoding.rb @@ -0,0 +1 @@ +p ARGV.map { |a| a.encoding.name } diff --git a/spec/rubyspec/language/fixtures/array.rb b/spec/rubyspec/language/fixtures/array.rb new file mode 100644 index 0000000000..4d8ce74ed6 --- /dev/null +++ b/spec/rubyspec/language/fixtures/array.rb @@ -0,0 +1,11 @@ +module ArraySpec + class Splat + def unpack_3args(a, b, c) + [a, b, c] + end + + def unpack_4args(a, b, c, d) + [a, b, c, d] + end + end +end diff --git a/spec/rubyspec/language/fixtures/block.rb b/spec/rubyspec/language/fixtures/block.rb new file mode 100644 index 0000000000..9848d18776 --- /dev/null +++ b/spec/rubyspec/language/fixtures/block.rb @@ -0,0 +1,57 @@ +module BlockSpecs + class Yielder + def z + yield + end + + def m(*a) + yield(*a) + end + + def s(a) + yield(a) + end + + def r(a) + yield(*a) + end + end + + # TODO: rewrite all specs that use Yield to use Yielder + + class Yield + def splat(*args) + yield(*args) + end + + def two_args + yield 1, 2 + end + + def two_arg_array + yield [1, 2] + end + + def yield_splat_inside_block + [1, 2].send(:each_with_index) {|*args| yield(*args)} + end + + def yield_this(obj) + yield obj + end + end + + class OverwriteBlockVariable + def initialize + @y = Yielder.new + end + + def method_missing(method, *args, &block) + self.class.send :define_method, method do |*a, &b| + @y.send method, *a, &b + end + + send method, *args, &block + end + end +end diff --git a/spec/rubyspec/language/fixtures/break.rb b/spec/rubyspec/language/fixtures/break.rb new file mode 100644 index 0000000000..165d3395c3 --- /dev/null +++ b/spec/rubyspec/language/fixtures/break.rb @@ -0,0 +1,263 @@ +module BreakSpecs + class Driver + def initialize(ensures=false) + @ensures = ensures + end + + def note(value) + ScratchPad << value + end + end + + class Block < Driver + def break_nil + note :a + note yielding { + note :b + break + note :c + } + note :d + end + + def break_value + note :a + note yielding { + note :b + break :break + note :c + } + note :d + end + + def yielding + note :aa + note yield + note :bb + end + + def create_block + note :za + b = capture_block do + note :zb + break :break + note :zc + end + note :zd + b + end + + def capture_block(&b) + note :xa + b + end + + def break_in_method_captured + note :a + create_block.call + note :b + end + + def break_in_yield_captured + note :a + yielding(&create_block) + note :b + end + + def break_in_method + note :a + b = capture_block { + note :b + break :break + note :c + } + note :d + note b.call + note :e + end + + def call_method(b) + note :aa + note b.call + note :bb + end + + def break_in_nested_method + note :a + b = capture_block { + note :b + break :break + note :c + } + note :c + note call_method(b) + note :d + end + + def break_in_yielding_method + note :a + b = capture_block { + note :b + break :break + note :c + } + note :c + note yielding(&b) + note :d + end + + def method(v) + yield v + end + + def invoke_yield_in_while + looping = true + while looping + note :aa + yield + note :bb + looping = false + end + note :should_not_reach_here + end + + def break_in_block_in_while + invoke_yield_in_while do + note :break + break :value + note :c + end + end + end + + class Lambda < Driver + # Cases for the invocation of the scope defining the lambda still active + # on the call stack when the lambda is invoked. + def break_in_defining_scope(value=true) + note :a + note lambda { + note :b + if value + break :break + else + break + end + note :c + }.call + note :d + end + + def break_in_nested_scope + note :a + l = lambda do + note :b + break :break + note :c + end + note :d + + invoke_lambda l + + note :e + end + + def invoke_lambda(l) + note :aa + note l.call + note :bb + end + + def break_in_nested_scope_yield + note :a + l = lambda do + note :b + break :break + note :c + end + note :d + + invoke_yield(&l) + + note :e + end + + def note_invoke_yield + note :aa + note yield + note :bb + end + + def break_in_nested_scope_block + note :a + l = lambda do + note :b + break :break + note :c + end + note :d + + invoke_lambda_block l + + note :e + end + + def invoke_yield + note :aaa + yield + note :bbb + end + + def invoke_lambda_block(b) + note :aa + invoke_yield do + note :bb + + note b.call + + note :cc + end + note :dd + end + + # Cases for the invocation of the scope defining the lambda NOT still + # active on the call stack when the lambda is invoked. + def create_lambda + note :la + l = lambda do + note :lb + break :break + note :lc + end + note :ld + l + end + + def break_in_method + note :a + + note create_lambda.call + + note :b + end + + def break_in_block_in_method + note :a + invoke_yield do + note :b + + note create_lambda.call + + note :c + end + note :d + end + + def break_in_method_yield + note :a + + invoke_yield(&create_lambda) + + note :b + end + end +end diff --git a/spec/rubyspec/language/fixtures/break_lambda_toplevel.rb b/spec/rubyspec/language/fixtures/break_lambda_toplevel.rb new file mode 100644 index 0000000000..05af1d3fdc --- /dev/null +++ b/spec/rubyspec/language/fixtures/break_lambda_toplevel.rb @@ -0,0 +1,9 @@ +print "a," + +print lambda { + print "b," + break "break," + print "c," +}.call + +puts "d" diff --git a/spec/rubyspec/language/fixtures/break_lambda_toplevel_block.rb b/spec/rubyspec/language/fixtures/break_lambda_toplevel_block.rb new file mode 100644 index 0000000000..a35cb8a8a1 --- /dev/null +++ b/spec/rubyspec/language/fixtures/break_lambda_toplevel_block.rb @@ -0,0 +1,23 @@ +print "a," + +l = lambda { + print "b," + break "break," + print "c," +} + +def a(l) + print "d," + b { l.call } + print "e," +end + +def b + print "f," + print yield + print "g," +end + +a(l) + +puts "h" diff --git a/spec/rubyspec/language/fixtures/break_lambda_toplevel_method.rb b/spec/rubyspec/language/fixtures/break_lambda_toplevel_method.rb new file mode 100644 index 0000000000..200040d614 --- /dev/null +++ b/spec/rubyspec/language/fixtures/break_lambda_toplevel_method.rb @@ -0,0 +1,17 @@ +print "a," + +l = lambda { + print "b," + break "break," + print "c," +} + +def a(l) + print "d," + print l.call + print "e," +end + +a(l) + +puts "f" diff --git a/spec/rubyspec/language/fixtures/classes.rb b/spec/rubyspec/language/fixtures/classes.rb new file mode 100644 index 0000000000..eb239e1e29 --- /dev/null +++ b/spec/rubyspec/language/fixtures/classes.rb @@ -0,0 +1,31 @@ +module LanguageSpecs + # Regexp support + + def self.paired_delimiters + [%w[( )], %w[{ }], %w[< >], ["[", "]"]] + end + + def self.non_paired_delimiters + %w[~ ! # $ % ^ & * _ + ` - = " ' , . ? / | \\] + end + + def self.blanks + " \t" + end + + def self.white_spaces + return blanks + "\f\n\r\v" + end + + def self.non_alphanum_non_space + '~!@#$%^&*()+-\|{}[]:";\'<>?,./' + end + + def self.punctuations + ",.?" # TODO - Need to fill in the full list + end + + def self.get_regexp_with_substitution o + /#{o}/o + end +end diff --git a/spec/rubyspec/language/fixtures/coding_us_ascii.rb b/spec/rubyspec/language/fixtures/coding_us_ascii.rb new file mode 100644 index 0000000000..7df66109d7 --- /dev/null +++ b/spec/rubyspec/language/fixtures/coding_us_ascii.rb @@ -0,0 +1,11 @@ +# encoding: us-ascii + +module CodingUS_ASCII + def self.encoding + __ENCODING__ + end + + def self.string_literal + "string literal" + end +end diff --git a/spec/rubyspec/language/fixtures/coding_utf_8.rb b/spec/rubyspec/language/fixtures/coding_utf_8.rb new file mode 100644 index 0000000000..3d8e1d9a34 --- /dev/null +++ b/spec/rubyspec/language/fixtures/coding_utf_8.rb @@ -0,0 +1,11 @@ +# encoding: utf-8 + +module CodingUTF_8 + def self.encoding + __ENCODING__ + end + + def self.string_literal + "string literal" + end +end diff --git a/spec/rubyspec/language/fixtures/constant_visibility.rb b/spec/rubyspec/language/fixtures/constant_visibility.rb new file mode 100644 index 0000000000..022554430e --- /dev/null +++ b/spec/rubyspec/language/fixtures/constant_visibility.rb @@ -0,0 +1,98 @@ +module ConstantVisibility + module ModuleContainer + module PrivateModule + end + private_constant :PrivateModule + + class PrivateClass + end + private_constant :PrivateClass + end + + class ClassContainer + module PrivateModule + end + private_constant :PrivateModule + + class PrivateClass + end + private_constant :PrivateClass + end + + module PrivConstModule + PRIVATE_CONSTANT_MODULE = true + private_constant :PRIVATE_CONSTANT_MODULE + + def self.private_constant_from_self + PRIVATE_CONSTANT_MODULE + end + + def self.defined_from_self + defined? PRIVATE_CONSTANT_MODULE + end + + module Nested + def self.private_constant_from_scope + PRIVATE_CONSTANT_MODULE + end + + def self.defined_from_scope + defined? PRIVATE_CONSTANT_MODULE + end + end + end + + class PrivConstClass + PRIVATE_CONSTANT_CLASS = true + private_constant :PRIVATE_CONSTANT_CLASS + + def self.private_constant_from_self + PRIVATE_CONSTANT_CLASS + end + + def self.defined_from_self + defined? PRIVATE_CONSTANT_CLASS + end + + module Nested + def self.private_constant_from_scope + PRIVATE_CONSTANT_CLASS + end + + def self.defined_from_scope + defined? PRIVATE_CONSTANT_CLASS + end + end + end + + class PrivConstModuleChild + include PrivConstModule + + def private_constant_from_include + PRIVATE_CONSTANT_MODULE + end + + def defined_from_include + defined? PRIVATE_CONSTANT_MODULE + end + end + + class PrivConstClassChild < PrivConstClass + def private_constant_from_subclass + PRIVATE_CONSTANT_CLASS + end + + def defined_from_subclass + defined? PRIVATE_CONSTANT_CLASS + end + end + + def self.reset_private_constants + Object.send :private_constant, :PRIVATE_CONSTANT_IN_OBJECT + end +end + +class Object + PRIVATE_CONSTANT_IN_OBJECT = true + private_constant :PRIVATE_CONSTANT_IN_OBJECT +end diff --git a/spec/rubyspec/language/fixtures/constants_sclass.rb b/spec/rubyspec/language/fixtures/constants_sclass.rb new file mode 100644 index 0000000000..21dc4081e2 --- /dev/null +++ b/spec/rubyspec/language/fixtures/constants_sclass.rb @@ -0,0 +1,54 @@ +module ConstantSpecs + + CS_SINGLETON1 = Object.new + class << CS_SINGLETON1 + CONST = 1 + def foo + CONST + end + end + + CS_SINGLETON2 = [Object.new, Object.new] + 2.times do |i| + obj = CS_SINGLETON2[i] + $spec_i = i + class << obj + CONST = ($spec_i + 1) + def foo + CONST + end + end + end + + CS_SINGLETON3 = [Object.new, Object.new] + 2.times do |i| + obj = CS_SINGLETON3[i] + class << obj + class X + # creates + end + + def x + X + end + end + end + + CS_SINGLETON4 = [Object.new, Object.new] + CS_SINGLETON4_CLASSES = [] + 2.times do |i| + obj = CS_SINGLETON4[i] + $spec_i = i + class << obj + class X + CS_SINGLETON4_CLASSES << self + CONST = ($spec_i + 1) + + def foo + CONST + end + end + end + end + +end diff --git a/spec/rubyspec/language/fixtures/def.rb b/spec/rubyspec/language/fixtures/def.rb new file mode 100644 index 0000000000..81bfce73d0 --- /dev/null +++ b/spec/rubyspec/language/fixtures/def.rb @@ -0,0 +1,8 @@ +def some_toplevel_method +end + +public +def public_toplevel_method +end + +private diff --git a/spec/rubyspec/language/fixtures/defined.rb b/spec/rubyspec/language/fixtures/defined.rb new file mode 100644 index 0000000000..ce330b486e --- /dev/null +++ b/spec/rubyspec/language/fixtures/defined.rb @@ -0,0 +1,296 @@ +module DefinedSpecs + self::SelfScoped = 42 + + def self.side_effects + ScratchPad.record :defined_specs_side_effects + end + + def self.fixnum_method + ScratchPad.record :defined_specs_fixnum_method + 42 + end + + def self.exception_method + ScratchPad.record :defined_specs_exception + raise "defined? specs exception method" + end + + def self.defined_method + DefinedSpecs + end + + class Basic + A = 42 + + def defined_method + DefinedSpecs + end + + def a_defined_method + end + + def protected_method + end + protected :protected_method + + def private_method + end + private :private_method + + def private_method_defined + defined? private_method + end + + def private_predicate? + end + private :private_predicate? + + def private_predicate_defined + defined? private_predicate? + end + + def local_variable_defined + x = 2 + defined? x + end + + def local_variable_defined_nil + x = nil + defined? x + end + + def instance_variable_undefined + defined? @instance_variable_undefined + end + + def instance_variable_read + value = @instance_variable_read + defined? @instance_variable_read + end + + def instance_variable_defined + @instance_variable_defined = 1 + defined? @instance_variable_defined + end + + def instance_variable_defined_nil + @instance_variable_defined_nil = nil + defined? @instance_variable_defined_nil + end + + def global_variable_undefined + defined? $defined_specs_global_variable_undefined + end + + def global_variable_read + value = $defined_specs_global_variable_read + defined? $defined_specs_global_variable_read + end + + def global_variable_defined + $defined_specs_global_variable_defined = 1 + defined? $defined_specs_global_variable_defined + end + + def class_variable_undefined + defined? @@class_variable_undefined + end + + def class_variable_defined + @@class_variable_defined = 1 + defined? @@class_variable_defined + end + + def yield_defined_method + defined? yield + end + + def yield_defined_parameter_method(&block) + defined? yield + end + + def no_yield_block + yield_defined_method + end + + def no_yield_block_parameter + yield_defined_parameter_method + end + + def yield_block + yield_defined_method { 42 } + end + + def yield_block_parameter + yield_defined_parameter_method { 42 } + end + end + + module Mixin + MixinConstant = 42 + + def defined_super + defined? super() + end + end + + class Parent + ParentConstant = 42 + + def defined_super; end + end + + class Child < Parent + include Mixin + + A = 42 + + def self.parent_constant_defined + defined? self::ParentConstant + end + + def self.module_defined + defined? Mixin + end + + def self.module_constant_defined + defined? MixinConstant + end + + def defined_super + super + end + end + + class Superclass + def yield_method + yield + end + + def method_no_args + end + + def method_args + end + + def method_block_no_args + end + + def method_block_args + end + + def define_method_no_args + end + + def define_method_args + end + + def define_method_block_no_args + end + + def define_method_block_args + end + end + + class Super < Superclass + def no_super_method_no_args + defined? super + end + + def no_super_method_args + defined? super() + end + + def method_no_args + defined? super + end + + def method_args + defined? super() + end + + def no_super_method_block_no_args + yield_method { defined? super } + end + + def no_super_method_block_args + yield_method { defined? super() } + end + + def method_block_no_args + yield_method { defined? super } + end + + def method_block_args + yield_method { defined? super() } + end + + define_method(:no_super_define_method_no_args) { defined? super } + define_method(:no_super_define_method_args) { defined? super() } + define_method(:define_method_no_args) { defined? super } + define_method(:define_method_args) { defined? super() } + + define_method(:no_super_define_method_block_no_args) do + yield_method { defined? super } + end + + define_method(:no_super_define_method_block_args) do + yield_method { defined? super() } + end + + define_method(:define_method_block_no_args) do + yield_method { defined? super } + end + + define_method(:define_method_block_args) do + yield_method { defined? super() } + end + end + + class ClassWithMethod + def test + end + end + + class ClassUndefiningMethod < ClassWithMethod + undef :test + end + + class ClassWithoutMethod < ClassUndefiningMethod + # If an undefined method overridden in descendants + # define?(super) should return nil + def test + defined?(super) + end + end + + module IntermediateModule1 + def method_no_args + end + end + + module IntermediateModule2 + def method_no_args + defined?(super) + end + end + + class SuperWithIntermediateModules + include IntermediateModule1 + include IntermediateModule2 + + def method_no_args + super + end + end +end + +class Object + def defined_specs_method + DefinedSpecs + end + + def defined_specs_receiver + DefinedSpecs::Basic.new + end +end diff --git a/spec/rubyspec/language/fixtures/dollar_zero.rb b/spec/rubyspec/language/fixtures/dollar_zero.rb new file mode 100644 index 0000000000..683bce8d4e --- /dev/null +++ b/spec/rubyspec/language/fixtures/dollar_zero.rb @@ -0,0 +1,6 @@ +puts $0 +puts __FILE__ + +if $0 == __FILE__ + print "OK" +end diff --git a/spec/rubyspec/language/fixtures/ensure.rb b/spec/rubyspec/language/fixtures/ensure.rb new file mode 100644 index 0000000000..0dad7d8401 --- /dev/null +++ b/spec/rubyspec/language/fixtures/ensure.rb @@ -0,0 +1,72 @@ +module EnsureSpec + class Container + attr_reader :executed + + def initialize + @executed = [] + end + + def raise_in_method_with_ensure + @executed << :method + raise "An Exception" + ensure + @executed << :ensure + end + + def raise_and_rescue_in_method_with_ensure + @executed << :method + raise "An Exception" + rescue + @executed << :rescue + ensure + @executed << :ensure + end + + def throw_in_method_with_ensure + @executed << :method + throw(:symbol) + ensure + @executed << :ensure + end + + def implicit_return_in_method_with_ensure + :method + ensure + :ensure + end + + def explicit_return_in_method_with_ensure + return :method + ensure + return :ensure + end + end +end + +module EnsureSpec + + class Test + + def initialize + @values = [] + end + + attr_reader :values + + def call_block + begin + @values << :start + yield + ensure + @values << :end + end + end + + def do_test + call_block do + @values << :in_block + return :did_test + end + end + end +end diff --git a/spec/rubyspec/language/fixtures/file.rb b/spec/rubyspec/language/fixtures/file.rb new file mode 100644 index 0000000000..7b862cfe1a --- /dev/null +++ b/spec/rubyspec/language/fixtures/file.rb @@ -0,0 +1 @@ +ScratchPad.record __FILE__ diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files.rb new file mode 100644 index 0000000000..3aed2f29b6 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative 'freeze_magic_comment_required' + +p "abc".object_id == $second_literal_id diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb new file mode 100644 index 0000000000..53ef959970 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative 'freeze_magic_comment_required_diff_enc' + +p "abc".object_id != $second_literal_id diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_no_comment.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_no_comment.rb new file mode 100644 index 0000000000..fc6cd5bf82 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_across_files_no_comment.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative 'freeze_magic_comment_required_no_comment' + +p "abc".object_id != $second_literal_id diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_one_literal.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_one_literal.rb new file mode 100644 index 0000000000..d35905b332 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_one_literal.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +ids = Array.new(2) { "abc".object_id } +p ids.first == ids.last diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_required.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_required.rb new file mode 100644 index 0000000000..a4ff4459b1 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_required.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +$second_literal_id = "abc".object_id diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_required_diff_enc.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_required_diff_enc.rb new file mode 100644 index 0000000000..d0558a2251 Binary files /dev/null and b/spec/rubyspec/language/fixtures/freeze_magic_comment_required_diff_enc.rb differ diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_required_no_comment.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_required_no_comment.rb new file mode 100644 index 0000000000..e09232a5f4 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_required_no_comment.rb @@ -0,0 +1 @@ +$second_literal_id = "abc".object_id diff --git a/spec/rubyspec/language/fixtures/freeze_magic_comment_two_literals.rb b/spec/rubyspec/language/fixtures/freeze_magic_comment_two_literals.rb new file mode 100644 index 0000000000..a4d655ad02 --- /dev/null +++ b/spec/rubyspec/language/fixtures/freeze_magic_comment_two_literals.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +p "abc".object_id == "abc".object_id diff --git a/spec/rubyspec/language/fixtures/hash_strings_ascii8bit.rb b/spec/rubyspec/language/fixtures/hash_strings_ascii8bit.rb new file mode 100644 index 0000000000..4ac11b9930 --- /dev/null +++ b/spec/rubyspec/language/fixtures/hash_strings_ascii8bit.rb @@ -0,0 +1,7 @@ +# encoding: ascii-8bit + +module HashStringsASCII8BIT + def self.literal_hash + {"foo" => "bar"} + end +end diff --git a/spec/rubyspec/language/fixtures/hash_strings_usascii.rb b/spec/rubyspec/language/fixtures/hash_strings_usascii.rb new file mode 100644 index 0000000000..18cfef7c8c --- /dev/null +++ b/spec/rubyspec/language/fixtures/hash_strings_usascii.rb @@ -0,0 +1,7 @@ +# encoding: us-ascii + +module HashStringsUSASCII + def self.literal_hash + {"foo" => "bar"} + end +end diff --git a/spec/rubyspec/language/fixtures/hash_strings_utf8.rb b/spec/rubyspec/language/fixtures/hash_strings_utf8.rb new file mode 100644 index 0000000000..7928090282 --- /dev/null +++ b/spec/rubyspec/language/fixtures/hash_strings_utf8.rb @@ -0,0 +1,7 @@ +# encoding: utf-8 + +module HashStringsUTF8 + def self.literal_hash + {"foo" => "bar"} + end +end diff --git a/spec/rubyspec/language/fixtures/match_operators.rb b/spec/rubyspec/language/fixtures/match_operators.rb new file mode 100644 index 0000000000..f04c54d723 --- /dev/null +++ b/spec/rubyspec/language/fixtures/match_operators.rb @@ -0,0 +1,9 @@ +class OperatorImplementor + def =~(val) + return val + end + + def !~(val) + return val + end +end diff --git a/spec/rubyspec/language/fixtures/metaclass.rb b/spec/rubyspec/language/fixtures/metaclass.rb new file mode 100644 index 0000000000..a1990b9225 --- /dev/null +++ b/spec/rubyspec/language/fixtures/metaclass.rb @@ -0,0 +1,34 @@ +module MetaClassSpecs + + def self.metaclass_of obj + class << obj + self + end + end + + class A + def self.cheese + 'edam' + end + end + + class B < A + def self.cheese + 'stilton' + end + end + + class C + class << self + class << self + def ham + 'iberico' + end + end + end + end + + class D < C; end + +end + diff --git a/spec/rubyspec/language/fixtures/module.rb b/spec/rubyspec/language/fixtures/module.rb new file mode 100644 index 0000000000..33d323846e --- /dev/null +++ b/spec/rubyspec/language/fixtures/module.rb @@ -0,0 +1,24 @@ +module ModuleSpecs + module Modules + class Klass + end + + A = "Module" + B = 1 + C = nil + D = true + E = false + end + + module Anonymous + end + + module IncludedInObject + module IncludedModuleSpecs + end + end +end + +class Object + include ModuleSpecs::IncludedInObject +end diff --git a/spec/rubyspec/language/fixtures/next.rb b/spec/rubyspec/language/fixtures/next.rb new file mode 100644 index 0000000000..fbca842334 --- /dev/null +++ b/spec/rubyspec/language/fixtures/next.rb @@ -0,0 +1,134 @@ +class NextSpecs + def self.yielding_method(expected) + yield.should == expected + :method_return_value + end + + def self.yielding + yield + end + + # The methods below are defined to spec the behavior of the next statement + # while specifically isolating whether the statement is in an Iter block or + # not. In a normal spec example, the code is always nested inside a block. + # Rather than rely on that implicit context in this case, the context is + # made explicit because of the interaction of next in a loop nested inside + # an Iter block. + def self.while_next(arg) + x = true + while x + begin + ScratchPad << :begin + x = false + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + + def self.while_within_iter(arg) + yielding do + x = true + while x + begin + ScratchPad << :begin + x = false + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + end + + def self.until_next(arg) + x = false + until x + begin + ScratchPad << :begin + x = true + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + + def self.until_within_iter(arg) + yielding do + x = false + until x + begin + ScratchPad << :begin + x = true + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + end + + def self.loop_next(arg) + x = 1 + loop do + break if x == 2 + + begin + ScratchPad << :begin + x += 1 + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + + def self.loop_within_iter(arg) + yielding do + x = 1 + loop do + break if x == 2 + + begin + ScratchPad << :begin + x += 1 + if arg + next 42 + else + next + end + ensure + ScratchPad << :ensure + end + end + end + end + + class Block + def method(v) + yield v + end + end +end diff --git a/spec/rubyspec/language/fixtures/precedence.rb b/spec/rubyspec/language/fixtures/precedence.rb new file mode 100644 index 0000000000..d2295c755b --- /dev/null +++ b/spec/rubyspec/language/fixtures/precedence.rb @@ -0,0 +1,16 @@ +module PrecedenceSpecs + class NonUnaryOpTest + def add_num(arg) + [1].collect { |i| arg + i +1 } + end + def sub_num(arg) + [1].collect { |i| arg + i -1 } + end + def add_str + %w[1].collect { |i| i +'1' } + end + def add_var + [1].collect { |i| i +i } + end + end +end diff --git a/spec/rubyspec/language/fixtures/private.rb b/spec/rubyspec/language/fixtures/private.rb new file mode 100644 index 0000000000..96f73cea3f --- /dev/null +++ b/spec/rubyspec/language/fixtures/private.rb @@ -0,0 +1,59 @@ +module Private + class A + def foo + "foo" + end + + private + def bar + "bar" + end + end + + class B + def foo + "foo" + end + + private + + def self.public_defs_method; 0; end + + class C + def baz + "baz" + end + end + + class << self + def public_class_method1; 1; end + private + def private_class_method1; 1; end + end + + def bar + "bar" + end + end + + module D + private + def foo + "foo" + end + end + + class E + include D + end + + class G + def foo + "foo" + end + end + + class H < A + private :foo + end +end diff --git a/spec/rubyspec/language/fixtures/rescue.rb b/spec/rubyspec/language/fixtures/rescue.rb new file mode 100644 index 0000000000..3fa5df1eb5 --- /dev/null +++ b/spec/rubyspec/language/fixtures/rescue.rb @@ -0,0 +1,63 @@ +module RescueSpecs + def self.begin_else(raise_exception) + begin + ScratchPad << :one + raise "an error occurred" if raise_exception + rescue + ScratchPad << :rescue_ran + :rescue_val + else + ScratchPad << :else_ran + :val + end + end + + def self.begin_else_ensure(raise_exception) + begin + ScratchPad << :one + raise "an error occurred" if raise_exception + rescue + ScratchPad << :rescue_ran + :rescue_val + else + ScratchPad << :else_ran + :val + ensure + ScratchPad << :ensure_ran + :ensure_val + end + end + + def self.begin_else_return(raise_exception) + begin + ScratchPad << :one + raise "an error occurred" if raise_exception + rescue + ScratchPad << :rescue_ran + :rescue_val + else + ScratchPad << :else_ran + :val + end + ScratchPad << :outside_begin + :return_val + end + + def self.begin_else_return_ensure(raise_exception) + begin + ScratchPad << :one + raise "an error occurred" if raise_exception + rescue + ScratchPad << :rescue_ran + :rescue_val + else + ScratchPad << :else_ran + :val + ensure + ScratchPad << :ensure_ran + :ensure_val + end + ScratchPad << :outside_begin + :return_val + end +end diff --git a/spec/rubyspec/language/fixtures/return.rb b/spec/rubyspec/language/fixtures/return.rb new file mode 100644 index 0000000000..0414c356e8 --- /dev/null +++ b/spec/rubyspec/language/fixtures/return.rb @@ -0,0 +1,139 @@ +module ReturnSpecs + class Blocks + def yielding_method + yield + ScratchPad.record :after_yield + end + + def enclosing_method + yielding_method do + ScratchPad.record :before_return + return :return_value + ScratchPad.record :after_return + end + + ScratchPad.record :after_call + end + end + + class NestedCalls < Blocks + def invoking_method(&b) + yielding_method(&b) + ScratchPad.record :after_invoke + end + + def enclosing_method + invoking_method do + ScratchPad.record :before_return + return :return_value + ScratchPad.record :after_return + end + ScratchPad.record :after_invoke + end + end + + class NestedBlocks < Blocks + def enclosing_method + yielding_method do + yielding_method do + ScratchPad.record :before_return + return :return_value + ScratchPad.record :after_return + end + ScratchPad.record :after_invoke1 + end + ScratchPad.record :after_invoke2 + end + end + + class SavedInnerBlock + def add(&b) + @block = b + end + + def outer + yield + @block.call + end + + def inner + yield + end + + def start + outer do + inner do + add do + ScratchPad.record :before_return + return :return_value + end + end + end + + ScratchPad.record :bottom_of_start + + return false + end + end + + class ThroughDefineMethod + lamb = proc { |x| x.call } + define_method :foo, lamb + + def mp(&b); b; end + + def outer + pr = mp { return :good } + + foo(pr) + return :bad + end + end + + class DefineMethod + lamb = proc { return :good } + define_method :foo, lamb + + def outer + val = :bad + + # This is tricky, but works. If lamb properly returns, then the + # return value will go into val before we run the ensure. + # + # If lamb's return keeps unwinding incorrectly, val will still + # have it's old value. + # + # We can therefore use val to figure out what happened. + begin + val = foo() + ensure + if val != :good + return :bad + end + end + + return val + end + end + + class MethodWithBlock + def method1 + return [2, 3].inject 0 do |a, b| + a + b + end + nil + end + + def get_ary(count, &blk) + count.times.to_a do |i| + blk.call(i) if blk + end + end + + def method2 + return get_ary 3 do |i| + end + nil + end + end +end diff --git a/spec/rubyspec/language/fixtures/send.rb b/spec/rubyspec/language/fixtures/send.rb new file mode 100644 index 0000000000..c3013616b2 --- /dev/null +++ b/spec/rubyspec/language/fixtures/send.rb @@ -0,0 +1,140 @@ +module LangSendSpecs + module_function + + def fooM0; 100 end + def fooM1(a); [a]; end + def fooM2(a,b); [a,b]; end + def fooM3(a,b,c); [a,b,c]; end + def fooM4(a,b,c,d); [a,b,c,d]; end + def fooM5(a,b,c,d,e); [a,b,c,d,e]; end + def fooM0O1(a=1); [a]; end + def fooM1O1(a,b=1); [a,b]; end + def fooM2O1(a,b,c=1); [a,b,c]; end + def fooM3O1(a,b,c,d=1); [a,b,c,d]; end + def fooM4O1(a,b,c,d,e=1); [a,b,c,d,e]; end + def fooM0O2(a=1,b=2); [a,b]; end + def fooM0R(*r); r; end + def fooM1R(a, *r); [a, r]; end + def fooM0O1R(a=1, *r); [a, r]; end + def fooM1O1R(a, b=1, *r); [a, b, r]; end + + def one(a); a; end + def oneb(a,&b); [a,yield(b)]; end + def twob(a,b,&c); [a,b,yield(c)]; end + def makeproc(&b) b end + + def yield_now; yield; end + + def double(x); x * 2 end + def weird_parens + # means double((5).to_s) + # NOT (double(5)).to_s + double (5).to_s + end + + def rest_len(*a); a.size; end + + def self.twos(a,b,*c) + [c.size, c.last] + end + + class PrivateSetter + attr_reader :foo + attr_writer :foo + private :foo= + + def call_self_foo_equals(value) + self.foo = value + end + + def call_self_foo_equals_masgn(value) + a, self.foo = 1, value + end + end + + class PrivateGetter + attr_reader :foo + private :foo + + def call_self_foo + self.foo + end + + def call_self_foo_or_equals(value) + self.foo ||= 6 + end + end + + class AttrSet + attr_reader :result + def []=(a, b, c, d); @result = [a,b,c,d]; end + end + + class ToProc + def initialize(val) + @val = val + end + + def to_proc + Proc.new { @val } + end + end + + class ToAry + def initialize(obj) + @obj = obj + end + + def to_ary + @obj + end + end + + class MethodMissing + def initialize + @message = nil + @args = nil + end + + attr_reader :message, :args + + def method_missing(m, *a) + @message = m + @args = a + end + end + + class Attr19Set + attr_reader :result + def []=(*args); @result = args; end + end + + module_function + + def fooR(*r); r; end + def fooM0RQ1(*r, q); [r, q]; end + def fooM0RQ2(*r, s, q); [r, s, q]; end + def fooM1RQ1(a, *r, q); [a, r, q]; end + def fooM1O1RQ1(a, b=9, *r, q); [a, b, r, q]; end + def fooM1O1RQ2(a, b=9, *r, q, t); [a, b, r, q, t]; end + + def fooO1Q1(a=1, b); [a,b]; end + def fooM1O1Q1(a,b=2,c); [a,b,c]; end + def fooM2O1Q1(a,b,c=3,d); [a,b,c,d]; end + def fooM2O2Q1(a,b,c=3,d=4,e); [a,b,c,d,e]; end + def fooO4Q1(a=1,b=2,c=3,d=4,e); [a,b,c,d,e]; end + def fooO4Q2(a=1,b=2,c=3,d=4,e,f); [a,b,c,d,e,f]; end + + def destructure2((a,b)); a+b; end + def destructure2b((a,b)); [a,b]; end + def destructure4r((a,b,*c,d,e)); [a,b,c,d,e]; end + def destructure4o(a=1,(b,c),d,&e); [a,b,c,d]; end + def destructure5o(a=1, f=2, (b,c),d,&e); [a,f,b,c,d]; end + def destructure7o(a=1, f=2, (b,c),(d,e), &g); [a,f,b,c,d,e]; end + def destructure7b(a=1, f=2, (b,c),(d,e), &g); g.call([a,f,b,c,d,e]); end + def destructure4os(a=1,(b,*c)); [a,b,c]; end +end + +def lang_send_rest_len(*a) + a.size +end diff --git a/spec/rubyspec/language/fixtures/squiggly_heredoc.rb b/spec/rubyspec/language/fixtures/squiggly_heredoc.rb new file mode 100644 index 0000000000..afc87514c7 --- /dev/null +++ b/spec/rubyspec/language/fixtures/squiggly_heredoc.rb @@ -0,0 +1,39 @@ +module SquigglyHeredocSpecs + def self.message + <<~HEREDOC + character density, n.: + The number of very weird people in the office. + HEREDOC + end + + def self.blank + <<~HERE + HERE + end + + def self.unquoted + <<~HERE + unquoted #{"interpolated"} + HERE + end + + def self.doublequoted + <<~"HERE" + doublequoted #{"interpolated"} + HERE + end + + def self.singlequoted + <<~'HERE' + singlequoted #{"interpolated"} + HERE + end + + def self.least_indented_on_the_last_line + <<~HERE + a + b + c + HERE + end +end diff --git a/spec/rubyspec/language/fixtures/super.rb b/spec/rubyspec/language/fixtures/super.rb new file mode 100644 index 0000000000..c7d47cc465 --- /dev/null +++ b/spec/rubyspec/language/fixtures/super.rb @@ -0,0 +1,569 @@ +module Super + module S1 + class A + def foo(a) + a << "A#foo" + bar(a) + end + def bar(a) + a << "A#bar" + end + end + class B < A + def foo(a) + a << "B#foo" + super(a) + end + def bar(a) + a << "B#bar" + super(a) + end + end + end + + module S2 + class A + def baz(a) + a << "A#baz" + end + end + class B < A + def foo(a) + a << "B#foo" + baz(a) + end + end + class C < B + def baz(a) + a << "C#baz" + super(a) + end + end + end + + module S3 + class A + def foo(a) + a << "A#foo" + end + def self.foo(a) + a << "A.foo" + end + def self.bar(a) + a << "A.bar" + foo(a) + end + end + class B < A + def self.foo(a) + a << "B.foo" + super(a) + end + def self.bar(a) + a << "B.bar" + super(a) + end + end + end + + module S4 + class A + def foo(a) + a << "A#foo" + end + end + class B < A + def foo(a, b) + a << "B#foo(a,#{b})" + super(a) + end + end + end + + class S5 + def here + :good + end + end + + class S6 < S5 + def under + yield + end + + def here + under { + super + } + end + end + + class S7 < S5 + define_method(:here) { super() } + end + + module MS1 + module ModA + def foo(a) + a << "ModA#foo" + bar(a) + end + def bar(a) + a << "ModA#bar" + end + end + class A + include ModA + end + module ModB + def bar(a) + a << "ModB#bar" + super(a) + end + end + class B < A + def foo(a) + a << "B#foo" + super(a) + end + include ModB + end + end + + module MS2 + class A + def baz(a) + a << "A#baz" + end + end + module ModB + def foo(a) + a << "ModB#foo" + baz(a) + end + end + class B < A + include ModB + end + class C < B + def baz(a) + a << "C#baz" + super(a) + end + end + end + + module MultiSuperTargets + module M + def foo + super + end + end + + class BaseA + def foo + :BaseA + end + end + + class BaseB + def foo + :BaseB + end + end + + class A < BaseA + include M + end + + class B < BaseB + include M + end + end + + module MS3 + module ModA + def foo(a) + a << "ModA#foo" + end + def bar(a) + a << "ModA#bar" + foo(a) + end + end + class A + def foo(a) + a << "A#foo" + end + class << self + include ModA + end + end + class B < A + def self.foo(a) + a << "B.foo" + super(a) + end + def self.bar(a) + a << "B.bar" + super(a) + end + end + end + + module MS4 + module Layer1 + def example + 5 + end + end + + module Layer2 + include Layer1 + def example + super + end + end + + class A + include Layer2 + public :example + end + end + + class MM_A + undef_method :is_a? + end + + class MM_B < MM_A + def is_a?(blah) + # should fire the method_missing below + super + end + + def method_missing(*) + false + end + end + + class Alias1 + def name + [:alias1] + end + end + + class Alias2 < Alias1 + def initialize + @times = 0 + end + + def name + if @times >= 10 + raise "runaway super" + end + + @times += 1 + + # Use this so that we can see collect all supers that we see. + # One bug that arises is that we call Alias2#name from Alias2#name + # as it's superclass. In that case, either we get a runaway recursion + # super OR we get the return value being [:alias2, :alias2, :alias1] + # rather than [:alias2, :alias1]. + # + # Which one depends on caches and how super is implemented. + [:alias2] + super + end + end + + class Alias3 < Alias2 + alias_method :name3, :name + # In the method table for Alias3 now should be a special alias entry + # that references Alias2 and Alias2#name (probably as an object). + # + # When name3 is called then, Alias2 (NOT Alias3) is presented as the + # current module to Alias2#name, so that when super is called, + # Alias2->superclass is next. + # + # Otherwise, Alias2 is next, which is where name was to begin with, + # causing the wrong #name method to be called. + end + + module AliasWithSuper + module AS1 + def foo + :a + end + end + + module BS1 + def foo + [:b, super] + end + end + + class Base + extend AS1 + extend BS1 + end + + class Trigger < Base + class << self + def foo_quux + foo_baz + end + + alias_method :foo_baz, :foo + alias_method :foo, :foo_quux + end + end + end + + module RestArgsWithSuper + class A + def a(*args) + args + end + end + + class B < A + def a(*args) + args << "foo" + + super + end + end + end + + class AnonymousModuleIncludedTwiceBase + def self.whatever + mod = Module.new do + def a(array) + array << "anon" + super + end + end + + include mod + end + + def a(array) + array << "non-anon" + end + end + + class AnonymousModuleIncludedTwice < AnonymousModuleIncludedTwiceBase + whatever + whatever + end + + module ZSuperWithBlock + class A + def a + yield + end + + def b(&block) + block.call + end + + def c + yield + end + end + + class B < A + def a + super { 14 } + end + + def b + block_ref = lambda { 15 } + [super { 14 }, super(&block_ref)] + end + + def c + block_ref = lambda { 16 } + super(&block_ref) + end + end + end + + module ZSuperWithOptional + class A + def m(x, y, z) + z + end + end + + class B < A + def m(x, y, z = 14) + super + end + end + + class C < A + def m(x, y, z = 14) + z = 100 + super + end + end + end + + module ZSuperWithRest + class A + def m(*args) + args + end + + def m_modified(*args) + args + end + end + + class B < A + def m(*args) + super + end + + def m_modified(*args) + args[1] = 14 + super + end + end + end + + module ZSuperWithRestAndOthers + class A + def m(a, b, *args) + args + end + + def m_modified(a, b, *args) + args + end + end + + class B < A + def m(a, b, *args) + super + end + + def m_modified(a, b, *args) + args[1] = 14 + super + end + end + end + + module ZSuperWithUnderscores + class A + def m(*args) + args + end + + def m_modified(*args) + args + end + end + + class B < A + def m(_, _) + super + end + + def m_modified(_, _) + _ = 14 + super + end + end + end + + module KeywordArguments + class A + def foo(**args) + args + end + end + + class B < A + def foo(**) + super + end + end + + class C < A + end + end + + module FromBasicObject + def __send__(name, *args, &block) + super + end + end + + module IntermediateBasic + include FromBasicObject + end + + class IncludesFromBasic + include FromBasicObject + + def foobar; 43; end + end + + class IncludesIntermediate + include IntermediateBasic + + def foobar; 42; end + end + + module SingletonCase + class Base + def foobar(array) + array << :base + end + end + + class Foo < Base + def foobar(array) + array << :foo + super + end + end + end + + module SingletonAliasCase + class Base + def foobar(array) + array << :base + end + + def alias_on_singleton + object = self + singleton = (class << object; self; end) + singleton.__send__(:alias_method, :new_foobar, :foobar) + end + end + + class Foo < Base + def foobar(array) + array << :foo + super + end + end + end + + module SplatAndKeyword + class A + def foo(*args, **options) + [args, options] + end + end + + class B < A + def foo(*args, **options) + super + end + end + end +end diff --git a/spec/rubyspec/language/fixtures/variables.rb b/spec/rubyspec/language/fixtures/variables.rb new file mode 100644 index 0000000000..07265dbb2b --- /dev/null +++ b/spec/rubyspec/language/fixtures/variables.rb @@ -0,0 +1,85 @@ +module VariablesSpecs + class ParAsgn + attr_accessor :x + + def initialize + @x = 0 + end + + def inc + @x += 1 + end + + def to_ary + [1,2,3,4] + end + end + + class OpAsgn + attr_accessor :a, :b, :side_effect + + def do_side_effect + self.side_effect = true + return @a + end + + def do_more_side_effects + @a += 5 + self + end + + def do_bool_side_effects + @b += 1 + self + end + end + + class Hashalike + def [](k) k end + def []=(k, v) [k, v] end + end + + def self.reverse_foo(a, b) + return b, a + end + + class ArrayLike + def initialize(array) + @array = array + end + + def to_a + @array + end + end + + class ArraySubclass < Array + end + + class PrivateMethods + private + + def to_ary + [1, 2] + end + + def to_a + [3, 4] + end + end + + class ToAryNil + def to_ary + end + end + + class Chain + def self.without_parenthesis a + a + end + end + + def self.false + false + end +end diff --git a/spec/rubyspec/language/fixtures/yield.rb b/spec/rubyspec/language/fixtures/yield.rb new file mode 100644 index 0000000000..a195616640 --- /dev/null +++ b/spec/rubyspec/language/fixtures/yield.rb @@ -0,0 +1,37 @@ +module YieldSpecs + class Yielder + def z + yield + end + + def ze(&block) + block = proc { block } + yield + end + + def s(a) + yield(a) + end + + def m(a, b, c) + yield(a, b, c) + end + + def r(a) + yield(*a) + end + + def rs(a, b, c) + yield(a, b, *c) + end + + def self.define_deep(&inned_block) + define_method 'deep' do |v| + # should yield to inner_block + yield v + end + end + + define_deep { |v| v * 2} + end +end diff --git a/spec/rubyspec/language/for_spec.rb b/spec/rubyspec/language/for_spec.rb new file mode 100644 index 0000000000..a043da5476 --- /dev/null +++ b/spec/rubyspec/language/for_spec.rb @@ -0,0 +1,171 @@ +require File.expand_path('../../spec_helper', __FILE__) + +# for name[, name]... in expr [do] +# body +# end +describe "The for expression" do + it "iterates over an Enumerable passing each element to the block" do + j = 0 + for i in 1..3 + j += i + end + j.should == 6 + end + + it "iterates over an Hash passing each key-value pair to the block" do + k = 0 + l = 0 + + for i, j in { 1 => 10, 2 => 20 } + k += i + l += j + end + + k.should == 3 + l.should == 30 + end + + it "iterates over any object responding to 'each'" do + class XYZ + def each + (0..10).each { |i| yield i } + end + end + + j = 0 + for i in XYZ.new + j += i + end + j.should == 55 + end + + it "allows an instance variable as an iterator name" do + m = [1,2,3] + n = 0 + for @var in m + n += 1 + end + @var.should == 3 + n.should == 3 + end + + it "allows a class variable as an iterator name" do + class OFor + m = [1,2,3] + n = 0 + for @@var in m + n += 1 + end + @@var.should == 3 + n.should == 3 + end + end + + it "allows a constant as an iterator name" do + class OFor + m = [1,2,3] + n = 0 + -> { + for CONST in m + n += 1 + end + }.should complain(/already initialized constant/) + CONST.should == 3 + n.should == 3 + end + end + + # 1.9 behaviour verified by nobu in + # http://redmine.ruby-lang.org/issues/show/2053 + it "yields only as many values as there are arguments" do + class OFor + def each + [[1,2,3], [4,5,6]].each do |a| + yield(a[0],a[1],a[2]) + end + end + end + o = OFor.new + qs = [] + for q in o + qs << q + end + qs.should == [1, 4] + q.should == 4 + end + + it "optionally takes a 'do' after the expression" do + j = 0 + for i in 1..3 do + j += i + end + j.should == 6 + end + + it "allows body begin on the same line if do is used" do + j = 0 + for i in 1..3 do j += i + end + j.should == 6 + end + + it "executes code in containing variable scope" do + for i in 1..2 + a = 123 + end + + a.should == 123 + end + + it "executes code in containing variable scope with 'do'" do + for i in 1..2 do + a = 123 + end + + a.should == 123 + end + + it "returns expr" do + for i in 1..3; end.should == (1..3) + for i,j in { 1 => 10, 2 => 20 }; end.should == { 1 => 10, 2 => 20 } + end + + it "breaks out of a loop upon 'break', returning nil" do + j = 0 + for i in 1..3 + j += i + + break if i == 2 + end.should == nil + + j.should == 3 + end + + it "allows 'break' to have an argument which becomes the value of the for expression" do + for i in 1..3 + break 10 if i == 2 + end.should == 10 + end + + it "starts the next iteration with 'next'" do + j = 0 + for i in 1..5 + next if i == 2 + + j += i + end + + j.should == 13 + end + + it "repeats current iteration with 'redo'" do + j = 0 + for i in 1..3 + j += i + + redo if i == 2 && j < 4 + end + + j.should == 8 + end +end diff --git a/spec/rubyspec/language/hash_spec.rb b/spec/rubyspec/language/hash_spec.rb new file mode 100644 index 0000000000..edd9d4fbb2 --- /dev/null +++ b/spec/rubyspec/language/hash_spec.rb @@ -0,0 +1,154 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/hash_strings_ascii8bit', __FILE__) +require File.expand_path('../fixtures/hash_strings_utf8', __FILE__) +require File.expand_path('../fixtures/hash_strings_usascii', __FILE__) + +describe "Hash literal" do + it "{} should return an empty hash" do + {}.size.should == 0 + {}.should == {} + end + + it "{} should return a new hash populated with the given elements" do + h = {a: 'a', 'b' => 3, 44 => 2.3} + h.size.should == 3 + h.should == {a: "a", "b" => 3, 44 => 2.3} + end + + it "treats empty expressions as nils" do + h = {() => ()} + h.keys.should == [nil] + h.values.should == [nil] + h[nil].should == nil + + h = {() => :value} + h.keys.should == [nil] + h.values.should == [:value] + h[nil].should == :value + + h = {key: ()} + h.keys.should == [:key] + h.values.should == [nil] + h[:key].should == nil + end + + it "freezes string keys on initialization" do + key = "foo" + h = {key => "bar"} + key.reverse! + h["foo"].should == "bar" + h.keys.first.should == "foo" + h.keys.first.frozen?.should == true + key.should == "oof" + end + + it "checks duplicated keys on initialization" do + -> { + @h = eval "{foo: :bar, foo: :foo}" + }.should complain(/key :foo is duplicated|duplicated key/) + @h.keys.size.should == 1 + @h.should == {foo: :foo} + end + + it "accepts a hanging comma" do + h = {a: 1, b: 2,} + h.size.should == 2 + h.should == {a: 1, b: 2} + end + + it "recognizes '=' at the end of the key" do + eval("{:a==>1}").should == {:"a=" => 1} + eval("{:a= =>1}").should == {:"a=" => 1} + eval("{:a= => 1}").should == {:"a=" => 1} + end + + it "with '==>' in the middle raises SyntaxError" do + lambda { eval("{:a ==> 1}") }.should raise_error(SyntaxError) + end + + it "constructs a new hash with the given elements" do + {foo: 123}.should == {foo: 123} + h = {rbx: :cool, specs: 'fail_sometimes'} + {rbx: :cool, specs: 'fail_sometimes'}.should == h + end + + it "ignores a hanging comma" do + {foo: 123,}.should == {foo: 123} + h = {rbx: :cool, specs: 'fail_sometimes'} + {rbx: :cool, specs: 'fail_sometimes',}.should == h + end + + it "accepts mixed 'key: value' and 'key => value' syntax" do + h = {:a => 1, :b => 2, "c" => 3} + {a: 1, b: 2, "c" => 3}.should == h + end + + it "accepts mixed 'key: value', 'key => value' and '\"key\"': value' syntax" do + h = {:a => 1, :b => 2, "c" => 3, :d => 4} + eval('{a: 1, :b => 2, "c" => 3, "d": 4}').should == h + end + + it "expands an '**{}' element into the containing Hash literal initialization" do + {a: 1, **{b: 2}, c: 3}.should == {a: 1, b: 2, c: 3} + end + + it "expands an '**obj' element into the containing Hash literal initialization" do + h = {b: 2, c: 3} + {**h, a: 1}.should == {b: 2, c: 3, a: 1} + {a: 1, **h}.should == {a: 1, b: 2, c: 3} + {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} + end + + it "expands a BasicObject using ** into the containing Hash literal initialization" do + h = BasicObject.new + def h.to_hash; {:b => 2, :c => 3}; end + {**h, a: 1}.should == {b: 2, c: 3, a: 1} + {a: 1, **h}.should == {a: 1, b: 2, c: 3} + {a: 1, **h, c: 4}.should == {a: 1, b: 2, c: 4} + end + + it "expands an '**{}' element with the last key/value pair taking precedence" do + -> { + @h = eval "{a: 1, **{a: 2, b: 3, c: 1}, c: 3}" + }.should complain(/key :a is duplicated|duplicated key/) + @h.should == {a: 2, b: 3, c: 3} + end + + it "merges multiple nested '**obj' in Hash literals" do + -> { + @h = eval "{a: 1, **{a: 2, **{b: 3, **{c: 4}}, **{d: 5}, }, **{d: 6}}" + }.should complain(/key :a is duplicated|duplicated key/) + @h.should == {a: 2, b: 3, c: 4, d: 6} + end + + it "calls #to_hash to expand an '**obj' element" do + obj = mock("hash splat") + obj.should_receive(:to_hash).and_return({b: 2, d: 4}) + + {a: 1, **obj, c: 3}.should == {a:1, b: 2, c: 3, d: 4} + end + + it "raises a TypeError if any splatted elements keys are not symbols" do + h = {1 => 2, b: 3} + lambda { {a: 1, **h} }.should raise_error(TypeError) + end + + it "raises a TypeError if #to_hash does not return a Hash" do + obj = mock("hash splat") + obj.should_receive(:to_hash).and_return(obj) + + lambda { {**obj} }.should raise_error(TypeError) + end + + it "does not change encoding of literal string keys during creation" do + ascii8bit_hash = HashStringsASCII8BIT.literal_hash + utf8_hash = HashStringsUTF8.literal_hash + usascii_hash = HashStringsUSASCII.literal_hash + + ascii8bit_hash.keys.first.encoding.should == Encoding::ASCII_8BIT + ascii8bit_hash.keys.first.should == utf8_hash.keys.first + utf8_hash.keys.first.encoding.should == Encoding::UTF_8 + utf8_hash.keys.first.should == usascii_hash.keys.first + usascii_hash.keys.first.encoding.should == Encoding::US_ASCII + end +end diff --git a/spec/rubyspec/language/heredoc_spec.rb b/spec/rubyspec/language/heredoc_spec.rb new file mode 100644 index 0000000000..436fcb9c9e --- /dev/null +++ b/spec/rubyspec/language/heredoc_spec.rb @@ -0,0 +1,87 @@ +# -*- encoding: us-ascii -*- + +require File.expand_path('../../spec_helper', __FILE__) + +describe "Heredoc string" do + + before :each do + @ip = 'xxx' # used for interpolation + end + + it "allows HEREDOC with < () { }" do + SpecEvaluate.desc = "for definition" + + it "returns a Proc object when used in a BasicObject method" do + klass = Class.new(BasicObject) do + def create_lambda + -> () { } + end + end + + klass.new.create_lambda.should be_an_instance_of(Proc) + end + + it "does not execute the block" do + ->() { fail }.should be_an_instance_of(Proc) + end + + it "returns a lambda" do + -> () { }.lambda?.should be_true + end + + it "has its own scope for local variables" do + l = -> arg { + var = arg + # this would override var if it was declared outside the lambda + l.call(arg-1) if arg > 0 + var + } + l.call(1).should == 1 + end + + context "assigns no local variables" do + evaluate <<-ruby do + @a = -> { } + @b = ->() { } + @c = -> () { } + @d = -> do end + ruby + + @a.().should be_nil + @b.().should be_nil + @c.().should be_nil + @d.().should be_nil + end + end + + context "assigns variables from parameters" do + evaluate <<-ruby do + @a = -> (a) { a } + ruby + + @a.(1).should == 1 + end + + evaluate <<-ruby do + @a = -> ((a)) { a } + ruby + + @a.(1).should == 1 + @a.([1, 2, 3]).should == 1 + end + + evaluate <<-ruby do + @a = -> ((*a, b)) { [a, b] } + ruby + + @a.(1).should == [[], 1] + @a.([1, 2, 3]).should == [[1, 2], 3] + end + + evaluate <<-ruby do + @a = -> (a={}) { a } + ruby + + @a.().should == {} + @a.(2).should == 2 + end + + evaluate <<-ruby do + @a = -> (*) { } + ruby + + @a.().should be_nil + @a.(1).should be_nil + @a.(1, 2, 3).should be_nil + end + + evaluate <<-ruby do + @a = -> (*a) { a } + ruby + + @a.().should == [] + @a.(1).should == [1] + @a.(1, 2, 3).should == [1, 2, 3] + end + + evaluate <<-ruby do + @a = -> (a:) { a } + ruby + + lambda { @a.() }.should raise_error(ArgumentError) + @a.(a: 1).should == 1 + end + + evaluate <<-ruby do + @a = -> (a: 1) { a } + ruby + + @a.().should == 1 + @a.(a: 2).should == 2 + end + + evaluate <<-ruby do + @a = -> (**) { } + ruby + + @a.().should be_nil + @a.(a: 1, b: 2).should be_nil + lambda { @a.(1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + @a = -> (**k) { k } + ruby + + @a.().should == {} + @a.(a: 1, b: 2).should == {a: 1, b: 2} + end + + evaluate <<-ruby do + @a = -> (&b) { b } + ruby + + @a.().should be_nil + @a.() { }.should be_an_instance_of(Proc) + end + + evaluate <<-ruby do + @a = -> (a, b) { [a, b] } + ruby + + @a.(1, 2).should == [1, 2] + lambda { @a.() }.should raise_error(ArgumentError) + lambda { @a.(1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + @a = -> ((a, b, *c, d), (*e, f, g), (*h)) do + [a, b, c, d, e, f, g, h] + end + ruby + + @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]] + result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10]) + result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]] + end + + evaluate <<-ruby do + @a = -> (a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))) do + [a, b, c, d, e, f, g, h, i, j] + end + ruby + + @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil] + result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]]) + result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] + end + + evaluate <<-ruby do + @a = -> (*, **k) { k } + ruby + + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + @a.(h).should == {a: 1} + end + + evaluate <<-ruby do + @a = -> (*, &b) { b } + ruby + + @a.().should be_nil + @a.(1, 2, 3, 4).should be_nil + @a.(&(l = ->{})).should equal(l) + end + + evaluate <<-ruby do + @a = -> (a:, b:) { [a, b] } + ruby + + @a.(a: 1, b: 2).should == [1, 2] + end + + evaluate <<-ruby do + @a = -> (a:, b: 1) { [a, b] } + ruby + + @a.(a: 1).should == [1, 1] + @a.(a: 1, b: 2).should == [1, 2] + end + + evaluate <<-ruby do + @a = -> (a: 1, b:) { [a, b] } + ruby + + @a.(b: 0).should == [1, 0] + @a.(b: 2, a: 3).should == [3, 2] + end + + evaluate <<-ruby do + @a = -> (a: @a = -> (a: 1) { a }, b:) do + [a, b] + end + ruby + + @a.(a: 2, b: 3).should == [2, 3] + @a.(b: 1).should == [@a, 1] + + # Note the default value of a: in the original method. + @a.().should == 1 + end + + evaluate <<-ruby do + @a = -> (a: 1, b: 2) { [a, b] } + ruby + + @a.().should == [1, 2] + @a.(b: 3, a: 4).should == [4, 3] + end + + evaluate <<-ruby do + @a = -> (a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) do + [a, b, c, d, e, f, g, h, k, l] + end + ruby + + result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{})) + result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l] + end + + evaluate <<-ruby do + @a = -> a, b=1, *c, d, e:, f: 2, g:, **k, &l do + [a, b, c, d, e, f, g, k, l] + end + ruby + + result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) + result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] + end + + describe "with circular optional argument reference" do + it "shadows an existing local with the same name as the argument" do + a = 1 + -> { + @proc = eval "-> (a=a) { a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "shadows an existing method with the same name as the argument" do + def a; 1; end + -> { + @proc = eval "-> (a=a) { a }" + }.should complain(/circular argument reference/) + @proc.call.should == nil + end + + it "calls an existing method with the same name as the argument if explicitly using ()" do + def a; 1; end + -> (a=a()) { a }.call.should == 1 + end + end + end +end + +describe "A lambda expression 'lambda { ... }'" do + SpecEvaluate.desc = "for definition" + + it "calls the #lambda method" do + obj = mock("lambda definition") + obj.should_receive(:lambda).and_return(obj) + + def obj.define + lambda { } + end + + obj.define.should equal(obj) + end + + it "does not execute the block" do + lambda { fail }.should be_an_instance_of(Proc) + end + + it "returns a lambda" do + lambda { }.lambda?.should be_true + end + + it "requires a block" do + lambda { lambda }.should raise_error(ArgumentError) + end + + context "with an implicit block" do + before do + def meth; lambda; end + end + + it "can be created" do + implicit_lambda = nil + -> { + implicit_lambda = meth { 1 } + }.should complain(/tried to create Proc object without a block/) + + implicit_lambda.lambda?.should be_true + implicit_lambda.call.should == 1 + end + end + + context "assigns no local variables" do + evaluate <<-ruby do + @a = lambda { } + @b = lambda { || } + ruby + + @a.().should be_nil + @b.().should be_nil + end + end + + context "assigns variables from parameters" do + evaluate <<-ruby do + @a = lambda { |a| a } + ruby + + @a.(1).should == 1 + end + + evaluate <<-ruby do + def m(*a) yield(*a) end + @a = lambda { |a| a } + ruby + + lambda { m(&@a) }.should raise_error(ArgumentError) + lambda { m(1, 2, &@a) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + @a = lambda { |a, | a } + ruby + + @a.(1).should == 1 + @a.([1, 2]).should == [1, 2] + + lambda { @a.() }.should raise_error(ArgumentError) + lambda { @a.(1, 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a) yield a end + def m2() yield end + + @a = lambda { |a, | a } + ruby + + m(1, &@a).should == 1 + m([1, 2], &@a).should == [1, 2] + + lambda { m2(&@a) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + @a = lambda { |(a)| a } + ruby + + @a.(1).should == 1 + @a.([1, 2, 3]).should == 1 + end + + evaluate <<-ruby do + @a = lambda { |(*a, b)| [a, b] } + ruby + + @a.(1).should == [[], 1] + @a.([1, 2, 3]).should == [[1, 2], 3] + end + + evaluate <<-ruby do + @a = lambda { |a={}| a } + ruby + + @a.().should == {} + @a.(2).should == 2 + end + + evaluate <<-ruby do + @a = lambda { |*| } + ruby + + @a.().should be_nil + @a.(1).should be_nil + @a.(1, 2, 3).should be_nil + end + + evaluate <<-ruby do + @a = lambda { |*a| a } + ruby + + @a.().should == [] + @a.(1).should == [1] + @a.(1, 2, 3).should == [1, 2, 3] + end + + evaluate <<-ruby do + @a = lambda { |a:| a } + ruby + + lambda { @a.() }.should raise_error(ArgumentError) + @a.(a: 1).should == 1 + end + + evaluate <<-ruby do + @a = lambda { |a: 1| a } + ruby + + @a.().should == 1 + @a.(a: 2).should == 2 + end + + evaluate <<-ruby do + @a = lambda { |**| } + ruby + + @a.().should be_nil + @a.(a: 1, b: 2).should be_nil + lambda { @a.(1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + @a = lambda { |**k| k } + ruby + + @a.().should == {} + @a.(a: 1, b: 2).should == {a: 1, b: 2} + end + + evaluate <<-ruby do + @a = lambda { |&b| b } + ruby + + @a.().should be_nil + @a.() { }.should be_an_instance_of(Proc) + end + + evaluate <<-ruby do + @a = lambda { |a, b| [a, b] } + ruby + + @a.(1, 2).should == [1, 2] + end + + evaluate <<-ruby do + @a = lambda do |(a, b, *c, d), (*e, f, g), (*h)| + [a, b, c, d, e, f, g, h] + end + ruby + + @a.(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]] + result = @a.([1, 2, 3], [4, 5, 6, 7, 8], [9, 10]) + result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]] + end + + evaluate <<-ruby do + @a = lambda do |a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))| + [a, b, c, d, e, f, g, h, i, j] + end + ruby + + @a.(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil] + result = @a.(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]]) + result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] + end + + evaluate <<-ruby do + @a = lambda { |*, **k| k } + ruby + + @a.().should == {} + @a.(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + @a.(h).should == {a: 1} + end + + evaluate <<-ruby do + @a = lambda { |*, &b| b } + ruby + + @a.().should be_nil + @a.(1, 2, 3, 4).should be_nil + @a.(&(l = ->{})).should equal(l) + end + + evaluate <<-ruby do + @a = lambda { |a:, b:| [a, b] } + ruby + + @a.(a: 1, b: 2).should == [1, 2] + end + + evaluate <<-ruby do + @a = lambda { |a:, b: 1| [a, b] } + ruby + + @a.(a: 1).should == [1, 1] + @a.(a: 1, b: 2).should == [1, 2] + end + + evaluate <<-ruby do + @a = lambda { |a: 1, b:| [a, b] } + ruby + + @a.(b: 0).should == [1, 0] + @a.(b: 2, a: 3).should == [3, 2] + end + + evaluate <<-ruby do + @a = lambda do |a: (@a = -> (a: 1) { a }), b:| + [a, b] + end + ruby + + @a.(a: 2, b: 3).should == [2, 3] + @a.(b: 1).should == [@a, 1] + + # Note the default value of a: in the original method. + @a.().should == 1 + end + + evaluate <<-ruby do + @a = lambda { |a: 1, b: 2| [a, b] } + ruby + + @a.().should == [1, 2] + @a.(b: 3, a: 4).should == [4, 3] + end + + evaluate <<-ruby do + @a = lambda do |a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l| + [a, b, c, d, e, f, g, h, k, l] + end + ruby + + result = @a.(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{})) + result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l] + end + + evaluate <<-ruby do + @a = lambda do |a, b=1, *c, d, e:, f: 2, g:, **k, &l| + [a, b, c, d, e, f, g, k, l] + end + ruby + + result = @a.(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) + result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] + end + end +end diff --git a/spec/rubyspec/language/line_spec.rb b/spec/rubyspec/language/line_spec.rb new file mode 100644 index 0000000000..d9fd307dab --- /dev/null +++ b/spec/rubyspec/language/line_spec.rb @@ -0,0 +1,45 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/code_loading', __FILE__) +require File.expand_path('../shared/__LINE__', __FILE__) + +describe "The __LINE__ pseudo-variable" do + it "raises a SyntaxError if assigned to" do + lambda { eval("__LINE__ = 1") }.should raise_error(SyntaxError) + end + + before :each do + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "equals the line number of the text inside an eval" do + eval <<-EOC +ScratchPad << __LINE__ + +# line 3 + +ScratchPad << __LINE__ + EOC + + ScratchPad.recorded.should == [1, 5] + end +end + +describe "The __LINE__ pseudo-variable" do + it_behaves_like :language___LINE__, :require, CodeLoadingSpecs::Method.new +end + +describe "The __LINE__ pseudo-variable" do + it_behaves_like :language___LINE__, :require, Kernel +end + +describe "The __LINE__ pseudo-variable" do + it_behaves_like :language___LINE__, :load, CodeLoadingSpecs::Method.new +end + +describe "The __LINE__ pseudo-variable" do + it_behaves_like :language___LINE__, :load, Kernel +end diff --git a/spec/rubyspec/language/loop_spec.rb b/spec/rubyspec/language/loop_spec.rb new file mode 100644 index 0000000000..4e60e0d8e6 --- /dev/null +++ b/spec/rubyspec/language/loop_spec.rb @@ -0,0 +1,67 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The loop expression" do + it "repeats the given block until a break is called" do + outer_loop = 0 + loop do + outer_loop += 1 + break if outer_loop == 10 + end + outer_loop.should == 10 + end + + it "executes code in its own scope" do + loop do + inner_loop = 123 + break + end + lambda { inner_loop }.should raise_error(NameError) + end + + it "returns the value passed to break if interrupted by break" do + loop do + break 123 + end.should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + loop do + break + end.should == nil + end + + it "skips to end of body with next" do + a = [] + i = 0 + loop do + break if (i+=1) >= 5 + next if i == 3 + a << i + end + a.should == [1, 2, 4] + end + + it "restarts the current iteration with redo" do + a = [] + loop do + a << 1 + redo if a.size < 2 + a << 2 + break if a.size == 3 + end + a.should == [1, 1, 2] + end + + it "uses a spaghetti nightmare of redo, next and break" do + a = [] + loop do + a << 1 + redo if a.size == 1 + a << 2 + next if a.size == 3 + a << 3 + break if a.size > 6 + end + a.should == [1, 1, 2, 1, 2, 3, 1, 2, 3] + end +end diff --git a/spec/rubyspec/language/magic_comment_spec.rb b/spec/rubyspec/language/magic_comment_spec.rb new file mode 100644 index 0000000000..2f6e3b5c3a --- /dev/null +++ b/spec/rubyspec/language/magic_comment_spec.rb @@ -0,0 +1,62 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "Magic comment" do + it "is optional" do + eval("__ENCODING__").should be_an_instance_of(Encoding) + end + + it "determines __ENCODING__" do + eval(<foo)(?bar)?/ + @string = "foofoo" + end + + describe "on syntax of /regexp/ =~ string_variable" do + it "sets local variables by the captured pairs" do + /(?foo)(?bar)?/ =~ @string + local_variables.should == [:matched, :unmatched] + matched.should == "foo" + unmatched.should == nil + end + end + + describe "on syntax of string_variable =~ /regexp/" do + it "does not set local variables" do + @string =~ /(?foo)(?bar)?/ + local_variables.should == [] + end + end + + describe "on syntax of regexp_variable =~ string_variable" do + it "does not set local variables" do + @regexp =~ @string + local_variables.should == [] + end + end + + describe "on the method calling" do + it "does not set local variables" do + @regexp.=~(@string) + local_variables.should == [] + + @regexp.send :=~, @string + local_variables.should == [] + end + end +end diff --git a/spec/rubyspec/language/metaclass_spec.rb b/spec/rubyspec/language/metaclass_spec.rb new file mode 100644 index 0000000000..b3bcd9ef18 --- /dev/null +++ b/spec/rubyspec/language/metaclass_spec.rb @@ -0,0 +1,143 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/class', __FILE__) +require File.expand_path('../fixtures/metaclass', __FILE__) + +describe "self in a metaclass body (class << obj)" do + it "is TrueClass for true" do + class << true; self; end.should == TrueClass + end + + it "is FalseClass for false" do + class << false; self; end.should == FalseClass + end + + it "is NilClass for nil" do + class << nil; self; end.should == NilClass + end + + it "raises a TypeError for numbers" do + lambda { class << 1; self; end }.should raise_error(TypeError) + end + + it "raises a TypeError for symbols" do + lambda { class << :symbol; self; end }.should raise_error(TypeError) + end + + it "is a singleton Class instance" do + cls = class << mock('x'); self; end + cls.is_a?(Class).should == true + cls.should_not equal(Object) + end +end + +describe "A constant on a metaclass" do + before :each do + @object = Object.new + class << @object + CONST = self + end + end + + it "can be accessed after the metaclass body is reopened" do + class << @object + CONST.should == self + end + end + + it "can be accessed via self::CONST" do + class << @object + self::CONST.should == self + end + end + + it "can be accessed via const_get" do + class << @object + const_get(:CONST).should == self + end + end + + it "is not defined on the object's class" do + @object.class.const_defined?(:CONST).should be_false + end + + it "is not defined in the metaclass opener's scope" do + class << @object + CONST + end + lambda { CONST }.should raise_error(NameError) + end + + it "cannot be accessed via object::CONST" do + lambda do + @object::CONST + end.should raise_error(TypeError) + end + + it "raises a NameError for anonymous_module::CONST" do + @object = Class.new + class << @object + CONST = 100 + end + + lambda do + @object::CONST + end.should raise_error(NameError) + end + + it "appears in the metaclass constant list" do + constants = class << @object; constants; end + constants.should include(:CONST) + end + + it "does not appear in the object's class constant list" do + @object.class.constants.should_not include(:CONST) + end + + it "is not preserved when the object is duped" do + @object = @object.dup + + lambda do + class << @object; CONST; end + end.should raise_error(NameError) + end + + it "is preserved when the object is cloned" do + @object = @object.clone + + class << @object + CONST.should_not be_nil + end + end +end + +describe "calling methods on the metaclass" do + + it "calls a method on the metaclass" do + MetaClassSpecs::A.cheese.should == 'edam' + MetaClassSpecs::B.cheese.should == 'stilton' + end + + it "calls a method on the instance's metaclass" do + b = MetaClassSpecs::B.new + b_meta = MetaClassSpecs.metaclass_of b + b_meta.send(:define_method, :cheese) {'cheshire'} + b.cheese.should == 'cheshire' + end + + it "calls a method in deeper chains of metaclasses" do + b = MetaClassSpecs::B.new + b_meta = MetaClassSpecs.metaclass_of b + b_meta_meta = MetaClassSpecs.metaclass_of b_meta + b_meta_meta.send(:define_method, :cheese) {'gouda'} + b_meta.cheese.should == 'gouda' + + b_meta_meta_meta = MetaClassSpecs.metaclass_of b_meta_meta + b_meta_meta_meta.send(:define_method, :cheese) {'wensleydale'} + b_meta_meta.cheese.should == 'wensleydale' + end + + it "calls a method defined on the metaclass of the metaclass" do + d_meta = MetaClassSpecs::D.singleton_class + d_meta.ham.should == 'iberico' + end +end diff --git a/spec/rubyspec/language/method_spec.rb b/spec/rubyspec/language/method_spec.rb new file mode 100644 index 0000000000..c92a8ee51b --- /dev/null +++ b/spec/rubyspec/language/method_spec.rb @@ -0,0 +1,1290 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "A method send" do + evaluate <<-ruby do + def m(a) a end + ruby + + a = b = m 1 + a.should == 1 + b.should == 1 + end + + context "with a single splatted Object argument" do + before :all do + def m(a) a end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(*x).should equal(x) + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + m(*x).should == 1 + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(*x).should == x + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { m(*x) }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + def m(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(*x, 1, 2, 3).should == [x, 1, [], 2, 3] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + m(*x, 2, 3, 4).should == [1, 2, [], 3, 4] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(*x, 2, 3, 4).should == [x, 2, [], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { m(*x, 2, 3) }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + def m(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([5, 6, 7]) + + m(1, 2, *x, 3).should == [1, 2, [5, 6], 7, 3] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(1, 2, *x, 4).should == [1, 2, [], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { m(1, *x, 2, 3) }.should raise_error(TypeError) + end + + it "copies the splatted array" do + args = [3, 4] + m(1, 2, *args, 4, 5).should == [1, 2, [3, 4], 4, 5] + m(1, 2, *args, 4, 5)[2].should_not equal(args) + end + + it "allows an array being splatted to be modified by another argument" do + args = [3, 4] + m(1, args.shift, *args, 4, 5).should == [1, 3, [4], 4, 5] + end + end + + context "with a trailing splatted Object argument" do + before :all do + def m(a, *b, c) [a, b, c] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + m(1, 2, *x).should == [1, [2], x] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([5, 6, 7]) + + m(1, 2, *x).should == [1, [2, 5, 6], 7] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + m(1, 2, *x, 4).should == [1, [2, x], 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { m(1, 2, *x) }.should raise_error(TypeError) + end + end +end + +describe "An element assignment method send" do + before :each do + ScratchPad.clear + end + + context "with a single splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b) ScratchPad.record [a, b] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[*x] = 1).should == 1 + ScratchPad.recorded.should == [x, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + (@o[*x] = 2).should == 2 + ScratchPad.recorded.should == [1, 2] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[*x] = 1).should == 1 + ScratchPad.recorded.should == [x, 1] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o[*x] = 1 }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[*x, 2, 3, 4] = 1).should == 1 + ScratchPad.recorded.should == [x, 2, [3], 4, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1, 2, 3]) + + (@o[*x, 4, 5] = 6).should == 6 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[*x, 2, 3, 4] = 5).should == 5 + ScratchPad.recorded.should == [x, 2, [3], 4, 5] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o[*x, 2, 3] = 4 }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[1, *x, 2, 3] = 4).should == 4 + ScratchPad.recorded.should == [1, x, [2], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([2, 3]) + + (@o[1, *x, 4] = 5).should == 5 + ScratchPad.recorded.should == [1, 2, [3], 4, 5] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[1, 2, *x, 3] = 4).should == 4 + ScratchPad.recorded.should == [1, 2, [x], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o[1, 2, *x, 3] = 4 }.should raise_error(TypeError) + end + end + + context "with a trailing splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.[]=(a, b, *c, d, e) ScratchPad.record [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o[1, 2, 3, 4, *x] = 5).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], x, 5] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([4, 5]) + + (@o[1, 2, 3, *x] = 6).should == 6 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o[1, 2, 3, *x] = 4).should == 4 + ScratchPad.recorded.should == [1, 2, [3], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o[1, 2, 3, *x] = 4 }.should raise_error(TypeError) + end + end +end + +describe "An attribute assignment method send" do + context "with a single splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b) [a, b] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, *x, 1).should == [x, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1]) + + (@o.send :m=, *x, 2).should == [1, 2] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, *x, 1).should == [x, 1] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o.send :m=, *x, 1 }.should raise_error(TypeError) + end + end + + context "with a leading splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, *x, 2, 3, 4, 1).should == [x, 2, [3], 4, 1] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([1, 2, 3]) + + (@o.send :m=, *x, 4, 5, 6).should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, *x, 2, 3, 4, 5).should == [x, 2, [3], 4, 5] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o.send :m=, *x, 2, 3, 4 }.should raise_error(TypeError) + end + end + + context "with a middle splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, 1, *x, 2, 3, 4).should == [1, x, [2], 3, 4] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([2, 3]) + + (@o.send :m=, 1, *x, 4, 5).should == [1, 2, [3], 4, 5] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, 1, 2, *x, 3, 4).should == [1, 2, [x], 3, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o.send :m=, 1, 2, *x, 3, 4 }.should raise_error(TypeError) + end + end + + context "with a trailing splatted Object argument" do + before :all do + @o = mock("element set receiver") + def @o.m=(a, b, *c, d, e) [a, b, c, d, e] end + end + + it "does not call #to_ary" do + x = mock("splat argument") + x.should_not_receive(:to_ary) + + (@o.send :m=, 1, 2, 3, 4, *x, 5).should == [1, 2, [3, 4], x, 5] + end + + it "calls #to_a" do + x = mock("splat argument") + x.should_receive(:to_a).and_return([4, 5]) + + (@o.send :m=, 1, 2, 3, *x, 6).should == [1, 2, [3, 4], 5, 6] + end + + it "wraps the argument in an Array if #to_a returns nil" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(nil) + + (@o.send :m=, 1, 2, 3, *x, 4).should == [1, 2, [3], x, 4] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("splat argument") + x.should_receive(:to_a).and_return(1) + + lambda { @o.send :m=, 1, 2, 3, *x, 4 }.should raise_error(TypeError) + end + end +end + +describe "A method" do + SpecEvaluate.desc = "for definition" + + context "assigns no local variables" do + evaluate <<-ruby do + def m + end + ruby + + m.should be_nil + end + + evaluate <<-ruby do + def m() + end + ruby + + m.should be_nil + end + end + + context "assigns local variables from method parameters" do + evaluate <<-ruby do + def m(a) a end + ruby + + m((args = 1, 2, 3)).should equal(args) + end + + evaluate <<-ruby do + def m((a)) a end + ruby + + m(1).should == 1 + m([1, 2, 3]).should == 1 + end + + evaluate <<-ruby do + def m((*a, b)) [a, b] end + ruby + + m(1).should == [[], 1] + m([1, 2, 3]).should == [[1, 2], 3] + end + + evaluate <<-ruby do + def m(a=1) a end + ruby + + m().should == 1 + m(2).should == 2 + end + + evaluate <<-ruby do + def m(*) end + ruby + + m().should be_nil + m(1).should be_nil + m(1, 2, 3).should be_nil + end + + evaluate <<-ruby do + def m(*a) a end + ruby + + m().should == [] + m(1).should == [1] + m(1, 2, 3).should == [1, 2, 3] + end + + evaluate <<-ruby do + def m(a:) a end + ruby + + lambda { m() }.should raise_error(ArgumentError) + m(a: 1).should == 1 + lambda { m("a" => 1, a: 1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a: 1) a end + ruby + + m().should == 1 + m(a: 2).should == 2 + end + + evaluate <<-ruby do + def m(**) end + ruby + + m().should be_nil + m(a: 1, b: 2).should be_nil + lambda { m(1) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(**k) k end + ruby + + m().should == {} + m(a: 1, b: 2).should == { a: 1, b: 2 } + lambda { m(2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(&b) b end + ruby + + m { }.should be_an_instance_of(Proc) + end + + evaluate <<-ruby do + def m(a, b) [a, b] end + ruby + + m(1, 2).should == [1, 2] + end + + evaluate <<-ruby do + def m(a, (b, c)) [a, b, c] end + ruby + + m(1, 2).should == [1, 2, nil] + m(1, [2, 3, 4]).should == [1, 2, 3] + end + + evaluate <<-ruby do + def m((a), (b)) [a, b] end + ruby + + m(1, 2).should == [1, 2] + m([1, 2], [3, 4]).should == [1, 3] + end + + evaluate <<-ruby do + def m((*), (*)) end + ruby + + m(2, 3).should be_nil + m([2, 3, 4], [5, 6]).should be_nil + lambda { m a: 1 }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m((*a), (*b)) [a, b] end + ruby + + m(1, 2).should == [[1], [2]] + m([1, 2], [3, 4]).should == [[1, 2], [3, 4]] + end + + evaluate <<-ruby do + def m((a, b), (c, d)) + [a, b, c, d] + end + ruby + + m(1, 2).should == [1, nil, 2, nil] + m([1, 2, 3], [4, 5, 6]).should == [1, 2, 4, 5] + end + + evaluate <<-ruby do + def m((a, *b), (*c, d)) + [a, b, c, d] + end + ruby + + m(1, 2).should == [1, [], [], 2] + m([1, 2, 3], [4, 5, 6]).should == [1, [2, 3], [4, 5], 6] + end + + evaluate <<-ruby do + def m((a, b, *c, d), (*e, f, g), (*h)) + [a, b, c, d, e, f, g, h] + end + ruby + + m(1, 2, 3).should == [1, nil, [], nil, [], 2, nil, [3]] + result = m([1, 2, 3], [4, 5, 6, 7, 8], [9, 10]) + result.should == [1, 2, [], 3, [4, 5, 6], 7, 8, [9, 10]] + end + + evaluate <<-ruby do + def m(a, (b, (c, *d), *e)) + [a, b, c, d, e] + end + ruby + + m(1, 2).should == [1, 2, nil, [], []] + m(1, [2, [3, 4, 5], 6, 7, 8]).should == [1, 2, 3, [4, 5], [6, 7, 8]] + end + + evaluate <<-ruby do + def m(a, (b, (c, *d, (e, (*f)), g), (h, (i, j)))) + [a, b, c, d, e, f, g, h, i, j] + end + ruby + + m(1, 2).should == [1, 2, nil, [], nil, [nil], nil, nil, nil, nil] + result = m(1, [2, [3, 4, 5, [6, [7, 8]], 9], [10, [11, 12]]]) + result.should == [1, 2, 3, [4, 5], 6, [7, 8], 9, 10, 11, 12] + end + + evaluate <<-ruby do + def m(a, b=1) [a, b] end + ruby + + m(2).should == [2, 1] + m(1, 2).should == [1, 2] + end + + evaluate <<-ruby do + def m(a, *) a end + ruby + + m(1).should == 1 + m(1, 2, 3).should == 1 + end + + evaluate <<-ruby do + def m(a, *b) [a, b] end + ruby + + m(1).should == [1, []] + m(1, 2, 3).should == [1, [2, 3]] + end + + evaluate <<-ruby do + def m(a, b:) [a, b] end + ruby + + m(1, b: 2).should == [1, 2] + lambda { m("a" => 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a, b: 1) [a, b] end + ruby + + m(2).should == [2, 1] + m(1, b: 2).should == [1, 2] + m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, 1] + end + + evaluate <<-ruby do + def m(a, **) a end + ruby + + m(1).should == 1 + m(1, a: 2, b: 3).should == 1 + m("a" => 1, b: 2).should == {"a" => 1, b: 2} + end + + evaluate <<-ruby do + def m(a, **k) [a, k] end + ruby + + m(1).should == [1, {}] + m(1, a: 2, b: 3).should == [1, {a: 2, b: 3}] + m("a" => 1, b: 2).should == [{"a" => 1, b: 2}, {}] + end + + evaluate <<-ruby do + def m(a, &b) [a, b] end + ruby + + m(1).should == [1, nil] + m(1, &(l = -> {})).should == [1, l] + end + + evaluate <<-ruby do + def m(a=1, b) [a, b] end + ruby + + m(2).should == [1, 2] + m(2, 3).should == [2, 3] + end + + evaluate <<-ruby do + def m(a=1, *) a end + ruby + + m().should == 1 + m(2, 3, 4).should == 2 + end + + evaluate <<-ruby do + def m(a=1, *b) [a, b] end + ruby + + m().should == [1, []] + m(2, 3, 4).should == [2, [3, 4]] + end + + evaluate <<-ruby do + def m(a=1, (b, c)) [a, b, c] end + ruby + + m(2).should == [1, 2, nil] + m(2, 3).should == [2, 3, nil] + m(2, [3, 4, 5]).should == [2, 3, 4] + end + + evaluate <<-ruby do + def m(a=1, (b, (c, *d))) [a, b, c, d] end + ruby + + m(2).should == [1, 2, nil, []] + m(2, 3).should == [2, 3, nil, []] + m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6]] + end + + evaluate <<-ruby do + def m(a=1, (b, (c, *d), *e)) [a, b, c, d, e] end + ruby + + m(2).should == [1, 2, nil, [], []] + m(2, [3, 4, 5, 6]).should == [2, 3, 4, [], [5, 6]] + m(2, [3, [4, 5, 6], 7]).should == [2, 3, 4, [5, 6], [7]] + end + + evaluate <<-ruby do + def m(a=1, (b), (c)) [a, b, c] end + ruby + + m(2, 3).should == [1, 2, 3] + m(2, 3, 4).should == [2, 3, 4] + m(2, [3, 4], [5, 6, 7]).should == [2, 3, 5] + end + + evaluate <<-ruby do + def m(a=1, (*b), (*c)) [a, b, c] end + ruby + + lambda { m() }.should raise_error(ArgumentError) + lambda { m(2) }.should raise_error(ArgumentError) + m(2, 3).should == [1, [2], [3]] + m(2, [3, 4], [5, 6]).should == [2, [3, 4], [5, 6]] + end + + evaluate <<-ruby do + def m(a=1, (b, c), (d, e)) [a, b, c, d, e] end + ruby + + m(2, 3).should == [1, 2, nil, 3, nil] + m(2, [3, 4, 5], [6, 7, 8]).should == [2, 3, 4, 6, 7] + end + + evaluate <<-ruby do + def m(a=1, (b, *c), (*d, e)) + [a, b, c, d, e] + end + ruby + + m(1, 2).should == [1, 1, [], [], 2] + m(1, [2, 3], [4, 5, 6]).should == [1, 2, [3], [4, 5], 6] + end + + evaluate <<-ruby do + def m(a=1, (b, *c), (d, (*e, f))) + [a, b, c, d, e, f] + end + ruby + + m(1, 2).should == [1, 1, [], 2, [], nil] + m(nil, nil).should == [1, nil, [], nil, [], nil] + result = m([1, 2, 3], [4, 5, 6], [7, 8, 9]) + result.should == [[1, 2, 3], 4, [5, 6], 7, [], 8] + end + + evaluate <<-ruby do + def m(a=1, b:) [a, b] end + ruby + + m(b: 2).should == [1, 2] + m(2, b: 1).should == [2, 1] + m("a" => 1, b: 2).should == [{"a" => 1}, 2] + end + + evaluate <<-ruby do + def m(a=1, b: 2) [a, b] end + ruby + + m().should == [1, 2] + m(2).should == [2, 2] + m(b: 3).should == [1, 3] + m("a" => 1, b: 2).should == [{"a" => 1}, 2] + end + + evaluate <<-ruby do + def m(a=1, **) a end + ruby + + m().should == 1 + m(2, a: 1, b: 0).should == 2 + m("a" => 1, a: 2).should == {"a" => 1} + end + + evaluate <<-ruby do + def m(a=1, **k) [a, k] end + ruby + + m().should == [1, {}] + m(2, a: 1, b: 2).should == [2, {a: 1, b: 2}] + end + + evaluate <<-ruby do + def m(a=1, &b) [a, b] end + ruby + + m().should == [1, nil] + m(&(l = -> {})).should == [1, l] + + p = -> {} + l = mock("to_proc") + l.should_receive(:to_proc).and_return(p) + m(&l).should == [1, p] + end + + evaluate <<-ruby do + def m(*, a) a end + ruby + + m(1).should == 1 + m(1, 2, 3).should == 3 + end + + evaluate <<-ruby do + def m(*a, b) [a, b] end + ruby + + m(1).should == [[], 1] + m(1, 2, 3).should == [[1, 2], 3] + end + + evaluate <<-ruby do + def m(*, a:) a end + ruby + + m(a: 1).should == 1 + m(1, 2, a: 3).should == 3 + m("a" => 1, a: 2).should == 2 + end + + evaluate <<-ruby do + def m(*a, b:) [a, b] end + ruby + + m(b: 1).should == [[], 1] + m(1, 2, b: 3).should == [[1, 2], 3] + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + end + + evaluate <<-ruby do + def m(*, a: 1) a end + ruby + + m().should == 1 + m(1, 2).should == 1 + m(a: 2).should == 2 + m(1, a: 2).should == 2 + m("a" => 1, a: 2).should == 2 + end + + evaluate <<-ruby do + def m(*a, b: 1) [a, b] end + ruby + + m().should == [[], 1] + m(1, 2, 3, b: 4).should == [[1, 2, 3], 4] + m("a" => 1, b: 2).should == [[{"a" => 1}], 2] + + a = mock("splat") + a.should_not_receive(:to_ary) + m(*a).should == [[a], 1] + end + + evaluate <<-ruby do + def m(*, **) end + ruby + + m().should be_nil + m(a: 1, b: 2).should be_nil + m(1, 2, 3, a: 4, b: 5).should be_nil + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + m(h).should be_nil + + h = mock("keyword splat") + error = RuntimeError.new("error while converting to a hash") + h.should_receive(:to_hash).and_raise(error) + lambda { m(h) }.should raise_error(error) + end + + evaluate <<-ruby do + def m(*a, **) a end + ruby + + m().should == [] + m(1, 2, 3, a: 4, b: 5).should == [1, 2, 3] + m("a" => 1, a: 1).should == [{"a" => 1}] + m(1, **{a: 2}).should == [1] + + h = mock("keyword splat") + h.should_receive(:to_hash) + lambda { m(**h) }.should raise_error(TypeError) + end + + evaluate <<-ruby do + def m(*, **k) k end + ruby + + m().should == {} + m(1, 2, 3, a: 4, b: 5).should == {a: 4, b: 5} + m("a" => 1, a: 1).should == {a: 1} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({a: 1}) + m(h).should == {a: 1} + end + + evaluate <<-ruby do + def m(a = nil, **k) [a, k] end + ruby + + m().should == [nil, {}] + m("a" => 1).should == [{"a" => 1}, {}] + m(a: 1).should == [nil, {a: 1}] + m("a" => 1, a: 1).should == [{"a" => 1}, {a: 1}] + m({ "a" => 1 }, a: 1).should == [{"a" => 1}, {a: 1}] + m({a: 1}, {}).should == [{a: 1}, {}] + + h = {"a" => 1, b: 2} + m(h).should == [{"a" => 1}, {b: 2}] + h.should == {"a" => 1, b: 2} + + h = {"a" => 1} + m(h).first.should == h + + h = {} + r = m(h) + r.first.should be_nil + r.last.should == {} + + hh = {} + h = mock("keyword splat empty hash") + h.should_receive(:to_hash).and_return(hh) + r = m(h) + r.first.should be_nil + r.last.should == {} + + h = mock("keyword splat") + h.should_receive(:to_hash).and_return({"a" => 1, a: 2}) + m(h).should == [{"a" => 1}, {a: 2}] + end + + evaluate <<-ruby do + def m(*a, **k) [a, k] end + ruby + + m().should == [[], {}] + m(1).should == [[1], {}] + m(a: 1, b: 2).should == [[], {a: 1, b: 2}] + m(1, 2, 3, a: 2).should == [[1, 2, 3], {a: 2}] + + m("a" => 1).should == [[{"a" => 1}], {}] + m(a: 1).should == [[], {a: 1}] + m("a" => 1, a: 1).should == [[{"a" => 1}], {a: 1}] + m({ "a" => 1 }, a: 1).should == [[{"a" => 1}], {a: 1}] + m({a: 1}, {}).should == [[{a: 1}], {}] + m({a: 1}, {"a" => 1}).should == [[{a: 1}, {"a" => 1}], {}] + + bo = BasicObject.new + def bo.to_a; [1, 2, 3]; end + def bo.to_hash; {:b => 2, :c => 3}; end + + m(*bo, **bo).should == [[1, 2, 3], {:b => 2, :c => 3}] + end + + evaluate <<-ruby do + def m(*, &b) b end + ruby + + m().should be_nil + m(1, 2, 3, 4).should be_nil + m(&(l = ->{})).should equal(l) + end + + evaluate <<-ruby do + def m(*a, &b) [a, b] end + ruby + + m().should == [[], nil] + m(1).should == [[1], nil] + m(1, 2, 3, &(l = -> {})).should == [[1, 2, 3], l] + end + + evaluate <<-ruby do + def m(a:, b:) [a, b] end + ruby + + m(a: 1, b: 2).should == [1, 2] + lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a:, b: 1) [a, b] end + ruby + + m(a: 1).should == [1, 1] + m(a: 1, b: 2).should == [1, 2] + lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a:, **) a end + ruby + + m(a: 1).should == 1 + m(a: 1, b: 2).should == 1 + lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a:, **k) [a, k] end + ruby + + m(a: 1).should == [1, {}] + m(a: 1, b: 2, c: 3).should == [1, {b: 2, c: 3}] + lambda { m("a" => 1, a: 1, b: 2) }.should raise_error(ArgumentError) + end + + evaluate <<-ruby do + def m(a:, &b) [a, b] end + ruby + + m(a: 1).should == [1, nil] + m(a: 1, &(l = ->{})).should == [1, l] + end + + evaluate <<-ruby do + def m(a: 1, b:) [a, b] end + ruby + + m(b: 0).should == [1, 0] + m(b: 2, a: 3).should == [3, 2] + end + + evaluate <<-ruby do + def m(a: def m(a: 1) a end, b:) + [a, b] + end + ruby + + m(a: 2, b: 3).should == [2, 3] + m(b: 1).should == [:m, 1] + + # Note the default value of a: in the original method. + m().should == 1 + end + + evaluate <<-ruby do + def m(a: 1, b: 2) [a, b] end + ruby + + m().should == [1, 2] + m(b: 3, a: 4).should == [4, 3] + end + + evaluate <<-ruby do + def m(a: 1, **) a end + ruby + + m().should == 1 + m(a: 2, b: 1).should == 2 + end + + evaluate <<-ruby do + def m(a: 1, **k) [a, k] end + ruby + + m(b: 2, c: 3).should == [1, {b: 2, c: 3}] + end + + evaluate <<-ruby do + def m(a: 1, &b) [a, b] end + ruby + + m(&(l = ->{})).should == [1, l] + m().should == [1, nil] + end + + evaluate <<-ruby do + def m(**, &b) b end + ruby + + m(a: 1, b: 2, &(l = ->{})).should == l + end + + evaluate <<-ruby do + def m(**k, &b) [k, b] end + ruby + + m(a: 1, b: 2).should == [{ a: 1, b: 2}, nil] + end + + evaluate <<-ruby do + def m(a, b=1, *c, (*d, (e)), f: 2, g:, h:, **k, &l) + [a, b, c, d, e, f, g, h, k, l] + end + ruby + + result = m(9, 8, 7, 6, f: 5, g: 4, h: 3, &(l = ->{})) + result.should == [9, 8, [7], [], 6, 5, 4, 3, {}, l] + end + + evaluate <<-ruby do + def m a, b=1, *c, d, e:, f: 2, g:, **k, &l + [a, b, c, d, e, f, g, k, l] + end + ruby + + result = m(1, 2, e: 3, g: 4, h: 5, i: 6, &(l = ->{})) + result.should == [1, 1, [], 2, 3, 2, 4, { h: 5, i: 6 }, l] + end + + evaluate <<-ruby do + def m (a, b = nil, c = nil, d, e: nil, **f) + [a, b, c, d, e, f] + end + ruby + + result = m(1, 2) + result.should == [1, nil, nil, 2, nil, {}] + end + end +end + +describe "A method call with a space between method name and parentheses" do + before(:each) do + def m(*args) + args + end + + def n(value, &block) + [value, block.call] + end + end + + context "when no arguments provided" do + it "assigns nil" do + args = m () + args.should == [nil] + end + end + + context "when a single argument provided" do + it "assigns it" do + args = m (1 == 1 ? true : false) + args.should == [true] + end + end + + context "when 2+ arguments provided" do + it "raises a syntax error" do + lambda { + eval("m (1, 2)") + }.should raise_error(SyntaxError) + + lambda { + eval("m (1, 2, 3)") + }.should raise_error(SyntaxError) + end + end + + it "allows to pass a block with curly braces" do + args = n () { :block_value } + args.should == [nil, :block_value] + + args = n (1) { :block_value } + args.should == [1, :block_value] + end + + it "allows to pass a block with do/end" do + args = n () do + :block_value + end + args.should == [nil, :block_value] + + args = n (1) do + :block_value + end + args.should == [1, :block_value] + end +end + +describe "An array-dereference method ([])" do + SpecEvaluate.desc = "for definition" + + context "received the passed-in block" do + evaluate <<-ruby do + def [](*, &b) + b.call + end + ruby + pr = proc {:ok} + + self[&pr].should == :ok + self['foo', &pr].should == :ok + self.[](&pr).should == :ok + self.[]('foo', &pr).should == :ok + end + + evaluate <<-ruby do + def [](*) + yield + end + ruby + pr = proc {:ok} + + self[&pr].should == :ok + self['foo', &pr].should == :ok + self.[](&pr).should == :ok + self.[]('foo', &pr).should == :ok + end + end +end diff --git a/spec/rubyspec/language/module_spec.rb b/spec/rubyspec/language/module_spec.rb new file mode 100644 index 0000000000..d5ca71b3b4 --- /dev/null +++ b/spec/rubyspec/language/module_spec.rb @@ -0,0 +1,91 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/module', __FILE__) + +describe "The module keyword" do + it "creates a new module without semicolon" do + module ModuleSpecsKeywordWithoutSemicolon end + ModuleSpecsKeywordWithoutSemicolon.should be_an_instance_of(Module) + end + + it "creates a new module with a non-qualified constant name" do + module ModuleSpecsToplevel; end + ModuleSpecsToplevel.should be_an_instance_of(Module) + end + + it "creates a new module with a qualified constant name" do + module ModuleSpecs::Nested; end + ModuleSpecs::Nested.should be_an_instance_of(Module) + end + + it "creates a new module with a variable qualified constant name" do + m = Module.new + module m::N; end + m::N.should be_an_instance_of(Module) + end + + it "reopens an existing module" do + module ModuleSpecs; Reopened = true; end + ModuleSpecs::Reopened.should be_true + end + + it "reopens a module included in Object" do + module IncludedModuleSpecs; Reopened = true; end + ModuleSpecs::IncludedInObject::IncludedModuleSpecs::Reopened.should be_true + end + + it "raises a TypeError if the constant is a Class" do + lambda do + module ModuleSpecs::Modules::Klass; end + end.should raise_error(TypeError) + end + + it "raises a TypeError if the constant is a String" do + lambda { module ModuleSpecs::Modules::A; end }.should raise_error(TypeError) + end + + it "raises a TypeError if the constant is a Fixnum" do + lambda { module ModuleSpecs::Modules::B; end }.should raise_error(TypeError) + end + + it "raises a TypeError if the constant is nil" do + lambda { module ModuleSpecs::Modules::C; end }.should raise_error(TypeError) + end + + it "raises a TypeError if the constant is true" do + lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError) + end + + it "raises a TypeError if the constant is false" do + lambda { module ModuleSpecs::Modules::D; end }.should raise_error(TypeError) + end +end + +describe "Assigning an anonymous module to a constant" do + it "sets the name of the module" do + mod = Module.new + mod.name.should be_nil + + ::ModuleSpecs_CS1 = mod + mod.name.should == "ModuleSpecs_CS1" + end + + it "does not set the name of a module scoped by an anonymous module" do + a, b = Module.new, Module.new + a::B = b + b.name.should be_nil + end + + it "sets the name of contained modules when assigning a toplevel anonymous module" do + a, b, c, d = Module.new, Module.new, Module.new, Module.new + a::B = b + a::B::C = c + a::B::C::E = c + a::D = d + + ::ModuleSpecs_CS2 = a + a.name.should == "ModuleSpecs_CS2" + b.name.should == "ModuleSpecs_CS2::B" + c.name.should == "ModuleSpecs_CS2::B::C" + d.name.should == "ModuleSpecs_CS2::D" + end +end diff --git a/spec/rubyspec/language/next_spec.rb b/spec/rubyspec/language/next_spec.rb new file mode 100644 index 0000000000..67da5224dc --- /dev/null +++ b/spec/rubyspec/language/next_spec.rb @@ -0,0 +1,410 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/next', __FILE__) + +describe "The next statement from within the block" do + before :each do + ScratchPad.record [] + end + + it "ends block execution" do + a = [] + lambda { + a << 1 + next + a << 2 + }.call + a.should == [1] + end + + it "causes block to return nil if invoked without arguments" do + lambda { 123; next; 456 }.call.should == nil + end + + it "causes block to return nil if invoked with an empty expression" do + lambda { next (); 456 }.call.should be_nil + end + + it "returns the argument passed" do + lambda { 123; next 234; 345 }.call.should == 234 + end + + it "returns to the invoking method" do + NextSpecs.yielding_method(nil) { next }.should == :method_return_value + end + + it "returns to the invoking method, with the specified value" do + NextSpecs.yielding_method(nil) { + next nil; + fail("next didn't end the block execution") + }.should == :method_return_value + + NextSpecs.yielding_method(1) { + next 1 + fail("next didn't end the block execution") + }.should == :method_return_value + + NextSpecs.yielding_method([1, 2, 3]) { + next 1, 2, 3 + fail("next didn't end the block execution") + }.should == :method_return_value + end + + it "returns to the currently yielding method in case of chained calls" do + class ChainedNextTest + def self.meth_with_yield(&b) + yield.should == :next_return_value + :method_return_value + end + def self.invoking_method(&b) + meth_with_yield(&b) + end + def self.enclosing_method + invoking_method do + next :next_return_value + :wrong_return_value + end + end + end + + ChainedNextTest.enclosing_method.should == :method_return_value + end + + it "causes ensure blocks to run" do + [1].each do |i| + begin + ScratchPad << :begin + next + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "skips following code outside an exception block" do + 3.times do |i| + begin + ScratchPad << :begin + next if i == 0 + break if i == 2 + ScratchPad << :begin_end + ensure + ScratchPad << :ensure + end + + ScratchPad << :after + end + + ScratchPad.recorded.should == [ + :begin, :ensure, :begin, :begin_end, :ensure, :after, :begin, :ensure] + end + + it "passes the value returned by a method with omitted parenthesis and passed block" do + obj = NextSpecs::Block.new + lambda { next obj.method :value do |x| x end }.call.should == :value + end +end + +describe "The next statement" do + describe "in a method" do + it "is invalid and raises a SyntaxError" do + lambda { + eval("def m; next; end") + }.should raise_error(SyntaxError) + end + end +end + +describe "The next statement" do + before :each do + ScratchPad.record [] + end + + describe "in a while loop" do + describe "when not passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.while_next(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.while_within_iter(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + describe "when passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.while_next(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.while_within_iter(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + it "causes nested ensure blocks to run" do + x = true + while x + begin + ScratchPad << :outer_begin + x = false + begin + ScratchPad << :inner_begin + next + ensure + ScratchPad << :inner_ensure + end + ensure + ScratchPad << :outer_ensure + end + end + + ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure] + end + + it "causes ensure blocks to run when mixed with break" do + x = 1 + while true + begin + ScratchPad << :begin + break if x > 1 + x += 1 + next + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure] + end + end + + describe "in an until loop" do + describe "when not passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.until_next(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.until_within_iter(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + describe "when passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.until_next(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.until_within_iter(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + it "causes nested ensure blocks to run" do + x = false + until x + begin + ScratchPad << :outer_begin + x = true + begin + ScratchPad << :inner_begin + next + ensure + ScratchPad << :inner_ensure + end + ensure + ScratchPad << :outer_ensure + end + end + + ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure] + end + + it "causes ensure blocks to run when mixed with break" do + x = 1 + until false + begin + ScratchPad << :begin + break if x > 1 + x += 1 + next + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure] + end + end + + describe "in a loop" do + describe "when not passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.loop_next(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.loop_within_iter(false) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + describe "when passed an argument" do + it "causes ensure blocks to run" do + NextSpecs.loop_next(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "causes ensure blocks to run when nested in an block" do + NextSpecs.loop_within_iter(true) + + ScratchPad.recorded.should == [:begin, :ensure] + end + end + + it "causes nested ensure blocks to run" do + x = 1 + loop do + break if x == 2 + + begin + ScratchPad << :outer_begin + begin + ScratchPad << :inner_begin + x += 1 + next + ensure + ScratchPad << :inner_ensure + end + ensure + ScratchPad << :outer_ensure + end + end + + ScratchPad.recorded.should == [:outer_begin, :inner_begin, :inner_ensure, :outer_ensure] + end + + it "causes ensure blocks to run when mixed with break" do + x = 1 + loop do + begin + ScratchPad << :begin + break if x > 1 + x += 1 + next + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure, :begin, :ensure] + end + end +end + +describe "Assignment via next" do + it "assigns objects" do + def r(val); a = yield(); val.should == a; end + r(nil){next} + r(nil){next nil} + r(1){next 1} + r([]){next []} + r([1]){next [1]} + r([nil]){next [nil]} + r([[]]){next [[]]} + r([]){next [*[]]} + r([1]){next [*[1]]} + r([1,2]){next [*[1,2]]} + end + + it "assigns splatted objects" do + def r(val); a = yield(); val.should == a; end + r([]){next *nil} + r([1]){next *1} + r([]){next *[]} + r([1]){next *[1]} + r([nil]){next *[nil]} + r([[]]){next *[[]]} + r([]){next *[*[]]} + r([1]){next *[*[1]]} + r([1,2]){next *[*[1,2]]} + end + + it "assigns objects to a splatted reference" do + def r(val); *a = yield(); val.should == a; end + r([nil]){next} + r([nil]){next nil} + r([1]){next 1} + r([]){next []} + r([1]){next [1]} + r([nil]){next [nil]} + r([[]]){next [[]]} + r([1,2]){next [1,2]} + r([]){next [*[]]} + r([1]){next [*[1]]} + r([1,2]){next [*[1,2]]} + end + + it "assigns splatted objects to a splatted reference via a splatted yield" do + def r(val); *a = *yield(); val.should == a; end + r([]){next *nil} + r([1]){next *1} + r([]){next *[]} + r([1]){next *[1]} + r([nil]){next *[nil]} + r([[]]){next *[[]]} + r([1,2]){next *[1,2]} + r([]){next *[*[]]} + r([1]){next *[*[1]]} + r([1,2]){next *[*[1,2]]} + end + + it "assigns objects to multiple variables" do + def r(val); a,b,*c = yield(); val.should == [a,b,c]; end + r([nil,nil,[]]){next} + r([nil,nil,[]]){next nil} + r([1,nil,[]]){next 1} + r([nil,nil,[]]){next []} + r([1,nil,[]]){next [1]} + r([nil,nil,[]]){next [nil]} + r([[],nil,[]]){next [[]]} + r([1,2,[]]){next [1,2]} + r([nil,nil,[]]){next [*[]]} + r([1,nil,[]]){next [*[1]]} + r([1,2,[]]){next [*[1,2]]} + end + + it "assigns splatted objects to multiple variables" do + def r(val); a,b,*c = *yield(); val.should == [a,b,c]; end + r([nil,nil,[]]){next *nil} + r([1,nil,[]]){next *1} + r([nil,nil,[]]){next *[]} + r([1,nil,[]]){next *[1]} + r([nil,nil,[]]){next *[nil]} + r([[],nil,[]]){next *[[]]} + r([1,2,[]]){next *[1,2]} + r([nil,nil,[]]){next *[*[]]} + r([1,nil,[]]){next *[*[1]]} + r([1,2,[]]){next *[*[1,2]]} + end +end diff --git a/spec/rubyspec/language/not_spec.rb b/spec/rubyspec/language/not_spec.rb new file mode 100644 index 0000000000..a0cf6b15a6 --- /dev/null +++ b/spec/rubyspec/language/not_spec.rb @@ -0,0 +1,51 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The not keyword" do + it "negates a `true' value" do + (not true).should be_false + (not 'true').should be_false + end + + it "negates a `false' value" do + (not false).should be_true + (not nil).should be_true + end + + it "accepts an argument" do + not(true).should be_false + end + + it "returns false if the argument is true" do + (not(true)).should be_false + end + + it "returns true if the argument is false" do + (not(false)).should be_true + end + + it "returns true if the argument is nil" do + (not(nil)).should be_true + end +end + +describe "The `!' keyword" do + it "negates a `true' value" do + (!true).should be_false + (!'true').should be_false + end + + it "negates a `false' value" do + (!false).should be_true + (!nil).should be_true + end + + it "doubled turns a truthful object into `true'" do + (!!true).should be_true + (!!'true').should be_true + end + + it "doubled turns a not truthful object into `false'" do + (!!false).should be_false + (!!nil).should be_false + end +end diff --git a/spec/rubyspec/language/numbers_spec.rb b/spec/rubyspec/language/numbers_spec.rb new file mode 100644 index 0000000000..e8c82f09a7 --- /dev/null +++ b/spec/rubyspec/language/numbers_spec.rb @@ -0,0 +1,97 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "A number literal" do + + it "can be a sequence of decimal digits" do + 435.should == 435 + end + + it "can have '_' characters between digits" do + 4_3_5_7.should == 4357 + end + + it "cannot have a leading underscore" do + lambda { eval("_4_2") }.should raise_error(NameError) + end + + it "can have a decimal point" do + 4.35.should == 4.35 + end + + it "must have a digit before the decimal point" do + 0.75.should == 0.75 + lambda { eval(".75") }.should raise_error(SyntaxError) + lambda { eval("-.75") }.should raise_error(SyntaxError) + end + + it "can have an exponent" do + 1.2e-3.should == 0.0012 + end + + it "can be a sequence of hexadecimal digits with a leading '0x'" do + 0xffff.should == 65535 + end + + it "can be a sequence of binary digits with a leading '0x'" do + 0b01011.should == 11 + end + + it "can be a sequence of octal digits with a leading '0'" do + 0377.should == 255 + end + + it "can be an integer literal with trailing 'r' to represent a Rational" do + eval('3r').should == Rational(3, 1) + eval('-3r').should == Rational(-3, 1) + end + + it "can be an bignum literal with trailing 'r' to represent a Rational" do + eval('1111111111111111111111111111111111111111111111r').should == Rational(1111111111111111111111111111111111111111111111, 1) + eval('-1111111111111111111111111111111111111111111111r').should == Rational(-1111111111111111111111111111111111111111111111, 1) + end + + it "can be a decimal literal with trailing 'r' to represent a Rational" do + eval('0.3r').should == Rational(3, 10) + eval('-0.3r').should == Rational(-3, 10) + end + + it "can be a hexadecimal literal with trailing 'r' to represent a Rational" do + eval('0xffr').should == Rational(255, 1) + eval('-0xffr').should == Rational(-255, 1) + end + + it "can be an octal literal with trailing 'r' to represent a Rational" do + eval('042r').should == Rational(34, 1) + eval('-042r').should == Rational(-34, 1) + end + + it "can be a binary literal with trailing 'r' to represent a Rational" do + eval('0b1111r').should == Rational(15, 1) + eval('-0b1111r').should == Rational(-15, 1) + end + + it "can be an integer literal with trailing 'i' to represent a Complex" do + eval('5i').should == Complex(0, 5) + eval('-5i').should == Complex(0, -5) + end + + it "can be a decimal literal with trailing 'i' to represent a Complex" do + eval('0.6i').should == Complex(0, 0.6) + eval('-0.6i').should == Complex(0, -0.6) + end + + it "can be a hexadecimal literal with trailing 'i' to represent a Complex" do + eval('0xffi').should == Complex(0, 255) + eval('-0xffi').should == Complex(0, -255) + end + + it "can be a octal literal with trailing 'i' to represent a Complex" do + eval("042i").should == Complex(0, 34) + eval("-042i").should == Complex(0, -34) + end + + it "can be a binary literal with trailing 'i' to represent a Complex" do + eval('0b1110i').should == Complex(0, 14) + eval('-0b1110i').should == Complex(0, -14) + end +end diff --git a/spec/rubyspec/language/optional_assignments_spec.rb b/spec/rubyspec/language/optional_assignments_spec.rb new file mode 100644 index 0000000000..c104d08253 --- /dev/null +++ b/spec/rubyspec/language/optional_assignments_spec.rb @@ -0,0 +1,226 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe 'Optional variable assignments' do + describe 'using ||=' do + describe 'using a single variable' do + it 'assigns a new variable' do + a ||= 10 + + a.should == 10 + end + + it 're-assigns an existing variable set to false' do + a = false + a ||= 10 + + a.should == 10 + end + + it 're-assigns an existing variable set to nil' do + a = nil + a ||= 10 + + a.should == 10 + end + + it 'does not re-assign a variable with a truthy value' do + a = 10 + a ||= 20 + + a.should == 10 + end + + it 'does not evaluate the right side when not needed' do + a = 10 + a ||= raise('should not be executed') + a.should == 10 + end + + it 'does not re-assign a variable with a truthy value when using an inline rescue' do + a = 10 + a ||= 20 rescue 30 + + a.should == 10 + end + end + + describe 'using a accessor' do + before do + klass = Class.new { attr_accessor :b } + @a = klass.new + end + + it 'assigns a new variable' do + @a.b ||= 10 + + @a.b.should == 10 + end + + it 're-assigns an existing variable set to false' do + @a.b = false + @a.b ||= 10 + + @a.b.should == 10 + end + + it 're-assigns an existing variable set to nil' do + @a.b = nil + @a.b ||= 10 + + @a.b.should == 10 + end + + it 'does not re-assign a variable with a truthy value' do + @a.b = 10 + @a.b ||= 20 + + @a.b.should == 10 + end + + it 'does not evaluate the right side when not needed' do + @a.b = 10 + @a.b ||= raise('should not be executed') + @a.b.should == 10 + end + + it 'does not re-assign a variable with a truthy value when using an inline rescue' do + @a.b = 10 + @a.b ||= 20 rescue 30 + + @a.b.should == 10 + end + end + end + + describe 'using &&=' do + describe 'using a single variable' do + it 'leaves new variable unassigned' do + a &&= 10 + + a.should == nil + end + + it 'leaves false' do + a = false + a &&= 10 + + a.should == false + end + + it 'leaves nil' do + a = nil + a &&= 10 + + a.should == nil + end + + it 'does not evaluate the right side when not needed' do + a = nil + a &&= raise('should not be executed') + a.should == nil + end + + it 'does re-assign a variable with a truthy value' do + a = 10 + a &&= 20 + + a.should == 20 + end + + it 'does re-assign a variable with a truthy value when using an inline rescue' do + a = 10 + a &&= 20 rescue 30 + + a.should == 20 + end + end + + describe 'using a single variable' do + before do + klass = Class.new { attr_accessor :b } + @a = klass.new + end + + it 'leaves new variable unassigned' do + @a.b &&= 10 + + @a.b.should == nil + end + + it 'leaves false' do + @a.b = false + @a.b &&= 10 + + @a.b.should == false + end + + it 'leaves nil' do + @a.b = nil + @a.b &&= 10 + + @a.b.should == nil + end + + it 'does not evaluate the right side when not needed' do + @a.b = nil + @a.b &&= raise('should not be executed') + @a.b.should == nil + end + + it 'does re-assign a variable with a truthy value' do + @a.b = 10 + @a.b &&= 20 + + @a.b.should == 20 + end + + it 'does re-assign a variable with a truthy value when using an inline rescue' do + @a.b = 10 + @a.b &&= 20 rescue 30 + + @a.b.should == 20 + end + end + end + + describe 'using compunded constants' do + before do + Object.send(:remove_const, :A) if defined? Object::A + end + + it 'with ||= assignments' do + Object::A ||= 10 + Object::A.should == 10 + end + + it 'with ||= do not reassign' do + Object::A = 20 + Object::A ||= 10 + Object::A.should == 20 + end + + it 'with &&= assignments' do + Object::A = 20 + -> { + Object::A &&= 10 + }.should complain(/already initialized constant/) + Object::A.should == 10 + end + + it 'with &&= assignments will fail with non-existant constants' do + lambda { Object::A &&= 10 }.should raise_error(NameError) + end + + it 'with operator assignments' do + Object::A = 20 + -> { + Object::A += 10 + }.should complain(/already initialized constant/) + Object::A.should == 30 + end + + it 'with operator assignments will fail with non-existant constants' do + lambda { Object::A += 10 }.should raise_error(NameError) + end + end +end diff --git a/spec/rubyspec/language/or_spec.rb b/spec/rubyspec/language/or_spec.rb new file mode 100644 index 0000000000..150d693996 --- /dev/null +++ b/spec/rubyspec/language/or_spec.rb @@ -0,0 +1,90 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The || operator" do + it "evaluates to true if any of its operands are true" do + if false || true || nil + x = true + end + x.should == true + end + + it "evaluated to false if all of its operands are false" do + if false || nil + x = true + end + x.should == nil + end + + it "is evaluated before assignment operators" do + x = nil || true + x.should == true + end + + it "has a lower precedence than the && operator" do + x = 1 || false && x = 2 + x.should == 1 + end + + it "treats empty expressions as nil" do + (() || true).should be_true + (() || false).should be_false + (true || ()).should be_true + (false || ()).should be_nil + (() || ()).should be_nil + end + + it "has a higher precedence than 'break' in 'break true || false'" do + # see also 'break true or false' below + lambda { break false || true }.call.should be_true + end + + it "has a higher precedence than 'next' in 'next true || false'" do + lambda { next false || true }.call.should be_true + end + + it "has a higher precedence than 'return' in 'return true || false'" do + lambda { return false || true }.call.should be_true + end +end + +describe "The or operator" do + it "evaluates to true if any of its operands are true" do + x = nil + if false or true + x = true + end + x.should == true + end + + it "is evaluated after variables are assigned" do + x = nil or true + x.should == nil + end + + it "has a lower precedence than the || operator" do + x,y = nil + x = true || false or y = 1 + y.should == nil + end + + it "treats empty expressions as nil" do + (() or true).should be_true + (() or false).should be_false + (true or ()).should be_true + (false or ()).should be_nil + (() or ()).should be_nil + end + + it "has a lower precedence than 'break' in 'break true or false'" do + # see also 'break true || false' above + lambda { eval "break true or false" }.should raise_error(SyntaxError, /void value expression/) + end + + it "has a lower precedence than 'next' in 'next true or false'" do + lambda { eval "next true or false" }.should raise_error(SyntaxError, /void value expression/) + end + + it "has a lower precedence than 'return' in 'return true or false'" do + lambda { eval "return true or false" }.should raise_error(SyntaxError, /void value expression/) + end +end diff --git a/spec/rubyspec/language/order_spec.rb b/spec/rubyspec/language/order_spec.rb new file mode 100644 index 0000000000..d02bf04077 --- /dev/null +++ b/spec/rubyspec/language/order_spec.rb @@ -0,0 +1,75 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "A method call" do + before :each do + @obj = Object.new + def @obj.foo0(&a) + [a ? a.call : nil] + end + def @obj.foo1(a, &b) + [a, b ? b.call : nil] + end + def @obj.foo2(a, b, &c) + [a, b, c ? c.call : nil] + end + def @obj.foo3(a, b, c, &d) + [a, b, c, d ? d.call : nil] + end + def @obj.foo4(a, b, c, d, &e) + [a, b, c, d, e ? e.call : nil] + end + end + + it "evaluates the receiver first" do + (obj = @obj).foo1(obj = nil).should == [nil, nil] + (obj = @obj).foo2(obj = nil, obj = nil).should == [nil, nil, nil] + (obj = @obj).foo3(obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil] + (obj = @obj).foo4(obj = nil, obj = nil, obj = nil, obj = nil).should == [nil, nil, nil, nil, nil] + end + + it "evaluates arguments after receiver" do + a = 0 + (a += 1; @obj).foo1(a).should == [1, nil] + (a += 1; @obj).foo2(a, a).should == [2, 2, nil] + (a += 1; @obj).foo3(a, a, a).should == [3, 3, 3, nil] + (a += 1; @obj).foo4(a, a, a, a).should == [4, 4, 4, 4, nil] + a.should == 4 + end + + it "evaluates arguments left-to-right" do + a = 0 + @obj.foo1(a += 1).should == [1, nil] + @obj.foo2(a += 1, a += 1).should == [2, 3, nil] + @obj.foo3(a += 1, a += 1, a += 1).should == [4, 5, 6, nil] + @obj.foo4(a += 1, a += 1, a += 1, a += 1).should == [7, 8, 9, 10, nil] + a.should == 10 + end + + it "evaluates block pass after arguments" do + a = 0 + p = proc {true} + @obj.foo1(a += 1, &(a += 1; p)).should == [1, true] + @obj.foo2(a += 1, a += 1, &(a += 1; p)).should == [3, 4, true] + @obj.foo3(a += 1, a += 1, a += 1, &(a += 1; p)).should == [6, 7, 8, true] + @obj.foo4(a += 1, a += 1, a += 1, a += 1, &(a += 1; p)).should == [10, 11, 12, 13, true] + a.should == 14 + end + + it "evaluates block pass after receiver" do + p1 = proc {true} + p2 = proc {false} + p1.should_not == p2 + + p = p1 + (p = p2; @obj).foo0(&p).should == [false] + p = p1 + (p = p2; @obj).foo1(1, &p).should == [1, false] + p = p1 + (p = p2; @obj).foo2(1, 1, &p).should == [1, 1, false] + p = p1 + (p = p2; @obj).foo3(1, 1, 1, &p).should == [1, 1, 1, false] + p = p1 + (p = p2; @obj).foo4(1, 1, 1, 1, &p).should == [1, 1, 1, 1, false] + p = p1 + end +end diff --git a/spec/rubyspec/language/precedence_spec.rb b/spec/rubyspec/language/precedence_spec.rb new file mode 100644 index 0000000000..90734022ff --- /dev/null +++ b/spec/rubyspec/language/precedence_spec.rb @@ -0,0 +1,448 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/precedence', __FILE__) + +# Specifying the behavior of operators in combination could +# lead to combinatorial explosion. A better way seems to be +# to use a technique from formal proofs that involve a set of +# equivalent statements. Suppose you have statements A, B, C. +# If they are claimed to be equivalent, this can be shown by +# proving that A implies B, B implies C, and C implies A. +# (Actually any closed circuit of implications.) +# +# Here, we can use a similar technique where we show starting +# at the top that each level of operator has precedence over +# the level below (as well as showing associativity within +# the precedence level). + +=begin +Excerpted from 'Programming Ruby: The Pragmatic Programmer's Guide' +Second Edition by Dave Thomas, Chad Fowler, and Andy Hunt, page 324 + +Table 22.4. Ruby operators (high to low precedence) +Method Operator Description +----------------------------------------------------------------------- + :: . + x* [ ] [ ]= Element reference, element set + x ** Exponentiation + x ! ~ + - Not, complement, unary plus and minus + (method names for the last two are +@ and -@) + x * / % Multiply, divide, and modulo + x + - Plus and minus + x >> << Right and left shift + x & “And” (bitwise for integers) + x ^ | Exclusive “or” and regular “or” (bitwise for integers) + x <= < > >= Comparison operators + x <=> == === != =~ !~ Equality and pattern match operators (!= + and !~ may not be defined as methods) + && Logical “and” + || Logical “or” + .. ... Range (inclusive and exclusive) + ? : Ternary if-then-else + = %= /= -= += |= &= Assignment + >>= <<= *= &&= ||= **= + defined? Check if symbol defined + not Logical negation + or and Logical composition + if unless while until Expression modifiers + begin/end Block expression +----------------------------------------------------------------------- + +* Operators marked with 'x' in the Method column are implemented as methods +and can be overridden (except != and !~ as noted). (But see the specs +below for implementations that define != and !~ as methods.) + +** These are not included in the excerpted table but are shown here for +completeness. +=end + +# ----------------------------------------------------------------------- +# It seems that this table is not correct anymore +# The correct table derived from MRI's parse.y is as follows: +# +# Operator Assoc Description +#--------------------------------------------------------------- +# ! ~ + > Not, complement, unary plus +# ** > Exponentiation +# - > Unary minus +# * / % < Multiply, divide, and modulo +# + - < Plus and minus +# >> << < Right and left shift +# & < “And” (bitwise for integers) +# ^ | < Exclusive “or” and regular “or” (bitwise for integers) +# <= < > >= < Comparison operators +# <=> == === != =~ !~ no Equality and pattern match operators (!= +# and !~ may not be defined as methods) +# && < Logical “and” +# || < Logical “or” +# .. ... no Range (inclusive and exclusive) +# ? : > Ternary if-then-else +# rescue < Rescue modifier +# = %= /= -= += |= &= > Assignment +# >>= <<= *= &&= ||= **= +# defined? no Check if symbol defined +# not > Logical negation +# or and < Logical composition +# if unless while until no Expression modifiers +# ----------------------------------------------------------------------- +# +# [] and []= seem to fall out of here, as well as begin/end +# + +# TODO: Resolve these two tables with actual specs. As the comment at the +# top suggests, these specs need to be reorganized into a single describe +# block for each operator. The describe block should include an example +# for associativity (if relevant), an example for any short circuit behavior +# (e.g. &&, ||, etc.) and an example block for each operator over which the +# instant operator has immediately higher precedence. + +describe "Operators" do + it "! ~ + is right-associative" do + (!!true).should == true + (~~0).should == 0 + (++2).should == 2 + end + + it "** is right-associative" do + (2**2**3).should == 256 + end + + it "** has higher precedence than unary minus" do + (-2**2).should == -4 + end + + it "unary minus is right-associative" do + (--2).should == 2 + end + + it "unary minus has higher precedence than * / %" do + class UnaryMinusTest; def -@; 50; end; end + b = UnaryMinusTest.new + + (-b * 5).should == 250 + (-b / 5).should == 10 + (-b % 7).should == 1 + end + + it "treats +/- as a regular send if the arguments are known locals or block locals" do + a = PrecedenceSpecs::NonUnaryOpTest.new + a.add_num(1).should == [3] + a.sub_num(1).should == [1] + a.add_str.should == ['11'] + a.add_var.should == [2] + end + + it "* / % are left-associative" do + (2*1/2).should == (2*1)/2 + # Guard against the Mathn library + # TODO: Make these specs not rely on specific behaviour / result values + # by using mocks. + conflicts_with :Prime do + (2*1/2).should_not == 2*(1/2) + end + + (10/7/5).should == (10/7)/5 + (10/7/5).should_not == 10/(7/5) + + (101 % 55 % 7).should == (101 % 55) % 7 + (101 % 55 % 7).should_not == 101 % (55 % 7) + + (50*20/7%42).should == ((50*20)/7)%42 + (50*20/7%42).should_not == 50*(20/(7%42)) + end + + it "* / % have higher precedence than + -" do + (2+2*2).should == 6 + (1+10/5).should == 3 + (2+10%5).should == 2 + + (2-2*2).should == -2 + (1-10/5).should == -1 + (10-10%4).should == 8 + end + + it "+ - are left-associative" do + (2-3-4).should == -5 + (4-3+2).should == 3 + + binary_plus = Class.new(String) do + alias_method :plus, :+ + def +(a) + plus(a) + "!" + end + end + s = binary_plus.new("a") + + (s+s+s).should == (s+s)+s + (s+s+s).should_not == s+(s+s) + end + + it "+ - have higher precedence than >> <<" do + (2<<1+2).should == 16 + (8>>1+2).should == 1 + (4<<1-3).should == 1 + (2>>1-3).should == 8 + end + + it ">> << are left-associative" do + (1 << 2 << 3).should == 32 + (10 >> 1 >> 1).should == 2 + (10 << 4 >> 1).should == 80 + end + + it ">> << have higher precedence than &" do + (4 & 2 << 1).should == 4 + (2 & 4 >> 1).should == 2 + end + + it "& is left-associative" do + class BitwiseAndTest; def &(a); a+1; end; end + c = BitwiseAndTest.new + + (c & 5 & 2).should == (c & 5) & 2 + (c & 5 & 2).should_not == c & (5 & 2) + end + + it "& has higher precedence than ^ |" do + (8 ^ 16 & 16).should == 24 + (8 | 16 & 16).should == 24 + end + + it "^ | are left-associative" do + class OrAndXorTest; def ^(a); a+10; end; def |(a); a-10; end; end + d = OrAndXorTest.new + + (d ^ 13 ^ 16).should == (d ^ 13) ^ 16 + (d ^ 13 ^ 16).should_not == d ^ (13 ^ 16) + + (d | 13 | 4).should == (d | 13) | 4 + (d | 13 | 4).should_not == d | (13 | 4) + end + + it "^ | have higher precedence than <= < > >=" do + (10 <= 7 ^ 7).should == false + (10 < 7 ^ 7).should == false + (10 > 7 ^ 7).should == true + (10 >= 7 ^ 7).should == true + (10 <= 7 | 7).should == false + (10 < 7 | 7).should == false + (10 > 7 | 7).should == true + (10 >= 7 | 7).should == true + end + + it "<= < > >= are left-associative" do + class ComparisonTest + def <=(a); 0; end; + def <(a); 0; end; + def >(a); 0; end; + def >=(a); 0; end; + end + + e = ComparisonTest.new + + (e <= 0 <= 1).should == (e <= 0) <= 1 + (e <= 0 <= 1).should_not == e <= (0 <= 1) + + (e < 0 < 1).should == (e < 0) < 1 + (e < 0 < 1).should_not == e < (0 < 1) + + (e >= 0 >= 1).should == (e >= 0) >= 1 + (e >= 0 >= 1).should_not == e >= (0 >= 1) + + (e > 0 > 1).should == (e > 0) > 1 + (e > 0 > 1).should_not == e > (0 > 1) + end + + it "<=> == === != =~ !~ are non-associative" do + lambda { eval("1 <=> 2 <=> 3") }.should raise_error(SyntaxError) + lambda { eval("1 == 2 == 3") }.should raise_error(SyntaxError) + lambda { eval("1 === 2 === 3") }.should raise_error(SyntaxError) + lambda { eval("1 != 2 != 3") }.should raise_error(SyntaxError) + lambda { eval("1 =~ 2 =~ 3") }.should raise_error(SyntaxError) + lambda { eval("1 !~ 2 !~ 3") }.should raise_error(SyntaxError) + end + + it "<=> == === != =~ !~ have higher precedence than &&" do + (false && 2 <=> 3).should == false + (false && 3 == false).should == false + (false && 3 === false).should == false + (false && 3 != true).should == false + + class FalseClass; def =~(o); o == false; end; end + (false && true =~ false).should == (false && (true =~ false)) + (false && true =~ false).should_not == ((false && true) =~ false) + class FalseClass; undef_method :=~; end + + (false && true !~ true).should == false + end + + # XXX: figure out how to test it + # (a && b) && c equals to a && (b && c) for all a,b,c values I can imagine so far + it "&& is left-associative" + + it "&& has higher precedence than ||" do + (true || false && false).should == true + end + + # XXX: figure out how to test it + it "|| is left-associative" + + it "|| has higher precedence than .. ..." do + (1..false||10).should == (1..10) + (1...false||10).should == (1...10) + end + + it ".. ... are non-associative" do + lambda { eval("1..2..3") }.should raise_error(SyntaxError) + lambda { eval("1...2...3") }.should raise_error(SyntaxError) + end + +# XXX: this is commented now due to a bug in compiler, which cannot +# distinguish between range and flip-flop operator so far. zenspider is +# currently working on a new lexer, which will be able to do that. +# As soon as it's done, these piece should be reenabled. +# +# it ".. ... have higher precedence than ? :" do +# (1..2 ? 3 : 4).should == 3 +# (1...2 ? 3 : 4).should == 3 +# end + + it "? : is right-associative" do + (true ? 2 : 3 ? 4 : 5).should == 2 + end + + def oops; raise end + + it "? : has higher precedence than rescue" do + (true ? oops : 0 rescue 10).should == 10 + end + + # XXX: figure how to test it (problem similar to || associativity) + it "rescue is left-associative" + + it "rescue has higher precedence than =" do + a = oops rescue 10 + a.should == 10 + + # rescue doesn't have the same sense for %= /= and friends + end + + it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= are right-associative" do + a = b = 10 + a.should == 10 + b.should == 10 + + a = b = 10 + a %= b %= 3 + a.should == 0 + b.should == 1 + + a = b = 10 + a /= b /= 2 + a.should == 2 + b.should == 5 + + a = b = 10 + a -= b -= 2 + a.should == 2 + b.should == 8 + + a = b = 10 + a += b += 2 + a.should == 22 + b.should == 12 + + a,b = 32,64 + a |= b |= 2 + a.should == 98 + b.should == 66 + + a,b = 25,13 + a &= b &= 7 + a.should == 1 + b.should == 5 + + a,b=8,2 + a >>= b >>= 1 + a.should == 4 + b.should == 1 + + a,b=8,2 + a <<= b <<= 1 + a.should == 128 + b.should == 4 + + a,b=8,2 + a *= b *= 2 + a.should == 32 + b.should == 4 + + a,b=10,20 + a &&= b &&= false + a.should == false + b.should == false + + a,b=nil,nil + a ||= b ||= 10 + a.should == 10 + b.should == 10 + + a,b=2,3 + a **= b **= 2 + a.should == 512 + b.should == 9 + end + + it "= %= /= -= += |= &= >>= <<= *= &&= ||= **= have higher precedence than defined? operator" do + (defined? a = 10).should == "assignment" + (defined? a %= 10).should == "assignment" + (defined? a /= 10).should == "assignment" + (defined? a -= 10).should == "assignment" + (defined? a += 10).should == "assignment" + (defined? a |= 10).should == "assignment" + (defined? a &= 10).should == "assignment" + (defined? a >>= 10).should == "assignment" + (defined? a <<= 10).should == "assignment" + (defined? a *= 10).should == "assignment" + (defined? a &&= 10).should == "assignment" + (defined? a ||= 10).should == "assignment" + (defined? a **= 10).should == "assignment" + end + + # XXX: figure out how to test it + it "defined? is non-associative" + + it "defined? has higher precedence than not" do + # does it have sense? + (not defined? qqq).should == true + end + + it "not is right-associative" do + (not not false).should == false + (not not 10).should == true + end + + it "not has higher precedence than or/and" do + (not false and false).should == false + (not false or true).should == true + end + + # XXX: figure out how to test it + it "or/and are left-associative" + + it "or/and have higher precedence than if unless while until modifiers" do + (1 if 2 and 3).should == 1 + (1 if 2 or 3).should == 1 + + (1 unless false and true).should == 1 + (1 unless false or false).should == 1 + + (1 while true and false).should == nil # would hang upon error + (1 while false or false).should == nil + + ((raise until true and false) rescue 10).should == 10 + (1 until false or true).should == nil # would hang upon error + end + + # XXX: it seems to me they are right-associative + it "if unless while until are non-associative" +end diff --git a/spec/rubyspec/language/predefined/data_spec.rb b/spec/rubyspec/language/predefined/data_spec.rb new file mode 100644 index 0000000000..f616879527 --- /dev/null +++ b/spec/rubyspec/language/predefined/data_spec.rb @@ -0,0 +1,29 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "The DATA constant" do + it "exists when the main script contains __END__" do + ruby_exe(fixture(__FILE__, "data1.rb")).chomp.should == "true" + end + + it "does not exist when the main script contains no __END__" do + ruby_exe("puts Object.const_defined?(:DATA)").chomp.should == 'false' + end + + it "does not exist when an included file has a __END__" do + ruby_exe(fixture(__FILE__, "data2.rb")).chomp.should == "false" + end + + it "does not change when an included files also has a __END__" do + ruby_exe(fixture(__FILE__, "data3.rb")).chomp.should == "data 3" + end + + it "is included in an otherwise empty file" do + ap = fixture(__FILE__, "print_data.rb") + str = ruby_exe(fixture(__FILE__, "data_only.rb"), options: "-r#{ap}") + str.chomp.should == "data only" + end + + it "rewinds to the head of the main script" do + ruby_exe(fixture(__FILE__, "data5.rb")).chomp.should == "DATA.rewind" + end +end diff --git a/spec/rubyspec/language/predefined/fixtures/data1.rb b/spec/rubyspec/language/predefined/fixtures/data1.rb new file mode 100644 index 0000000000..cb9572255b --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data1.rb @@ -0,0 +1,4 @@ +puts Object.const_defined?(:DATA) + +__END__ +data1 diff --git a/spec/rubyspec/language/predefined/fixtures/data2.rb b/spec/rubyspec/language/predefined/fixtures/data2.rb new file mode 100644 index 0000000000..0f714b06d4 --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data2.rb @@ -0,0 +1,4 @@ + +require File.expand_path("../data4.rb", __FILE__) + +p Object.const_defined?(:DATA) diff --git a/spec/rubyspec/language/predefined/fixtures/data3.rb b/spec/rubyspec/language/predefined/fixtures/data3.rb new file mode 100644 index 0000000000..6cbf63dae6 --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data3.rb @@ -0,0 +1,7 @@ + +require File.expand_path("../data4.rb", __FILE__) + +puts DATA.read + +__END__ +data 3 diff --git a/spec/rubyspec/language/predefined/fixtures/data4.rb b/spec/rubyspec/language/predefined/fixtures/data4.rb new file mode 100644 index 0000000000..139ef80d7b --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data4.rb @@ -0,0 +1,4 @@ +# nothing + +__END__ +data 4 diff --git a/spec/rubyspec/language/predefined/fixtures/data5.rb b/spec/rubyspec/language/predefined/fixtures/data5.rb new file mode 100644 index 0000000000..48f060e1a9 --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data5.rb @@ -0,0 +1,5 @@ +DATA.rewind +puts DATA.gets + +__END__ +data 5 diff --git a/spec/rubyspec/language/predefined/fixtures/data_only.rb b/spec/rubyspec/language/predefined/fixtures/data_only.rb new file mode 100644 index 0000000000..004ac62737 --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/data_only.rb @@ -0,0 +1,2 @@ +__END__ +data only diff --git a/spec/rubyspec/language/predefined/fixtures/print_data.rb b/spec/rubyspec/language/predefined/fixtures/print_data.rb new file mode 100644 index 0000000000..4a5692e6a7 --- /dev/null +++ b/spec/rubyspec/language/predefined/fixtures/print_data.rb @@ -0,0 +1,3 @@ +at_exit { + puts DATA.read +} diff --git a/spec/rubyspec/language/predefined_spec.rb b/spec/rubyspec/language/predefined_spec.rb new file mode 100644 index 0000000000..6bbd2e216f --- /dev/null +++ b/spec/rubyspec/language/predefined_spec.rb @@ -0,0 +1,1221 @@ +require File.expand_path('../../spec_helper', __FILE__) +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. + +=begin +Exception Information +--------------------------------------------------------------------------------------------------- + +$! Exception The exception object passed to raise. [thread] +$@ Array The stack backtrace generated by the last exception. [thread] +=end + +=begin +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 $9 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 $9 are all derived from $~. Assigning to $~ + changes the values of these derived variables. This variable is local to the + current scope. [thread] +=end + + +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) + $~.object_id.should == md.object_id + + /bar/ =~ 'bar' + $~.should be_kind_of(MatchData) + $~.object_id.should_not == md.object_id + end + + it "is set to nil if the last match was unsuccessful" do + /foo/ =~ 'foo' + $~.nil?.should == false + + /foo/ =~ 'bar' + $~.nil?.should == true + 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 + + eval '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) + + lambda { $~ = Object.new }.should raise_error(TypeError) + lambda { $~ = 1 }.should raise_error(TypeError) + 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 + + with_feature :encoding do + it "sets the encoding to the encoding of the source String" do + "abc".force_encoding(Encoding::EUC_JP) =~ /b/ + $&.encoding.should equal(Encoding::EUC_JP) + end + 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 + + with_feature :encoding do + it "sets the encoding to the encoding of the source String" do + "abc".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".force_encoding(Encoding::ISO_8859_1) =~ /a/ + $`.encoding.should equal(Encoding::ISO_8859_1) + end + 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 + + with_feature :encoding do + it "sets the encoding to the encoding of the source String" do + "abc".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".force_encoding(Encoding::ISO_8859_1) =~ /c/ + $'.encoding.should equal(Encoding::ISO_8859_1) + end + 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 + + with_feature :encoding do + it "sets the encoding to the encoding of the source String" do + "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ + $+.encoding.should equal(Encoding::EUC_JP) + end + 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 + + with_feature :encoding do + it "sets the encoding to the encoding of the source String" do + "abc".force_encoding(Encoding::EUC_JP) =~ /(b)/ + $1.encoding.should equal(Encoding::EUC_JP) + end + 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 + lambda { $stdout = nil }.should raise_error(TypeError) + end + + it "raises TypeError error if assigned to object that doesn't respond to #write" do + obj = mock('object') + lambda { $stdout = obj }.should raise_error(TypeError) + + obj.stub!(:write) + $stdout = obj + $stdout.should equal(obj) + end +end + +describe "Predefined global $!" do + # 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 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 + +=begin +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. +$. Fixnum 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. +=end + +describe "Predefined global $/" do + before :each do + @dollar_slash = $/ + @dollar_dash_zero = $-0 + end + + after :each do + $/ = @dollar_slash + $-0 = @dollar_dash_zero + 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 "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) + + lambda { $/ = obj }.should raise_error(TypeError) + end + + it "raises a TypeError if assigned a Fixnum" do + lambda { $/ = 1 }.should raise_error(TypeError) + end + + it "raises a TypeError if assigned a boolean" do + lambda { $/ = true }.should raise_error(TypeError) + end +end + +describe "Predefined global $-0" do + before :each do + @dollar_slash = $/ + @dollar_dash_zero = $-0 + end + + after :each do + $/ = @dollar_slash + $-0 = @dollar_dash_zero + end + + it "can be assigned a String" do + str = "abc" + $-0 = str + $-0.should equal(str) + 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) + + lambda { $-0 = obj }.should raise_error(TypeError) + end + + it "raises a TypeError if assigned a Fixnum" do + lambda { $-0 = 1 }.should raise_error(TypeError) + end + + it "raises a TypeError if assigned a boolean" do + lambda { $-0 = true }.should raise_error(TypeError) + 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 + lambda { $, = Object.new }.should raise_error(TypeError) + 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 + + eval '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 + +=begin +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] +$$ Fixnum 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] +$SAFE Fixnum The current safe level. This variable’s value may never be + reduced by assignment. [thread] (Not implemented in Rubinius) +$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. +=end +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") + end + + it "is read-only" do + lambda { + $: = [] + }.should raise_error(NameError) + + lambda { + $LOAD_PATH = [] + }.should raise_error(NameError) + + lambda { + $-I = [] + }.should raise_error(NameError) + end +end + +describe "Global variable $\"" do + it "is an alias for $LOADED_FEATURES" do + $".object_id.should == $LOADED_FEATURES.object_id + end + + it "is read-only" do + lambda { + $" = [] + }.should raise_error(NameError) + + lambda { + $LOADED_FEATURES = [] + }.should raise_error(NameError) + end +end + +describe "Global variable $<" do + it "is read-only" do + lambda { + $< = nil + }.should raise_error(NameError) + end +end + +describe "Global variable $FILENAME" do + it "is read-only" do + lambda { + $FILENAME = "-" + }.should raise_error(NameError) + end +end + +describe "Global variable $?" do + it "is read-only" do + lambda { + $? = nil + }.should raise_error(NameError) + 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 + lambda { $-a = true }.should raise_error(NameError) + end +end + +describe "Global variable $-l" do + it "is read-only" do + lambda { $-l = true }.should raise_error(NameError) + end +end + +describe "Global variable $-p" do + it "is read-only" do + lambda { $-p = true }.should raise_error(NameError) + 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 :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(File.dirname(__FILE__)) 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 + lambda { $0 = nil }.should raise_error(TypeError) + end +end + +=begin +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] +=end + +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 + lambda { eval("nil = true") }.should raise_error(SyntaxError) + 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 + lambda { eval("true = false") }.should raise_error(SyntaxError) + 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 + lambda { eval("false = nil") }.should raise_error(SyntaxError) + end +end + +describe "The self pseudo-variable" do + it "raises a SyntaxError if assigned to" do + lambda { eval("self = 1") }.should raise_error(SyntaxError) + end +end + +=begin +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. +NIL NilClass Synonym for nil. +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. +=end + +describe "The predefined global constants" do + ruby_version_is ""..."2.4" do + it "includes TRUE" do + Object.const_defined?(:TRUE).should == true + TRUE.should equal(true) + end + + it "includes FALSE" do + Object.const_defined?(:FALSE).should == true + FALSE.should equal(false) + end + + it "includes NIL" do + Object.const_defined?(:NIL).should == true + NIL.should equal(nil) + end + end + + ruby_version_is "2.4" do + it "includes TRUE" do + Object.const_defined?(:TRUE).should == true + -> { + TRUE.should equal(true) + }.should complain(/constant ::TRUE is deprecated/) + end + + it "includes FALSE" do + Object.const_defined?(:FALSE).should == true + -> { + FALSE.should equal(false) + }.should complain(/constant ::FALSE is deprecated/) + end + + it "includes NIL" do + Object.const_defined?(:NIL).should == true + -> { + NIL.should equal(nil) + }.should complain(/constant ::NIL is deprecated/) + 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 + +with_feature :encoding do + 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 + 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 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 + + 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 + + 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 +end diff --git a/spec/rubyspec/language/private_spec.rb b/spec/rubyspec/language/private_spec.rb new file mode 100644 index 0000000000..796c0c1711 --- /dev/null +++ b/spec/rubyspec/language/private_spec.rb @@ -0,0 +1,67 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/private', __FILE__) + +describe "The private keyword" do + it "marks following methods as being private" do + a = Private::A.new + a.methods.should_not include(:bar) + lambda { a.bar }.should raise_error(NoMethodError) + + b = Private::B.new + b.methods.should_not include(:bar) + lambda { b.bar }.should raise_error(NoMethodError) + end + + # def expr.meth() methods are always public + it "has no effect on def expr.meth() methods" do + Private::B.public_defs_method.should == 0 + end + + it "is overridden when a new class is opened" do + c = Private::B::C.new + c.methods.should include(:baz) + c.baz + Private::B.public_class_method1.should == 1 + lambda { Private::B.private_class_method1 }.should raise_error(NoMethodError) + end + + it "is no longer in effect when the class is closed" do + b = Private::B.new + b.methods.should include(:foo) + b.foo + end + + it "changes visibility of previously called method" do + klass = Class.new do + def foo + "foo" + end + end + f = klass.new + f.foo + klass.class_eval do + private :foo + end + lambda { f.foo }.should raise_error(NoMethodError) + end + + it "changes visiblity of previously called methods with same send/call site" do + g = ::Private::G.new + lambda { + 2.times do + g.foo + module ::Private + class G + private :foo + end + end + end + }.should raise_error(NoMethodError) + end + + it "changes the visibility of the existing method in the subclass" do + ::Private::A.new.foo.should == 'foo' + lambda {::Private::H.new.foo}.should raise_error(NoMethodError) + ::Private::H.new.send(:foo).should == 'foo' + end +end diff --git a/spec/rubyspec/language/proc_spec.rb b/spec/rubyspec/language/proc_spec.rb new file mode 100644 index 0000000000..bbef318826 --- /dev/null +++ b/spec/rubyspec/language/proc_spec.rb @@ -0,0 +1,220 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "A Proc" do + it "captures locals from the surrounding scope" do + var = 1 + lambda { var }.call.should == 1 + end + + it "does not capture a local when an argument has the same name" do + var = 1 + lambda { |var| var }.call(2).should == 2 + var.should == 1 + end + + describe "taking zero arguments" do + before :each do + @l = lambda { 1 } + end + + it "does not raise an exception if no values are passed" do + @l.call.should == 1 + end + + it "raises an ArgumentErro if a value is passed" do + lambda { @l.call(0) }.should raise_error(ArgumentError) + end + end + + describe "taking || arguments" do + before :each do + @l = lambda { || 1 } + end + + it "does not raise an exception when passed no values" do + @l.call.should == 1 + end + + it "raises an ArgumentError if a value is passed" do + lambda { @l.call(0) }.should raise_error(ArgumentError) + end + end + + describe "taking |a| arguments" do + before :each do + @l = lambda { |a| a } + end + + it "assigns the value passed to the argument" do + @l.call(2).should == 2 + end + + it "does not destructure a single Array value" do + @l.call([1, 2]).should == [1, 2] + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @l.call(obj).should equal(obj) + end + + it "raises an ArgumentError if no value is passed" do + lambda { @l.call }.should raise_error(ArgumentError) + end + end + + describe "taking |a, b| arguments" do + before :each do + @l = lambda { |a, b| [a, b] } + end + + it "raises an ArgumentError if passed no values" do + lambda { @l.call }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if passed one value" do + lambda { @l.call(0) }.should raise_error(ArgumentError) + end + + it "assigns the values passed to the arguments" do + @l.call(1, 2).should == [1, 2] + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("proc call to_ary") + obj.should_not_receive(:to_ary) + + lambda { @l.call(obj) }.should raise_error(ArgumentError) + end + end + + describe "taking |a, *b| arguments" do + before :each do + @l = lambda { |a, *b| [a, b] } + end + + it "raises an ArgumentError if passed no values" do + lambda { @l.call }.should raise_error(ArgumentError) + end + + it "does not destructure a single Array value yielded" do + @l.call([1, 2, 3]).should == [[1, 2, 3], []] + end + + it "assigns all passed values after the first to the rest argument" do + @l.call(1, 2, 3).should == [1, [2, 3]] + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @l.call(obj).should == [obj, []] + end + end + + describe "taking |*| arguments" do + before :each do + @l = lambda { |*| 1 } + end + + it "does not raise an exception when passed no values" do + @l.call.should == 1 + end + + it "does not raise an exception when passed multiple values" do + @l.call(2, 3, 4).should == 1 + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @l.call(obj).should == 1 + end + end + + describe "taking |*a| arguments" do + before :each do + @l = lambda { |*a| a } + end + + it "assigns [] to the argument when passed no values" do + @l.call.should == [] + end + + it "assigns the argument an Array wrapping one passed value" do + @l.call(1).should == [1] + end + + it "assigns the argument an Array wrapping all values passed" do + @l.call(1, 2, 3).should == [1, 2, 3] + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @l.call(obj).should == [obj] + end + end + + describe "taking |a, | arguments" do + before :each do + @l = lambda { |a, | a } + end + + it "raises an ArgumentError when passed no values" do + lambda { @l.call }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed more than one value" do + lambda { @l.call(1, 2) }.should raise_error(ArgumentError) + end + + it "assigns the argument the value passed" do + @l.call(1).should == 1 + end + + it "does not destructure when passed a single Array" do + @l.call([1,2]).should == [1, 2] + end + + it "does not call #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_not_receive(:to_ary) + + @l.call(obj).should == obj + end + end + + describe "taking |(a, b)| arguments" do + before :each do + @l = lambda { |(a, b)| [a, b] } + end + + it "raises an ArgumentError when passed no values" do + lambda { @l.call }.should raise_error(ArgumentError) + end + + it "destructures a single Array value yielded" do + @l.call([1, 2]).should == [1, 2] + end + + it "calls #to_ary to convert a single passed object to an Array" do + obj = mock("block yield to_ary") + obj.should_receive(:to_ary).and_return([1, 2]) + + @l.call(obj).should == [1, 2] + end + + it "raises a TypeError if #to_ary does not return an Array" do + obj = mock("block yield to_ary invalid") + obj.should_receive(:to_ary).and_return(1) + + lambda { @l.call(obj) }.should raise_error(TypeError) + end + end +end diff --git a/spec/rubyspec/language/redo_spec.rb b/spec/rubyspec/language/redo_spec.rb new file mode 100644 index 0000000000..53fd30b4f2 --- /dev/null +++ b/spec/rubyspec/language/redo_spec.rb @@ -0,0 +1,66 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The redo statement" do + it "restarts block execution if used within block" do + a = [] + lambda { + a << 1 + redo if a.size < 2 + a << 2 + }.call + a.should == [1, 1, 2] + end + + it "re-executes the closest loop" do + exist = [2,3] + processed = [] + order = [] + [1,2,3,4].each do |x| + order << x + begin + processed << x + if exist.include?(x) + raise StandardError, "included" + end + rescue StandardError + exist.delete(x) + redo + end + end + processed.should == [1,2,2,3,3,4] + exist.should == [] + order.should == [1,2,2,3,3,4] + end + + it "re-executes the last step in enumeration" do + list = [] + [1,2,3].each do |x| + list << x + break if list.size == 6 + redo if x == 3 + end + list.should == [1,2,3,3,3,3] + end + + it "triggers ensure block when re-executing a block" do + list = [] + [1,2,3].each do |x| + list << x + begin + list << 10*x + redo if list.count(1) == 1 + ensure + list << 100*x + end + end + list.should == [1,10,100,1,10,100,2,20,200,3,30,300] + end + + describe "in a method" do + it "is invalid and raises a SyntaxError" do + lambda { + eval("def m; redo; end") + }.should raise_error(SyntaxError) + end + end +end diff --git a/spec/rubyspec/language/regexp/anchors_spec.rb b/spec/rubyspec/language/regexp/anchors_spec.rb new file mode 100644 index 0000000000..c6a620a221 --- /dev/null +++ b/spec/rubyspec/language/regexp/anchors_spec.rb @@ -0,0 +1,179 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with anchors" do + it "supports ^ (line start anchor)" do + # Basic matching + /^foo/.match("foo").to_a.should == ["foo"] + /^bar/.match("foo\nbar").to_a.should == ["bar"] + # Basic non-matching + /^foo/.match(" foo").should be_nil + /foo^/.match("foo\n\n\n").should be_nil + + # A bit advanced + /^^^foo/.match("foo").to_a.should == ["foo"] + (/^[^f]/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["\n"] + (/($^)($^)/ =~ "foo\n\n").should == "foo\n".size and $~.to_a.should == ["", "", ""] + + # Different start of line chars + /^bar/.match("foo\rbar").should be_nil + /^bar/.match("foo\0bar").should be_nil + + # Trivial + /^/.match("foo").to_a.should == [""] + + # Grouping + /(^foo)/.match("foo").to_a.should == ["foo", "foo"] + /(^)/.match("foo").to_a.should == ["", ""] + /(foo\n^)(^bar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo\n", "bar"] + end + + it "does not match ^ after trailing \\n" do + /^(?!\A)/.match("foo\n").should be_nil # There is no (empty) line after a trailing \n + end + + it "supports $ (line end anchor)" do + # Basic matching + /foo$/.match("foo").to_a.should == ["foo"] + /foo$/.match("foo\nbar").to_a.should == ["foo"] + # Basic non-matching + /foo$/.match("foo ").should be_nil + /$foo/.match("\n\n\nfoo").should be_nil + + # A bit advanced + /foo$$$/.match("foo").to_a.should == ["foo"] + (/[^o]$/ =~ "foo\n\n").should == ("foo\n".size - 1) and $~.to_a.should == ["\n"] + + # Different end of line chars + /foo$/.match("foo\r\nbar").should be_nil + /foo$/.match("foo\0bar").should be_nil + + # Trivial + (/$/ =~ "foo").should == "foo".size and $~.to_a.should == [""] + + # Grouping + /(foo$)/.match("foo").to_a.should == ["foo", "foo"] + (/($)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""] + /(foo$)($\nbar)/.match("foo\nbar").to_a.should == ["foo\nbar", "foo", "\nbar"] + end + + it "supports \\A (string start anchor)" do + # Basic matching + /\Afoo/.match("foo").to_a.should == ["foo"] + # Basic non-matching + /\Abar/.match("foo\nbar").should be_nil + /\Afoo/.match(" foo").should be_nil + + # A bit advanced + /\A\A\Afoo/.match("foo").to_a.should == ["foo"] + /(\A\Z)(\A\Z)/.match("").to_a.should == ["", "", ""] + + # Different start of line chars + /\Abar/.match("foo\0bar").should be_nil + + # Grouping + /(\Afoo)/.match("foo").to_a.should == ["foo", "foo"] + /(\A)/.match("foo").to_a.should == ["", ""] + end + + it "supports \\Z (string end anchor, including before trailing \\n)" do + # Basic matching + /foo\Z/.match("foo").to_a.should == ["foo"] + /foo\Z/.match("foo\n").to_a.should == ["foo"] + # Basic non-matching + /foo\Z/.match("foo\nbar").should be_nil + /foo\Z/.match("foo ").should be_nil + + # A bit advanced + /foo\Z\Z\Z/.match("foo\n").to_a.should == ["foo"] + (/($\Z)($\Z)/ =~ "foo\n").should == "foo".size and $~.to_a.should == ["", "", ""] + (/(\z\Z)(\z\Z)/ =~ "foo\n").should == "foo\n".size and $~.to_a.should == ["", "", ""] + + # Different end of line chars + /foo\Z/.match("foo\0bar").should be_nil + /foo\Z/.match("foo\r\n").should be_nil + + # Grouping + /(foo\Z)/.match("foo").to_a.should == ["foo", "foo"] + (/(\Z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""] + end + + it "supports \\z (string end anchor)" do + # Basic matching + /foo\z/.match("foo").to_a.should == ["foo"] + # Basic non-matching + /foo\z/.match("foo\nbar").should be_nil + /foo\z/.match("foo\n").should be_nil + /foo\z/.match("foo ").should be_nil + + # A bit advanced + /foo\z\z\z/.match("foo").to_a.should == ["foo"] + (/($\z)($\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", "", ""] + + # Different end of line chars + /foo\z/.match("foo\0bar").should be_nil + /foo\z/.match("foo\r\nbar").should be_nil + + # Grouping + /(foo\z)/.match("foo").to_a.should == ["foo", "foo"] + (/(\z)/ =~ "foo").should == "foo".size and $~.to_a.should == ["", ""] + end + + it "supports \\b (word boundary)" do + # Basic matching + /foo\b/.match("foo").to_a.should == ["foo"] + /foo\b/.match("foo\n").to_a.should == ["foo"] + LanguageSpecs.white_spaces.scan(/./).each do |c| + /foo\b/.match("foo" + c).to_a.should == ["foo"] + end + LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c| + /foo\b/.match("foo" + c).to_a.should == ["foo"] + end + /foo\b/.match("foo\0").to_a.should == ["foo"] + # Basic non-matching + /foo\b/.match("foobar").should be_nil + /foo\b/.match("foo123").should be_nil + /foo\b/.match("foo_").should be_nil + end + + it "supports \\B (non-word-boundary)" do + # Basic matching + /foo\B/.match("foobar").to_a.should == ["foo"] + /foo\B/.match("foo123").to_a.should == ["foo"] + /foo\B/.match("foo_").to_a.should == ["foo"] + # Basic non-matching + /foo\B/.match("foo").should be_nil + /foo\B/.match("foo\n").should be_nil + LanguageSpecs.white_spaces.scan(/./).each do |c| + /foo\B/.match("foo" + c).should be_nil + end + LanguageSpecs.non_alphanum_non_space.scan(/./).each do |c| + /foo\B/.match("foo" + c).should be_nil + end + /foo\B/.match("foo\0").should be_nil + end + + it "supports (?= ) (positive lookahead)" do + /foo.(?=bar)/.match("foo1 foo2bar").to_a.should == ["foo2"] + end + + it "supports (?! ) (negative lookahead)" do + /foo.(?!bar)/.match("foo1bar foo2").to_a.should == ["foo2"] + end + + it "supports (?!<) (negative lookbehind)" do + /(? (backreference to previous group match)" do + /(foo.)\1/.match("foo1foo1").to_a.should == ["foo1foo1", "foo1"] + /(foo.)\1/.match("foo1foo2").should be_nil + end + + it "resets nested \ backreference before match of outer subexpression" do + /(a\1?){2}/.match("aaaa").to_a.should == ["aa", "a"] + end +end diff --git a/spec/rubyspec/language/regexp/character_classes_spec.rb b/spec/rubyspec/language/regexp/character_classes_spec.rb new file mode 100644 index 0000000000..c922ba8a5a --- /dev/null +++ b/spec/rubyspec/language/regexp/character_classes_spec.rb @@ -0,0 +1,617 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexp with character classes" do + it "supports \\w (word character)" do + /\w/.match("a").to_a.should == ["a"] + /\w/.match("1").to_a.should == ["1"] + /\w/.match("_").to_a.should == ["_"] + + # Non-matches + /\w/.match(LanguageSpecs.white_spaces).should be_nil + /\w/.match(LanguageSpecs.non_alphanum_non_space).should be_nil + /\w/.match("\0").should be_nil + end + + it "supports \\W (non-word character)" do + /\W+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces] + /\W+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space] + /\W/.match("\0").to_a.should == ["\0"] + + # Non-matches + /\W/.match("a").should be_nil + /\W/.match("1").should be_nil + /\W/.match("_").should be_nil + end + + it "supports \\s (space character)" do + /\s+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces] + + # Non-matches + /\s/.match("a").should be_nil + /\s/.match("1").should be_nil + /\s/.match(LanguageSpecs.non_alphanum_non_space).should be_nil + /\s/.match("\0").should be_nil + end + + it "supports \\S (non-space character)" do + /\S/.match("a").to_a.should == ["a"] + /\S/.match("1").to_a.should == ["1"] + /\S+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space] + /\S/.match("\0").to_a.should == ["\0"] + + # Non-matches + /\S/.match(LanguageSpecs.white_spaces).should be_nil + end + + it "supports \\d (numeric digit)" do + /\d/.match("1").to_a.should == ["1"] + + # Non-matches + /\d/.match("a").should be_nil + /\d/.match(LanguageSpecs.white_spaces).should be_nil + /\d/.match(LanguageSpecs.non_alphanum_non_space).should be_nil + /\d/.match("\0").should be_nil + end + + it "supports \\D (non-digit)" do + /\D/.match("a").to_a.should == ["a"] + /\D+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces] + /\D+/.match(LanguageSpecs.non_alphanum_non_space).to_a.should == [LanguageSpecs.non_alphanum_non_space] + /\D/.match("\0").to_a.should == ["\0"] + + # Non-matches + /\D/.match("1").should be_nil + end + + it "supports [] (character class)" do + /[a-z]+/.match("fooBAR").to_a.should == ["foo"] + /[\b]/.match("\b").to_a.should == ["\b"] # \b inside character class is backspace + end + + it "supports [[:alpha:][:digit:][:etc:]] (predefined character classes)" do + /[[:alnum:]]+/.match("a1").to_a.should == ["a1"] + /[[:alpha:]]+/.match("Aa1").to_a.should == ["Aa"] + /[[:blank:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.blanks] + # /[[:cntrl:]]/.match("").to_a.should == [""] # TODO: what should this match? + /[[:digit:]]/.match("1").to_a.should == ["1"] + # /[[:graph:]]/.match("").to_a.should == [""] # TODO: what should this match? + /[[:lower:]]+/.match("Aa1").to_a.should == ["a"] + /[[:print:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [" "] # include all of multibyte encoded characters + /[[:punct:]]+/.match(LanguageSpecs.punctuations).to_a.should == [LanguageSpecs.punctuations] + /[[:space:]]+/.match(LanguageSpecs.white_spaces).to_a.should == [LanguageSpecs.white_spaces] + /[[:upper:]]+/.match("123ABCabc").to_a.should == ["ABC"] + /[[:xdigit:]]+/.match("xyz0123456789ABCDEFabcdefXYZ").to_a.should == ["0123456789ABCDEFabcdef"] + + # Parsing + /[[:lower:][:digit:]A-C]+/.match("a1ABCDEF").to_a.should == ["a1ABC"] # can be composed with other constructs in the character class + /[^[:lower:]A-C]+/.match("abcABCDEF123def").to_a.should == ["DEF123"] # negated character class + /[:alnum:]+/.match("a:l:n:u:m").to_a.should == ["a:l:n:u:m"] # should behave like regular character class composed of the individual letters + /[\[:alnum:]+/.match("[:a:l:n:u:m").to_a.should == ["[:a:l:n:u:m"] # should behave like regular character class composed of the individual letters + lambda { eval('/[[:alpha:]-[:digit:]]/') }.should raise_error(SyntaxError) # can't use character class as a start value of range + end + + it "matches ASCII characters with [[:ascii:]]" do + "\x00".match(/[[:ascii:]]/).to_a.should == ["\x00"] + "\x7F".match(/[[:ascii:]]/).to_a.should == ["\x7F"] + end + + not_supported_on :opal do + it "doesn't match non-ASCII characters with [[:ascii:]]" do + /[[:ascii:]]/.match("\u{80}").should be_nil + /[[:ascii:]]/.match("\u{9898}").should be_nil + end + end + + it "matches Unicode letter characters with [[:alnum:]]" do + "à".match(/[[:alnum:]]/).to_a.should == ["à"] + end + + it "matches Unicode digits with [[:alnum:]]" do + "\u{0660}".match(/[[:alnum:]]/).to_a.should == ["\u{0660}"] + end + + it "doesn't matches Unicode marks with [[:alnum:]]" do + "\u{36F}".match(/[[:alnum:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:alnum:]]" do + "\u{16}".match(/[[:alnum:]]/).to_a.should == [] + end + + it "doesn't match Unicode punctuation characters with [[:alnum:]]" do + "\u{3F}".match(/[[:alnum:]]/).to_a.should == [] + end + + it "matches Unicode letter characters with [[:alpha:]]" do + "à".match(/[[:alpha:]]/).to_a.should == ["à"] + end + + it "doesn't match Unicode digits with [[:alpha:]]" do + "\u{0660}".match(/[[:alpha:]]/).to_a.should == [] + end + + it "doesn't matches Unicode marks with [[:alpha:]]" do + "\u{36F}".match(/[[:alpha:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:alpha:]]" do + "\u{16}".match(/[[:alpha:]]/).to_a.should == [] + end + + it "doesn't match Unicode punctuation characters with [[:alpha:]]" do + "\u{3F}".match(/[[:alpha:]]/).to_a.should == [] + end + + it "matches Unicode space characters with [[:blank:]]" do + "\u{1680}".match(/[[:blank:]]/).to_a.should == ["\u{1680}"] + end + + it "doesn't match Unicode control characters with [[:blank:]]" do + "\u{16}".match(/[[:blank:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:blank:]]" do + "\u{3F}".match(/[[:blank:]]/).should be_nil + end + + it "doesn't match Unicode letter characters with [[:blank:]]" do + "à".match(/[[:blank:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:blank:]]" do + "\u{0660}".match(/[[:blank:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:blank:]]" do + "\u{36F}".match(/[[:blank:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:blank:]]" do + "\u{16}".match(/[[:blank:]]/).should be_nil + end + + it "doesn't Unicode letter characters with [[:cntrl:]]" do + "à".match(/[[:cntrl:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:cntrl:]]" do + "\u{0660}".match(/[[:cntrl:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:cntrl:]]" do + "\u{36F}".match(/[[:cntrl:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:cntrl:]]" do + "\u{3F}".match(/[[:cntrl:]]/).should be_nil + end + + it "matches Unicode control characters with [[:cntrl:]]" do + "\u{16}".match(/[[:cntrl:]]/).to_a.should == ["\u{16}"] + end + + it "doesn't match Unicode format characters with [[:cntrl:]]" do + "\u{2060}".match(/[[:cntrl:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:cntrl:]]" do + "\u{E001}".match(/[[:cntrl:]]/).should be_nil + end + + it "doesn't match Unicode letter characters with [[:digit:]]" do + "à".match(/[[:digit:]]/).should be_nil + end + + it "matches Unicode digits with [[:digit:]]" do + "\u{0660}".match(/[[:digit:]]/).to_a.should == ["\u{0660}"] + "\u{FF12}".match(/[[:digit:]]/).to_a.should == ["\u{FF12}"] + end + + it "doesn't match Unicode marks with [[:digit:]]" do + "\u{36F}".match(/[[:digit:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:digit:]]" do + "\u{3F}".match(/[[:digit:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:digit:]]" do + "\u{16}".match(/[[:digit:]]/).should be_nil + end + + it "doesn't match Unicode format characters with [[:digit:]]" do + "\u{2060}".match(/[[:digit:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:digit:]]" do + "\u{E001}".match(/[[:digit:]]/).should be_nil + end + + it "matches Unicode letter characters with [[:graph:]]" do + "à".match(/[[:graph:]]/).to_a.should == ["à"] + end + + it "matches Unicode digits with [[:graph:]]" do + "\u{0660}".match(/[[:graph:]]/).to_a.should == ["\u{0660}"] + "\u{FF12}".match(/[[:graph:]]/).to_a.should == ["\u{FF12}"] + end + + it "matches Unicode marks with [[:graph:]]" do + "\u{36F}".match(/[[:graph:]]/).to_a.should ==["\u{36F}"] + end + + it "matches Unicode punctuation characters with [[:graph:]]" do + "\u{3F}".match(/[[:graph:]]/).to_a.should == ["\u{3F}"] + end + + it "doesn't match Unicode control characters with [[:graph:]]" do + "\u{16}".match(/[[:graph:]]/).should be_nil + end + + it "match Unicode format characters with [[:graph:]]" do + "\u{2060}".match(/[[:graph:]]/).to_a.should == ["\u2060"] + end + + it "match Unicode private-use characters with [[:graph:]]" do + "\u{E001}".match(/[[:graph:]]/).to_a.should == ["\u{E001}"] + end + + it "matches Unicode lowercase letter characters with [[:lower:]]" do + "\u{FF41}".match(/[[:lower:]]/).to_a.should == ["\u{FF41}"] + "\u{1D484}".match(/[[:lower:]]/).to_a.should == ["\u{1D484}"] + "\u{E8}".match(/[[:lower:]]/).to_a.should == ["\u{E8}"] + end + + it "doesn't match Unicode uppercase letter characters with [[:lower:]]" do + "\u{100}".match(/[[:lower:]]/).should be_nil + "\u{130}".match(/[[:lower:]]/).should be_nil + "\u{405}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode title-case characters with [[:lower:]]" do + "\u{1F88}".match(/[[:lower:]]/).should be_nil + "\u{1FAD}".match(/[[:lower:]]/).should be_nil + "\u{01C5}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:lower:]]" do + "\u{0660}".match(/[[:lower:]]/).should be_nil + "\u{FF12}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:lower:]]" do + "\u{36F}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:lower:]]" do + "\u{3F}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:lower:]]" do + "\u{16}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode format characters with [[:lower:]]" do + "\u{2060}".match(/[[:lower:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:lower:]]" do + "\u{E001}".match(/[[:lower:]]/).should be_nil + end + + it "matches Unicode lowercase letter characters with [[:print:]]" do + "\u{FF41}".match(/[[:print:]]/).to_a.should == ["\u{FF41}"] + "\u{1D484}".match(/[[:print:]]/).to_a.should == ["\u{1D484}"] + "\u{E8}".match(/[[:print:]]/).to_a.should == ["\u{E8}"] + end + + it "matches Unicode uppercase letter characters with [[:print:]]" do + "\u{100}".match(/[[:print:]]/).to_a.should == ["\u{100}"] + "\u{130}".match(/[[:print:]]/).to_a.should == ["\u{130}"] + "\u{405}".match(/[[:print:]]/).to_a.should == ["\u{405}"] + end + + it "matches Unicode title-case characters with [[:print:]]" do + "\u{1F88}".match(/[[:print:]]/).to_a.should == ["\u{1F88}"] + "\u{1FAD}".match(/[[:print:]]/).to_a.should == ["\u{1FAD}"] + "\u{01C5}".match(/[[:print:]]/).to_a.should == ["\u{01C5}"] + end + + it "matches Unicode digits with [[:print:]]" do + "\u{0660}".match(/[[:print:]]/).to_a.should == ["\u{0660}"] + "\u{FF12}".match(/[[:print:]]/).to_a.should == ["\u{FF12}"] + end + + it "matches Unicode marks with [[:print:]]" do + "\u{36F}".match(/[[:print:]]/).to_a.should == ["\u{36F}"] + end + + it "matches Unicode punctuation characters with [[:print:]]" do + "\u{3F}".match(/[[:print:]]/).to_a.should == ["\u{3F}"] + end + + it "doesn't match Unicode control characters with [[:print:]]" do + "\u{16}".match(/[[:print:]]/).should be_nil + end + + it "match Unicode format characters with [[:print:]]" do + "\u{2060}".match(/[[:print:]]/).to_a.should == ["\u{2060}"] + end + + it "match Unicode private-use characters with [[:print:]]" do + "\u{E001}".match(/[[:print:]]/).to_a.should == ["\u{E001}"] + end + + + it "doesn't match Unicode lowercase letter characters with [[:punct:]]" do + "\u{FF41}".match(/[[:punct:]]/).should be_nil + "\u{1D484}".match(/[[:punct:]]/).should be_nil + "\u{E8}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode uppercase letter characters with [[:punct:]]" do + "\u{100}".match(/[[:punct:]]/).should be_nil + "\u{130}".match(/[[:punct:]]/).should be_nil + "\u{405}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode title-case characters with [[:punct:]]" do + "\u{1F88}".match(/[[:punct:]]/).should be_nil + "\u{1FAD}".match(/[[:punct:]]/).should be_nil + "\u{01C5}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:punct:]]" do + "\u{0660}".match(/[[:punct:]]/).should be_nil + "\u{FF12}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:punct:]]" do + "\u{36F}".match(/[[:punct:]]/).should be_nil + end + + it "matches Unicode Pc characters with [[:punct:]]" do + "\u{203F}".match(/[[:punct:]]/).to_a.should == ["\u{203F}"] + end + + it "matches Unicode Pd characters with [[:punct:]]" do + "\u{2E17}".match(/[[:punct:]]/).to_a.should == ["\u{2E17}"] + end + + it "matches Unicode Ps characters with [[:punct:]]" do + "\u{0F3A}".match(/[[:punct:]]/).to_a.should == ["\u{0F3A}"] + end + + it "matches Unicode Pe characters with [[:punct:]]" do + "\u{2046}".match(/[[:punct:]]/).to_a.should == ["\u{2046}"] + end + + it "matches Unicode Pi characters with [[:punct:]]" do + "\u{00AB}".match(/[[:punct:]]/).to_a.should == ["\u{00AB}"] + end + + it "matches Unicode Pf characters with [[:punct:]]" do + "\u{201D}".match(/[[:punct:]]/).to_a.should == ["\u{201D}"] + end + + it "matches Unicode Pf characters with [[:punct:]]" do + "\u{00BB}".match(/[[:punct:]]/).to_a.should == ["\u{00BB}"] + end + + it "matches Unicode Po characters with [[:punct:]]" do + "\u{00BF}".match(/[[:punct:]]/).to_a.should == ["\u{00BF}"] + end + + it "doesn't match Unicode format characters with [[:punct:]]" do + "\u{2060}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:punct:]]" do + "\u{E001}".match(/[[:punct:]]/).should be_nil + end + + it "doesn't match Unicode lowercase letter characters with [[:space:]]" do + "\u{FF41}".match(/[[:space:]]/).should be_nil + "\u{1D484}".match(/[[:space:]]/).should be_nil + "\u{E8}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode uppercase letter characters with [[:space:]]" do + "\u{100}".match(/[[:space:]]/).should be_nil + "\u{130}".match(/[[:space:]]/).should be_nil + "\u{405}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode title-case characters with [[:space:]]" do + "\u{1F88}".match(/[[:space:]]/).should be_nil + "\u{1FAD}".match(/[[:space:]]/).should be_nil + "\u{01C5}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:space:]]" do + "\u{0660}".match(/[[:space:]]/).should be_nil + "\u{FF12}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:space:]]" do + "\u{36F}".match(/[[:space:]]/).should be_nil + end + + it "matches Unicode Zs characters with [[:space:]]" do + "\u{205F}".match(/[[:space:]]/).to_a.should == ["\u{205F}"] + end + + it "matches Unicode Zl characters with [[:space:]]" do + "\u{2028}".match(/[[:space:]]/).to_a.should == ["\u{2028}"] + end + + it "matches Unicode Zp characters with [[:space:]]" do + "\u{2029}".match(/[[:space:]]/).to_a.should == ["\u{2029}"] + end + + it "doesn't match Unicode format characters with [[:space:]]" do + "\u{2060}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:space:]]" do + "\u{E001}".match(/[[:space:]]/).should be_nil + end + + it "doesn't match Unicode lowercase characters with [[:upper:]]" do + "\u{FF41}".match(/[[:upper:]]/).should be_nil + "\u{1D484}".match(/[[:upper:]]/).should be_nil + "\u{E8}".match(/[[:upper:]]/).should be_nil + end + + it "matches Unicode uppercase characters with [[:upper:]]" do + "\u{100}".match(/[[:upper:]]/).to_a.should == ["\u{100}"] + "\u{130}".match(/[[:upper:]]/).to_a.should == ["\u{130}"] + "\u{405}".match(/[[:upper:]]/).to_a.should == ["\u{405}"] + end + + it "doesn't match Unicode title-case characters with [[:upper:]]" do + "\u{1F88}".match(/[[:upper:]]/).should be_nil + "\u{1FAD}".match(/[[:upper:]]/).should be_nil + "\u{01C5}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode digits with [[:upper:]]" do + "\u{0660}".match(/[[:upper:]]/).should be_nil + "\u{FF12}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:upper:]]" do + "\u{36F}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:upper:]]" do + "\u{3F}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:upper:]]" do + "\u{16}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode format characters with [[:upper:]]" do + "\u{2060}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:upper:]]" do + "\u{E001}".match(/[[:upper:]]/).should be_nil + end + + it "doesn't match Unicode letter characters [^a-fA-F] with [[:xdigit:]]" do + "à".match(/[[:xdigit:]]/).should be_nil + "g".match(/[[:xdigit:]]/).should be_nil + "X".match(/[[:xdigit:]]/).should be_nil + end + + it "matches Unicode letter characters [a-fA-F] with [[:xdigit:]]" do + "a".match(/[[:xdigit:]]/).to_a.should == ["a"] + "F".match(/[[:xdigit:]]/).to_a.should == ["F"] + end + + it "doesn't match Unicode digits [^0-9] with [[:xdigit:]]" do + "\u{0660}".match(/[[:xdigit:]]/).should be_nil + "\u{FF12}".match(/[[:xdigit:]]/).should be_nil + end + + it "doesn't match Unicode marks with [[:xdigit:]]" do + "\u{36F}".match(/[[:xdigit:]]/).should be_nil + end + + it "doesn't match Unicode punctuation characters with [[:xdigit:]]" do + "\u{3F}".match(/[[:xdigit:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:xdigit:]]" do + "\u{16}".match(/[[:xdigit:]]/).should be_nil + end + + it "doesn't match Unicode format characters with [[:xdigit:]]" do + "\u{2060}".match(/[[:xdigit:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:xdigit:]]" do + "\u{E001}".match(/[[:xdigit:]]/).should be_nil + end + + it "matches Unicode lowercase characters with [[:word:]]" do + "\u{FF41}".match(/[[:word:]]/).to_a.should == ["\u{FF41}"] + "\u{1D484}".match(/[[:word:]]/).to_a.should == ["\u{1D484}"] + "\u{E8}".match(/[[:word:]]/).to_a.should == ["\u{E8}"] + end + + it "matches Unicode uppercase characters with [[:word:]]" do + "\u{100}".match(/[[:word:]]/).to_a.should == ["\u{100}"] + "\u{130}".match(/[[:word:]]/).to_a.should == ["\u{130}"] + "\u{405}".match(/[[:word:]]/).to_a.should == ["\u{405}"] + end + + it "matches Unicode title-case characters with [[:word:]]" do + "\u{1F88}".match(/[[:word:]]/).to_a.should == ["\u{1F88}"] + "\u{1FAD}".match(/[[:word:]]/).to_a.should == ["\u{1FAD}"] + "\u{01C5}".match(/[[:word:]]/).to_a.should == ["\u{01C5}"] + end + + it "matches Unicode decimal digits with [[:word:]]" do + "\u{FF10}".match(/[[:word:]]/).to_a.should == ["\u{FF10}"] + "\u{096C}".match(/[[:word:]]/).to_a.should == ["\u{096C}"] + end + + it "matches Unicode marks with [[:word:]]" do + "\u{36F}".match(/[[:word:]]/).to_a.should == ["\u{36F}"] + end + + it "match Unicode Nl characters with [[:word:]]" do + "\u{16EE}".match(/[[:word:]]/).to_a.should == ["\u{16EE}"] + end + + it "doesn't match Unicode No characters with [[:word:]]" do + "\u{17F0}".match(/[[:word:]]/).should be_nil + end + it "doesn't match Unicode punctuation characters with [[:word:]]" do + "\u{3F}".match(/[[:word:]]/).should be_nil + end + + it "doesn't match Unicode control characters with [[:word:]]" do + "\u{16}".match(/[[:word:]]/).should be_nil + end + + it "doesn't match Unicode format characters with [[:word:]]" do + "\u{2060}".match(/[[:word:]]/).should be_nil + end + + it "doesn't match Unicode private-use characters with [[:word:]]" do + "\u{E001}".match(/[[:word:]]/).should be_nil + end + + it "matches unicode named character properties" do + "a1".match(/\p{Alpha}/).to_a.should == ["a"] + end + + it "matches unicode abbreviated character properties" do + "a1".match(/\p{L}/).to_a.should == ["a"] + end + + it "matches unicode script properties" do + "a\u06E9b".match(/\p{Arabic}/).to_a.should == ["\u06E9"] + end + + it "matches unicode Han properties" do + "松本行弘 Ruby".match(/\p{Han}+/u).to_a.should == ["松本行弘"] + end + + it "matches unicode Hiragana properties" do + "Ruby(ルビー)、まつもとゆきひろ".match(/\p{Hiragana}+/u).to_a.should == ["まつもとゆきひろ"] + end + + it "matches unicode Katakana properties" do + "Ruby(ルビー)、まつもとゆきひろ".match(/\p{Katakana}+/u).to_a.should == ["ルビ"] + end + + it "matches unicode Hangul properties" do + "루비(Ruby)".match(/\p{Hangul}+/u).to_a.should == ["루비"] + end +end diff --git a/spec/rubyspec/language/regexp/encoding_spec.rb b/spec/rubyspec/language/regexp/encoding_spec.rb new file mode 100644 index 0000000000..1f62244a28 --- /dev/null +++ b/spec/rubyspec/language/regexp/encoding_spec.rb @@ -0,0 +1,103 @@ +# -*- encoding: binary -*- +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with encoding modifiers" do + it "supports /e (EUC encoding)" do + match = /./e.match("\303\251".force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + end + + it "supports /e (EUC encoding) with interpolation" do + match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + end + + it "supports /e (EUC encoding) with interpolation /o" do + match = /#{/./}/e.match("\303\251".force_encoding(Encoding::EUC_JP)) + match.to_a.should == ["\303\251".force_encoding(Encoding::EUC_JP)] + end + + it 'uses EUC-JP as /e encoding' do + /./e.encoding.should == Encoding::EUC_JP + end + + it 'preserves EUC-JP as /e encoding through interpolation' do + /#{/./}/e.encoding.should == Encoding::EUC_JP + end + + it "supports /n (No encoding)" do + /./n.match("\303\251").to_a.should == ["\303"] + end + + it "supports /n (No encoding) with interpolation" do + /#{/./}/n.match("\303\251").to_a.should == ["\303"] + end + + it "supports /n (No encoding) with interpolation /o" do + /#{/./}/n.match("\303\251").to_a.should == ["\303"] + end + + it 'uses US-ASCII as /n encoding if all chars are 7-bit' do + /./n.encoding.should == Encoding::US_ASCII + end + + it 'uses ASCII-8BIT as /n encoding if not all chars are 7-bit' do + /\xFF/n.encoding.should == Encoding::ASCII_8BIT + end + + it 'preserves US-ASCII as /n encoding through interpolation if all chars are 7-bit' do + /.#{/./}/n.encoding.should == Encoding::US_ASCII + end + + it 'preserves ASCII-8BIT as /n encoding through interpolation if all chars are 7-bit' do + /\xFF#{/./}/n.encoding.should == Encoding::ASCII_8BIT + end + + it "supports /s (Windows_31J encoding)" do + match = /./s.match("\303\251".force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + end + + it "supports /s (Windows_31J encoding) with interpolation" do + match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + end + + it "supports /s (Windows_31J encoding) with interpolation and /o" do + match = /#{/./}/s.match("\303\251".force_encoding(Encoding::Windows_31J)) + match.to_a.should == ["\303".force_encoding(Encoding::Windows_31J)] + end + + it 'uses Windows-31J as /s encoding' do + /./s.encoding.should == Encoding::Windows_31J + end + + it 'preserves Windows-31J as /s encoding through interpolation' do + /#{/./}/s.encoding.should == Encoding::Windows_31J + end + + it "supports /u (UTF8 encoding)" do + /./u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + end + + it "supports /u (UTF8 encoding) with interpolation" do + /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + end + + it "supports /u (UTF8 encoding) with interpolation and /o" do + /#{/./}/u.match("\303\251".force_encoding('utf-8')).to_a.should == ["\u{e9}"] + end + + it 'uses UTF-8 as /u encoding' do + /./u.encoding.should == Encoding::UTF_8 + end + + it 'preserves UTF-8 as /u encoding through interpolation' do + /#{/./}/u.encoding.should == Encoding::UTF_8 + end + + it "selects last of multiple encoding specifiers" do + /foo/ensuensuens.should == /foo/s + end +end diff --git a/spec/rubyspec/language/regexp/escapes_spec.rb b/spec/rubyspec/language/regexp/escapes_spec.rb new file mode 100644 index 0000000000..50ac22e51e --- /dev/null +++ b/spec/rubyspec/language/regexp/escapes_spec.rb @@ -0,0 +1,81 @@ +# -*- encoding: binary -*- +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with escape characters" do + it "they're supported" do + /\t/.match("\t").to_a.should == ["\t"] # horizontal tab + /\v/.match("\v").to_a.should == ["\v"] # vertical tab + /\n/.match("\n").to_a.should == ["\n"] # newline + /\r/.match("\r").to_a.should == ["\r"] # return + /\f/.match("\f").to_a.should == ["\f"] # form feed + /\a/.match("\a").to_a.should == ["\a"] # bell + /\e/.match("\e").to_a.should == ["\e"] # escape + + # \nnn octal char (encoded byte value) + end + + it "support quoting meta-characters via escape sequence" do + /\\/.match("\\").to_a.should == ["\\"] + /\//.match("/").to_a.should == ["/"] + # parenthesis, etc + /\(/.match("(").to_a.should == ["("] + /\)/.match(")").to_a.should == [")"] + /\[/.match("[").to_a.should == ["["] + /\]/.match("]").to_a.should == ["]"] + /\{/.match("{").to_a.should == ["{"] + /\}/.match("}").to_a.should == ["}"] + # alternation separator + /\|/.match("|").to_a.should == ["|"] + # quantifiers + /\?/.match("?").to_a.should == ["?"] + /\./.match(".").to_a.should == ["."] + /\*/.match("*").to_a.should == ["*"] + /\+/.match("+").to_a.should == ["+"] + # line anchors + /\^/.match("^").to_a.should == ["^"] + /\$/.match("$").to_a.should == ["$"] + end + + it "allows any character to be escaped" do + /\y/.match("y").to_a.should == ["y"] + end + + it "support \\x (hex characters)" do + /\xA/.match("\nxyz").to_a.should == ["\n"] + /\x0A/.match("\n").to_a.should == ["\n"] + /\xAA/.match("\nA").should be_nil + /\x0AA/.match("\nA").to_a.should == ["\nA"] + /\xAG/.match("\nG").to_a.should == ["\nG"] + # Non-matches + lambda { eval('/\xG/') }.should raise_error(SyntaxError) + + # \x{7HHHHHHH} wide hexadecimal char (character code point value) + end + + it "support \\c (control characters)" do + #/\c \c@\c`/.match("\00\00\00").to_a.should == ["\00\00\00"] + /\c#\cc\cC/.match("\03\03\03").to_a.should == ["\03\03\03"] + /\c'\cG\cg/.match("\a\a\a").to_a.should == ["\a\a\a"] + /\c(\cH\ch/.match("\b\b\b").to_a.should == ["\b\b\b"] + /\c)\cI\ci/.match("\t\t\t").to_a.should == ["\t\t\t"] + /\c*\cJ\cj/.match("\n\n\n").to_a.should == ["\n\n\n"] + /\c+\cK\ck/.match("\v\v\v").to_a.should == ["\v\v\v"] + /\c,\cL\cl/.match("\f\f\f").to_a.should == ["\f\f\f"] + /\c-\cM\cm/.match("\r\r\r").to_a.should == ["\r\r\r"] + + /\cJ/.match("\r").should be_nil + + # Parsing precedence + /\cJ+/.match("\n\n").to_a.should == ["\n\n"] # Quantifers apply to entire escape sequence + /\\cJ/.match("\\cJ").to_a.should == ["\\cJ"] + lambda { eval('/[abc\x]/') }.should raise_error(SyntaxError) # \x is treated as a escape sequence even inside a character class + # Syntax error + lambda { eval('/\c/') }.should raise_error(SyntaxError) + + # \cx control char (character code point value) + # \C-x control char (character code point value) + # \M-x meta (x|0x80) (character code point value) + # \M-\C-x meta control char (character code point value) + end +end diff --git a/spec/rubyspec/language/regexp/grouping_spec.rb b/spec/rubyspec/language/regexp/grouping_spec.rb new file mode 100644 index 0000000000..443cab7ee0 --- /dev/null +++ b/spec/rubyspec/language/regexp/grouping_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with grouping" do + it "support ()" do + /(a)/.match("a").to_a.should == ["a", "a"] + end + + it "allows groups to be nested" do + md = /(hay(st)a)ck/.match('haystack') + md.to_a.should == ['haystack','haysta', 'st'] + end + + it "raises a SyntaxError when parentheses aren't balanced" do + lambda { eval "/(hay(st)ack/" }.should raise_error(SyntaxError) + end + + it "supports (?: ) (non-capturing group)" do + /(?:foo)(bar)/.match("foobar").to_a.should == ["foobar", "bar"] + # Parsing precedence + /(?:xdigit:)/.match("xdigit:").to_a.should == ["xdigit:"] + end +end diff --git a/spec/rubyspec/language/regexp/interpolation_spec.rb b/spec/rubyspec/language/regexp/interpolation_spec.rb new file mode 100644 index 0000000000..5536c718f1 --- /dev/null +++ b/spec/rubyspec/language/regexp/interpolation_spec.rb @@ -0,0 +1,58 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with interpolation" do + + it "allows interpolation of strings" do + str = "foo|bar" + /#{str}/.should == /foo|bar/ + end + + it "allows interpolation of literal regexps" do + re = /foo|bar/ + /#{re}/.should == /(?-mix:foo|bar)/ + end + + it "allows interpolation of any object that responds to to_s" do + o = Object.new + def o.to_s + "object_with_to_s" + end + /#{o}/.should == /object_with_to_s/ + end + + it "allows interpolation which mixes modifiers" do + re = /foo/i + /#{re} bar/m.should == /(?i-mx:foo) bar/m + end + + it "allows interpolation to interact with other Regexp constructs" do + str = "foo)|(bar" + /(#{str})/.should == /(foo)|(bar)/ + + str = "a" + /[#{str}-z]/.should == /[a-z]/ + end + + it "gives precedence to escape sequences over substitution" do + str = "J" + /\c#{str}/.to_s.should == '(?-mix:\c#' + '{str})' + end + + it "throws RegexpError for malformed interpolation" do + s = "" + lambda { /(#{s}/ }.should raise_error(RegexpError) + s = "(" + lambda { /#{s}/ }.should raise_error(RegexpError) + end + + it "allows interpolation in extended mode" do + var = "#comment\n foo #comment\n | bar" + (/#{var}/x =~ "foo").should == (/foo|bar/ =~ "foo") + end + + it "allows escape sequences in interpolated regexps" do + escape_seq = %r{"\x80"}n + %r{#{escape_seq}}n.should == /(?-mix:"\x80")/n + end +end diff --git a/spec/rubyspec/language/regexp/modifiers_spec.rb b/spec/rubyspec/language/regexp/modifiers_spec.rb new file mode 100644 index 0000000000..03dec26f3f --- /dev/null +++ b/spec/rubyspec/language/regexp/modifiers_spec.rb @@ -0,0 +1,110 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with modifers" do + it "supports /i (case-insensitive)" do + /foo/i.match("FOO").to_a.should == ["FOO"] + end + + it "supports /m (multiline)" do + /foo.bar/m.match("foo\nbar").to_a.should == ["foo\nbar"] + /foo.bar/.match("foo\nbar").should be_nil + end + + it "supports /x (extended syntax)" do + /\d +/x.match("abc123").to_a.should == ["123"] # Quantifiers can be separated from the expression they apply to + end + + it "supports /o (once)" do + 2.times do |i| + /#{i}/o.should == /0/ + end + end + + it "invokes substitutions for /o only once" do + ScratchPad.record [] + o = Object.new + def o.to_s + ScratchPad << :to_s + "class_with_to_s" + end + eval "2.times { /#{o}/o }" + ScratchPad.recorded.should == [:to_s] + end + + it "supports modifier combinations" do + /foo/imox.match("foo").to_a.should == ["foo"] + /foo/imoximox.match("foo").to_a.should == ["foo"] + + lambda { eval('/foo/a') }.should raise_error(SyntaxError) + end + + it "supports (?imx-imx) (inline modifiers)" do + /(?i)foo/.match("FOO").to_a.should == ["FOO"] + /foo(?i)/.match("FOO").should be_nil + # Interaction with /i + /(?-i)foo/i.match("FOO").should be_nil + /foo(?-i)/i.match("FOO").to_a.should == ["FOO"] + # Multiple uses + /foo (?i)bar (?-i)baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"] + /foo (?i)bar (?-i)baz/.match("foo BAR BAZ").should be_nil + + /(?m)./.match("\n").to_a.should == ["\n"] + /.(?m)/.match("\n").should be_nil + # Interaction with /m + /(?-m)./m.match("\n").should be_nil + /.(?-m)/m.match("\n").to_a.should == ["\n"] + # Multiple uses + /. (?m). (?-m)./.match(". \n .").to_a.should == [". \n ."] + /. (?m). (?-m)./.match(". \n \n").should be_nil + + /(?x) foo /.match("foo").to_a.should == ["foo"] + / foo (?x)/.match("foo").should be_nil + # Interaction with /x + /(?-x) foo /x.match("foo").should be_nil + / foo (?-x)/x.match("foo").to_a.should == ["foo"] + # Multiple uses + /( foo )(?x)( bar )(?-x)( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", "bar", " baz "] + /( foo )(?x)( bar )(?-x)( baz )/.match(" foo barbaz").should be_nil + + # Parsing + /(?i-i)foo/.match("FOO").should be_nil + /(?ii)foo/.match("FOO").to_a.should == ["FOO"] + /(?-)foo/.match("foo").to_a.should == ["foo"] + lambda { eval('/(?o)/') }.should raise_error(SyntaxError) + end + + it "supports (?imx-imx:expr) (scoped inline modifiers)" do + /foo (?i:bar) baz/.match("foo BAR baz").to_a.should == ["foo BAR baz"] + /foo (?i:bar) baz/.match("foo BAR BAZ").should be_nil + /foo (?-i:bar) baz/i.match("foo BAR BAZ").should be_nil + + /. (?m:.) ./.match(". \n .").to_a.should == [". \n ."] + /. (?m:.) ./.match(". \n \n").should be_nil + /. (?-m:.) ./m.match("\n \n \n").should be_nil + + /( foo )(?x: bar )( baz )/.match(" foo bar baz ").to_a.should == [" foo bar baz ", " foo ", " baz "] + /( foo )(?x: bar )( baz )/.match(" foo barbaz").should be_nil + /( foo )(?-x: bar )( baz )/x.match("foo bar baz").to_a.should == ["foo bar baz", "foo", "baz"] + + # Parsing + /(?i-i:foo)/.match("FOO").should be_nil + /(?ii:foo)/.match("FOO").to_a.should == ["FOO"] + /(?-:)foo/.match("foo").to_a.should == ["foo"] + lambda { eval('/(?o:)/') }.should raise_error(SyntaxError) + end + + it "supports . with /m" do + # Basic matching + /./m.match("\n").to_a.should == ["\n"] + end + + it "supports ASII/Unicode modifiers" do + eval('/(?a)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a"] + eval('/(?d)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"] + eval('/(?u)[[:alpha:]]+/').match("a\u3042").to_a.should == ["a\u3042"] + eval('/(?a)\w+/').match("a\u3042").to_a.should == ["a"] + eval('/(?d)\w+/').match("a\u3042").to_a.should == ["a"] + eval('/(?u)\w+/').match("a\u3042").to_a.should == ["a\u3042"] + end +end diff --git a/spec/rubyspec/language/regexp/repetition_spec.rb b/spec/rubyspec/language/regexp/repetition_spec.rb new file mode 100644 index 0000000000..2fc8a74a47 --- /dev/null +++ b/spec/rubyspec/language/regexp/repetition_spec.rb @@ -0,0 +1,57 @@ +require File.expand_path('../../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/classes', __FILE__) + +describe "Regexps with repetition" do + it "supports * (0 or more of previous subexpression)" do + /a*/.match("aaa").to_a.should == ["aaa"] + /a*/.match("bbb").to_a.should == [""] + /<.*>/.match("foo").to_a.should == ["foo"] # it is greedy + end + + it "supports *? (0 or more of previous subexpression - lazy)" do + /a*?/.match("aaa").to_a.should == [""] + /<.*?>/.match("foo").to_a.should == [""] + end + + it "supports + (1 or more of previous subexpression)" do + /a+/.match("aaa").to_a.should == ["aaa"] + /a+/.match("bbb").should be_nil + /<.+>/.match("foo").to_a.should == ["foo"] # it is greedy + end + + it "supports +? (0 or more of previous subexpression - lazy)" do + /a+?/.match("aaa").to_a.should == ["a"] + /<.+?>/.match("foo").to_a.should == [""] + end + + it "supports {m,n} (m to n of previous subexpression)" do + /a{2,4}/.match("aaaaaa").to_a.should == ["aaaa"] + /<.{1,}>/.match("foo").to_a.should == ["foo"] # it is greedy + end + + it "supports {m,n}? (m to n of previous subexpression) - lazy)" do + /<.{1,}?>/.match("foo").to_a.should == [""] + /.([0-9]){3,5}?foo/.match("9876543210foo").to_a.should == ["543210foo", "0"] + end + + ruby_version_is ""..."2.4" do + it "does not treat {m,n}+ as possessive" do + @regexp = eval "/foo(A{0,1}+)Abar/" + @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"] + end + end + + ruby_version_is "2.4" do + it "does not treat {m,n}+ as possessive" do + -> { + @regexp = eval "/foo(A{0,1}+)Abar/" + }.should complain(/nested repeat operato/) + @regexp.match("fooAAAbar").to_a.should == ["fooAAAbar", "AA"] + end + end + + it "supports ? (0 or 1 of previous subexpression)" do + /a?/.match("aaa").to_a.should == ["a"] + /a?/.match("bbb").to_a.should == [""] + end +end diff --git a/spec/rubyspec/language/regexp_spec.rb b/spec/rubyspec/language/regexp_spec.rb new file mode 100644 index 0000000000..4a5828f2d1 --- /dev/null +++ b/spec/rubyspec/language/regexp_spec.rb @@ -0,0 +1,150 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/classes', __FILE__) + +describe "Literal Regexps" do + it "matches against $_ (last input) in a conditional if no explicit matchee provided" do + -> { + eval <<-EOR + $_ = nil + (true if /foo/).should_not == true + + $_ = "foo" + (true if /foo/).should == true + EOR + }.should complain(/regex literal in condition/) + end + + it "yields a Regexp" do + /Hello/.should be_kind_of(Regexp) + end + + it "caches the Regexp object" do + rs = [] + 2.times do |i| + rs << /foo/ + end + rs[0].should equal(rs[1]) + end + + it "throws SyntaxError for malformed literals" do + lambda { eval('/(/') }.should raise_error(SyntaxError) + end + + ############################################################################# + # %r + ############################################################################# + + it "supports paired delimiters with %r" do + LanguageSpecs.paired_delimiters.each do |p0, p1| + eval("%r#{p0} foo #{p1}").should == / foo / + end + end + + it "supports grouping constructs that are also paired delimiters" do + LanguageSpecs.paired_delimiters.each do |p0, p1| + eval("%r#{p0} () [c]{1} #{p1}").should == / () [c]{1} / + end + end + + it "allows second part of paired delimiters to be used as non-paired delimiters" do + LanguageSpecs.paired_delimiters.each do |p0, p1| + eval("%r#{p1} foo #{p1}").should == / foo / + end + end + + it "disallows first part of paired delimiters to be used as non-paired delimiters" do + LanguageSpecs.paired_delimiters.each do |p0, p1| + lambda { eval("%r#{p0} foo #{p0}") }.should raise_error(SyntaxError) + end + end + + it "supports non-paired delimiters delimiters with %r" do + LanguageSpecs.non_paired_delimiters.each do |c| + eval("%r#{c} foo #{c}").should == / foo / + end + end + + it "disallows alphabets as non-paired delimiter with %r" do + lambda { eval('%ra foo a') }.should raise_error(SyntaxError) + end + + it "disallows spaces after %r and delimiter" do + lambda { eval('%r !foo!') }.should raise_error(SyntaxError) + end + + it "allows unescaped / to be used with %r" do + %r[/].to_s.should == /\//.to_s + end + + + ############################################################################# + # Specs for the matching semantics + ############################################################################# + + it "supports . (any character except line terminator)" do + # Basic matching + /./.match("foo").to_a.should == ["f"] + # Basic non-matching + /./.match("").should be_nil + /./.match("\n").should be_nil + /./.match("\0").to_a.should == ["\0"] + end + + + it "supports | (alternations)" do + /a|b/.match("a").to_a.should == ["a"] + end + + it "supports (?> ) (embedded subexpression)" do + /(?>foo)(?>bar)/.match("foobar").to_a.should == ["foobar"] + /(?>foo*)obar/.match("foooooooobar").should be_nil # it is possesive + end + + it "supports (?# )" do + /foo(?#comment)bar/.match("foobar").to_a.should == ["foobar"] + /foo(?#)bar/.match("foobar").to_a.should == ["foobar"] + end + + it "supports (?<= ) (positive lookbehind)" do + /foo.(?<=\d)/.match("fooA foo1").to_a.should == ["foo1"] + end + + it "supports (?foo.)bar\g/.match("foo1barfoo2").to_a.should == ["foo1barfoo2", "foo2"] + end + + it "supports character class composition" do + /[a-z&&[^a-c]]+/.match("abcdef").to_a.should == ["def"] + /[a-z&&[^d-i&&[^d-f]]]+/.match("abcdefghi").to_a.should == ["abcdef"] + end + + it "supports possessive quantifiers" do + /fooA++bar/.match("fooAAAbar").to_a.should == ["fooAAAbar"] + + /fooA++Abar/.match("fooAAAbar").should be_nil + /fooA?+Abar/.match("fooAAAbar").should be_nil + /fooA*+Abar/.match("fooAAAbar").should be_nil + end + + it "supports conditional regular expressions with positional capture groups" do + pattern = /\A(foo)?(?(1)(T)|(F))\z/ + + pattern.should =~ 'fooT' + pattern.should =~ 'F' + pattern.should_not =~ 'fooF' + pattern.should_not =~ 'T' + end + + it "supports conditional regular expressions with positional capture groups" do + pattern = /\A(?foo)?(?()(T)|(F))\z/ + + pattern.should =~ 'fooT' + pattern.should =~ 'F' + pattern.should_not =~ 'fooF' + pattern.should_not =~ 'T' + end +end diff --git a/spec/rubyspec/language/rescue_spec.rb b/spec/rubyspec/language/rescue_spec.rb new file mode 100644 index 0000000000..3d45eb1917 --- /dev/null +++ b/spec/rubyspec/language/rescue_spec.rb @@ -0,0 +1,293 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/rescue', __FILE__) + +class SpecificExampleException < StandardError +end +class OtherCustomException < StandardError +end +class ArbitraryException < StandardError +end + +exception_list = [SpecificExampleException, ArbitraryException] + +describe "The rescue keyword" do + before :each do + ScratchPad.record [] + end + + it "can be used to handle a specific exception" do + begin + raise SpecificExampleException, "Raising this to be handled below" + rescue SpecificExampleException + :caught + end.should == :caught + end + + it "can capture the raised exception in a local variable" do + begin + raise SpecificExampleException, "some text" + rescue SpecificExampleException => e + e.message.should == "some text" + end + end + + it "can rescue multiple raised exceptions with a single rescue block" do + [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].map do |block| + begin + block.call + rescue SpecificExampleException, ArbitraryException + :caught + end + end.should == [:caught, :caught] + end + + it "can rescue a splatted list of exceptions" do + caught_it = false + begin + raise SpecificExampleException, "not important" + rescue *exception_list + caught_it = true + end + caught_it.should be_true + caught = [] + [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block| + begin + block.call + rescue *exception_list + caught << $! + end + end + caught.size.should == 2 + exception_list.each do |exception_class| + caught.map{|e| e.class}.should include(exception_class) + end + end + + it "can combine a splatted list of exceptions with a literal list of exceptions" do + caught_it = false + begin + raise SpecificExampleException, "not important" + rescue ArbitraryException, *exception_list + caught_it = true + end + caught_it.should be_true + caught = [] + [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block| + begin + block.call + rescue ArbitraryException, *exception_list + caught << $! + end + end + caught.size.should == 2 + exception_list.each do |exception_class| + caught.map{|e| e.class}.should include(exception_class) + end + end + + it "will only rescue the specified exceptions when doing a splat rescue" do + lambda do + begin + raise OtherCustomException, "not rescued!" + rescue *exception_list + end + end.should raise_error(OtherCustomException) + end + + it "will execute an else block only if no exceptions were raised" do + result = begin + ScratchPad << :one + rescue + ScratchPad << :does_not_run + else + ScratchPad << :two + :val + end + result.should == :val + ScratchPad.recorded.should == [:one, :two] + end + + it "will execute an else block with ensure only if no exceptions were raised" do + result = begin + ScratchPad << :one + rescue + ScratchPad << :does_not_run + else + ScratchPad << :two + :val + ensure + ScratchPad << :ensure + :ensure_val + end + result.should == :val + ScratchPad.recorded.should == [:one, :two, :ensure] + end + + it "will execute an else block only if no exceptions were raised in a method" do + result = RescueSpecs.begin_else(false) + result.should == :val + ScratchPad.recorded.should == [:one, :else_ran] + end + + it "will execute an else block with ensure only if no exceptions were raised in a method" do + result = RescueSpecs.begin_else_ensure(false) + result.should == :val + ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran] + end + + it "will execute an else block but use the outer scope return value in a method" do + result = RescueSpecs.begin_else_return(false) + result.should == :return_val + ScratchPad.recorded.should == [:one, :else_ran, :outside_begin] + end + + it "will execute an else block with ensure but use the outer scope return value in a method" do + result = RescueSpecs.begin_else_return_ensure(false) + result.should == :return_val + ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin] + end + + it "will not execute an else block if an exception was raised" do + result = begin + ScratchPad << :one + raise "an error occurred" + rescue + ScratchPad << :two + :val + else + ScratchPad << :does_not_run + end + result.should == :val + ScratchPad.recorded.should == [:one, :two] + end + + it "will not execute an else block with ensure if an exception was raised" do + result = begin + ScratchPad << :one + raise "an error occurred" + rescue + ScratchPad << :two + :val + else + ScratchPad << :does_not_run + ensure + ScratchPad << :ensure + :ensure_val + end + result.should == :val + ScratchPad.recorded.should == [:one, :two, :ensure] + end + + it "will not execute an else block if an exception was raised in a method" do + result = RescueSpecs.begin_else(true) + result.should == :rescue_val + ScratchPad.recorded.should == [:one, :rescue_ran] + end + + it "will not execute an else block with ensure if an exception was raised in a method" do + result = RescueSpecs.begin_else_ensure(true) + result.should == :rescue_val + ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran] + end + + it "will not execute an else block but use the outer scope return value in a method" do + result = RescueSpecs.begin_else_return(true) + result.should == :return_val + ScratchPad.recorded.should == [:one, :rescue_ran, :outside_begin] + end + + it "will not execute an else block with ensure but use the outer scope return value in a method" do + result = RescueSpecs.begin_else_return_ensure(true) + result.should == :return_val + ScratchPad.recorded.should == [:one, :rescue_ran, :ensure_ran, :outside_begin] + end + + it "will not rescue errors raised in an else block in the rescue block above it" do + lambda do + begin + ScratchPad << :one + rescue Exception + ScratchPad << :does_not_run + else + ScratchPad << :two + raise SpecificExampleException, "an error from else" + end + end.should raise_error(SpecificExampleException) + ScratchPad.recorded.should == [:one, :two] + end + + it "parses 'a += b rescue c' as 'a += (b rescue c)'" do + a = 'a' + c = 'c' + a += b rescue c + a.should == 'ac' + end + + it "without classes will not rescue Exception" do + lambda do + begin + raise Exception + rescue + 'Exception wrongly rescued' + end + end.should raise_error(Exception) + end + + it "uses === to compare against rescued classes" do + rescuer = Class.new + + def rescuer.===(exception) + true + end + + begin + raise Exception + rescue rescuer + rescued = :success + rescue Exception + rescued = :failure + end + + rescued.should == :success + end + + it "only accepts Module or Class in rescue clauses" do + rescuer = 42 + lambda { + begin + raise "error" + rescue rescuer + end + }.should raise_error(TypeError) { |e| + e.message.should =~ /class or module required for rescue clause/ + } + end + + it "only accepts Module or Class in splatted rescue clauses" do + rescuer = [42] + lambda { + begin + raise "error" + rescue *rescuer + end + }.should raise_error(TypeError) { |e| + e.message.should =~ /class or module required for rescue clause/ + } + end + + it "evaluates rescue expressions only when needed" do + invalid_rescuer = Object.new + begin + :foo + rescue rescuer + end.should == :foo + end + + it "should splat the handling Error classes" do + begin + raise "raise" + rescue *(RuntimeError) => e + :expected + end.should == :expected + end +end diff --git a/spec/rubyspec/language/retry_spec.rb b/spec/rubyspec/language/retry_spec.rb new file mode 100644 index 0000000000..96e69b763a --- /dev/null +++ b/spec/rubyspec/language/retry_spec.rb @@ -0,0 +1,52 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The retry statement" do + it "re-executes the closest block" do + retry_first = true + retry_second = true + results = [] + begin + results << 1 + raise + rescue + results << 2 + if retry_first + results << 3 + retry_first = false + retry + end + begin + results << 4 + raise + rescue + results << 5 + if retry_second + results << 6 + retry_second = false + retry + end + end + end + + results.should == [1, 2, 3, 1, 2, 4, 5, 6, 4, 5] + end + + it "raises a SyntaxError when used outside of a begin statement" do + lambda { eval 'retry' }.should raise_error(SyntaxError) + end +end + +describe "The retry keyword inside a begin block's rescue block" do + it "causes the begin block to be executed again" do + counter = 0 + + begin + counter += 1 + raise "An exception" + rescue + retry unless counter == 7 + end + + counter.should == 7 + end +end diff --git a/spec/rubyspec/language/return_spec.rb b/spec/rubyspec/language/return_spec.rb new file mode 100644 index 0000000000..323f74aad3 --- /dev/null +++ b/spec/rubyspec/language/return_spec.rb @@ -0,0 +1,245 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/return', __FILE__) + +describe "The return keyword" do + it "returns any object directly" do + def r; return 1; end + r().should == 1 + end + + it "returns an single element array directly" do + def r; return [1]; end + r().should == [1] + end + + it "returns an multi element array directly" do + def r; return [1,2]; end + r().should == [1,2] + end + + it "returns nil by default" do + def r; return; end + r().should be_nil + end + + describe "in a Thread" do + it "raises a LocalJumpError if used to exit a thread" do + lambda { Thread.new { return }.join }.should raise_error(LocalJumpError) + end + end + + describe "when passed a splat" do + it "returns [] when the ary is empty" do + def r; ary = []; return *ary; end + r.should == [] + end + + it "returns the array when the array is size of 1" do + def r; ary = [1]; return *ary; end + r.should == [1] + end + + it "returns the whole array when size is greater than 1" do + def r; ary = [1,2]; return *ary; end + r.should == [1,2] + + def r; ary = [1,2,3]; return *ary; end + r.should == [1,2,3] + end + + it "returns an array when used as a splat" do + def r; value = 1; return *value; end + r.should == [1] + end + + it "calls 'to_a' on the splatted value first" do + def r + obj = Object.new + def obj.to_a + [1,2] + end + + return *obj + end + + r().should == [1,2] + end + end + + describe "within a begin" do + before :each do + ScratchPad.record [] + end + + it "executes ensure before returning" do + def f() + begin + ScratchPad << :begin + return :begin + ScratchPad << :after_begin + ensure + ScratchPad << :ensure + end + ScratchPad << :function + end + f().should == :begin + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "returns last value returned in ensure" do + def f() + begin + ScratchPad << :begin + return :begin + ScratchPad << :after_begin + ensure + ScratchPad << :ensure + return :ensure + ScratchPad << :after_ensure + end + ScratchPad << :function + end + f().should == :ensure + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "executes nested ensures before returning" do + def f() + begin + begin + ScratchPad << :inner_begin + return :inner_begin + ScratchPad << :after_inner_begin + ensure + ScratchPad << :inner_ensure + end + ScratchPad << :outer_begin + return :outer_begin + ScratchPad << :after_outer_begin + ensure + ScratchPad << :outer_ensure + end + ScratchPad << :function + end + f().should == :inner_begin + ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure] + end + + it "returns last value returned in nested ensures" do + def f() + begin + begin + ScratchPad << :inner_begin + return :inner_begin + ScratchPad << :after_inner_begin + ensure + ScratchPad << :inner_ensure + return :inner_ensure + ScratchPad << :after_inner_ensure + end + ScratchPad << :outer_begin + return :outer_begin + ScratchPad << :after_outer_begin + ensure + ScratchPad << :outer_ensure + return :outer_ensure + ScratchPad << :after_outer_ensure + end + ScratchPad << :function + end + f().should == :outer_ensure + ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure] + end + + it "executes the ensure clause when begin/ensure are inside a lambda" do + lambda do + begin + return + ensure + ScratchPad.recorded << :ensure + end + end.call + ScratchPad.recorded.should == [:ensure] + end + end + + describe "within a block" do + before :each do + ScratchPad.clear + end + + it "causes lambda to return nil if invoked without any arguments" do + lambda { return; 456 }.call.should be_nil + end + + it "causes lambda to return nil if invoked with an empty expression" do + lambda { return (); 456 }.call.should be_nil + end + + it "causes lambda to return the value passed to return" do + lambda { return 123; 456 }.call.should == 123 + end + + it "causes the method that lexically encloses the block to return" do + ReturnSpecs::Blocks.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "returns from the lexically enclosing method even in case of chained calls" do + ReturnSpecs::NestedCalls.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "returns from the lexically enclosing method even in case of chained calls(in yield)" do + ReturnSpecs::NestedBlocks.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "causes the method to return even when the immediate parent has already returned" do + ReturnSpecs::SavedInnerBlock.new.start.should == :return_value + ScratchPad.recorded.should == :before_return + end + + # jruby/jruby#3143 + describe "downstream from a lambda" do + it "returns to its own return-capturing lexical enclosure" do + def a + ->{ yield }.call + return 2 + end + def b + a { return 1 } + end + + b.should == 1 + end + end + + end + + describe "within two blocks" do + it "causes the method that lexically encloses the block to return" do + def f + 1.times { 1.times {return true}; false}; false + end + f.should be_true + end + end + + describe "within define_method" do + it "goes through the method via a closure" do + ReturnSpecs::ThroughDefineMethod.new.outer.should == :good + end + + it "stops at the method when the return is used directly" do + ReturnSpecs::DefineMethod.new.outer.should == :good + end + end + + describe "invoked with a method call without parentheses with a block" do + it "returns the value returned from the method call" do + ReturnSpecs::MethodWithBlock.new.method1.should == 5 + ReturnSpecs::MethodWithBlock.new.method2.should == [0, 1, 2] + end + end +end diff --git a/spec/rubyspec/language/safe_navigator_spec.rb b/spec/rubyspec/language/safe_navigator_spec.rb new file mode 100644 index 0000000000..a8b29dc5a3 --- /dev/null +++ b/spec/rubyspec/language/safe_navigator_spec.rb @@ -0,0 +1,101 @@ +require File.expand_path("../../spec_helper", __FILE__) + +ruby_version_is "2.3" do + describe "Safe navigator" do + it "requires a method name to be provided" do + lambda { eval("obj&. {}") }.should raise_error(SyntaxError) + end + + context "when context is nil" do + it "always returns nil" do + eval("nil&.unknown").should == nil + eval("[][10]&.unknown").should == nil + end + + it "can be chained" do + eval("nil&.one&.two&.three").should == nil + end + + it "doesn't evaluate arguments" do + obj = Object.new + obj.should_not_receive(:m) + eval("nil&.unknown(obj.m) { obj.m }") + end + end + + context "when context is false" do + it "calls the method" do + eval("false&.to_s").should == "false" + + lambda { eval("false&.unknown") }.should raise_error(NoMethodError) + end + end + + context "when context is truthy" do + it "calls the method" do + eval("1&.to_s").should == "1" + + lambda { eval("1&.unknown") }.should raise_error(NoMethodError) + end + end + + it "takes a list of arguments" do + eval("[1,2,3]&.first(2)").should == [1,2] + end + + it "takes a block" do + eval("[1,2]&.map { |i| i * 2 }").should == [2, 4] + end + + it "allows assignment methods" do + klass = Class.new do + attr_reader :foo + def foo=(val) + @foo = val + 42 + end + end + obj = klass.new + + eval("obj&.foo = 3").should == 3 + obj.foo.should == 3 + + obj = nil + eval("obj&.foo = 3").should == nil + end + + it "allows assignment operators" do + klass = Class.new do + attr_accessor :m + + def initialize + @m = 0 + end + end + + obj = klass.new + + eval("obj&.m += 3") + obj.m.should == 3 + + obj = nil + eval("obj&.m += 3").should == nil + end + + it "does not call the operator method lazily with an assignment operator" do + klass = Class.new do + attr_writer :foo + def foo + nil + end + end + obj = klass.new + + lambda { + eval("obj&.foo += 3") + }.should raise_error(NoMethodError) { |e| + e.name.should == :+ + } + end + end +end diff --git a/spec/rubyspec/language/send_spec.rb b/spec/rubyspec/language/send_spec.rb new file mode 100644 index 0000000000..646a700785 --- /dev/null +++ b/spec/rubyspec/language/send_spec.rb @@ -0,0 +1,521 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/send', __FILE__) + +# Why so many fixed arg tests? JRuby and I assume other Ruby impls have +# separate call paths for simple fixed arity methods. Testing up to five +# will verify special and generic arity code paths for all impls. +# +# Method naming conventions: +# M - Manditory Args +# O - Optional Arg +# R - Rest Arg +# Q - Post Manditory Args + +specs = LangSendSpecs + +describe "Invoking a method" do + describe "with zero arguments" do + it "requires no arguments passed" do + specs.fooM0.should == 100 + end + + it "raises ArgumentError if the method has a positive arity" do + lambda { + specs.fooM1 + }.should raise_error(ArgumentError) + end + end + + describe "with only mandatory arguments" do + it "requires exactly the same number of passed values" do + specs.fooM1(1).should == [1] + specs.fooM2(1,2).should == [1,2] + specs.fooM3(1,2,3).should == [1,2,3] + specs.fooM4(1,2,3,4).should == [1,2,3,4] + specs.fooM5(1,2,3,4,5).should == [1,2,3,4,5] + end + + it "raises ArgumentError if the methods arity doesn't match" do + lambda { + specs.fooM1(1,2) + }.should raise_error(ArgumentError) + end + end + + describe "with optional arguments" do + it "uses the optional argument if none is is passed" do + specs.fooM0O1.should == [1] + end + + it "uses the passed argument if available" do + specs.fooM0O1(2).should == [2] + end + + it "raises ArgumentError if extra arguments are passed" do + lambda { + specs.fooM0O1(2,3) + }.should raise_error(ArgumentError) + end + end + + describe "with mandatory and optional arguments" do + it "uses the passed values in left to right order" do + specs.fooM1O1(2).should == [2,1] + end + + it "raises an ArgumentError if there are no values for the mandatory args" do + lambda { + specs.fooM1O1 + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if too many values are passed" do + lambda { + specs.fooM1O1(1,2,3) + }.should raise_error(ArgumentError) + end + end + + describe "with a rest argument" do + it "is an empty array if there are no additional arguments" do + specs.fooM0R().should == [] + specs.fooM1R(1).should == [1, []] + end + + it "gathers unused arguments" do + specs.fooM0R(1).should == [1] + specs.fooM1R(1,2).should == [1, [2]] + end + end + + it "with a block makes it available to yield" do + specs.oneb(10) { 200 }.should == [10,200] + end + + it "with a block converts the block to a Proc" do + prc = specs.makeproc { "hello" } + prc.should be_kind_of(Proc) + prc.call.should == "hello" + end + + it "with an object as a block uses 'to_proc' for coercion" do + o = LangSendSpecs::ToProc.new(:from_to_proc) + + specs.makeproc(&o).call.should == :from_to_proc + + specs.yield_now(&o).should == :from_to_proc + end + + it "raises a SyntaxError with both a literal block and an object as block" do + lambda { + eval "specs.oneb(10, &l){ 42 }" + }.should raise_error(SyntaxError) + end + + it "with same names as existing variables is ok" do + foobar = 100 + + def foobar; 200; end + + foobar.should == 100 + foobar().should == 200 + end + + it "with splat operator makes the object the direct arguments" do + a = [1,2,3] + specs.fooM3(*a).should == [1,2,3] + end + + it "without parentheses works" do + (specs.fooM3 1,2,3).should == [1,2,3] + end + + it "with a space separating method name and parenthesis treats expression in parenthesis as first argument" do + specs.weird_parens().should == "55" + end + + describe "allows []=" do + before :each do + @obj = LangSendSpecs::AttrSet.new + end + + it "with *args in the [] expanded to individual arguments" do + ary = [2,3] + (@obj[1, *ary] = 4).should == 4 + @obj.result.should == [1,2,3,4] + end + + it "with multiple *args" do + ary = [2,3] + post = [4,5] + (@obj[1, *ary] = *post).should == [4,5] + @obj.result.should == [1,2,3,[4,5]] + end + + it "with multiple *args and does not unwrap the last splat" do + ary = [2,3] + post = [4] + (@obj[1, *ary] = *post).should == [4] + @obj.result.should == [1,2,3,[4]] + end + + it "with a *args and multiple rhs args" do + ary = [2,3] + (@obj[1, *ary] = 4, 5).should == [4,5] + @obj.result.should == [1,2,3,[4,5]] + end + end + + it "passes literal hashes without curly braces as the last parameter" do + specs.fooM3('abc', 456, 'rbx' => 'cool', + 'specs' => 'fail sometimes', 'oh' => 'weh').should == \ + ['abc', 456, {'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}] + end + + it "passes a literal hash without curly braces or parens" do + (specs.fooM3 'abc', 456, 'rbx' => 'cool', + 'specs' => 'fail sometimes', 'oh' => 'weh').should == \ + ['abc', 456, { 'rbx' => 'cool', 'specs' => 'fail sometimes', 'oh' => 'weh'}] + end + + it "allows to literal hashes without curly braces as the only parameter" do + specs.fooM1(rbx: :cool, specs: :fail_sometimes).should == + [{ rbx: :cool, specs: :fail_sometimes }] + + (specs.fooM1 rbx: :cool, specs: :fail_sometimes).should == + [{ rbx: :cool, specs: :fail_sometimes }] + end + + describe "when the method is not available" do + it "invokes method_missing if it is defined" do + o = LangSendSpecs::MethodMissing.new + o.not_there(1,2) + o.message.should == :not_there + o.args.should == [1,2] + end + + it "raises NameError if invoked as a vcall" do + lambda { no_such_method }.should raise_error NameError + end + + it "raises NoMethodError if invoked as an unambiguous method call" do + lambda { no_such_method() }.should raise_error NoMethodError + lambda { no_such_method(1,2,3) }.should raise_error NoMethodError + end + end + +end + +describe "Invoking a public setter method" do + it 'returns the set value' do + klass = Class.new do + def foobar=(*) + 1 + end + end + + (klass.new.foobar = 'bar').should == 'bar' + (klass.new.foobar = 'bar', 'baz').should == ["bar", "baz"] + end +end + +describe "Invoking []= methods" do + it 'returns the set value' do + klass = Class.new do + def []=(*) + 1 + end + end + + (klass.new[33] = 'bar').should == 'bar' + (klass.new[33] = 'bar', 'baz').should == ['bar', 'baz'] + (klass.new[33, 34] = 'bar', 'baz').should == ['bar', 'baz'] + end +end + +describe "Invoking a private setter method" do + describe "permits self as a receiver" do + it "for normal assignment" do + receiver = LangSendSpecs::PrivateSetter.new + receiver.call_self_foo_equals(42) + receiver.foo.should == 42 + end + + it "for multiple assignment" do + receiver = LangSendSpecs::PrivateSetter.new + receiver.call_self_foo_equals_masgn(42) + receiver.foo.should == 42 + end + end +end + +describe "Invoking a private getter method" do + it "does not permit self as a receiver" do + receiver = LangSendSpecs::PrivateGetter.new + lambda { receiver.call_self_foo }.should raise_error(NoMethodError) + lambda { receiver.call_self_foo_or_equals(6) }.should raise_error(NoMethodError) + end +end + +describe "Invoking a method" do + describe "with required args after the rest arguments" do + it "binds the required arguments first" do + specs.fooM0RQ1(1).should == [[], 1] + specs.fooM0RQ1(1,2).should == [[1], 2] + specs.fooM0RQ1(1,2,3).should == [[1,2], 3] + + specs.fooM1RQ1(1,2).should == [1, [], 2] + specs.fooM1RQ1(1,2,3).should == [1, [2], 3] + specs.fooM1RQ1(1,2,3,4).should == [1, [2, 3], 4] + + specs.fooM1O1RQ1(1,2).should == [1, 9, [], 2] + specs.fooM1O1RQ1(1,2,3).should == [1, 2, [], 3] + specs.fooM1O1RQ1(1,2,3,4).should == [1, 2, [3], 4] + + specs.fooM1O1RQ2(1,2,3).should == [1, 9, [], 2, 3] + specs.fooM1O1RQ2(1,2,3,4).should == [1, 2, [], 3, 4] + specs.fooM1O1RQ2(1,2,3,4,5).should == [1, 2, [3], 4, 5] + end + end + + describe "with mandatory arguments after optional arguments" do + it "binds the required arguments first" do + specs.fooO1Q1(0,1).should == [0,1] + specs.fooO1Q1(2).should == [1,2] + + specs.fooM1O1Q1(2,3,4).should == [2,3,4] + specs.fooM1O1Q1(1,3).should == [1,2,3] + + specs.fooM2O1Q1(1,2,4).should == [1,2,3,4] + + specs.fooM2O2Q1(1,2,3,4,5).should == [1,2,3,4,5] + specs.fooM2O2Q1(1,2,3,5).should == [1,2,3,4,5] + specs.fooM2O2Q1(1,2,5).should == [1,2,3,4,5] + + specs.fooO4Q1(1,2,3,4,5).should == [1,2,3,4,5] + specs.fooO4Q1(1,2,3,5).should == [1,2,3,4,5] + specs.fooO4Q1(1,2,5).should == [1,2,3,4,5] + specs.fooO4Q1(1,5).should == [1,2,3,4,5] + specs.fooO4Q1(5).should == [1,2,3,4,5] + + specs.fooO4Q2(1,2,3,4,5,6).should == [1,2,3,4,5,6] + specs.fooO4Q2(1,2,3,5,6).should == [1,2,3,4,5,6] + specs.fooO4Q2(1,2,5,6).should == [1,2,3,4,5,6] + specs.fooO4Q2(1,5,6).should == [1,2,3,4,5,6] + specs.fooO4Q2(5,6).should == [1,2,3,4,5,6] + end + end + + it "with .() invokes #call" do + q = proc { |z| z } + q.(1).should == 1 + + obj = mock("paren call") + obj.should_receive(:call).and_return(:called) + obj.().should == :called + end + + it "allows a vestigial trailing ',' in the arguments" do + specs.fooM1(1,).should == [1] + end + + it "with splat operator attempts to coerce it to an Array if the object respond_to?(:to_a)" do + ary = [2,3,4] + obj = mock("to_a") + obj.should_receive(:to_a).and_return(ary).twice + specs.fooM0R(*obj).should == ary + specs.fooM1R(1,*obj).should == [1, ary] + end + + it "with splat operator * and non-Array value uses value unchanged if it does not respond_to?(:to_ary)" do + obj = Object.new + obj.should_not respond_to(:to_a) + + specs.fooM0R(*obj).should == [obj] + specs.fooM1R(1,*obj).should == [1, [obj]] + end + + it "accepts additional arguments after splat expansion" do + a = [1,2] + specs.fooM4(*a,3,4).should == [1,2,3,4] + specs.fooM4(0,*a,3).should == [0,1,2,3] + end + + it "does not expand final array arguments after a splat expansion" do + a = [1, 2] + specs.fooM3(*a, [3, 4]).should == [1, 2, [3, 4]] + end + + it "accepts final explicit literal Hash arguments after the splat" do + a = [1, 2] + specs.fooM0RQ1(*a, { a: 1 }).should == [[1, 2], { a: 1 }] + end + + it "accepts final implicit literal Hash arguments after the splat" do + a = [1, 2] + specs.fooM0RQ1(*a, a: 1).should == [[1, 2], { a: 1 }] + end + + it "accepts final Hash arguments after the splat" do + a = [1, 2] + b = { a: 1 } + specs.fooM0RQ1(*a, b).should == [[1, 2], { a: 1 }] + end + + it "accepts mandatory and explicit literal Hash arguments after the splat" do + a = [1, 2] + specs.fooM0RQ2(*a, 3, { a: 1 }).should == [[1, 2], 3, { a: 1 }] + end + + it "accepts mandatory and implicit literal Hash arguments after the splat" do + a = [1, 2] + specs.fooM0RQ2(*a, 3, a: 1).should == [[1, 2], 3, { a: 1 }] + end + + it "accepts mandatory and Hash arguments after the splat" do + a = [1, 2] + b = { a: 1 } + specs.fooM0RQ2(*a, 3, b).should == [[1, 2], 3, { a: 1 }] + end + + it "converts a final splatted explicit Hash to an Array" do + a = [1, 2] + specs.fooR(*a, 3, *{ a: 1 }).should == [1, 2, 3, [:a, 1]] + end + + it "calls #to_a to convert a final splatted Hash object to an Array" do + a = [1, 2] + b = { a: 1 } + b.should_receive(:to_a).and_return([:a, 1]) + + specs.fooR(*a, 3, *b).should == [1, 2, 3, :a, 1] + end + + it "accepts multiple splat expansions in the same argument list" do + a = [1,2,3] + b = 7 + c = mock("pseudo-array") + c.should_receive(:to_a).and_return([0,0]) + + d = [4,5] + specs.rest_len(*a,*d,6,*b).should == 7 + specs.rest_len(*a,*a,*a).should == 9 + specs.rest_len(0,*a,4,*5,6,7,*c,-1).should == 11 + end + + it "expands an array to arguments grouped in parentheses" do + specs.destructure2([40,2]).should == 42 + end + + it "expands an array to arguments grouped in parentheses and ignores any rest arguments in the array" do + specs.destructure2([40,2,84]).should == 42 + end + + it "expands an array to arguments grouped in parentheses and sets not specified arguments to nil" do + specs.destructure2b([42]).should == [42, nil] + end + + it "expands an array to arguments grouped in parentheses which in turn takes rest arguments" do + specs.destructure4r([1, 2, 3]).should == [1, 2, [], 3, nil] + specs.destructure4r([1, 2, 3, 4]).should == [1, 2, [], 3, 4] + specs.destructure4r([1, 2, 3, 4, 5]).should == [1, 2, [3], 4, 5] + end + + it "with optional argument(s), expands an array to arguments grouped in parentheses" do + specs.destructure4o(1, [2, 3]).should == [1, 1, nil, [2, 3]] + specs.destructure4o(1, [], 2).should == [1, nil, nil, 2] + specs.destructure4os(1, [2, 3]).should == [1, 2, [3]] + specs.destructure5o(1, [2, 3]).should == [1, 2, 1, nil, [2, 3]] + specs.destructure7o(1, [2, 3]).should == [1, 2, 1, nil, 2, 3] + specs.destructure7b(1, [2, 3]) do |(a,*b,c)| + [a, c] + end.should == [1, 3] + end + + describe "new-style hash arguments" do + describe "as the only parameter" do + it "passes without curly braces" do + specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "passes without curly braces or parens" do + (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "handles a hanging comma without curly braces" do + specs.fooM1(abc: 123,).should == [{abc: 123}] + specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + end + + describe "as the last parameter" do + it "passes without curly braces" do + specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "passes without curly braces or parens" do + (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "handles a hanging comma without curly braces" do + specs.fooM3('abc', 123, abc: 123,).should == ['abc', 123, {abc: 123}] + specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + end + end + + describe "mixed new- and old-style hash arguments" do + describe "as the only parameter" do + it "passes without curly braces" do + specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "passes without curly braces or parens" do + (specs.fooM1 rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "handles a hanging comma without curly braces" do + specs.fooM1(rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should == + [{ rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + end + + describe "as the last parameter" do + it "passes without curly braces" do + specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "passes without curly braces or parens" do + (specs.fooM3 'abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + + it "handles a hanging comma without curly braces" do + specs.fooM3('abc', 123, rbx: 'cool', specs: :fail_sometimes, non_sym: 1234,).should == + ['abc', 123, { rbx: 'cool', specs: :fail_sometimes, non_sym: 1234 }] + end + end + end + +end + +describe "allows []= with arguments after splat" do + before :each do + @obj = LangSendSpecs::Attr19Set.new + @ary = ["a"] + end + + it "with *args in the [] and post args" do + @obj[1,*@ary,123] = 2 + @obj.result.should == [1, "a", 123, 2] + end +end diff --git a/spec/rubyspec/language/shared/__FILE__.rb b/spec/rubyspec/language/shared/__FILE__.rb new file mode 100644 index 0000000000..3e4f5c958d --- /dev/null +++ b/spec/rubyspec/language/shared/__FILE__.rb @@ -0,0 +1,23 @@ +describe :language___FILE__, shared: true do + before :each do + CodeLoadingSpecs.spec_setup + @path = File.join(CODE_LOADING_DIR, "file_fixture.rb") + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + it "equals the absolute path of a file loaded by an absolute path" do + @object.send(@method, @path).should be_true + ScratchPad.recorded.should == [@path] + end + + it "equals the absolute path of a file loaded by a relative path" do + $LOAD_PATH << "." + Dir.chdir CODE_LOADING_DIR do + @object.send(@method, "file_fixture.rb").should be_true + end + ScratchPad.recorded.should == [@path] + end +end diff --git a/spec/rubyspec/language/shared/__LINE__.rb b/spec/rubyspec/language/shared/__LINE__.rb new file mode 100644 index 0000000000..076b74b3ba --- /dev/null +++ b/spec/rubyspec/language/shared/__LINE__.rb @@ -0,0 +1,15 @@ +describe :language___LINE__, shared: true do + before :each do + CodeLoadingSpecs.spec_setup + @path = File.expand_path("line_fixture.rb", CODE_LOADING_DIR) + end + + after :each do + CodeLoadingSpecs.spec_cleanup + end + + it "equals the line number of the text in a loaded file" do + @object.send(@method, @path).should be_true + ScratchPad.recorded.should == [1, 5] + end +end diff --git a/spec/rubyspec/language/singleton_class_spec.rb b/spec/rubyspec/language/singleton_class_spec.rb new file mode 100644 index 0000000000..837f479440 --- /dev/null +++ b/spec/rubyspec/language/singleton_class_spec.rb @@ -0,0 +1,293 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../../fixtures/class', __FILE__) + +describe "A singleton class" do + it "is TrueClass for true" do + true.singleton_class.should == TrueClass + end + + it "is FalseClass for false" do + false.singleton_class.should == FalseClass + end + + it "is NilClass for nil" do + nil.singleton_class.should == NilClass + end + + it "raises a TypeError for Fixnum's" do + lambda { 1.singleton_class }.should raise_error(TypeError) + end + + it "raises a TypeError for symbols" do + lambda { :symbol.singleton_class }.should raise_error(TypeError) + end + + it "is a singleton Class instance" do + o = mock('x') + o.singleton_class.should be_kind_of(Class) + o.singleton_class.should_not equal(Object) + o.should be_kind_of(o.singleton_class) + end + + it "is a Class for classes" do + ClassSpecs::A.singleton_class.should be_kind_of(Class) + end + + it "inherits from Class for classes" do + Class.should be_ancestor_of(Object.singleton_class) + end + + it "is a subclass of Class's singleton class" do + ec = ClassSpecs::A.singleton_class + ec.should be_kind_of(Class.singleton_class) + end + + it "is a subclass of the same level of Class's singleton class" do + ecec = ClassSpecs::A.singleton_class.singleton_class + class_ec = Class.singleton_class + + ecec.should be_kind_of(class_ec.singleton_class) + ecec.should be_kind_of(class_ec) + end + + it "is a subclass of a superclass's singleton class" do + ClassSpecs::K.singleton_class.superclass.should == + ClassSpecs::H.singleton_class + end + + it "is a subclass of the same level of superclass's singleton class" do + ClassSpecs::K.singleton_class.singleton_class.superclass.should == + ClassSpecs::H.singleton_class.singleton_class + end + + it "for BasicObject has Class as it's superclass" do + BasicObject.singleton_class.superclass.should == Class + end + + it "for BasicObject has the proper level of superclass for Class" do + BasicObject.singleton_class.singleton_class.superclass.should == + Class.singleton_class + end + + it "has class String as the superclass of a String instance" do + "blah".singleton_class.superclass.should == String + end + + it "doesn't have singleton class" do + lambda { bignum_value.singleton_class.superclass.should == Bignum }.should raise_error(TypeError) + end +end + +describe "A constant on a singleton class" do + before :each do + @object = Object.new + class << @object + CONST = self + end + end + + it "can be accessed after the singleton class body is reopened" do + class << @object + CONST.should == self + end + end + + it "can be accessed via self::CONST" do + class << @object + self::CONST.should == self + end + end + + it "can be accessed via const_get" do + class << @object + const_get(:CONST).should == self + end + end + + it "is not defined on the object's class" do + @object.class.const_defined?(:CONST).should be_false + end + + it "is not defined in the singleton class opener's scope" do + class << @object + CONST + end + lambda { CONST }.should raise_error(NameError) + end + + it "cannot be accessed via object::CONST" do + lambda do + @object::CONST + end.should raise_error(TypeError) + end + + it "raises a NameError for anonymous_module::CONST" do + @object = Class.new + class << @object + CONST = 100 + end + + lambda do + @object::CONST + end.should raise_error(NameError) + end + + it "appears in the singleton class constant list" do + @object.singleton_class.should have_constant(:CONST) + end + + it "does not appear in the object's class constant list" do + @object.class.should_not have_constant(:CONST) + end + + it "is not preserved when the object is duped" do + @object = @object.dup + + lambda do + class << @object; CONST; end + end.should raise_error(NameError) + end + + it "is preserved when the object is cloned" do + @object = @object.clone + + class << @object + CONST.should_not be_nil + end + end +end + +describe "Defining instance methods on a singleton class" do + before :each do + @k = ClassSpecs::K.new + class << @k + def singleton_method; 1 end + end + + @k_sc = @k.singleton_class + end + + it "defines public methods" do + @k_sc.should have_public_instance_method(:singleton_method) + end +end + +describe "Instance methods of a singleton class" do + before :each do + k = ClassSpecs::K.new + @k_sc = k.singleton_class + @a_sc = ClassSpecs::A.new.singleton_class + @a_c_sc = ClassSpecs::A.singleton_class + end + + it "include ones of the object's class" do + @k_sc.should have_instance_method(:example_instance_method) + end + + it "does not include class methods of the object's class" do + @k_sc.should_not have_instance_method(:example_class_method) + end + + it "include instance methods of Object" do + @a_sc.should have_instance_method(:example_instance_method_of_object) + end + + it "does not include class methods of Object" do + @a_sc.should_not have_instance_method(:example_class_method_of_object) + end + + describe "for a class" do + it "include instance methods of Class" do + @a_c_sc.should have_instance_method(:example_instance_method_of_class) + end + + it "does not include class methods of Class" do + @a_c_sc.should_not have_instance_method(:example_class_method_of_class) + end + + it "does not include instance methods of the singleton class of Class" do + @a_c_sc.should_not have_instance_method(:example_instance_method_of_singleton_class) + end + + it "does not include class methods of the singleton class of Class" do + @a_c_sc.should_not have_instance_method(:example_class_method_of_singleton_class) + end + end + + describe "for a singleton class" do + it "includes instance methods of the singleton class of Class" do + @a_c_sc.singleton_class.should have_instance_method(:example_instance_method_of_singleton_class) + end + + it "does not include class methods of the singleton class of Class" do + @a_c_sc.singleton_class.should_not have_instance_method(:example_class_method_of_singleton_class) + end + end +end + +describe "Class methods of a singleton class" do + before :each do + k = ClassSpecs::K.new + @k_sc = k.singleton_class + @a_sc = ClassSpecs::A.new.singleton_class + @a_c_sc = ClassSpecs::A.singleton_class + end + + it "include ones of the object's class" do + @k_sc.should have_method(:example_class_method) + end + + it "does not include instance methods of the object's class" do + @k_sc.should_not have_method(:example_instance_method) + end + + it "include instance methods of Class" do + @a_sc.should have_method(:example_instance_method_of_class) + end + + it "does not include class methods of Class" do + @a_sc.should_not have_method(:example_class_method_of_class) + end + + describe "for a class" do + it "include instance methods of Class" do + @a_c_sc.should have_method(:example_instance_method_of_class) + end + + it "include class methods of Class" do + @a_c_sc.should have_method(:example_class_method_of_class) + end + + it "include instance methods of the singleton class of Class" do + @a_c_sc.should have_method(:example_instance_method_of_singleton_class) + end + + it "does not include class methods of the singleton class of Class" do + @a_c_sc.should_not have_method(:example_class_method_of_singleton_class) + end + end + + describe "for a singleton class" do + it "include instance methods of the singleton class of Class" do + @a_c_sc.singleton_class.should have_method(:example_instance_method_of_singleton_class) + end + + it "include class methods of the singleton class of Class" do + @a_c_sc.singleton_class.should have_method(:example_class_method_of_singleton_class) + end + end +end + +describe "Instantiating a singleton class" do + it "raises a TypeError when new is called" do + lambda { + Object.new.singleton_class.new + }.should raise_error(TypeError) + end + + it "raises a TypeError when allocate is called" do + lambda { + Object.new.singleton_class.allocate + }.should raise_error(TypeError) + end +end diff --git a/spec/rubyspec/language/string_spec.rb b/spec/rubyspec/language/string_spec.rb new file mode 100644 index 0000000000..dbec2652ed --- /dev/null +++ b/spec/rubyspec/language/string_spec.rb @@ -0,0 +1,282 @@ +# -*- encoding: binary -*- + +require File.expand_path('../../spec_helper', __FILE__) + +# TODO: rewrite these horrid specs. it "are..." seriously?! + +describe "Ruby character strings" do + + before :each do + @ip = 'xxx' # used for interpolation + $ip = 'xxx' + end + + it "don't get interpolated when put in single quotes" do + '#{@ip}'.should == '#{@ip}' + end + + it 'get interpolated with #{} when put in double quotes' do + "#{@ip}".should == 'xxx' + end + + it "interpolate instance variables just with the # character" do + "#@ip".should == 'xxx' + end + + it "interpolate global variables just with the # character" do + "#$ip".should == 'xxx' + end + + it "allows underscore as part of a variable name in a simple interpolation" do + @my_ip = 'xxx' + "#@my_ip".should == 'xxx' + end + + it "has characters [.(=?!# end simple # interpolation" do + "#@ip[".should == 'xxx[' + "#@ip.".should == 'xxx.' + "#@ip(".should == 'xxx(' + "#@ip=".should == 'xxx=' + "#@ip?".should == 'xxx?' + "#@ip!".should == 'xxx!' + "#@ip#@ip".should == 'xxxxxx' + end + + it "taints the result of interpolation when an interpolated value is tainted" do + "#{"".taint}".tainted?.should be_true + + @ip.taint + "#@ip".tainted?.should be_true + + $ip.taint + "#$ip".tainted?.should be_true + end + + it "untrusts the result of interpolation when an interpolated value is untrusted" do + "#{"".untrust}".untrusted?.should be_true + + @ip.untrust + "#@ip".untrusted?.should be_true + + $ip.untrust + "#$ip".untrusted?.should be_true + end + + it "allows using non-alnum characters as string delimiters" do + %(hey #{@ip}).should == "hey xxx" + %[hey #{@ip}].should == "hey xxx" + %{hey #{@ip}}.should == "hey xxx" + %.should == "hey xxx" + %!hey #{@ip}!.should == "hey xxx" + %@hey #{@ip}@.should == "hey xxx" + %#hey hey#.should == "hey hey" + %%hey #{@ip}%.should == "hey xxx" + %^hey #{@ip}^.should == "hey xxx" + %&hey #{@ip}&.should == "hey xxx" + %*hey #{@ip}*.should == "hey xxx" + %-hey #{@ip}-.should == "hey xxx" + %_hey #{@ip}_.should == "hey xxx" + %=hey #{@ip}=.should == "hey xxx" + %+hey #{@ip}+.should == "hey xxx" + %~hey #{@ip}~.should == "hey xxx" + %:hey #{@ip}:.should == "hey xxx" + %;hey #{@ip};.should == "hey xxx" + %"hey #{@ip}".should == "hey xxx" + %|hey #{@ip}|.should == "hey xxx" + %?hey #{@ip}?.should == "hey xxx" + %/hey #{@ip}/.should == "hey xxx" + %,hey #{@ip},.should == "hey xxx" + %.hey #{@ip}..should == "hey xxx" + + # surprised? huh + %'hey #{@ip}'.should == "hey xxx" + %\hey #{@ip}\.should == "hey xxx" + %`hey #{@ip}`.should == "hey xxx" + %$hey #{@ip}$.should == "hey xxx" + end + + it "using percent with 'q', stopping interpolation" do + %q(#{@ip}).should == '#{@ip}' + end + + it "using percent with 'Q' to interpolate" do + %Q(#{@ip}).should == 'xxx' + end + + # The backslashes : + # + # \t (tab), \n (newline), \r (carriage return), \f (form feed), \b + # (backspace), \a (bell), \e (escape), \s (whitespace), \nnn (octal), + # \xnn (hexadecimal), \cx (control x), \C-x (control x), \M-x (meta x), + # \M-\C-x (meta control x) + + it "backslashes follow the same rules as interpolation" do + "\t\n\r\f\b\a\e\s\075\x62\cx".should == "\t\n\r\f\b\a\e =b\030" + '\t\n\r\f\b\a\e =b\030'.should == "\\t\\n\\r\\f\\b\\a\\e =b\\030" + end + + it "calls #to_s when the object is not a String" do + obj = mock('to_s') + obj.stub!(:to_s).and_return('42') + + "#{obj}".should == '42' + end + + it "calls #to_s as a private method" do + obj = mock('to_s') + obj.stub!(:to_s).and_return('42') + + class << obj + private :to_s + end + + "#{obj}".should == '42' + end + + it "uses an internal representation when #to_s doesn't return a String" do + obj = mock('to_s') + obj.stub!(:to_s).and_return(42) + + # See rubyspec commit 787c132d by yugui. There is value in + # ensuring that this behavior works. So rather than removing + # this spec completely, the only thing that can be asserted + # is that if you interpolate an object that fails to return + # a String, you will still get a String and not raise an + # exception. + "#{obj}".should be_an_instance_of(String) + end + + it "allows a dynamic string to parse a nested do...end block as an argument to a call without parens, interpolated" do + s = eval 'eval "#{proc do; 1; end.call}"' + s.should == 1 + end + + it "are produced from character shortcuts" do + ?z.should == 'z' + end + + it "are produced from control character shortcuts" do + # Control-Z + ?\C-z.should == "\x1A" + + # Meta-Z + ?\M-z.should == "\xFA" + + # Meta-Control-Z + ?\M-\C-z.should == "\x9A" + end + + describe "Unicode escaping" do + it "can be done with \\u and four hex digits" do + [ ["\u0000", 0x0000], + ["\u2020", 0x2020] + ].should be_computed_by(:ord) + end + + it "can be done with \\u{} and one to six hex digits" do + [ ["\u{a}", 0xa], + ["\u{ab}", 0xab], + ["\u{abc}", 0xabc], + ["\u{1abc}", 0x1abc], + ["\u{12abc}", 0x12abc], + ["\u{100000}", 0x100000] + ].should be_computed_by(:ord) + end + + # TODO: spec other source encodings + describe "with ASCII_8BIT source encoding" do + it "produces an ASCII string when escaping ASCII characters via \\u" do + "\u0000".encoding.should == Encoding::ASCII_8BIT + end + + it "produces an ASCII string when escaping ASCII characters via \\u{}" do + "\u{0000}".encoding.should == Encoding::ASCII_8BIT + end + + it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u" do + "\u1234".encoding.should == Encoding::UTF_8 + end + + it "produces a UTF-8-encoded string when escaping non-ASCII characters via \\u{}" do + "\u{1234}".encoding.should == Encoding::UTF_8 + end + end + end +end + +# TODO: rewrite all specs above this + +describe "Ruby String literals" do + def str_concat + "foo" "bar" "baz" + end + + def long_string_literals + "Beautiful is better than ugly." \ + "Explicit is better than implicit." + end + + it "on a single line with spaces in between are concatenated together" do + str_concat.should == "foobarbaz" + end + + it "on multiple lines with newlines and backslash in between are concatenated together" do + long_string_literals.should == "Beautiful is better than ugly.Explicit is better than implicit." + end + + ruby_version_is "2.3" do + describe "with a magic frozen comment" do + it "produce the same object each time" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_one_literal.rb")).chomp.should == "true" + end + + it "produce the same object for literals with the same content" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_two_literals.rb")).chomp.should == "true" + end + + it "produce the same object for literals with the same content in different files" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files.rb")).chomp.should == "true" + end + + it "produce different objects for literals with the same content in different files if the other file doesn't have the comment" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_no_comment.rb")).chomp.should == "true" + end + + it "produce different objects for literals with the same content in different files if they have different encodings" do + ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true" + end + end + end + +end + +with_feature :encoding do + describe "Ruby String interpolation" do + it "creates a String having an Encoding compatible with all components" do + a = "\u3042" + b = "abc".encode("ascii-8bit") + + str = "#{a} x #{b}" + + str.should == "\xe3\x81\x82\x20\x78\x20\x61\x62\x63".force_encoding("utf-8") + str.encoding.should == Encoding::UTF_8 + end + + it "creates a String having the Encoding of the components when all are the same Encoding" do + a = "abc".force_encoding("euc-jp") + b = "def".force_encoding("euc-jp") + str = '"#{a} x #{b}"'.force_encoding("euc-jp") + + result = eval(str) + result.should == "\x61\x62\x63\x20\x78\x20\x64\x65\x66".force_encoding("euc-jp") + result.encoding.should == Encoding::EUC_JP + end + + it "raises an Encoding::CompatibilityError if the Encodings are not compatible" do + a = "\u3042" + b = "\xff".force_encoding "ascii-8bit" + + lambda { "#{a} #{b}" }.should raise_error(Encoding::CompatibilityError) + end + end +end diff --git a/spec/rubyspec/language/super_spec.rb b/spec/rubyspec/language/super_spec.rb new file mode 100644 index 0000000000..f7dc0d132c --- /dev/null +++ b/spec/rubyspec/language/super_spec.rb @@ -0,0 +1,282 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/super', __FILE__) + +describe "The super keyword" do + it "calls the method on the calling class" do + Super::S1::A.new.foo([]).should == ["A#foo","A#bar"] + Super::S1::A.new.bar([]).should == ["A#bar"] + Super::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"] + Super::S1::B.new.bar([]).should == ["B#bar","A#bar"] + end + + it "searches the full inheritence chain" do + Super::S2::B.new.foo([]).should == ["B#foo","A#baz"] + Super::S2::B.new.baz([]).should == ["A#baz"] + Super::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"] + Super::S2::C.new.baz([]).should == ["C#baz","A#baz"] + end + + it "searches class methods" do + Super::S3::A.new.foo([]).should == ["A#foo"] + Super::S3::A.foo([]).should == ["A.foo"] + Super::S3::A.bar([]).should == ["A.bar","A.foo"] + Super::S3::B.new.foo([]).should == ["A#foo"] + Super::S3::B.foo([]).should == ["B.foo","A.foo"] + Super::S3::B.bar([]).should == ["B.bar","A.bar","B.foo","A.foo"] + end + + it "calls the method on the calling class including modules" do + Super::MS1::A.new.foo([]).should == ["ModA#foo","ModA#bar"] + Super::MS1::A.new.bar([]).should == ["ModA#bar"] + Super::MS1::B.new.foo([]).should == ["B#foo","ModA#foo","ModB#bar","ModA#bar"] + Super::MS1::B.new.bar([]).should == ["ModB#bar","ModA#bar"] + end + + it "searches the full inheritence chain including modules" do + Super::MS2::B.new.foo([]).should == ["ModB#foo","A#baz"] + Super::MS2::B.new.baz([]).should == ["A#baz"] + Super::MS2::C.new.baz([]).should == ["C#baz","A#baz"] + Super::MS2::C.new.foo([]).should == ["ModB#foo","C#baz","A#baz"] + end + + it "can resolve to different methods in an included module method" do + Super::MultiSuperTargets::A.new.foo.should == :BaseA + Super::MultiSuperTargets::B.new.foo.should == :BaseB + end + + it "searches class methods including modules" do + Super::MS3::A.new.foo([]).should == ["A#foo"] + Super::MS3::A.foo([]).should == ["ModA#foo"] + Super::MS3::A.bar([]).should == ["ModA#bar","ModA#foo"] + Super::MS3::B.new.foo([]).should == ["A#foo"] + Super::MS3::B.foo([]).should == ["B.foo","ModA#foo"] + Super::MS3::B.bar([]).should == ["B.bar","ModA#bar","B.foo","ModA#foo"] + end + + it "searches BasicObject from a module for methods defined there" do + Super::IncludesFromBasic.new.__send__(:foobar).should == 43 + end + + it "searches BasicObject through another module for methods defined there" do + Super::IncludesIntermediate.new.__send__(:foobar).should == 42 + end + + it "calls the correct method when the method visibility is modified" do + Super::MS4::A.new.example.should == 5 + end + + it "calls the correct method when the superclass argument list is different from the subclass" do + Super::S4::A.new.foo([]).should == ["A#foo"] + Super::S4::B.new.foo([],"test").should == ["B#foo(a,test)", "A#foo"] + end + + it "raises an error error when super method does not exist" do + sup = Class.new + sub_normal = Class.new(sup) do + def foo + super() + end + end + sub_zsuper = Class.new(sup) do + def foo + super + end + end + + lambda {sub_normal.new.foo}.should raise_error(NoMethodError, /super/) + lambda {sub_zsuper.new.foo}.should raise_error(NoMethodError, /super/) + end + + it "uses given block even if arguments are passed explicitly" do + c1 = Class.new do + def m + yield + end + end + c2 = Class.new(c1) do + def m(v) + super() + end + end + + c2.new.m(:dump) { :value }.should == :value + end + + it "calls the superclass method when in a block" do + Super::S6.new.here.should == :good + end + + it "calls the superclass method when initial method is defined_method'd" do + Super::S7.new.here.should == :good + end + + it "can call through a define_method multiple times (caching check)" do + obj = Super::S7.new + + 2.times do + obj.here.should == :good + end + end + + it "supers up appropriate name even if used for multiple method names" do + sup = Class.new do + def a; "a"; end + def b; "b"; end + end + + sub = Class.new(sup) do + [:a, :b].each do |name| + define_method name do + super() + end + end + end + + sub.new.a.should == "a" + sub.new.b.should == "b" + sub.new.a.should == "a" + end + + it "raises a RuntimeError when called with implicit arguments from a method defined with define_method" do + super_class = Class.new do + def a(arg) + arg + end + end + + klass = Class.new super_class do + define_method :a do |arg| + super + end + end + + lambda { klass.new.a(:a_called) }.should raise_error(RuntimeError) + end + + # Rubinius ticket github#157 + it "calls method_missing when a superclass method is not found" do + Super::MM_B.new.is_a?(Hash).should == false + end + + # Rubinius ticket github#180 + it "respects the original module a method is aliased from" do + Super::Alias3.new.name3.should == [:alias2, :alias1] + end + + it "sees the included version of a module a method is alias from" do + Super::AliasWithSuper::Trigger.foo.should == [:b, :a] + end + + it "find super from a singleton class" do + obj = Super::SingletonCase::Foo.new + def obj.foobar(array) + array << :singleton + super + end + obj.foobar([]).should == [:singleton, :foo, :base] + end + + it "finds super on other objects if a singleton class aliased the method" do + orig_obj = Super::SingletonAliasCase::Foo.new + orig_obj.alias_on_singleton + orig_obj.new_foobar([]).should == [:foo, :base] + Super::SingletonAliasCase::Foo.new.foobar([]).should == [:foo, :base] + end + + it "passes along modified rest args when they weren't originally empty" do + Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"] + end + + it "passes along modified rest args when they were originally empty" do + Super::RestArgsWithSuper::B.new.a.should == ["foo"] + end + + it "invokes methods from a chain of anonymous modules" do + Super::AnonymousModuleIncludedTwice.new.a([]).should == ["anon", "anon", "non-anon"] + end + + it "without explicit arguments can accept a block but still pass the original arguments" do + Super::ZSuperWithBlock::B.new.a.should == 14 + end + + it "passes along block via reference to method expecting a reference" do + Super::ZSuperWithBlock::B.new.b.should == [14, 15] + end + + it "passes along a block via reference to a method that yields" do + Super::ZSuperWithBlock::B.new.c.should == 16 + end + + it "without explicit arguments passes optional arguments that have a default value" do + Super::ZSuperWithOptional::B.new.m(1, 2).should == 14 + end + + it "without explicit arguments passes optional arguments that have a non-default value" do + Super::ZSuperWithOptional::B.new.m(1, 2, 3).should == 3 + end + + it "without explicit arguments passes optional arguments that have a default value but were modified" do + Super::ZSuperWithOptional::C.new.m(1, 2).should == 100 + end + + it "without explicit arguments passes optional arguments that have a non-default value but were modified" do + Super::ZSuperWithOptional::C.new.m(1, 2, 3).should == 100 + end + + it "without explicit arguments passes rest arguments" do + Super::ZSuperWithRest::B.new.m(1, 2, 3).should == [1, 2, 3] + end + + it "without explicit arguments passes rest arguments including any modifications" do + Super::ZSuperWithRest::B.new.m_modified(1, 2, 3).should == [1, 14, 3] + end + + it "without explicit arguments passes arguments and rest arguments" do + Super::ZSuperWithRestAndOthers::B.new.m(1, 2, 3, 4, 5).should == [3, 4, 5] + end + + it "without explicit arguments passes arguments and rest arguments including any modifications" do + Super::ZSuperWithRestAndOthers::B.new.m_modified(1, 2, 3, 4, 5).should == [3, 14, 5] + end + + it "without explicit arguments that are '_'" do + Super::ZSuperWithUnderscores::B.new.m(1, 2).should == [1, 2] + end + + it "without explicit arguments that are '_' including any modifications" do + Super::ZSuperWithUnderscores::B.new.m_modified(1, 2).should == [14, 2] + end + + describe 'when using keyword arguments' do + it 'passes any given keyword arguments to the parent' do + b = Super::KeywordArguments::B.new + b.foo(:number => 10).should == {:number => 10} + end + + it "passes any given keyword arguments including optional and required ones to the parent" do + class Super::KeywordArguments::C + eval <<-RUBY + def foo(a:, b: 'b', **) + super + end + RUBY + end + c = Super::KeywordArguments::C.new + + c.foo(a: 'a', c: 'c').should == {a: 'a', b: 'b', c: 'c'} + end + + it 'does not pass any keyword arguments to the parent when none are given' do + b = Super::KeywordArguments::B.new + b.foo.should == {} + end + + describe 'when using splat arguments' do + it 'passes splat arguments and keyword arguments to the parent' do + b = Super::SplatAndKeyword::B.new + + b.foo('bar', baz: true).should == [['bar'], {baz: true}] + end + end + end +end diff --git a/spec/rubyspec/language/symbol_spec.rb b/spec/rubyspec/language/symbol_spec.rb new file mode 100644 index 0000000000..41be5b2236 --- /dev/null +++ b/spec/rubyspec/language/symbol_spec.rb @@ -0,0 +1,93 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "A Symbol literal" do + it "is a ':' followed by any number of valid characters" do + a = :foo + a.should be_kind_of(Symbol) + a.inspect.should == ':foo' + end + + it "is a ':' followed by any valid variable, method, or constant name" do + # Add more of these? + [ :Foo, + :foo, + :@foo, + :@@foo, + :$foo, + :_, + :~, + :- , + :FOO, + :_Foo, + :&, + :_9 + ].each { |s| s.should be_kind_of(Symbol) } + end + + it "is a ':' followed by a single- or double-quoted string that may contain otherwise invalid characters" do + [ [:'foo bar', ':"foo bar"'], + [:'++', ':"++"'], + [:'9', ':"9"'], + [:"foo #{1 + 1}", ':"foo 2"'], + [:"foo\nbar", ':"foo\nbar"'], + ].each { |sym, str| + sym.should be_kind_of(Symbol) + sym.inspect.should == str + } + end + + it "may contain '::' in the string" do + :'Some::Class'.should be_kind_of(Symbol) + end + + it "is converted to a literal, unquoted representation if the symbol contains only valid characters" do + a, b, c = :'foo', :'+', :'Foo__9' + a.should be_kind_of(Symbol) + a.inspect.should == ':foo' + b.should be_kind_of(Symbol) + b.inspect.should == ':+' + c.should be_kind_of(Symbol) + c.inspect.should == ':Foo__9' + end + + it "can be created by the %s-delimited expression" do + a, b = :'foo bar', %s{foo bar} + b.should be_kind_of(Symbol) + b.inspect.should == ':"foo bar"' + b.should == a + end + + it "is the same object when created from identical strings" do + var = "@@var" + [ [:symbol, :symbol], + [:'a string', :'a string'], + [:"#{var}", :"#{var}"] + ].each { |a, b| + a.should equal(b) + } + end + + it "can contain null in the string" do + eval(':"\0" ').inspect.should == ':"\\x00"' + end + + it "can be an empty string" do + c = :'' + c.should be_kind_of(Symbol) + c.inspect.should == ':""' + end + + it "can be :!, :!=, or :!~" do + %w{'!', '!=', '!~'}.each do |sym| + sym.to_sym.to_s.should == sym + end + end + + it "can be created from list syntax %i{a b c} without interpolation" do + %i{a b #{c}}.should == [:a, :b, :"\#{c}"] + end + + it "can be created from list syntax %I{a b c} with interpolation" do + %I{a b #{"c"}}.should == [:a, :b, :c] + end +end diff --git a/spec/rubyspec/language/throw_spec.rb b/spec/rubyspec/language/throw_spec.rb new file mode 100644 index 0000000000..d78c137708 --- /dev/null +++ b/spec/rubyspec/language/throw_spec.rb @@ -0,0 +1,80 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The throw keyword" do + it "abandons processing" do + i = 0 + catch(:done) do + loop do + i += 1 + throw :done if i > 4 + end + i += 1 + end + i.should == 5 + end + + it "supports a second parameter" do + msg = catch(:exit) do + throw :exit,:msg + end + msg.should == :msg + end + + it "uses nil as a default second parameter" do + msg = catch(:exit) do + throw :exit + end + msg.should == nil + end + + it "clears the current exception" do + catch :exit do + begin + raise "exception" + rescue + throw :exit + end + end + $!.should be_nil + end + + it "allows any object as its argument" do + catch(1) { throw 1, 2 }.should == 2 + o = Object.new + catch(o) { throw o, o }.should == o + end + + it "does not convert strings to a symbol" do + lambda { catch(:exit) { throw "exit" } }.should raise_error(ArgumentError) + end + + it "unwinds stack from within a method" do + def throw_method(handler, val) + throw handler, val + end + + catch(:exit) do + throw_method(:exit, 5) + end.should == 5 + end + + it "unwinds stack from within a lambda" do + c = lambda { throw :foo, :msg } + catch(:foo) { c.call }.should == :msg + end + + it "raises an ArgumentError if outside of scope of a matching catch" do + lambda { throw :test, 5 }.should raise_error(ArgumentError) + lambda { catch(:different) { throw :test, 5 } }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if used to exit a thread" do + lambda { + catch(:what) do + Thread.new { + throw :what + }.join + end + }.should raise_error(ArgumentError) + end +end diff --git a/spec/rubyspec/language/undef_spec.rb b/spec/rubyspec/language/undef_spec.rb new file mode 100644 index 0000000000..45fe4e9043 --- /dev/null +++ b/spec/rubyspec/language/undef_spec.rb @@ -0,0 +1,39 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The undef keyword" do + it "undefines a method" do + undef_class = Class.new do + def meth(o); o; end + end + obj = undef_class.new + obj.meth(5).should == 5 + undef_class.class_eval do + undef meth + end + lambda { obj.meth(5) }.should raise_error(NoMethodError) + end + + it "allows undefining multiple methods at a time" do + undef_multiple = Class.new do + def method1; end + def method2; :nope; end + + undef :method1, :method2 + end + + obj = undef_multiple.new + obj.respond_to?(:method1).should == false + obj.respond_to?(:method2).should == false + end + + it "raises a NameError when passed a missing name" do + Class.new do + lambda { + undef not_exist + }.should raise_error(NameError) { |e| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + end +end diff --git a/spec/rubyspec/language/unless_spec.rb b/spec/rubyspec/language/unless_spec.rb new file mode 100644 index 0000000000..681f0adfdd --- /dev/null +++ b/spec/rubyspec/language/unless_spec.rb @@ -0,0 +1,43 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The unless expression" do + it "evaluates the unless body when the expression is false" do + unless false + a = true + else + a = false + end + + a.should == true + end + + it "returns the last statement in the body" do + unless false + 'foo' + 'bar' + 'baz' + end.should == 'baz' + end + + it "evaluates the else body when the expression is true" do + unless true + 'foo' + else + 'bar' + end.should == 'bar' + end + + it "takes an optional then after the expression" do + unless false then + 'baz' + end.should == 'baz' + end + + it "does not return a value when the expression is true" do + unless true; end.should == nil + end + + it "allows expression and body to be on one line (using 'then')" do + unless false then 'foo'; else 'bar'; end.should == 'foo' + end +end diff --git a/spec/rubyspec/language/until_spec.rb b/spec/rubyspec/language/until_spec.rb new file mode 100644 index 0000000000..08898644ce --- /dev/null +++ b/spec/rubyspec/language/until_spec.rb @@ -0,0 +1,234 @@ +require File.expand_path('../../spec_helper', __FILE__) + +# until bool-expr [do] +# body +# end +# +# begin +# body +# end until bool-expr +# +# expr until bool-expr +describe "The until expression" do + it "runs while the expression is false" do + i = 0 + until i > 9 + i += 1 + end + + i.should == 10 + end + + it "optionally takes a 'do' after the expression" do + i = 0 + until i > 9 do + i += 1 + end + + i.should == 10 + end + + it "allows body begin on the same line if do is used" do + i = 0 + until i > 9 do i += 1 + end + + i.should == 10 + end + + it "executes code in containing variable scope" do + i = 0 + until i == 1 + a = 123 + i = 1 + end + + a.should == 123 + end + + it "executes code in containing variable scope with 'do'" do + i = 0 + until i == 1 do + a = 123 + i = 1 + end + + a.should == 123 + end + + it "returns nil if ended when condition became true" do + i = 0 + until i > 9 + i += 1 + end.should == nil + end + + it "evaluates the body if expression is empty" do + a = [] + until () + a << :body_evaluated + break + end + a.should == [:body_evaluated] + end + + it "stops running body if interrupted by break" do + i = 0 + until i > 9 + i += 1 + break if i > 5 + end + i.should == 6 + end + + it "returns value passed to break if interrupted by break" do + until false + break 123 + end.should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + until false + break + end.should == nil + end + + it "skips to end of body with next" do + a = [] + i = 0 + until (i+=1)>=5 + next if i==3 + a << i + end + a.should == [1, 2, 4] + end + + it "restarts the current iteration without reevaluating condition with redo" do + a = [] + i = 0 + j = 0 + until (i+=1)>=3 + a << i + j+=1 + redo if j<3 + end + a.should == [1, 1, 1, 2] + end +end + +describe "The until modifier" do + it "runs preceding statement while the condition is false" do + i = 0 + i += 1 until i > 9 + i.should == 10 + end + + it "evaluates condition before statement execution" do + a = [] + i = 0 + a << i until (i+=1) >= 3 + a.should == [1, 2] + end + + it "does not run preceding statement if the condition is true" do + i = 0 + i += 1 until true + i.should == 0 + end + + it "returns nil if ended when condition became true" do + i = 0 + (i += 1 until i>9).should == nil + end + + it "returns value passed to break if interrupted by break" do + (break 123 until false).should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + (break until false).should == nil + end + + it "skips to end of body with next" do + i = 0 + j = 0 + ((i+=1) == 3 ? next : j+=i) until i > 10 + j.should == 63 + end + + it "restarts the current iteration without reevaluating condition with redo" do + i = 0 + j = 0 + (i+=1) == 4 ? redo : j+=i until (i+=1) > 10 + j.should == 34 + end +end + +describe "The until modifier with begin .. end block" do + it "runs block while the expression is false" do + i = 0 + begin + i += 1 + end until i > 9 + + i.should == 10 + end + + it "stops running block if interrupted by break" do + i = 0 + begin + i += 1 + break if i > 5 + end until i > 9 + + i.should == 6 + end + + it "returns value passed to break if interrupted by break" do + (begin; break 123; end until false).should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + (begin; break; end until false).should == nil + end + + it "runs block at least once (even if the expression is true)" do + i = 0 + begin + i += 1 + end until true + + i.should == 1 + end + + it "evaluates condition after block execution" do + a = [] + i = 0 + begin + a << i + end until (i+=1)>=5 + a.should == [0, 1, 2, 3, 4] + end + + it "skips to end of body with next" do + a = [] + i = 0 + begin + next if i==3 + a << i + end until (i+=1)>=5 + a.should == [0, 1, 2, 4] + end + + it "restart the current iteration without reevaluting condition with redo" do + a = [] + i = 0 + j = 0 + begin + a << i + j+=1 + redo if j<3 + end until (i+=1)>=3 + a.should == [0, 0, 0, 1, 2] + end +end diff --git a/spec/rubyspec/language/variables_spec.rb b/spec/rubyspec/language/variables_spec.rb new file mode 100644 index 0000000000..e0ef6b21c4 --- /dev/null +++ b/spec/rubyspec/language/variables_spec.rb @@ -0,0 +1,776 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/variables', __FILE__) + +describe "Multiple assignment" do + context "with a single RHS value" do + it "assigns a simple MLHS" do + (a, b, c = 1).should == 1 + [a, b, c].should == [1, nil, nil] + end + + it "calls #to_ary to convert an Object RHS when assigning a simple MLHS" do + x = mock("multi-assign single RHS") + x.should_receive(:to_ary).and_return([1, 2]) + + (a, b, c = x).should == x + [a, b, c].should == [1, 2, nil] + end + + it "calls #to_ary if it is private" do + x = mock("multi-assign single RHS") + x.should_receive(:to_ary).and_return([1, 2]) + class << x; private :to_ary; end + + (a, b, c = x).should == x + [a, b, c].should == [1, 2, nil] + end + + it "does not call #to_ary if #respond_to? returns false" do + x = mock("multi-assign single RHS") + x.should_receive(:respond_to?).with(:to_ary, true).and_return(false) + x.should_not_receive(:to_ary) + + (a, b, c = x).should == x + [a, b, c].should == [x, nil, nil] + end + + it "wraps the Object in an Array if #to_ary returns nil" do + x = mock("multi-assign single RHS") + x.should_receive(:to_ary).and_return(nil) + + (a, b, c = x).should == x + [a, b, c].should == [x, nil, nil] + end + + it "raises a TypeError of #to_ary does not return an Array" do + x = mock("multi-assign single RHS") + x.should_receive(:to_ary).and_return(1) + + lambda { a, b, c = x }.should raise_error(TypeError) + end + + it "does not call #to_a to convert an Object RHS when assigning a simple MLHS" do + x = mock("multi-assign single RHS") + x.should_not_receive(:to_a) + + (a, b, c = x).should == x + [a, b, c].should == [x, nil, nil] + end + + it "does not call #to_ary on an Array instance" do + x = [1, 2] + x.should_not_receive(:to_ary) + + (a, b = x).should == x + [a, b].should == [1, 2] + end + + it "does not call #to_a on an Array instance" do + x = [1, 2] + x.should_not_receive(:to_a) + + (a, b = x).should == x + [a, b].should == [1, 2] + end + + it "returns the RHS when it is an Array" do + ary = [1, 2] + + x = (a, b = ary) + x.should equal(ary) + end + + it "returns the RHS when it is an Array subclass" do + cls = Class.new(Array) + ary = cls.new [1, 2] + + x = (a, b = ary) + x.should equal(ary) + end + + it "does not call #to_ary on an Array subclass instance" do + x = Class.new(Array).new [1, 2] + x.should_not_receive(:to_ary) + + (a, b = x).should == x + [a, b].should == [1, 2] + end + + it "does not call #to_a on an Array subclass instance" do + x = Class.new(Array).new [1, 2] + x.should_not_receive(:to_a) + + (a, b = x).should == x + [a, b].should == [1, 2] + end + + it "assigns a MLHS with a trailing comma" do + a, = 1 + b, c, = [] + [a, b, c].should == [1, nil, nil] + end + + it "assigns a single LHS splat" do + (*a = 1).should == 1 + a.should == [1] + end + + it "calls #to_ary to convert an Object RHS" do + x = mock("multi-assign splat") + x.should_receive(:to_ary).and_return([1, 2]) + + (*a = x).should == x + a.should == [1, 2] + end + + it "raises a TypeError if #to_ary does not return an Array" do + x = mock("multi-assign splat") + x.should_receive(:to_ary).and_return(1) + + lambda { *a = x }.should raise_error(TypeError) + end + + it "does not call #to_ary on an Array subclass" do + cls = Class.new(Array) + ary = cls.new [1, 2] + ary.should_not_receive(:to_ary) + + (*a = ary).should == [1, 2] + a.should == [1, 2] + end + + it "assigns an Array when the RHS is an Array subclass" do + cls = Class.new(Array) + ary = cls.new [1, 2] + + x = (*a = ary) + x.should equal(ary) + a.should be_an_instance_of(Array) + end + + it "calls #to_ary to convert an Object RHS with MLHS" do + x = mock("multi-assign splat") + x.should_receive(:to_ary).and_return([1, 2]) + + (a, *b, c = x).should == x + [a, b, c].should == [1, [], 2] + end + + it "raises a TypeError if #to_ary does not return an Array" do + x = mock("multi-assign splat") + x.should_receive(:to_ary).and_return(1) + + lambda { a, *b, c = x }.should raise_error(TypeError) + end + + it "does not call #to_a to convert an Object RHS with a MLHS" do + x = mock("multi-assign splat") + x.should_not_receive(:to_a) + + (a, *b = x).should == x + [a, b].should == [x, []] + end + + it "assigns a MLHS with leading splat" do + (*a, b, c = 1).should == 1 + [a, b, c].should == [[], 1, nil] + end + + it "assigns a MLHS with a middle splat" do + a, b, *c, d, e = 1 + [a, b, c, d, e].should == [1, nil, [], nil, nil] + end + + it "assigns a MLHS with a trailing splat" do + a, b, *c = 1 + [a, b, c].should == [1, nil, []] + end + + it "assigns a grouped LHS without splat" do + ((a, b), c), (d, (e,), (f, (g, h))) = 1 + [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil] + end + + it "assigns a single grouped LHS splat" do + (*a) = nil + a.should == [nil] + end + + it "assigns a grouped LHS with splats" do + (a, *b), c, (*d, (e, *f, g)) = 1 + [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil] + end + + it "consumes values for an anonymous splat" do + (* = 1).should == 1 + end + + it "consumes values for a grouped anonymous splat" do + ((*) = 1).should == 1 + end + + it "does not mutate a RHS Array" do + x = [1, 2, 3, 4] + a, *b, c, d = x + [a, b, c, d].should == [1, [2], 3, 4] + x.should == [1, 2, 3, 4] + end + + it "assigns values from a RHS method call" do + def x() 1 end + + (a, b = x).should == 1 + [a, b].should == [1, nil] + end + + it "assigns values from a RHS method call with arguments" do + def x(a) a end + + (a, b = x []).should == [] + [a, b].should == [nil, nil] + end + + it "assigns values from a RHS method call with receiver" do + x = mock("multi-assign attributes") + x.should_receive(:m).and_return([1, 2, 3]) + + a, b = x.m + [a, b].should == [1, 2] + end + + it "calls #to_ary on the value returned by the method call" do + y = mock("multi-assign method return value") + y.should_receive(:to_ary).and_return([1, 2]) + + x = mock("multi-assign attributes") + x.should_receive(:m).and_return(y) + + (a, b = x.m).should == y + [a, b].should == [1, 2] + end + + it "raises a TypeError if #to_ary does not return an Array" do + y = mock("multi-assign method return value") + y.should_receive(:to_ary).and_return(1) + + x = mock("multi-assign attributes") + x.should_receive(:m).and_return(y) + + lambda { a, b = x.m }.should raise_error(TypeError) + end + + it "assigns values from a RHS method call with receiver and arguments" do + x = mock("multi-assign attributes") + x.should_receive(:m).with(1, 2).and_return([1, 2, 3]) + + a, b = x.m 1, 2 + [a, b].should == [1, 2] + end + + it "assigns global variables" do + $spec_a, $spec_b = 1 + [$spec_a, $spec_b].should == [1, nil] + end + + it "assigns instance variables" do + @a, @b = 1 + [@a, @b].should == [1, nil] + end + + it "assigns attributes" do + a = mock("multi-assign attributes") + a.should_receive(:x=).with(1) + a.should_receive(:y=).with(nil) + + a.x, a.y = 1 + end + + it "assigns indexed elements" do + a = [] + a[1], a[2] = 1 + a.should == [nil, 1, nil] + end + + it "assigns constants" do + module VariableSpecs + SINGLE_RHS_1, SINGLE_RHS_2 = 1 + [SINGLE_RHS_1, SINGLE_RHS_2].should == [1, nil] + end + end + end + + context "with a single splatted RHS value" do + it "assigns a single grouped LHS splat" do + (*a) = *1 + a.should == [1] + end + + it "assigns an empty Array to a single LHS value when passed nil" do + (a = *nil).should == [] + a.should == [] + end + + it "calls #to_a to convert nil to an empty Array" do + nil.should_receive(:to_a).and_return([]) + + (*a = *nil).should == [] + a.should == [] + end + + it "does not call #to_a on an Array" do + ary = [1, 2] + ary.should_not_receive(:to_a) + + (a = *ary).should == [1, 2] + a.should == [1, 2] + end + + it "returns a copy of a splatted Array" do + ary = [1, 2] + + (a = *ary).should == [1, 2] + a.should_not equal(ary) + end + + it "does not call #to_a on an Array subclass" do + cls = Class.new(Array) + ary = cls.new [1, 2] + ary.should_not_receive(:to_a) + + (a = *ary).should == [1, 2] + a.should == [1, 2] + end + + it "returns an Array when the splatted object is an Array subclass" do + cls = Class.new(Array) + ary = cls.new [1, 2] + + x = (a = *ary) + + x.should == [1, 2] + x.should be_an_instance_of(Array) + + a.should == [1, 2] + a.should be_an_instance_of(Array) + end + + it "consumes values for an anonymous splat" do + a = 1 + (* = *a).should == [1] + end + + it "assigns a single LHS splat" do + x = 1 + (*a = *x).should == [1] + a.should == [1] + end + + it "calls #to_a to convert an Object RHS with a single splat LHS" do + x = mock("multi-assign RHS splat") + x.should_receive(:to_a).and_return([1, 2]) + + (*a = *x).should == [1, 2] + a.should == [1, 2] + end + + it "calls #to_a if it is private" do + x = mock("multi-assign RHS splat") + x.should_receive(:to_a).and_return([1, 2]) + class << x; private :to_a; end + + (*a = *x).should == [1, 2] + a.should == [1, 2] + end + + it "does not call #to_a if #respond_to? returns false" do + x = mock("multi-assign RHS splat") + x.should_receive(:respond_to?).with(:to_a, true).and_return(false) + x.should_not_receive(:to_a) + + (*a = *x).should == [x] + a.should == [x] + end + + it "wraps the Object in an Array if #to_a returns nil" do + x = mock("multi-assign RHS splat") + x.should_receive(:to_a).and_return(nil) + + (*a = *x).should == [x] + a.should == [x] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign RHS splat") + x.should_receive(:to_a).and_return(1) + + lambda { *a = *x }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert an Object RHS with a single splat LHS" do + x = mock("multi-assign RHS splat") + x.should_not_receive(:to_ary) + + (*a = *x).should == [x] + a.should == [x] + end + + it "assigns a MLHS with leading splat" do + (*a, b, c = *1).should == [1] + [a, b, c].should == [[], 1, nil] + end + + it "assigns a MLHS with a middle splat" do + a, b, *c, d, e = *1 + [a, b, c, d, e].should == [1, nil, [], nil, nil] + end + + it "assigns a MLHS with a trailing splat" do + a, b, *c = *nil + [a, b, c].should == [nil, nil, []] + end + + it "calls #to_a to convert an Object RHS with a single LHS" do + x = mock("multi-assign RHS splat") + x.should_receive(:to_a).and_return([1, 2]) + + (a = *x).should == [1, 2] + a.should == [1, 2] + end + + it "does not call #to_ary to convert an Object RHS with a single LHS" do + x = mock("multi-assign RHS splat") + x.should_not_receive(:to_ary) + + (a = *x).should == [x] + a.should == [x] + end + + it "calls #to_a to convert an Object RHS with a single LHS" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return([1, 2]) + + a = *x + a.should == [1, 2] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return(1) + + lambda { a = *x }.should raise_error(TypeError) + end + + it "calls #to_a to convert an Object splat RHS when assigned to a simple MLHS" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return([1, 2]) + + (a, b, c = *x).should == [1, 2] + [a, b, c].should == [1, 2, nil] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return(1) + + lambda { a, b, c = *x }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert an Object splat RHS when assigned to a simple MLHS" do + x = mock("multi-assign splat") + x.should_not_receive(:to_ary) + + (a, b, c = *x).should == [x] + [a, b, c].should == [x, nil, nil] + end + + it "calls #to_a to convert an Object RHS with MLHS" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return([1, 2]) + + a, *b, c = *x + [a, b, c].should == [1, [], 2] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign splat") + x.should_receive(:to_a).and_return(1) + + lambda { a, *b, c = *x }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert an Object RHS with a MLHS" do + x = mock("multi-assign splat") + x.should_not_receive(:to_ary) + + a, *b = *x + [a, b].should == [x, []] + end + + it "assigns a grouped LHS without splats" do + ((a, b), c), (d, (e,), (f, (g, h))) = *1 + [a, b, c, d, e, f, g, h].should == [1, nil, nil, nil, nil, nil, nil, nil] + end + + it "assigns a grouped LHS with splats" do + (a, *b), c, (*d, (e, *f, g)) = *1 + [a, b, c, d, e, f, g].should == [1, [], nil, [], nil, [], nil] + end + + it "consumes values for an anonymous splat" do + (* = *1).should == [1] + end + + it "consumes values for a grouped anonymous splat" do + ((*) = *1).should == [1] + end + + it "does not mutate a RHS Array" do + x = [1, 2, 3, 4] + a, *b, c, d = *x + [a, b, c, d].should == [1, [2], 3, 4] + x.should == [1, 2, 3, 4] + end + + it "assigns constants" do + module VariableSpecs + (*SINGLE_SPLATTED_RHS) = *1 + SINGLE_SPLATTED_RHS.should == [1] + end + end + end + + context "with a MRHS value" do + it "consumes values for an anonymous splat" do + (* = 1, 2, 3).should == [1, 2, 3] + end + + it "consumes values for multiple '_' variables" do + a, _, b, _, c = 1, 2, 3, 4, 5 + [a, b, c].should == [1, 3, 5] + end + + it "does not call #to_a to convert an Object in a MRHS" do + x = mock("multi-assign MRHS") + x.should_not_receive(:to_a) + + (a, b = 1, x).should == [1, x] + [a, b].should == [1, x] + end + + it "does not call #to_ary to convert an Object in a MRHS" do + x = mock("multi-assign MRHS") + x.should_not_receive(:to_ary) + + (a, b = 1, x).should == [1, x] + [a, b].should == [1, x] + end + + it "calls #to_a to convert a splatted Object as part of a MRHS with a splat MLHS" do + x = mock("multi-assign splat MRHS") + x.should_receive(:to_a).and_return([3, 4]) + + (a, *b = 1, *x).should == [1, 3, 4] + [a, b].should == [1, [3, 4]] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign splat MRHS") + x.should_receive(:to_a).and_return(1) + + lambda { a, *b = 1, *x }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do + x = mock("multi-assign splat MRHS") + x.should_not_receive(:to_ary) + + (a, *b = 1, *x).should == [1, x] + [a, b].should == [1, [x]] + end + + it "calls #to_a to convert a splatted Object as part of a MRHS with a splat MLHS" do + x = mock("multi-assign splat MRHS") + x.should_receive(:to_a).and_return([3, 4]) + + (a, *b = *x, 1).should == [3, 4, 1] + [a, b].should == [3, [4, 1]] + end + + it "raises a TypeError if #to_a does not return an Array" do + x = mock("multi-assign splat MRHS") + x.should_receive(:to_a).and_return(1) + + lambda { a, *b = *x, 1 }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert a splatted Object as part of a MRHS with a splat MRHS" do + x = mock("multi-assign splat MRHS") + x.should_not_receive(:to_ary) + + (a, *b = *x, 1).should == [x, 1] + [a, b].should == [x, [1]] + end + + it "assigns a grouped LHS without splat from a simple Array" do + ((a, b), c), (d, (e,), (f, (g, h))) = 1, 2, 3, 4, 5 + [a, b, c, d, e, f, g, h].should == [1, nil, nil, 2, nil, nil, nil, nil] + end + + it "assigns a grouped LHS without splat from nested Arrays" do + ary = [[1, 2, 3], 4], [[5], [6, 7], [8, [9, 10]]] + ((a, b), c), (d, (e,), (f, (g, h))) = ary + [a, b, c, d, e, f, g, h].should == [1, 2, 4, [5], 6, 8, 9, 10] + end + + it "assigns a single grouped LHS splat" do + (*a) = 1, 2, 3 + a.should == [1, 2, 3] + end + + it "assigns a grouped LHS with splats from nested Arrays" do + (a, *b), c, (*d, (e, *f, g)) = 1, 2, 3, 4 + [a, b, c, d, e, f, g].should == [1, [], 2, [], 3, [], nil] + end + + it "assigns a grouped LHS with splats from nested Arrays" do + (a, *b), c, (*d, (e, *f, g)) = [1, [2, 3]], [4, 5], [6, 7, 8] + [a, b, c, d, e, f, g].should == [1, [[2, 3]], [4, 5], [6, 7], 8, [], nil] + end + + it "consumes values for an anonymous splat" do + (* = 1, 2, 3).should == [1, 2, 3] + end + + it "consumes values for a grouped anonymous splat" do + ((*) = 1, 2, 3).should == [1, 2, 3] + end + + it "calls #to_ary to convert an Object when the position receiving the value is a multiple assignment" do + x = mock("multi-assign mixed RHS") + x.should_receive(:to_ary).and_return([1, 2]) + + (a, (b, c), d, e = 1, x, 3, 4).should == [1, x, 3, 4] + [a, b, c, d, e].should == [1, 1, 2, 3, 4] + end + + it "raises a TypeError if #to_ary does not return an Array" do + x = mock("multi-assign mixed RHS") + x.should_receive(:to_ary).and_return(x) + + lambda { a, (b, c), d = 1, x, 3, 4 }.should raise_error(TypeError) + end + + it "calls #to_a to convert a splatted Object value in a MRHS" do + x = mock("multi-assign mixed splatted RHS") + x.should_receive(:to_a).and_return([4, 5]) + + (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, 4, 5] + [a, b, c, d].should == [1, [2, 3, 4], 5, nil] + + end + + it "calls #to_ary to convert a splatted Object when the position receiving the value is a multiple assignment" do + x = mock("multi-assign mixed splatted RHS") + x.should_receive(:to_ary).and_return([4, 5]) + + (a, *b, (c, d) = 1, 2, 3, *x).should == [1, 2, 3, x] + [a, b, c, d].should == [1, [2, 3], 4, 5] + end + + it "raises a TypeError if #to_ary does not return an Array" do + x = mock("multi-assign mixed splatted RHS") + x.should_receive(:to_ary).and_return(x) + + lambda { a, *b, (c, d) = 1, 2, 3, *x }.should raise_error(TypeError) + end + + it "does not call #to_ary to convert an Object when the position receiving the value is a simple variable" do + x = mock("multi-assign mixed RHS") + x.should_not_receive(:to_ary) + + a, b, c, d = 1, x, 3, 4 + [a, b, c, d].should == [1, x, 3, 4] + end + + it "does not call #to_ary to convert an Object when the position receiving the value is a rest variable" do + x = mock("multi-assign mixed RHS") + x.should_not_receive(:to_ary) + + a, *b, c, d = 1, x, 3, 4 + [a, b, c, d].should == [1, [x], 3, 4] + end + + it "does not call #to_ary to convert a splatted Object when the position receiving the value is a simple variable" do + x = mock("multi-assign mixed splatted RHS") + x.should_not_receive(:to_ary) + + a, *b, c = 1, 2, *x + [a, b, c].should == [1, [2], x] + end + + it "does not call #to_ary to convert a splatted Object when the position receiving the value is a rest variable" do + x = mock("multi-assign mixed splatted RHS") + x.should_not_receive(:to_ary) + + a, b, *c = 1, 2, *x + [a, b, c].should == [1, 2, [x]] + end + + it "does not mutate the assigned Array" do + x = ((a, *b, c, d) = 1, 2, 3, 4, 5) + x.should == [1, 2, 3, 4, 5] + end + + it "assigns RHS values to LHS constants" do + module VariableSpecs + MRHS_VALUES_1, MRHS_VALUES_2 = 1, 2 + MRHS_VALUES_1.should == 1 + MRHS_VALUES_2.should == 2 + end + end + + it "assigns all RHS values as an array to a single LHS constant" do + module VariableSpecs + MRHS_VALUES = 1, 2, 3 + MRHS_VALUES.should == [1, 2, 3] + end + end + end + + context "with a RHS assignment value" do + it "consumes values for an anonymous splat" do + (* = (a = 1)).should == 1 + a.should == 1 + end + + it "does not mutate a RHS Array" do + a, *b, c, d = (e = [1, 2, 3, 4]) + [a, b, c, d].should == [1, [2], 3, 4] + e.should == [1, 2, 3, 4] + end + end +end + +describe "A local variable assigned only within a conditional block" do + context "accessed from a later closure" do + it "is defined?" do + if VariablesSpecs.false + a = 1 + end + + 1.times do + defined?(a).should == "local-variable" + end + end + + it "is nil" do + if VariablesSpecs.false + a = 1 + end + + 1.times do + a.inspect.should == "nil" + end + end + end +end diff --git a/spec/rubyspec/language/while_spec.rb b/spec/rubyspec/language/while_spec.rb new file mode 100644 index 0000000000..a498112919 --- /dev/null +++ b/spec/rubyspec/language/while_spec.rb @@ -0,0 +1,344 @@ +require File.expand_path('../../spec_helper', __FILE__) + +# while bool-expr [do] +# body +# end +# +# begin +# body +# end while bool-expr +# +# expr while bool-expr +describe "The while expression" do + it "runs while the expression is true" do + i = 0 + while i < 3 + i += 1 + end + i.should == 3 + end + + it "optionally takes a 'do' after the expression" do + i = 0 + while i < 3 do + i += 1 + end + + i.should == 3 + end + + it "allows body begin on the same line if do is used" do + i = 0 + while i < 3 do i += 1 + end + + i.should == 3 + end + + it "executes code in containing variable scope" do + i = 0 + while i != 1 + a = 123 + i = 1 + end + + a.should == 123 + end + + it "executes code in containing variable scope with 'do'" do + i = 0 + while i != 1 do + a = 123 + i = 1 + end + + a.should == 123 + end + + it "returns nil if ended when condition became false" do + i = 0 + while i < 3 + i += 1 + end.should == nil + end + + it "does not evaluate the body if expression is empty" do + a = [] + while () + a << :body_evaluated + end + a.should == [] + end + + it "stops running body if interrupted by break" do + i = 0 + while i < 10 + i += 1 + break if i > 5 + end + i.should == 6 + end + + it "stops running body if interrupted by break in a parenthesized element op-assign-or value" do + c = true + a = [] + while c + a[1] ||= + ( + break if c + c = false + ) + end.should be_nil + end + + it "stops running body if interrupted by break in a begin ... end element op-assign-or value" do + c = true + a = [] + while c + a[1] ||= begin + break if c + c = false + end + end.should be_nil + end + + it "stops running body if interrupted by break in a parenthesized element op-assign value" do + c = true + a = [1, 2] + while c + a[1] += + ( + break if c + c = false + ) + end.should be_nil + a.should == [1, 2] + end + + it "stops running body if interrupted by break in a begin ... end element op-assign value" do + c = true + a = [1, 2] + while c + a[1] += begin + break if c + c = false + end + end.should be_nil + a.should == [1, 2] + end + + it "stops running body if interrupted by break in a parenthesized attribute op-assign-or value" do + a = mock("attribute assignment break") + a.should_receive(:m).twice.and_return(nil) + a.should_receive(:m=) + + c = d = true + while c + a.m ||= + ( + break unless d + d = false + ) + end.should be_nil + end + + it "stops running body if interrupted by break in a begin ... end attribute op-assign-or value" do + a = mock("attribute assignment break") + a.should_receive(:m).twice.and_return(nil) + a.should_receive(:m=) + + c = d = true + while c + a.m ||= begin + break unless d + d = false + end + end.should be_nil + end + + it "stops running body if interrupted by break in a parenthesized attribute op-assign-or value" do + a = mock("attribute assignment break") + a.should_receive(:m).and_return(nil) + a.should_not_receive(:m=) + + c = true + while c + a.m += + ( + break if c + c = false + ) + end.should be_nil + end + + it "stops running body if interrupted by break in a begin ... end attribute op-assign-or value" do + a = mock("attribute assignment break") + a.should_receive(:m).and_return(nil) + a.should_not_receive(:m=) + + c = true + while c + a.m += begin + break if c + c = false + end + end.should be_nil + end + + it "returns value passed to break if interrupted by break" do + while true + break 123 + end.should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + while true + break + end.should == nil + end + + it "skips to end of body with next" do + a = [] + i = 0 + while (i+=1)<5 + next if i==3 + a << i + end + a.should == [1, 2, 4] + end + + it "restarts the current iteration without reevaluating condition with redo" do + a = [] + i = 0 + j = 0 + while (i+=1)<3 + a << i + j+=1 + redo if j<3 + end + a.should == [1, 1, 1, 2] + end +end + +describe "The while modifier" do + it "runs preceding statement while the condition is true" do + i = 0 + i += 1 while i < 3 + i.should == 3 + end + + it "evaluates condition before statement execution" do + a = [] + i = 0 + a << i while (i+=1) < 3 + a.should == [1, 2] + end + + it "does not run preceding statement if the condition is false" do + i = 0 + i += 1 while false + i.should == 0 + end + + it "does not run preceding statement if the condition is empty" do + i = 0 + i += 1 while () + i.should == 0 + end + + it "returns nil if ended when condition became false" do + i = 0 + (i += 1 while i<10).should == nil + end + + it "returns value passed to break if interrupted by break" do + (break 123 while true).should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + (break while true).should == nil + end + + it "skips to end of body with next" do + i = 0 + j = 0 + ((i+=1) == 3 ? next : j+=i) while i <= 10 + j.should == 63 + end + + it "restarts the current iteration without reevaluating condition with redo" do + i = 0 + j = 0 + (i+=1) == 4 ? redo : j+=i while (i+=1) <= 10 + j.should == 34 + end +end + +describe "The while modifier with begin .. end block" do + it "runs block while the expression is true" do + i = 0 + begin + i += 1 + end while i < 3 + + i.should == 3 + end + + it "stops running block if interrupted by break" do + i = 0 + begin + i += 1 + break if i > 5 + end while i < 10 + + i.should == 6 + end + + it "returns value passed to break if interrupted by break" do + (begin; break 123; end while true).should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + (begin; break; end while true).should == nil + end + + it "runs block at least once (even if the expression is false)" do + i = 0 + begin + i += 1 + end while false + + i.should == 1 + end + + it "evaluates condition after block execution" do + a = [] + i = 0 + begin + a << i + end while (i+=1)<5 + a.should == [0, 1, 2, 3, 4] + end + + it "skips to end of body with next" do + a = [] + i = 0 + begin + next if i==3 + a << i + end while (i+=1)<5 + a.should == [0, 1, 2, 4] + end + + it "restarts the current iteration without reevaluting condition with redo" do + a = [] + i = 0 + j = 0 + begin + a << i + j+=1 + redo if j<3 + end while (i+=1)<3 + a.should == [0, 0, 0, 1, 2] + end +end diff --git a/spec/rubyspec/language/yield_spec.rb b/spec/rubyspec/language/yield_spec.rb new file mode 100644 index 0000000000..663110cbe6 --- /dev/null +++ b/spec/rubyspec/language/yield_spec.rb @@ -0,0 +1,179 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/yield', __FILE__) + +# Note that these specs use blocks defined as { |*a| ... } to capture the +# arguments with which the block is invoked. This is slightly confusing +# because the outer Array is a consequence of |*a| but it is necessary to +# clearly distinguish some behaviors. + +describe "The yield call" do + before :each do + @y = YieldSpecs::Yielder.new + end + + describe "taking no arguments" do + it "raises a LocalJumpError when the method is not passed a block" do + lambda { @y.z }.should raise_error(LocalJumpError) + end + + it "ignores assignment to the explicit block argument and calls the passed block" do + @y.ze { 42 }.should == 42 + end + end + + describe "taking a single argument" do + describe "when no block is given" do + it "raises a LocalJumpError" do + lambda { @y.s(1) }.should raise_error(LocalJumpError) + end + end + + describe "yielding to a literal block" do + it "passes an empty Array when the argument is an empty Array" do + @y.s([]) { |*a| a }.should == [[]] + end + + it "passes nil as a value" do + @y.s(nil) { |*a| a }.should == [nil] + end + + it "passes a single value" do + @y.s(1) { |*a| a }.should == [1] + end + + it "passes a single, multi-value Array" do + @y.s([1, 2, 3]) { |*a| a }.should == [[1, 2, 3]] + end + end + + describe "yielding to a lambda" do + it "passes an empty Array when the argument is an empty Array" do + @y.s([], &lambda { |*a| a }).should == [[]] + end + + it "passes nil as a value" do + @y.s(nil, &lambda { |*a| a }).should == [nil] + end + + it "passes a single value" do + @y.s(1, &lambda { |*a| a }).should == [1] + end + + it "passes a single, multi-value Array" do + @y.s([1, 2, 3], &lambda { |*a| a }).should == [[1, 2, 3]] + end + + it "raises an ArgumentError if too few arguments are passed" do + lambda { + @y.s(1, &lambda { |a,b| [a,b] }) + }.should raise_error(ArgumentError) + end + + ruby_bug "#12705", "2.2"..."2.5" do + it "should not destructure an Array into multiple arguments" do + lambda { + @y.s([1, 2], &lambda { |a,b| [a,b] }) + }.should raise_error(ArgumentError) + end + end + end + end + + describe "taking multiple arguments" do + it "raises a LocalJumpError when the method is not passed a block" do + lambda { @y.m(1, 2, 3) }.should raise_error(LocalJumpError) + end + + it "passes the arguments to the block" do + @y.m(1, 2, 3) { |*a| a }.should == [1, 2, 3] + end + + it "passes only the first argument if the block takes one parameter" do + @y.m(1, 2, 3) { |a| a }.should == 1 + end + + it "raises an ArgumentError if too many arguments are passed to a lambda" do + lambda { + @y.m(1, 2, 3, &lambda { |a| }) + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if too few arguments are passed to a lambda" do + lambda { + @y.m(1, 2, 3, &lambda { |a,b,c,d| }) + }.should raise_error(ArgumentError) + end + end + + describe "taking a single splatted argument" do + it "raises a LocalJumpError when the method is not passed a block" do + lambda { @y.r(0) }.should raise_error(LocalJumpError) + end + + it "passes a single value" do + @y.r(1) { |*a| a }.should == [1] + end + + it "passes no arguments when the argument is an empty Array" do + @y.r([]) { |*a| a }.should == [] + end + + it "passes the value when the argument is an Array containing a single value" do + @y.r([1]) { |*a| a }.should == [1] + end + + it "passes the values of the Array as individual arguments" do + @y.r([1, 2, 3]) { |*a| a }.should == [1, 2, 3] + end + + it "passes the element of a single element Array" do + @y.r([[1, 2]]) { |*a| a }.should == [[1, 2]] + @y.r([nil]) { |*a| a }.should == [nil] + @y.r([[]]) { |*a| a }.should == [[]] + end + + it "passes no values when give nil as an argument" do + @y.r(nil) { |*a| a }.should == [] + end + end + + describe "taking multiple arguments with a splat" do + it "raises a LocalJumpError when the method is not passed a block" do + lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError) + end + + it "passes the arguments to the block" do + @y.rs(1, 2, 3) { |*a| a }.should == [1, 2, 3] + end + + it "does not pass an argument value if the splatted argument is an empty Array" do + @y.rs(1, 2, []) { |*a| a }.should == [1, 2] + end + + it "passes the Array elements as arguments if the splatted argument is a non-empty Array" do + @y.rs(1, 2, [3]) { |*a| a }.should == [1, 2, 3] + @y.rs(1, 2, [nil]) { |*a| a }.should == [1, 2, nil] + @y.rs(1, 2, [[]]) { |*a| a }.should == [1, 2, []] + @y.rs(1, 2, [3, 4, 5]) { |*a| a }.should == [1, 2, 3, 4, 5] + end + + it "does not pass an argument value if the splatted argument is nil" do + @y.rs(1, 2, nil) { |*a| a }.should == [1, 2] + end + end + + describe "taking matching arguments with splats and post args" do + it "raises a LocalJumpError when the method is not passed a block" do + lambda { @y.rs(1, 2, [3, 4]) }.should raise_error(LocalJumpError) + end + + it "passes the arguments to the block" do + @y.rs([1, 2], 3, 4) { |(*a, b), c, d| [a, b, c, d] }.should == [[1], 2, 3, 4] + end + end + + it "uses captured block of a block used in define_method" do + @y.deep(2).should == 4 + end + +end -- cgit v1.2.3