diff options
author | Benoit Daloze <eregontp@gmail.com> | 2023-10-30 13:49:46 +0100 |
---|---|---|
committer | Benoit Daloze <eregontp@gmail.com> | 2023-10-30 13:49:46 +0100 |
commit | ab4781b64d945e962575f2eac20b72185235d23b (patch) | |
tree | 9c4456926c1616b0417db0b389371d578876a136 /spec/ruby | |
parent | 14fa5e39d72c84d3e12e10dc5d77a6e6200c10f5 (diff) | |
download | ruby-ab4781b64d945e962575f2eac20b72185235d23b.tar.gz |
Update to ruby/spec@bd7017f
Diffstat (limited to 'spec/ruby')
88 files changed, 1634 insertions, 289 deletions
diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml index 99209b607b..9ad57dd51c 100644 --- a/spec/ruby/.rubocop.yml +++ b/spec/ruby/.rubocop.yml @@ -33,6 +33,10 @@ Lint/AssignmentInCondition: Lint/BooleanSymbol: Enabled: false +Lint/DeprecatedOpenSSLConstant: + Exclude: + - library/openssl/digest/**/*.rb + Lint/InterpolationCheck: Enabled: false diff --git a/spec/ruby/CONTRIBUTING.md b/spec/ruby/CONTRIBUTING.md index 4a1e1d46bd..c82eb5ea4f 100644 --- a/spec/ruby/CONTRIBUTING.md +++ b/spec/ruby/CONTRIBUTING.md @@ -179,6 +179,7 @@ In case there is a bug in MRI and the fix will be backported to previous version If it is not backported or not likely, use `ruby_version_is` instead. First, file a bug at https://bugs.ruby-lang.org/. The problem is `ruby_bug` would make non-MRI implementations fail this spec while MRI itself does not pass it, so it should only be used if the bug is/will be fixed and backported. +Otherwise, non-MRI implementations would have to choose between being incompatible with the latest release of MRI to pass the spec or fail the spec, both which make no sense. ```ruby ruby_bug '#13669', ''...'3.2' do diff --git a/spec/ruby/README.md b/spec/ruby/README.md index 536388e7c8..115392835f 100644 --- a/spec/ruby/README.md +++ b/spec/ruby/README.md @@ -128,6 +128,12 @@ MSpec can automatically add new top-level constants in this file with: $ CHECK_LEAKS=save mspec ../mspec/bin/mspec file +### Running Specs on S390x CPU Architecture + +Run the specs with `DFLTCC=0` if you see failing specs related to the zlib library on s390x CPU architecture. The failures can happen with the zlib library applying the patch madler/zlib#410 to enable the deflate algorithm producing a different compressed byte stream. + + $ DFLTCC=0 ../mspec/bin/mspec + ### Contributing and Writing Specs See [CONTRIBUTING.md](https://github.com/ruby/spec/blob/master/CONTRIBUTING.md) for documentation about contributing and writing specs (guards, matchers, etc). diff --git a/spec/ruby/command_line/dash_a_spec.rb b/spec/ruby/command_line/dash_a_spec.rb index 9ea135dc76..43d923ce16 100644 --- a/spec/ruby/command_line/dash_a_spec.rb +++ b/spec/ruby/command_line/dash_a_spec.rb @@ -6,13 +6,13 @@ describe "The -a command line option" do end it "runs the code in loop conditional on Kernel.gets()" do - ruby_exe("puts $F.last", options: "-n -a", escape: true, + ruby_exe("puts $F.last", options: "-n -a", args: " < #{@names}").should == "jones\nfield\ngrey\n" end it "sets $-a" do - ruby_exe("puts $-a", options: "-n -a", escape: true, + ruby_exe("puts $-a", options: "-n -a", args: " < #{@names}").should == "true\ntrue\ntrue\n" end diff --git a/spec/ruby/command_line/dash_l_spec.rb b/spec/ruby/command_line/dash_l_spec.rb index 5c1d3cf4cd..44a98445f3 100644 --- a/spec/ruby/command_line/dash_l_spec.rb +++ b/spec/ruby/command_line/dash_l_spec.rb @@ -6,25 +6,25 @@ describe "The -l command line option" do end it "chomps lines with default separator" do - ruby_exe('puts $_.end_with?("\n")', options: "-n -l", escape: true, + ruby_exe('puts $_.end_with?("\n")', options: "-n -l", args: " < #{@names}").should == "false\nfalse\nfalse\n" end it "chomps last line based on $/" do - ruby_exe('BEGIN { $/ = "ones\n" }; puts $_', options: "-W0 -n -l", escape: true, + ruby_exe('BEGIN { $/ = "ones\n" }; puts $_', options: "-W0 -n -l", args: " < #{@names}").should == "alice j\nbob field\njames grey\n" end it "sets $\\ to the value of $/" do - ruby_exe("puts $\\ == $/", options: "-W0 -n -l", escape: true, + ruby_exe("puts $\\ == $/", options: "-W0 -n -l", args: " < #{@names}").should == "true\ntrue\ntrue\n" end it "sets $-l" do - ruby_exe("puts $-l", options: "-n -l", escape: true, + ruby_exe("puts $-l", options: "-n -l", args: " < #{@names}").should == "true\ntrue\ntrue\n" end diff --git a/spec/ruby/command_line/dash_n_spec.rb b/spec/ruby/command_line/dash_n_spec.rb index 9d331d6065..1dd9379259 100644 --- a/spec/ruby/command_line/dash_n_spec.rb +++ b/spec/ruby/command_line/dash_n_spec.rb @@ -6,19 +6,19 @@ describe "The -n command line option" do end it "runs the code in loop conditional on Kernel.gets()" do - ruby_exe("puts $_", options: "-n", escape: true, + ruby_exe("puts $_", options: "-n", args: " < #{@names}").should == "alice\nbob\njames\n" end it "only evaluates BEGIN blocks once" do - ruby_exe("BEGIN { puts \"hi\" }; puts $_", options: "-n", escape: true, + ruby_exe("BEGIN { puts \"hi\" }; puts $_", options: "-n", args: " < #{@names}").should == "hi\nalice\nbob\njames\n" end it "only evaluates END blocks once" do - ruby_exe("puts $_; END {puts \"bye\"}", options: "-n", escape: true, + ruby_exe("puts $_; END {puts \"bye\"}", options: "-n", args: " < #{@names}").should == "alice\nbob\njames\nbye\n" end @@ -29,7 +29,7 @@ describe "The -n command line option" do $total += 1 END { puts $total } script - ruby_exe(script, options: "-n", escape: true, + ruby_exe(script, options: "-n", args: " < #{@names}").should == "3\n" end diff --git a/spec/ruby/command_line/dash_p_spec.rb b/spec/ruby/command_line/dash_p_spec.rb index 39827c3868..967e3796de 100644 --- a/spec/ruby/command_line/dash_p_spec.rb +++ b/spec/ruby/command_line/dash_p_spec.rb @@ -6,13 +6,13 @@ describe "The -p command line option" do end it "runs the code in loop conditional on Kernel.gets() and prints $_" do - ruby_exe("$_ = $_.upcase", options: "-p", escape: true, + ruby_exe("$_ = $_.upcase", options: "-p", args: " < #{@names}").should == "ALICE\nBOB\nJAMES\n" end it "sets $-p" do - ruby_exe("$_ = $-p", options: "-p", escape: true, + ruby_exe("$_ = $-p", options: "-p", args: " < #{@names}").should == "truetruetrue" end diff --git a/spec/ruby/command_line/dash_upper_f_spec.rb b/spec/ruby/command_line/dash_upper_f_spec.rb index 967acc2ece..5c10a7140d 100644 --- a/spec/ruby/command_line/dash_upper_f_spec.rb +++ b/spec/ruby/command_line/dash_upper_f_spec.rb @@ -6,7 +6,7 @@ describe "the -F command line option" do end it "specifies the field separator pattern for -a" do - ruby_exe("puts $F[0]", options: "-naF:", escape: true, + ruby_exe("puts $F[0]", options: "-naF:", args: " < #{@passwd}").should == "nobody\nroot\ndaemon\n" end diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb index bbea4d557d..734db8d519 100644 --- a/spec/ruby/command_line/rubyopt_spec.rb +++ b/spec/ruby/command_line/rubyopt_spec.rb @@ -11,14 +11,14 @@ describe "Processing RUBYOPT" do it "adds the -I path to $LOAD_PATH" do ENV["RUBYOPT"] = "-Ioptrubyspecincl" - result = ruby_exe("puts $LOAD_PATH.grep(/byspecin/)", escape: true) + result = ruby_exe("puts $LOAD_PATH.grep(/byspecin/)") result.chomp[-15..-1].should == "optrubyspecincl" end it "sets $DEBUG to true for '-d'" do ENV["RUBYOPT"] = '-d' command = %[puts "value of $DEBUG is \#{$DEBUG}"] - result = ruby_exe(command, escape: true, args: "2>&1") + result = ruby_exe(command, args: "2>&1") result.should =~ /value of \$DEBUG is true/ end @@ -36,27 +36,27 @@ describe "Processing RUBYOPT" do it "sets $VERBOSE to true for '-w'" do ENV["RUBYOPT"] = '-w' - ruby_exe("p $VERBOSE", escape: true).chomp.should == "true" + ruby_exe("p $VERBOSE").chomp.should == "true" end it "sets $VERBOSE to true for '-W'" do ENV["RUBYOPT"] = '-W' - ruby_exe("p $VERBOSE", escape: true).chomp.should == "true" + ruby_exe("p $VERBOSE").chomp.should == "true" end it "sets $VERBOSE to nil for '-W0'" do ENV["RUBYOPT"] = '-W0' - ruby_exe("p $VERBOSE", escape: true).chomp.should == "nil" + ruby_exe("p $VERBOSE").chomp.should == "nil" end it "sets $VERBOSE to false for '-W1'" do ENV["RUBYOPT"] = '-W1' - ruby_exe("p $VERBOSE", escape: true).chomp.should == "false" + ruby_exe("p $VERBOSE").chomp.should == "false" end it "sets $VERBOSE to true for '-W2'" do ENV["RUBYOPT"] = '-W2' - ruby_exe("p $VERBOSE", escape: true).chomp.should == "true" + ruby_exe("p $VERBOSE").chomp.should == "true" end it "suppresses deprecation warnings for '-W:no-deprecated'" do diff --git a/spec/ruby/core/argf/readpartial_spec.rb b/spec/ruby/core/argf/readpartial_spec.rb index 5e284b3423..bbc8831131 100644 --- a/spec/ruby/core/argf/readpartial_spec.rb +++ b/spec/ruby/core/argf/readpartial_spec.rb @@ -69,7 +69,7 @@ describe "ARGF.readpartial" do print ARGF.readpartial(#{@stdin.size}) ARGF.readpartial(1) rescue print $!.class STR - stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}", escape: true) + stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}") stdin.should == @stdin + "EOFError" end end diff --git a/spec/ruby/core/array/bsearch_index_spec.rb b/spec/ruby/core/array/bsearch_index_spec.rb index df2c7c098e..94d85b37f3 100644 --- a/spec/ruby/core/array/bsearch_index_spec.rb +++ b/spec/ruby/core/array/bsearch_index_spec.rb @@ -63,10 +63,6 @@ describe "Array#bsearch_index" do @array.bsearch_index { |x| -1 }.should be_nil end - it "returns the middle element when block always returns zero" do - @array.bsearch_index { |x| 0 }.should == 2 - end - context "magnitude does not effect the result" do it "returns the index of any matched elements where element is between 4n <= xn < 8n" do [1, 2].should include(@array.bsearch_index { |x| (1 - x / 4) * (2**100) }) diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb index 61115b5eec..32f515c81d 100644 --- a/spec/ruby/core/dir/glob_spec.rb +++ b/spec/ruby/core/dir/glob_spec.rb @@ -106,11 +106,11 @@ describe "Dir.glob" do ruby_version_is '3.1' do it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do expected = %w[ - nested/. - nested/.dotsubir - nested/.dotsubir/.dotfile - nested/.dotsubir/nondotfile - ] + nested/. + nested/.dotsubir + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + ] Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort end diff --git a/spec/ruby/core/exception/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb index bd85927dbe..fbe4443daa 100644 --- a/spec/ruby/core/exception/detailed_message_spec.rb +++ b/spec/ruby/core/exception/detailed_message_spec.rb @@ -7,6 +7,14 @@ describe "Exception#detailed_message" do RuntimeError.new("new error").detailed_message.should == "new error (RuntimeError)" end + it "is called by #full_message to allow message customization" do + exception = Exception.new("new error") + def exception.detailed_message(**) + "<prefix>#{message}<suffix>" + end + exception.full_message(highlight: false).should.include? "<prefix>new error<suffix>" + end + it "accepts highlight keyword argument and adds escape control sequences" do RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m" end @@ -23,13 +31,13 @@ describe "Exception#detailed_message" do RuntimeError.new("").detailed_message.should == "unhandled exception" end - it "returns just class name for an instance of RuntimeError sublass with empty message" do + it "returns just class name for an instance of RuntimeError subclass with empty message" do DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C" end it "returns a generated class name for an instance of RuntimeError anonymous subclass with empty message" do klass = Class.new(RuntimeError) - klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/ + klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/ end end end diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb index ee66582022..e15649ca75 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -107,21 +107,49 @@ describe "Exception#full_message" do ruby_version_is "3.2" do it "relies on #detailed_message" do e = RuntimeError.new("new error") - e.define_singleton_method(:detailed_message) { |**opt| "DETAILED MESSAGE" } + e.define_singleton_method(:detailed_message) { |**| "DETAILED MESSAGE" } e.full_message.lines.first.should =~ /DETAILED MESSAGE/ end - it "passes all its own keyword arguments to #detailed_message" do + it "passes all its own keyword arguments (with :highlight default value and without :order default value) to #detailed_message" do e = RuntimeError.new("new error") - opt_ = nil - e.define_singleton_method(:detailed_message) do |**opt| - opt_ = opt + options_passed = nil + e.define_singleton_method(:detailed_message) do |**options| + options_passed = options "DETAILED MESSAGE" end e.full_message(foo: "bar") - opt_.should == { foo: "bar", highlight: Exception.to_tty? } + options_passed.should == { foo: "bar", highlight: Exception.to_tty? } + end + + it "converts #detailed_message returned value to String if it isn't a String" do + message = Object.new + def message.to_str; "DETAILED MESSAGE"; end + + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| message } + + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end + + it "uses class name if #detailed_message returns nil" do + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| nil } + + e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ + e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ + end + + it "uses class name if exception object doesn't respond to #detailed_message" do + e = RuntimeError.new("new error") + class << e + undef :detailed_message + end + + e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ + e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ end end end diff --git a/spec/ruby/core/file/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb index 751e99d994..070d830bc4 100644 --- a/spec/ruby/core/file/flock_spec.rb +++ b/spec/ruby/core/file/flock_spec.rb @@ -30,7 +30,7 @@ describe "File#flock" do it "returns false if trying to lock an exclusively locked file" do @file.flock File::LOCK_EX - ruby_exe(<<-END_OF_CODE, escape: true).should == "false" + ruby_exe(<<-END_OF_CODE).should == "false" File.open('#{@name}', "w") do |f2| print f2.flock(File::LOCK_EX | File::LOCK_NB).to_s end @@ -40,7 +40,7 @@ describe "File#flock" do it "blocks if trying to lock an exclusively locked file" do @file.flock File::LOCK_EX - out = ruby_exe(<<-END_OF_CODE, escape: true) + out = ruby_exe(<<-END_OF_CODE) running = false t = Thread.new do diff --git a/spec/ruby/core/file/new_spec.rb b/spec/ruby/core/file/new_spec.rb index 715ac1aaf3..3e2641aed3 100644 --- a/spec/ruby/core/file/new_spec.rb +++ b/spec/ruby/core/file/new_spec.rb @@ -188,6 +188,12 @@ describe "File.new" do }.should raise_error(Errno::EEXIST, /File exists/) end + it "does not use the given block and warns to use File::open" do + -> { + @fh = File.new(@file) { raise } + }.should complain(/warning: File::new\(\) does not take block; use File::open\(\) instead/) + end + it "raises a TypeError if the first parameter can't be coerced to a string" do -> { File.new(true) }.should raise_error(TypeError) -> { File.new(false) }.should raise_error(TypeError) diff --git a/spec/ruby/core/file/shared/fnmatch.rb b/spec/ruby/core/file/shared/fnmatch.rb index 94f22144b0..db4b5c5d8c 100644 --- a/spec/ruby/core/file/shared/fnmatch.rb +++ b/spec/ruby/core/file/shared/fnmatch.rb @@ -102,6 +102,7 @@ describe :file_fnmatch, shared: true do it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do File.send(@method, 'ca[^t]', 'cat').should == false + File.send(@method, 'ca[^t]', 'cas').should == true File.send(@method, 'ca[!t]', 'cat').should == false end @@ -125,6 +126,13 @@ describe :file_fnmatch, shared: true do end end + it "matches wildcard with characters when flags includes FNM_PATHNAME" do + File.send(@method, '*a', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aaa', File::FNM_PATHNAME).should == true + File.send(@method, '*a', 'aaa', File::FNM_PATHNAME).should == true + end + it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do File.send(@method, '?', '/', File::FNM_PATHNAME).should == false File.send(@method, '*', '/', File::FNM_PATHNAME).should == false @@ -165,9 +173,19 @@ describe :file_fnmatch, shared: true do File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME) end - it "matches patterns with leading periods to dotfiles by default" do + it "matches patterns with leading periods to dotfiles" do File.send(@method, '.*', '.profile').should == true + File.send(@method, '.*', '.profile', File::FNM_PATHNAME).should == true File.send(@method, ".*file", "nondotfile").should == false + File.send(@method, ".*file", "nondotfile", File::FNM_PATHNAME).should == false + end + + it "does not match directories with leading periods by default with FNM_PATHNAME" do + File.send(@method, '.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', '.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false end it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do @@ -221,6 +239,33 @@ describe :file_fnmatch, shared: true do File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true end + it "has special handling for ./ when using * and FNM_PATHNAME" do + File.send(@method, './*', '.', File::FNM_PATHNAME).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches **/* with FNM_PATHNAME to recurse directories" do + File.send(@method, 'nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches ** with FNM_PATHNAME only in current directory" do + File.send(@method, 'nested/**', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should be_false + File.send(@method, 'nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + end + it "accepts an object that has a #to_path method" do File.send(@method, '\*', mock_to_path('a')).should == false end diff --git a/spec/ruby/core/io/copy_stream_spec.rb b/spec/ruby/core/io/copy_stream_spec.rb index df9c5c7390..ffa2ea992c 100644 --- a/spec/ruby/core/io/copy_stream_spec.rb +++ b/spec/ruby/core/io/copy_stream_spec.rb @@ -69,9 +69,12 @@ describe :io_copy_stream_to_io, shared: true do end it "raises an IOError if the destination IO is not open for writing" do - @to_io.close - @to_io = new_io @to_name, "r" - -> { IO.copy_stream @object.from, @to_io }.should raise_error(IOError) + to_io = new_io __FILE__, "r" + begin + -> { IO.copy_stream @object.from, to_io }.should raise_error(IOError) + ensure + to_io.close + end end it "does not close the destination IO" do @@ -109,7 +112,8 @@ describe "IO.copy_stream" do end after :each do - rm_r @to_name, @from_bigfile + rm_r @to_name if @to_name + rm_r @from_bigfile end describe "from an IO" do @@ -164,6 +168,25 @@ describe "IO.copy_stream" do it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream end + + describe "to a Tempfile" do + before :all do + require 'tempfile' + end + + before :each do + @to_io = Tempfile.new("rubyspec_copy_stream", encoding: Encoding::BINARY, mode: File::RDONLY) + @to_name = @to_io.path + end + + after :each do + @to_io.close! + @to_name = nil # do not rm_r it, already done by Tempfile#close! + end + + it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream + it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream + end end describe "from a file name" do @@ -277,10 +300,8 @@ describe "IO.copy_stream" do @io.should_not_receive(:pos) IO.copy_stream(@io, @to_name) end - end - describe "with a destination that does partial reads" do before do @from_out, @from_in = IO.pipe diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb index 73d76b3abd..ca64bf860e 100644 --- a/spec/ruby/core/io/gets_spec.rb +++ b/spec/ruby/core/io/gets_spec.rb @@ -24,6 +24,12 @@ describe "IO#gets" do end end + it "sets $_ to nil after the last line has been read" do + while @io.gets + end + $_.should be_nil + end + it "returns nil if called at the end of the stream" do IOSpecs.lines.length.times { @io.gets } @io.gets.should == nil diff --git a/spec/ruby/core/io/new_spec.rb b/spec/ruby/core/io/new_spec.rb index 9d14ec18ad..979ac0efcb 100644 --- a/spec/ruby/core/io/new_spec.rb +++ b/spec/ruby/core/io/new_spec.rb @@ -5,6 +5,12 @@ require_relative 'shared/new' describe "IO.new" do it_behaves_like :io_new, :new + + it "does not use the given block and warns to use IO::open" do + -> { + @io = IO.send(@method, @fd) { raise } + }.should complain(/warning: IO::new\(\) does not take block; use IO::open\(\) instead/) + end end describe "IO.new" do diff --git a/spec/ruby/core/io/open_spec.rb b/spec/ruby/core/io/open_spec.rb index d3a3961df7..d151da9ce5 100644 --- a/spec/ruby/core/io/open_spec.rb +++ b/spec/ruby/core/io/open_spec.rb @@ -37,6 +37,19 @@ describe "IO.open" do ScratchPad.recorded.should == :called end + it "propagate an exception in the block after calling #close" do + -> do + IO.open(@fd, "w") do |io| + IOSpecs.io_mock(io, :close) do + super() + ScratchPad.record :called + end + raise Exception + end + end.should raise_error(Exception) + ScratchPad.recorded.should == :called + end + it "propagates an exception raised by #close that is not a StandardError" do -> do IO.open(@fd, "w") do |io| diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb index fb0645dec6..aa496ee803 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -26,11 +26,88 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do buffer.should == "567" end + it "shrinks the buffer in case of less bytes read" do + buffer = "foo" + @file.pread(1, 0, buffer) + buffer.should == "1" + end + + it "grows the buffer in case of more bytes read" do + buffer = "foo" + @file.pread(5, 0, buffer) + buffer.should == "12345" + end + it "does not advance the file pointer" do @file.pread(4, 0).should == "1234" @file.read.should == "1234567890" end + it "ignores the current offset" do + @file.pos = 3 + @file.pread(4, 0).should == "1234" + end + + it "returns an empty string for maxlen = 0" do + @file.pread(0, 4).should == "" + end + + it "ignores the offset for maxlen = 0, even if it is out of file bounds" do + @file.pread(0, 400).should == "" + end + + it "does not reset the buffer when reading with maxlen = 0" do + buffer = "foo" + @file.pread(0, 4, buffer) + buffer.should == "foo" + + @file.pread(0, 400, buffer) + buffer.should == "foo" + end + + it "converts maxlen to Integer using #to_int" do + maxlen = mock('maxlen') + maxlen.should_receive(:to_int).and_return(4) + @file.pread(maxlen, 0).should == "1234" + end + + it "converts offset to Integer using #to_int" do + offset = mock('offset') + offset.should_receive(:to_int).and_return(0) + @file.pread(4, offset).should == "1234" + end + + it "converts a buffer to String using to_str" do + buffer = mock('buffer') + buffer.should_receive(:to_str).at_least(1).and_return("foo") + @file.pread(4, 0, buffer) + buffer.should_not.is_a?(String) + buffer.to_str.should == "1234" + end + + it "raises TypeError if maxlen is not an Integer and cannot be coerced into Integer" do + maxlen = Object.new + -> { @file.pread(maxlen, 0) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end + + it "raises TypeError if offset is not an Integer and cannot be coerced into Integer" do + offset = Object.new + -> { @file.pread(4, offset) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end + + it "raises ArgumentError for negative values of maxlen" do + -> { @file.pread(-4, 0) }.should raise_error(ArgumentError, 'negative string size (or size too big)') + end + + it "raised Errno::EINVAL for negative values of offset" do + -> { @file.pread(4, -1) }.should raise_error(Errno::EINVAL, /Invalid argument/) + end + + it "raises TypeError if the buffer is not a String and cannot be coerced into String" do + buffer = Object.new + -> { @file.pread(4, 0, buffer) }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + it "raises EOFError if end-of-file is reached" do -> { @file.pread(1, 10) }.should raise_error(EOFError) end diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb index c10578a8eb..00d40db28d 100644 --- a/spec/ruby/core/io/pwrite_spec.rb +++ b/spec/ruby/core/io/pwrite_spec.rb @@ -28,16 +28,42 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do @file.pread(6, 0).should == "foobar" end + it "calls #to_s on the object to be written" do + object = mock("to_s") + object.should_receive(:to_s).and_return("foo") + @file.pwrite(object, 0) + @file.pread(3, 0).should == "foo" + end + + it "calls #to_int on the offset" do + offset = mock("to_int") + offset.should_receive(:to_int).and_return(2) + @file.pwrite("foo", offset) + @file.pread(3, 2).should == "foo" + end + it "raises IOError when file is not open in write mode" do File.open(@fname, "r") do |file| - -> { file.pwrite("foo", 1) }.should raise_error(IOError) + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "not opened for writing") end end it "raises IOError when file is closed" do file = File.open(@fname, "w+") file.close - -> { file.pwrite("foo", 1) }.should raise_error(IOError) + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "closed stream") + end + + it "raises a NoMethodError if object does not respond to #to_s" do + -> { + @file.pwrite(BasicObject.new, 0) + }.should raise_error(NoMethodError, /undefined method `to_s'/) + end + + it "raises a TypeError if the offset cannot be converted to an Integer" do + -> { + @file.pwrite("foo", Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") end end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index bd22fb6d6e..996f70bf20 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -113,6 +113,15 @@ describe "IO.read" do IO.read(@fname, 1, 10).should == nil end + it "returns an empty string when reading zero bytes" do + IO.read(@fname, 0).should == '' + end + + it "returns a String in BINARY when passed a size" do + IO.read(@fname, 1).encoding.should == Encoding::BINARY + IO.read(@fname, 0).encoding.should == Encoding::BINARY + end + it "raises an Errno::ENOENT when the requested file does not exist" do rm_r @fname -> { IO.read @fname }.should raise_error(Errno::ENOENT) @@ -274,6 +283,14 @@ describe "IO#read" do @io.read(4).should == '7890' end + it "treats first nil argument as no length limit" do + @io.read(nil).should == @contents + end + + it "raises an ArgumentError when not passed a valid length" do + -> { @io.read(-1) }.should raise_error(ArgumentError) + end + it "clears the output buffer if there is nothing to read" do @io.pos = 10 @@ -565,6 +582,7 @@ describe :io_read_size_internal_encoding, shared: true do it "returns a String in BINARY when passed a size" do @io.read(4).encoding.should equal(Encoding::BINARY) + @io.read(0).encoding.should equal(Encoding::BINARY) end it "does not change the buffer's encoding when passed a limit" do diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb index dbc0178dd6..aca622834f 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -33,10 +33,6 @@ describe :io_each, shared: true do $_.should == "test" end - it "returns self" do - @io.send(@method) { |l| l }.should equal(@io) - end - it "raises an IOError when self is not readable" do -> { IOSpecs.closed_io.send(@method) {} }.should raise_error(IOError) end diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb index 1b4a7ae6f4..3d9520ad67 100644 --- a/spec/ruby/core/kernel/exec_spec.rb +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -7,12 +7,12 @@ describe "Kernel#exec" do end it "runs the specified command, replacing current process" do - ruby_exe('exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('exec "echo hello"; puts "fail"').should == "hello\n" end end describe "Kernel.exec" do it "runs the specified command, replacing current process" do - ruby_exe('Kernel.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('Kernel.exec "echo hello"; puts "fail"').should == "hello\n" end end diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index d8f93ce429..61bf955c25 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -31,6 +31,13 @@ describe "Kernel.printf" do object.should_receive(:write).with("string") Kernel.printf(object, "%s", "string") end + + it "calls #to_str to convert the format object to a String" do + object = mock('format string') + object.should_receive(:to_str).and_return("to_str: %i") + $stdout.should_receive(:write).with("to_str: 42") + Kernel.printf($stdout, object, 42) + end end describe "Kernel.printf" do diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb index 5c41c19bf6..0fe2d5ce16 100644 --- a/spec/ruby/core/kernel/shared/load.rb +++ b/spec/ruby/core/kernel/shared/load.rb @@ -1,5 +1,6 @@ main = self +# The big difference is Kernel#load does not attempt to add an extension to the passed path, unlike Kernel#require describe :kernel_load, shared: true do before :each do CodeLoadingSpecs.spec_setup @@ -10,22 +11,31 @@ describe :kernel_load, shared: true do CodeLoadingSpecs.spec_cleanup end - it "loads a non-extensioned file as a Ruby source file" do - path = File.expand_path "load_fixture", CODE_LOADING_DIR - @object.load(path).should be_true - ScratchPad.recorded.should == [:no_ext] - end + describe "(path resolution)" do + # This behavior is specific to Kernel#load, it differs for Kernel#require + it "loads a non-extensioned file as a Ruby source file" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.load(path).should be_true + ScratchPad.recorded.should == [:no_ext] + end - it "loads a non .rb extensioned file as a Ruby source file" do - path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR - @object.load(path).should be_true - ScratchPad.recorded.should == [:no_rb_ext] - end + it "loads a non .rb extensioned file as a Ruby source file" do + path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR + @object.load(path).should be_true + ScratchPad.recorded.should == [:no_rb_ext] + end - it "loads from the current working directory" do - Dir.chdir CODE_LOADING_DIR do - @object.load("load_fixture.rb").should be_true - ScratchPad.recorded.should == [:loaded] + it "loads from the current working directory" do + Dir.chdir CODE_LOADING_DIR do + @object.load("load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + end + + # This behavior is specific to Kernel#load, it differs for Kernel#require + it "does not look for a c-extension file when passed a path without extension (when no .rb is present)" do + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) end end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index 61081b200c..250813191b 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -212,6 +212,34 @@ end describe :kernel_require, shared: true do describe "(path resolution)" do + it "loads .rb file when passed absolute path without extension" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.send(@method, path).should be_true + # This should _not_ be [:no_ext] + ScratchPad.recorded.should == [:loaded] + end + + platform_is :linux, :darwin do + it "loads c-extension file when passed absolute path without extension when no .rb is present" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(Exception, /file too short|not a mach-o file/) + end + end + + platform_is :darwin do + it "loads .bundle file when passed absolute path with .so" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.so" + -> { @object.send(@method, path) }.should raise_error(Exception, /load_fixture\.bundle.+(file too short|not a mach-o file)/) + end + end + + it "does not try an extra .rb if the path already ends in .rb" do + path = File.join CODE_LOADING_DIR, "d", "load_fixture.rb" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + # For reference see [ruby-core:24155] in which matz confirms this feature is # intentional for security reasons. it "does not load a bare filename unless the current working directory is in $LOAD_PATH" do diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index 44b417a92e..0570629723 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' describe "Kernel#sleep" do it "is a private method" do diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 7adf71be76..9ef7f86f16 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -3,6 +3,14 @@ require_relative 'fixtures/classes' require_relative 'shared/sprintf' require_relative 'shared/sprintf_encoding' +describe :kernel_sprintf_to_str, shared: true do + it "calls #to_str to convert the format object to a String" do + obj = mock('format string') + obj.should_receive(:to_str).and_return("to_str: %i") + @method.call(obj, 42).should == "to_str: 42" + end +end + describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { sprintf(format, *args) @@ -11,6 +19,10 @@ describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf_encoding, -> format, *args { sprintf(format, *args) } + + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + sprintf(format, *args) + } end describe "Kernel.sprintf" do @@ -21,4 +33,8 @@ describe "Kernel.sprintf" do it_behaves_like :kernel_sprintf_encoding, -> format, *args { Kernel.sprintf(format, *args) } + + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + Kernel.sprintf(format, *args) + } end diff --git a/spec/ruby/core/matchdata/values_at_spec.rb b/spec/ruby/core/matchdata/values_at_spec.rb index 4fd0bfc42a..535719a2ee 100644 --- a/spec/ruby/core/matchdata/values_at_spec.rb +++ b/spec/ruby/core/matchdata/values_at_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' -describe "Struct#values_at" do +describe "MatchData#values_at" do # Should be synchronized with core/array/values_at_spec.rb and core/struct/values_at_spec.rb # # /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").to_a # => ["HX1138", "H", "X", "113", "8"] @@ -34,7 +34,7 @@ describe "Struct#values_at" do end it "supports beginningless Range" do - /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..2).should == ["HX1138", "H", "X"] + /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(..2).should == ["HX1138", "H", "X"] end it "returns an empty Array when Range is empty" do diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb index f9a18f3878..c63a7aaa0f 100644 --- a/spec/ruby/core/method/super_method_spec.rb +++ b/spec/ruby/core/method/super_method_spec.rb @@ -55,12 +55,10 @@ describe "Method#super_method" do end end - ruby_version_is "2.7.3" do - context "after aliasing an inherited method" do - it "returns the expected super_method" do - method = MethodSpecs::InheritedMethods::C.new.method(:meow) - method.super_method.owner.should == MethodSpecs::InheritedMethods::A - end + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A end end end diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb index d742ece6bc..e04bb87ceb 100644 --- a/spec/ruby/core/module/define_method_spec.rb +++ b/spec/ruby/core/module/define_method_spec.rb @@ -724,7 +724,7 @@ describe "Module#define_method" do end end -describe "Method#define_method when passed a Method object" do +describe "Module#define_method when passed a Method object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -749,7 +749,7 @@ describe "Method#define_method when passed a Method object" do end end -describe "Method#define_method when passed an UnboundMethod object" do +describe "Module#define_method when passed an UnboundMethod object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -774,7 +774,7 @@ describe "Method#define_method when passed an UnboundMethod object" do end end -describe "Method#define_method when passed a Proc object" do +describe "Module#define_method when passed a Proc object" do describe "and a method is defined inside" do it "defines the nested method in the default definee where the Proc was created" do prc = nil @@ -799,7 +799,7 @@ describe "Method#define_method when passed a Proc object" do end end -describe "Method#define_method when passed a block" do +describe "Module#define_method when passed a block" do describe "behaves exactly like a lambda" do it "for return" do Class.new do diff --git a/spec/ruby/core/process/constants_spec.rb b/spec/ruby/core/process/constants_spec.rb index 4130bb58a5..616c54b8e1 100644 --- a/spec/ruby/core/process/constants_spec.rb +++ b/spec/ruby/core/process/constants_spec.rb @@ -56,12 +56,18 @@ describe "Process::Constants" do end platform_is :netbsd, :freebsd do - it "Process::RLIMIT_SBSIZE" do + it "has the correct constant values on NetBSD and FreeBSD" do Process::RLIMIT_SBSIZE.should == 9 # FIXME: what's it equal? Process::RLIMIT_AS.should == 10 end end + platform_is :freebsd do + it "has the correct constant values on FreeBSD" do + Process::RLIMIT_NPTS.should == 11 + end + end + platform_is :windows do it "does not define RLIMIT constants" do %i[ diff --git a/spec/ruby/core/process/exec_spec.rb b/spec/ruby/core/process/exec_spec.rb index 2fa8b08975..0f371b39c8 100644 --- a/spec/ruby/core/process/exec_spec.rb +++ b/spec/ruby/core/process/exec_spec.rb @@ -34,16 +34,16 @@ describe "Process.exec" do end it "runs the specified command, replacing current process" do - ruby_exe('Process.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('Process.exec "echo hello"; puts "fail"').should == "hello\n" end it "sets the current directory when given the :chdir option" do tmpdir = tmp("")[0..-2] platform_is_not :windows do - ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})", escape: true).should == "#{tmpdir}\n" + ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})").should == "#{tmpdir}\n" end platform_is :windows do - ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})", escape: true).tr('\\', '/').should == "#{tmpdir}\n" + ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})").tr('\\', '/').should == "#{tmpdir}\n" end end @@ -73,13 +73,13 @@ describe "Process.exec" do platform_is_not :windows do it "subjects the specified command to shell expansion" do result = Dir.chdir(@dir) do - ruby_exe('Process.exec "echo *"', escape: true) + ruby_exe('Process.exec "echo *"') end result.chomp.should == @name end it "creates an argument array with shell parsing semantics for whitespace" do - ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + ruby_exe('Process.exec "echo a b c d"').should == "a b c d\n" end end @@ -87,13 +87,13 @@ describe "Process.exec" do # There is no shell expansion on Windows it "does not subject the specified command to shell expansion on Windows" do result = Dir.chdir(@dir) do - ruby_exe('Process.exec "echo *"', escape: true) + ruby_exe('Process.exec "echo *"') end result.should == "*\n" end it "does not create an argument array with shell parsing semantics for whitespace on Windows" do - ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + ruby_exe('Process.exec "echo a b c d"').should == "a b c d\n" end end @@ -105,7 +105,7 @@ describe "Process.exec" do platform_is :windows do cmd = '"cmd.exe", "/C", "echo", "*"' end - ruby_exe("Process.exec #{cmd}", escape: true).should == "*\n" + ruby_exe("Process.exec #{cmd}").should == "*\n" end end @@ -124,29 +124,29 @@ describe "Process.exec" do end it "sets environment variables in the child environment" do - ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")', escape: true).should == "BAR\n" + ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")').should == "BAR\n" end it "unsets environment variables whose value is nil" do platform_is_not :windows do - ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == "\n" + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")').should == "\n" end platform_is :windows do # On Windows, echo-ing a non-existent env var is treated as echo-ing any other string of text - ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == var + "\n" + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")').should == var + "\n" end end it "coerces environment argument using to_hash" do - ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")', escape: true).should == "BAR\n" + ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")').should == "BAR\n" end it "unsets other environment variables when given a true :unsetenv_others option" do platform_is_not :windows do - ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)', escape: true).should == "\n" + ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)').should == "\n" end platform_is :windows do - ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)', escape: true).should == var + "\n" + ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)').should == var + "\n" end end end @@ -154,19 +154,19 @@ describe "Process.exec" do describe "with a command array" do it "uses the first element as the command name and the second as the argv[0] value" do platform_is_not :windows do - ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")', escape: true).should == "argv_zero\n" + ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")').should == "argv_zero\n" end platform_is :windows do - ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")').should == "argv_zero\n" end end it "coerces the argument using to_ary" do platform_is_not :windows do - ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")', escape: true).should == "argv_zero\n" + ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")').should == "argv_zero\n" end platform_is :windows do - ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")').should == "argv_zero\n" end end @@ -200,7 +200,7 @@ describe "Process.exec" do end EOC - ruby_exe(cmd, escape: true) + ruby_exe(cmd) child_fd = IO.read(@child_fd_file).to_i child_fd.to_i.should > STDERR.fileno @@ -216,7 +216,7 @@ describe "Process.exec" do Process.exec("#{ruby_cmd(map_fd_fixture)} \#{f.fileno}", f.fileno => f.fileno) EOC - output = ruby_exe(cmd, escape: true) + output = ruby_exe(cmd) child_fd, close_on_exec = output.split child_fd.to_i.should > STDERR.fileno @@ -232,7 +232,7 @@ describe "Process.exec" do puts(f.close_on_exec?) EOC - output = ruby_exe(cmd, escape: true) + output = ruby_exe(cmd) output.split.should == ['true', 'false'] end end diff --git a/spec/ruby/core/regexp/union_spec.rb b/spec/ruby/core/regexp/union_spec.rb index 8076836471..ea5a5053f7 100644 --- a/spec/ruby/core/regexp/union_spec.rb +++ b/spec/ruby/core/regexp/union_spec.rb @@ -43,6 +43,27 @@ describe "Regexp.union" do Regexp.union("\u00A9".encode("ISO-8859-1"), "a".encode("UTF-8")).encoding.should == Encoding::ISO_8859_1 end + it "returns ASCII-8BIT if the regexp encodings are ASCII-8BIT and at least one has non-ASCII characters" do + us_ascii_implicit, us_ascii_explicit, binary = /abc/, /[\x00-\x7f]/n, /[\x80-\xBF]/n + us_ascii_implicit.encoding.should == Encoding::US_ASCII + us_ascii_explicit.encoding.should == Encoding::US_ASCII + binary.encoding.should == Encoding::BINARY + + Regexp.union(us_ascii_implicit, us_ascii_explicit, binary).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_implicit, binary, us_ascii_explicit).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_explicit, us_ascii_implicit, binary).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_explicit, binary, us_ascii_implicit).encoding.should == Encoding::BINARY + Regexp.union(binary, us_ascii_implicit, us_ascii_explicit).encoding.should == Encoding::BINARY + Regexp.union(binary, us_ascii_explicit, us_ascii_implicit).encoding.should == Encoding::BINARY + end + + it "return US-ASCII if all patterns are ASCII-only" do + Regexp.union(/abc/e, /def/e).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/n, /def/n).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/s, /def/s).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/u, /def/u).encoding.should == Encoding::US_ASCII + end + it "returns a Regexp with UTF-8 if one part is UTF-8" do Regexp.union(/probl[éeè]me/i, /help/i).encoding.should == Encoding::UTF_8 end @@ -54,83 +75,83 @@ describe "Regexp.union" do it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Strings" do -> { Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-16BE")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and UTF-16BE') end it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Regexps" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("b".encode("UTF-16BE"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and UTF-16BE') end it "raises ArgumentError if the arguments include conflicting fixed encoding Regexps" do -> { Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING), Regexp.new("b".encode("US-ASCII"), Regexp::FIXEDENCODING)) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-8 and US-ASCII') end it "raises ArgumentError if the arguments include a fixed encoding Regexp and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-8 and ISO-8859-1') end it "raises ArgumentError if the arguments include a String containing non-ASCII-compatible characters and a fixed encoding Regexp in a different encoding" do -> { Regexp.union("\u00A9".encode("ISO-8859-1"), Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING)) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: ISO-8859-1 and UTF-8') end it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only String" do -> { Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-8")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only String" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), "b".encode("UTF-8")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only Regexp" do -> { Regexp.union("a".encode("UTF-16LE"), Regexp.new("b".encode("UTF-8"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only Regexp" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("b".encode("UTF-8"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible String and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union("a".encode("UTF-16LE"), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible String and a Regexp containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union("a".encode("UTF-16LE"), Regexp.new("\u00A9".encode("ISO-8859-1"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a Regexp containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("\u00A9".encode("ISO-8859-1"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "uses to_str to convert arguments (if not Regexp)" do @@ -154,6 +175,8 @@ describe "Regexp.union" do not_supported_on :opal do Regexp.union([/dogs/, /cats/i]).should == /(?-mix:dogs)|(?i-mx:cats)/ end - ->{Regexp.union(["skiing", "sledding"], [/dogs/, /cats/i])}.should raise_error(TypeError) + -> { + Regexp.union(["skiing", "sledding"], [/dogs/, /cats/i]) + }.should raise_error(TypeError, 'no implicit conversion of Array into String') end end diff --git a/spec/ruby/core/signal/signame_spec.rb b/spec/ruby/core/signal/signame_spec.rb index b66de9fc85..adfe895d97 100644 --- a/spec/ruby/core/signal/signame_spec.rb +++ b/spec/ruby/core/signal/signame_spec.rb @@ -9,10 +9,22 @@ describe "Signal.signame" do Signal.signame(-1).should == nil end + it "calls #to_int on an object to convert to an Integer" do + obj = mock('signal') + obj.should_receive(:to_int).and_return(0) + Signal.signame(obj).should == "EXIT" + end + it "raises a TypeError when the passed argument can't be coerced to Integer" do -> { Signal.signame("hello") }.should raise_error(TypeError) end + it "raises a TypeError when the passed argument responds to #to_int but does not return an Integer" do + obj = mock('signal') + obj.should_receive(:to_int).and_return('not an int') + -> { Signal.signame(obj) }.should raise_error(TypeError) + end + platform_is_not :windows do it "the original should take precedence over alias when looked up by number" do Signal.signame(Signal.list["ABRT"]).should == "ABRT" diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb index 10e122e072..b3186cda92 100644 --- a/spec/ruby/core/signal/trap_spec.rb +++ b/spec/ruby/core/signal/trap_spec.rb @@ -221,6 +221,25 @@ describe "Signal.trap" do Signal.trap(:HUP, @saved_trap).should equal(@proc) end + it "calls #to_str on an object to convert to a String" do + obj = mock("signal") + obj.should_receive(:to_str).exactly(2).times.and_return("HUP") + Signal.trap obj, @proc + Signal.trap(obj, @saved_trap).should equal(@proc) + end + + it "accepts Integer values" do + hup = Signal.list["HUP"] + Signal.trap hup, @proc + Signal.trap(hup, @saved_trap).should equal(@proc) + end + + it "does not call #to_int on an object to convert to an Integer" do + obj = mock("signal") + obj.should_not_receive(:to_int) + -> { Signal.trap obj, @proc }.should raise_error(ArgumentError, /bad signal type/) + end + it "raises ArgumentError when passed unknown signal" do -> { Signal.trap(300) { } }.should raise_error(ArgumentError, "invalid signal number (300)") -> { Signal.trap("USR10") { } }.should raise_error(ArgumentError, "unsupported signal `SIGUSR10'") diff --git a/spec/ruby/core/string/start_with_spec.rb b/spec/ruby/core/string/start_with_spec.rb index 81eed47f96..35e33b46a6 100644 --- a/spec/ruby/core/string/start_with_spec.rb +++ b/spec/ruby/core/string/start_with_spec.rb @@ -11,7 +11,14 @@ describe "String#start_with?" do "\xA9".should.start_with?("\xA9") # A9 is not a character head for UTF-8 end - ruby_bug "#19784", ""..."3.3" do + ruby_version_is ""..."3.3" do + it "does not check we are matching only part of a character" do + "\xe3\x81\x82".size.should == 1 + "\xe3\x81\x82".should.start_with?("\xe3") + end + end + + ruby_version_is "3.3" do # #19784 it "checks we are matching only part of a character" do "\xe3\x81\x82".size.should == 1 "\xe3\x81\x82".should_not.start_with?("\xe3") diff --git a/spec/ruby/core/string/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb index 038b283c90..65b66260dd 100644 --- a/spec/ruby/core/string/uplus_spec.rb +++ b/spec/ruby/core/string/uplus_spec.rb @@ -7,6 +7,9 @@ describe 'String#+@' do output.should_not.frozen? output.should == 'foo' + + output << 'bar' + output.should == 'foobar' end it 'returns self if the String is not frozen' do diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb index 101c83b8b3..aa7c129377 100644 --- a/spec/ruby/core/unboundmethod/super_method_spec.rb +++ b/spec/ruby/core/unboundmethod/super_method_spec.rb @@ -40,12 +40,10 @@ describe "UnboundMethod#super_method" do end end - ruby_version_is "2.7.3" do - context "after aliasing an inherited method" do - it "returns the expected super_method" do - method = MethodSpecs::InheritedMethods::C.instance_method(:meow) - method.super_method.owner.should == MethodSpecs::InheritedMethods::A - end + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A end end end diff --git a/spec/ruby/core/warning/element_reference_spec.rb b/spec/ruby/core/warning/element_reference_spec.rb index bd024d19b4..8cb4018c20 100644 --- a/spec/ruby/core/warning/element_reference_spec.rb +++ b/spec/ruby/core/warning/element_reference_spec.rb @@ -1,11 +1,9 @@ require_relative '../../spec_helper' describe "Warning.[]" do - ruby_version_is '2.7.2' do - it "returns default values for categories :deprecated and :experimental" do - ruby_exe('p [Warning[:deprecated], Warning[:experimental]]').chomp.should == "[false, true]" - ruby_exe('p [Warning[:deprecated], Warning[:experimental]]', options: "-w").chomp.should == "[true, true]" - end + it "returns default values for categories :deprecated and :experimental" do + ruby_exe('p [Warning[:deprecated], Warning[:experimental]]').chomp.should == "[false, true]" + ruby_exe('p [Warning[:deprecated], Warning[:experimental]]', options: "-w").chomp.should == "[true, true]" end ruby_version_is '3.3' do diff --git a/spec/ruby/fixtures/code/d/load_fixture.rb.rb b/spec/ruby/fixtures/code/d/load_fixture.rb.rb new file mode 100644 index 0000000000..7e9217729a --- /dev/null +++ b/spec/ruby/fixtures/code/d/load_fixture.rb.rb @@ -0,0 +1 @@ +ScratchPad << :rbrb diff --git a/spec/ruby/language/alias_spec.rb b/spec/ruby/language/alias_spec.rb index ee928cbfaa..61fddb0184 100644 --- a/spec/ruby/language/alias_spec.rb +++ b/spec/ruby/language/alias_spec.rb @@ -252,7 +252,7 @@ describe "The alias keyword" do 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" + ruby_exe("def foo; end; alias bla foo; print method(:bla).owner").should == "Object" end it "raises a NameError when passed a missing name" do diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb index 58e1aaed96..1a3925c9c6 100644 --- a/spec/ruby/language/case_spec.rb +++ b/spec/ruby/language/case_spec.rb @@ -329,49 +329,6 @@ describe "The 'case'-construct" do 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 @@ -442,6 +399,49 @@ describe "The 'case'-construct with no target expression" do :called end.should == :called 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 # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately. # See https://github.com/jruby/jruby/issues/6440 @@ -451,4 +451,13 @@ describe "The 'case'-construct with no target expression" do when 2; 'bar' end.should == 'foo' end + + it "expands arrays to lists of values" do + case + when *[false] + "foo" + when *[true] + "bar" + end.should == "bar" + end end diff --git a/spec/ruby/language/delegation_spec.rb b/spec/ruby/language/delegation_spec.rb index 3f24a79d5c..020787aff6 100644 --- a/spec/ruby/language/delegation_spec.rb +++ b/spec/ruby/language/delegation_spec.rb @@ -38,28 +38,26 @@ describe "delegation with def(...)" do end end -ruby_version_is "2.7.3" do - describe "delegation with def(x, ...)" do - it "delegates rest and kwargs" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate(x, ...) - target(...) - end - RUBY +describe "delegation with def(x, ...)" do + it "delegates rest and kwargs" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate(x, ...) + target(...) + end + RUBY - a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}] - end + a.new.delegate(0, 1, b: 2).should == [[1], {b: 2}] + end - it "delegates block" do - a = Class.new(DelegationSpecs::Target) - a.class_eval(<<-RUBY) - def delegate_block(x, ...) - target_block(...) - end - RUBY + it "delegates block" do + a = Class.new(DelegationSpecs::Target) + a.class_eval(<<-RUBY) + def delegate_block(x, ...) + target_block(...) + end + RUBY - a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] - end + a.new.delegate_block(0, 1, b: 2) { |x| x }.should == [{b: 2}, [1]] end end diff --git a/spec/ruby/library/cgi/escapeURIComponent_spec.rb b/spec/ruby/library/cgi/escapeURIComponent_spec.rb new file mode 100644 index 0000000000..2cf283c778 --- /dev/null +++ b/spec/ruby/library/cgi/escapeURIComponent_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require 'cgi' + +ruby_version_is "3.2" do + describe "CGI.escapeURIComponent" do + it "escapes whitespace" do + string = "&<>\" \xE3\x82\x86\xE3\x82\x93\xE3\x82\x86\xE3\x82\x93" + CGI.escapeURIComponent(string).should == '%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93' + end + + it "does not escape with unreserved characters" do + string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + CGI.escapeURIComponent(string).should == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + end + + it "supports String with invalid encoding" do + string = "\xC0\<\<".force_encoding("UTF-8") + CGI.escapeURIComponent(string).should == "%C0%3C%3C" + end + + it "processes String bytes one by one, not characters" do + CGI.escapeURIComponent("β").should == "%CE%B2" # "β" bytes representation is CE B2 + end + + it "raises a TypeError with nil" do + -> { + CGI.escapeURIComponent(nil) + }.should raise_error(TypeError, 'no implicit conversion of nil into String') + end + + it "encodes empty string" do + CGI.escapeURIComponent("").should == "" + end + + it "encodes single whitespace" do + CGI.escapeURIComponent(" ").should == "%20" + end + + it "encodes double whitespace" do + CGI.escapeURIComponent(" ").should == "%20%20" + end + + it "preserves encoding" do + string = "whatever".encode("ASCII-8BIT") + CGI.escapeURIComponent(string).encoding.should == Encoding::ASCII_8BIT + end + + it "uses implicit type conversion to String" do + object = Object.new + def object.to_str + "a b" + end + + CGI.escapeURIComponent(object).should == "a%20b" + end + end +end diff --git a/spec/ruby/library/datetime/rfc2822_spec.rb b/spec/ruby/library/datetime/rfc2822_spec.rb index 70bfca60b4..83f7fa8d5b 100644 --- a/spec/ruby/library/datetime/rfc2822_spec.rb +++ b/spec/ruby/library/datetime/rfc2822_spec.rb @@ -3,4 +3,8 @@ require 'date' describe "DateTime.rfc2822" do it "needs to be reviewed for spec completeness" + + it "raises DateError if passed nil" do + -> { DateTime.rfc2822(nil) }.should raise_error(Date::Error, "invalid date") + end end diff --git a/spec/ruby/library/openssl/digest/append_spec.rb b/spec/ruby/library/openssl/digest/append_spec.rb new file mode 100644 index 0000000000..08802b7253 --- /dev/null +++ b/spec/ruby/library/openssl/digest/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#<<" do + it_behaves_like :openssl_digest_update, :<< +end diff --git a/spec/ruby/library/openssl/digest/block_length_spec.rb b/spec/ruby/library/openssl/digest/block_length_spec.rb new file mode 100644 index 0000000000..444ed9d20d --- /dev/null +++ b/spec/ruby/library/openssl/digest/block_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#block_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 block length" do + OpenSSL::Digest.new('sha1').block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest.new('sha256').block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest.new('sha384').block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest.new('sha512').block_length.should == SHA512Constants::BlockLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 block length" do + OpenSSL::Digest::SHA1.new.block_length.should == SHA1Constants::BlockLength + end + + it "returns a SHA256 block length" do + OpenSSL::Digest::SHA256.new.block_length.should == SHA256Constants::BlockLength + end + + it "returns a SHA384 block length" do + OpenSSL::Digest::SHA384.new.block_length.should == SHA384Constants::BlockLength + end + + it "returns a SHA512 block length" do + OpenSSL::Digest::SHA512.new.block_length.should == SHA512Constants::BlockLength + end + end +end diff --git a/spec/ruby/library/openssl/digest/digest_length_spec.rb b/spec/ruby/library/openssl/digest/digest_length_spec.rb new file mode 100644 index 0000000000..37d1cba9a7 --- /dev/null +++ b/spec/ruby/library/openssl/digest/digest_length_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#digest_length" do + context "when the digest object is created via a name argument" do + it "returns a SHA1 digest length" do + OpenSSL::Digest.new('sha1').digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest.new('sha256').digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest.new('sha384').digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest.new('sha512').digest_length.should == SHA512Constants::DigestLength + end + end + + context "when the digest object is created via a subclass" do + it "returns a SHA1 digest length" do + OpenSSL::Digest::SHA1.new.digest_length.should == SHA1Constants::DigestLength + end + + it "returns a SHA256 digest length" do + OpenSSL::Digest::SHA256.new.digest_length.should == SHA256Constants::DigestLength + end + + it "returns a SHA384 digest length" do + OpenSSL::Digest::SHA384.new.digest_length.should == SHA384Constants::DigestLength + end + + it "returns a SHA512 digest length" do + OpenSSL::Digest::SHA512.new.digest_length.should == SHA512Constants::DigestLength + end + end +end diff --git a/spec/ruby/library/openssl/digest_spec.rb b/spec/ruby/library/openssl/digest/digest_spec.rb index b8e82d073f..cf27d01b6d 100644 --- a/spec/ruby/library/openssl/digest_spec.rb +++ b/spec/ruby/library/openssl/digest/digest_spec.rb @@ -1,12 +1,11 @@ -require_relative '../../spec_helper' -require_relative '../../library/digest/sha1/shared/constants' -require_relative '../../library/digest/sha256/shared/constants' -require_relative '../../library/digest/sha384/shared/constants' -require_relative '../../library/digest/sha512/shared/constants' +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' require 'openssl' -describe "OpenSSL::Digest" do - +describe "OpenSSL::Digest class methods" do describe ".digest" do it "returns a SHA1 digest" do OpenSSL::Digest.digest('sha1', SHA1Constants::Contents).should == SHA1Constants::Digest diff --git a/spec/ruby/library/openssl/digest/initialize_spec.rb b/spec/ruby/library/openssl/digest/initialize_spec.rb new file mode 100644 index 0000000000..1cd0409c4d --- /dev/null +++ b/spec/ruby/library/openssl/digest/initialize_spec.rb @@ -0,0 +1,141 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#initialize" do + describe "can be called with a digest name" do + it "returns a SHA1 object" do + OpenSSL::Digest.new("sha1").name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new("sha256").name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new("sha384").name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new("sha512").name.should == "SHA512" + end + + it "throws an error when called with an unknown digest" do + -> { OpenSSL::Digest.new("wd40") }.should raise_error(RuntimeError, /Unsupported digest algorithm \(wd40\)/) + end + + it "cannot be called with a symbol" do + -> { OpenSSL::Digest.new(:SHA1) }.should raise_error(TypeError, /wrong argument type Symbol/) + end + + it "does not call #to_str on the argument" do + name = mock("digest name") + name.should_not_receive(:to_str) + -> { OpenSSL::Digest.new(name) }.should raise_error(TypeError, /wrong argument type/) + end + end + + describe "can be called with a digest object" do + it "returns a SHA1 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA1.new).name.should == "SHA1" + end + + it "returns a SHA256 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA256.new).name.should == "SHA256" + end + + it "returns a SHA384 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA384.new).name.should == "SHA384" + end + + it "returns a SHA512 object" do + OpenSSL::Digest.new(OpenSSL::Digest::SHA512.new).name.should == "SHA512" + end + + it "ignores the state of the digest object" do + sha1 = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + OpenSSL::Digest.new(sha1).digest.should == SHA1Constants::BlankDigest + end + end + + it "cannot be called with a digest class" do + -> { OpenSSL::Digest.new(OpenSSL::Digest::SHA1) }.should raise_error(TypeError, /wrong argument type Class/) + end + + context "when called without an initial String argument" do + it "returns a SHA1 digest" do + OpenSSL::Digest.new("sha1").digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest.new("sha256").digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest.new("sha384").digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest.new("sha512").digest.should == SHA512Constants::BlankDigest + end + end + + context "when called with an initial String argument" do + it "returns a SHA1 digest of that argument" do + OpenSSL::Digest.new("sha1", SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest of that argument" do + OpenSSL::Digest.new("sha256", SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest of that argument" do + OpenSSL::Digest.new("sha384", SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest of that argument" do + OpenSSL::Digest.new("sha512", SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + + context "can be called on subclasses" do + describe "can be called without an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new.digest.should == SHA1Constants::BlankDigest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new.digest.should == SHA256Constants::BlankDigest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new.digest.should == SHA384Constants::BlankDigest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new.digest.should == SHA512Constants::BlankDigest + end + end + + describe "can be called with an initial String argument on subclasses" do + it "returns a SHA1 digest" do + OpenSSL::Digest::SHA1.new(SHA1Constants::Contents).digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + OpenSSL::Digest::SHA256.new(SHA256Constants::Contents).digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + OpenSSL::Digest::SHA384.new(SHA384Constants::Contents).digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + OpenSSL::Digest::SHA512.new(SHA512Constants::Contents).digest.should == SHA512Constants::Digest + end + end + end +end diff --git a/spec/ruby/library/openssl/digest/name_spec.rb b/spec/ruby/library/openssl/digest/name_spec.rb new file mode 100644 index 0000000000..b379f35c1c --- /dev/null +++ b/spec/ruby/library/openssl/digest/name_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::Digest#name" do + it "returns the name of digest" do + OpenSSL::Digest.new('SHA1').name.should == 'SHA1' + end + + it "converts the name to the internal representation of OpenSSL" do + OpenSSL::Digest.new('sha1').name.should == 'SHA1' + end + + it "works on subclasses too" do + OpenSSL::Digest::SHA1.new.name.should == 'SHA1' + end +end diff --git a/spec/ruby/library/openssl/digest/reset_spec.rb b/spec/ruby/library/openssl/digest/reset_spec.rb new file mode 100644 index 0000000000..c19bf46633 --- /dev/null +++ b/spec/ruby/library/openssl/digest/reset_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../../spec_helper' +require_relative '../../../library/digest/sha1/shared/constants' +require_relative '../../../library/digest/sha256/shared/constants' +require_relative '../../../library/digest/sha384/shared/constants' +require_relative '../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe "OpenSSL::Digest#reset" do + it "works for a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1', SHA1Constants::Contents) + digest.reset + digest.update(SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "works for a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256', SHA256Constants::Contents) + digest.reset + digest.update(SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "works for a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384', SHA384Constants::Contents) + digest.reset + digest.update(SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "works for a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512', SHA512Constants::Contents) + digest.reset + digest.update(SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end +end diff --git a/spec/ruby/library/openssl/digest/shared/update.rb b/spec/ruby/library/openssl/digest/shared/update.rb new file mode 100644 index 0000000000..e5ff9dcb16 --- /dev/null +++ b/spec/ruby/library/openssl/digest/shared/update.rb @@ -0,0 +1,123 @@ +require_relative '../../../../library/digest/sha1/shared/constants' +require_relative '../../../../library/digest/sha256/shared/constants' +require_relative '../../../../library/digest/sha384/shared/constants' +require_relative '../../../../library/digest/sha512/shared/constants' +require 'openssl' + +describe :openssl_digest_update, shared: true do + context "when given input as a single string" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, SHA1Constants::Contents) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, SHA256Constants::Contents) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, SHA384Constants::Contents) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, SHA512Constants::Contents) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when given input as multiple smaller substrings" do + it "returns a SHA1 digest" do + digest = OpenSSL::Digest.new('sha1') + SHA1Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + digest = OpenSSL::Digest.new('sha256') + SHA256Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + digest = OpenSSL::Digest.new('sha384') + SHA384Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + digest = OpenSSL::Digest.new('sha512') + SHA512Constants::Contents.each_char { |b| digest.send(@method, b) } + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and responds to #to_str" do + it "returns a SHA1 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA1Constants::Contents) + digest = OpenSSL::Digest.new('sha1') + digest.send(@method, str) + digest.digest.should == SHA1Constants::Digest + end + + it "returns a SHA256 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA256Constants::Contents) + digest = OpenSSL::Digest.new('sha256') + digest.send(@method, str) + digest.digest.should == SHA256Constants::Digest + end + + it "returns a SHA384 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA384Constants::Contents) + digest = OpenSSL::Digest.new('sha384') + digest.send(@method, str) + digest.digest.should == SHA384Constants::Digest + end + + it "returns a SHA512 digest" do + str = mock('str') + str.should_receive(:to_str).and_return(SHA512Constants::Contents) + digest = OpenSSL::Digest.new('sha512') + digest.send(@method, str) + digest.digest.should == SHA512Constants::Digest + end + end + + context "when input is not a String and does not respond to #to_str" do + it "raises a TypeError with SHA1" do + digest = OpenSSL::Digest.new('sha1') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA256" do + digest = OpenSSL::Digest.new('sha256') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA384" do + digest = OpenSSL::Digest.new('sha384') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + + it "raises a TypeError with SHA512" do + digest = OpenSSL::Digest.new('sha512') + -> { + digest.send(@method, Object.new) + }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + end +end diff --git a/spec/ruby/library/openssl/digest/update_spec.rb b/spec/ruby/library/openssl/digest/update_spec.rb new file mode 100644 index 0000000000..3a90b06c6b --- /dev/null +++ b/spec/ruby/library/openssl/digest/update_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../../spec_helper' +require_relative 'shared/update' + +describe "OpenSSL::Digest#update" do + it_behaves_like :openssl_digest_update, :update +end diff --git a/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb new file mode 100644 index 0000000000..ebf71482de --- /dev/null +++ b/spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb @@ -0,0 +1,184 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::KDF.pbkdf2_hmac" do + before :each do + @defaults = { + salt: "\x00".b * 16, + iterations: 20_000, + length: 16, + hash: "sha1" + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "supports nullbytes embedded in the password" do + key = OpenSSL::KDF.pbkdf2_hmac("sec\x00ret".b, **@defaults) + key.should == "\xB9\x7F\xB0\xC2\th\xC8<\x86\xF3\x94Ij7\xEF\xF1".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.pbkdf2_hmac(pass, **@defaults) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: salt) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the iterations into an Integer using #to_int" do + iterations = mock("iterations") + iterations.should_receive(:to_int).and_return(20_000) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: iterations) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(16) + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: length) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts a OpenSSL::Digest object as hash" do + hash = OpenSSL::Digest.new("sha1") + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.pbkdf2_hmac("", **@defaults) + key.should == "k\x9F-\xB1\xF7\x9A\v\xA1(C\xF9\x85!P\xEF\x8C".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: "") + key.should == "\xD5f\xE5\xEA\xF91\x1D\xD3evD\xED\xDB\xE80\x80".b + end + + it "accepts an empty length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: 19) + key.should == "!\x99+\xF0^\xD0\x8BM\x158\xC4\xAC\x9C\xF1\xF0\xE0\xCF\xBB\x7F".b + end + + it "accepts any hash function known to OpenSSL" do + key = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "sha512") + key.should == "N\x12}D\xCE\x99\xDBC\x8E\xEC\xAAr\xEA1\xDF\xFF".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac(Object.new, **@defaults) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, salt: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when iterations is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, length: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: Object.new) + }.should raise_error(TypeError, "wrong argument type Object (expected OpenSSL/Digest)") + end + + it "raises a TypeError when hash is neither a String nor an OpenSSL::Digest, it does not try to call #to_str" do + hash = mock("hash") + hash.should_not_receive(:to_str) + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: hash) + }.should raise_error(TypeError, "wrong argument type MockObject (expected OpenSSL/Digest)") + end + + it "raises a RuntimeError for unknown digest algorithms" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, hash: "wd40") + }.should raise_error(RuntimeError, /Unsupported digest algorithm \(wd40\)/) + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:salt)) + }.should raise_error(ArgumentError, 'missing keyword: :salt') + end + + it "treats iterations as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:iterations)) + }.should raise_error(ArgumentError, 'missing keyword: :iterations') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:length)) + }.should raise_error(ArgumentError, 'missing keyword: :length') + end + + it "treats hash as a required keyword" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults.except(:hash)) + }.should raise_error(ArgumentError, 'missing keyword: :hash') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret") + }.should raise_error(ArgumentError, 'missing keywords: :salt, :iterations, :length, :hash') + end + + + guard -> { OpenSSL::OPENSSL_VERSION_NUMBER < 0x30000000 } do + it "treats 0 or less iterations as a single iteration" do + salt = "\x00".b * 16 + length = 16 + hash = "sha1" + + # "Any iter less than 1 is treated as a single iteration." + key0 = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0) + key_negative = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1) + key1 = OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 1) + key0.should == key1 + key_negative.should == key1 + end + end + + guard -> { OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 } do + it "raises an OpenSSL::KDF::KDFError for 0 or less iterations" do + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: 0) + }.should raise_error(OpenSSL::KDF::KDFError, "PKCS5_PBKDF2_HMAC: invalid iteration count") + + -> { + OpenSSL::KDF.pbkdf2_hmac("secret", **@defaults, iterations: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /PKCS5_PBKDF2_HMAC/) + end + end +end diff --git a/spec/ruby/library/openssl/kdf/scrypt_spec.rb b/spec/ruby/library/openssl/kdf/scrypt_spec.rb new file mode 100644 index 0000000000..d9b83eaa55 --- /dev/null +++ b/spec/ruby/library/openssl/kdf/scrypt_spec.rb @@ -0,0 +1,207 @@ +require_relative '../../../spec_helper' +require 'openssl' + +describe "OpenSSL::KDF.scrypt" do + before :each do + @defaults = { + salt: "\x00".b * 16, + N: 2**14, + r: 8, + p: 1, + length: 32 + } + end + + it "creates the same value with the same input" do + key = OpenSSL::KDF.scrypt("secret", **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "supports nullbytes embedded into the password" do + key = OpenSSL::KDF.scrypt("sec\x00ret".b, **@defaults) + key.should == "\xF9\xA4\xA0\xF1p\xF4\xF0\xCAT\xB4v\xEB\r7\x88N\xF7\x15]Ns\xFCwt4a\xC9\xC6\xA7\x13\x81&".b + end + + it "coerces the password into a String using #to_str" do + pass = mock("pass") + pass.should_receive(:to_str).and_return("secret") + key = OpenSSL::KDF.scrypt(pass, **@defaults) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the salt into a String using #to_str" do + salt = mock("salt") + salt.should_receive(:to_str).and_return("\x00".b * 16) + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: salt) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the N into an Integer using #to_int" do + n = mock("N") + n.should_receive(:to_int).and_return(2**14) + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: n) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the r into an Integer using #to_int" do + r = mock("r") + r.should_receive(:to_int).and_return(8) + key = OpenSSL::KDF.scrypt("secret", **@defaults, r: r) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the p into an Integer using #to_int" do + p = mock("p") + p.should_receive(:to_int).and_return(1) + key = OpenSSL::KDF.scrypt("secret", **@defaults, p: p) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "coerces the length into an Integer using #to_int" do + length = mock("length") + length.should_receive(:to_int).and_return(32) + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: length) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D\x17}\xF2\x84T\xD4)\xC2>\xFE\x93\xE3\xF4".b + end + + it "accepts an empty password" do + key = OpenSSL::KDF.scrypt("", **@defaults) + key.should == "\xAA\xFC\xF5^E\x94v\xFFk\xE6\xF0vR\xE7\x13\xA7\xF5\x15'\x9A\xE4C\x9Dn\x18F_E\xD2\v\e\xB3".b + end + + it "accepts an empty salt" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, salt: "") + key.should == "\x96\xACDl\xCB3/aN\xB0F\x8A#\xD7\x92\xD2O\x1E\v\xBB\xCE\xC0\xAA\xB9\x0F]\xB09\xEA8\xDD\e".b + end + + it "accepts a zero length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 0) + key.should.empty? + end + + it "accepts an arbitrary length" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, length: 19) + key.should == "h\xB2k\xDF]\xDA\xE1.-(\xCF\xAC\x91D\x8F\xC2a\x9C\x9D".b + end + + it "raises a TypeError when password is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt(Object.new, **@defaults) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when salt is not a String and does not respond to #to_str" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, salt: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises a TypeError when N is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when r is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when p is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "raises a TypeError when length is not an Integer and does not respond to #to_int" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") + end + + it "treats salt as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:salt)) + }.should raise_error(ArgumentError, 'missing keyword: :salt') + end + + it "treats N as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:N)) + }.should raise_error(ArgumentError, 'missing keyword: :N') + end + + it "treats r as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:r)) + }.should raise_error(ArgumentError, 'missing keyword: :r') + end + + it "treats p as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:p)) + }.should raise_error(ArgumentError, 'missing keyword: :p') + end + + it "treats length as a required keyword" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults.except(:length)) + }.should raise_error(ArgumentError, 'missing keyword: :length') + end + + it "treats all keywords as required" do + -> { + OpenSSL::KDF.scrypt("secret") + }.should raise_error(ArgumentError, 'missing keywords: :salt, :N, :r, :p, :length') + end + + it "requires N to be a power of 2" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 2**14 - 1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires N to be at least 2" do + key = OpenSSL::KDF.scrypt("secret", **@defaults, N: 2) + key.should == "\x06A$a\xA9!\xBE\x01\x85\xA7\x18\xBCEa\x82\xC5\xFEl\x93\xAB\xBD\xF7\x8B\x84\v\xFC\eN\xEBQ\xE6\xD2".b + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, N: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires r to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, r: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires p to be positive" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: 0) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, p: -1) + }.should raise_error(OpenSSL::KDF::KDFError, /EVP_PBE_scrypt/) + end + + it "requires length to be not negative" do + -> { + OpenSSL::KDF.scrypt("secret", **@defaults, length: -1) + }.should raise_error(ArgumentError, "negative string size (or size too big)") + end +end diff --git a/spec/ruby/library/socket/ipsocket/getaddress_spec.rb b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb index 746d2ab86b..96324982e5 100644 --- a/spec/ruby/library/socket/ipsocket/getaddress_spec.rb +++ b/spec/ruby/library/socket/ipsocket/getaddress_spec.rb @@ -19,7 +19,7 @@ describe "Socket::IPSocket#getaddress" do # traditionally invalidly named ones. it "raises an error on unknown hostnames" do -> { - IPSocket.getaddress("rubyspecdoesntexist.fallingsnow.net") + IPSocket.getaddress("rubyspecdoesntexist.ruby-lang.org") }.should raise_error(SocketError) end end diff --git a/spec/ruby/library/socket/tcpserver/new_spec.rb b/spec/ruby/library/socket/tcpserver/new_spec.rb index 8d9696c9d8..dd1ba676bd 100644 --- a/spec/ruby/library/socket/tcpserver/new_spec.rb +++ b/spec/ruby/library/socket/tcpserver/new_spec.rb @@ -97,6 +97,12 @@ describe "TCPServer.new" do addr[1].should be_kind_of(Integer) end + it "does not use the given block and warns to use TCPServer::open" do + -> { + @server = TCPServer.new(0) { raise } + }.should complain(/warning: TCPServer::new\(\) does not take block; use TCPServer::open\(\) instead/) + end + it "raises Errno::EADDRNOTAVAIL when the address is unknown" do -> { TCPServer.new("1.2.3.4", 0) }.should raise_error(Errno::EADDRNOTAVAIL) end diff --git a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb index 065c8f4190..3bec06c697 100644 --- a/spec/ruby/library/socket/tcpsocket/initialize_spec.rb +++ b/spec/ruby/library/socket/tcpsocket/initialize_spec.rb @@ -4,6 +4,27 @@ require_relative 'shared/new' describe 'TCPSocket#initialize' do it_behaves_like :tcpsocket_new, :new + + describe "with a running server" do + before :each do + @server = SocketSpecs::SpecTCPServer.new + @hostname = @server.hostname + end + + after :each do + if @socket + @socket.write "QUIT" + @socket.close + end + @server.shutdown + end + + it "does not use the given block and warns to use TCPSocket::open" do + -> { + @socket = TCPSocket.new(@hostname, @server.port, nil) { raise } + }.should complain(/warning: TCPSocket::new\(\) does not take block; use TCPSocket::open\(\) instead/) + end + end end describe 'TCPSocket#initialize' do diff --git a/spec/ruby/library/socket/udpsocket/new_spec.rb b/spec/ruby/library/socket/udpsocket/new_spec.rb index 6cc0cadbcb..79bfcb624d 100644 --- a/spec/ruby/library/socket/udpsocket/new_spec.rb +++ b/spec/ruby/library/socket/udpsocket/new_spec.rb @@ -26,6 +26,12 @@ describe 'UDPSocket.new' do @socket.should be_an_instance_of(UDPSocket) end + it "does not use the given block and warns to use UDPSocket::open" do + -> { + @socket = UDPSocket.new { raise } + }.should complain(/warning: UDPSocket::new\(\) does not take block; use UDPSocket::open\(\) instead/) + end + it 'raises Errno::EAFNOSUPPORT or Errno::EPROTONOSUPPORT if unsupported family passed' do -> { UDPSocket.new(-1) }.should raise_error(SystemCallError) { |e| [Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT].should include(e.class) diff --git a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb index 30688b46b6..dba3de7359 100644 --- a/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb +++ b/spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXServer#accept_nonblock" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXServer#accept_nonblock" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -33,9 +32,7 @@ describe "UNIXServer#accept_nonblock" do @server.accept_nonblock(exception: false).should == :wait_readable end end -end -with_feature :unix_socket do describe 'UNIXServer#accept_nonblock' do before do @path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/unixserver/accept_spec.rb b/spec/ruby/library/socket/unixserver/accept_spec.rb index c05fbe7f22..624782d6b9 100644 --- a/spec/ruby/library/socket/unixserver/accept_spec.rb +++ b/spec/ruby/library/socket/unixserver/accept_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -platform_is_not :windows do +with_feature :unix_socket do describe "UNIXServer#accept" do before :each do @path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/unixserver/for_fd_spec.rb b/spec/ruby/library/socket/unixserver/for_fd_spec.rb index 4f3816ad37..e00c4d9526 100644 --- a/spec/ruby/library/socket/unixserver/for_fd_spec.rb +++ b/spec/ruby/library/socket/unixserver/for_fd_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -platform_is_not :windows do +with_feature :unix_socket do describe "UNIXServer#for_fd" do before :each do @unix_path = SocketSpecs.socket_path diff --git a/spec/ruby/library/socket/unixserver/new_spec.rb b/spec/ruby/library/socket/unixserver/new_spec.rb index f831f40bc6..a160e3ce5c 100644 --- a/spec/ruby/library/socket/unixserver/new_spec.rb +++ b/spec/ruby/library/socket/unixserver/new_spec.rb @@ -1,6 +1,14 @@ require_relative '../spec_helper' require_relative 'shared/new' -describe "UNIXServer.new" do - it_behaves_like :unixserver_new, :new +with_feature :unix_socket do + describe "UNIXServer.new" do + it_behaves_like :unixserver_new, :new + + it "does not use the given block and warns to use UNIXServer::open" do + -> { + @server = UNIXServer.new(@path) { raise } + }.should complain(/warning: UNIXServer::new\(\) does not take block; use UNIXServer::open\(\) instead/) + end + end end diff --git a/spec/ruby/library/socket/unixserver/open_spec.rb b/spec/ruby/library/socket/unixserver/open_spec.rb index f2506d9f6f..16453dd3bd 100644 --- a/spec/ruby/library/socket/unixserver/open_spec.rb +++ b/spec/ruby/library/socket/unixserver/open_spec.rb @@ -2,10 +2,10 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/new' -describe "UNIXServer.open" do - it_behaves_like :unixserver_new, :open +with_feature :unix_socket do + describe "UNIXServer.open" do + it_behaves_like :unixserver_new, :open - platform_is_not :windows do before :each do @path = SocketSpecs.socket_path end diff --git a/spec/ruby/library/socket/unixserver/shared/new.rb b/spec/ruby/library/socket/unixserver/shared/new.rb index 35395826c9..b537f2a871 100644 --- a/spec/ruby/library/socket/unixserver/shared/new.rb +++ b/spec/ruby/library/socket/unixserver/shared/new.rb @@ -2,21 +2,19 @@ require_relative '../../spec_helper' require_relative '../../fixtures/classes' describe :unixserver_new, shared: true do - platform_is_not :windows do - before :each do - @path = SocketSpecs.socket_path - end + before :each do + @path = SocketSpecs.socket_path + end - after :each do - @server.close if @server - @server = nil - SocketSpecs.rm_socket @path - end + after :each do + @server.close if @server + @server = nil + SocketSpecs.rm_socket @path + end - it "creates a new UNIXServer" do - @server = UNIXServer.send(@method, @path) - @server.path.should == @path - @server.addr.should == ["AF_UNIX", @path] - end + it "creates a new UNIXServer" do + @server = UNIXServer.send(@method, @path) + @server.path.should == @path + @server.addr.should == ["AF_UNIX", @path] end end diff --git a/spec/ruby/library/socket/unixsocket/addr_spec.rb b/spec/ruby/library/socket/unixsocket/addr_spec.rb index e8431bea16..d93e061312 100644 --- a/spec/ruby/library/socket/unixsocket/addr_spec.rb +++ b/spec/ruby/library/socket/unixsocket/addr_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#addr" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#addr" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) diff --git a/spec/ruby/library/socket/unixsocket/inspect_spec.rb b/spec/ruby/library/socket/unixsocket/inspect_spec.rb index d2e3cabbd3..a542ba6db5 100644 --- a/spec/ruby/library/socket/unixsocket/inspect_spec.rb +++ b/spec/ruby/library/socket/unixsocket/inspect_spec.rb @@ -1,8 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#inspect" do - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#inspect" do it "returns sockets fd for unnamed sockets" do begin s1, s2 = UNIXSocket.socketpair diff --git a/spec/ruby/library/socket/unixsocket/local_address_spec.rb b/spec/ruby/library/socket/unixsocket/local_address_spec.rb index cbf315f9f4..734253e7f5 100644 --- a/spec/ruby/library/socket/unixsocket/local_address_spec.rb +++ b/spec/ruby/library/socket/unixsocket/local_address_spec.rb @@ -46,9 +46,7 @@ with_feature :unix_socket do end end end -end -with_feature :unix_socket do describe 'UNIXSocket#local_address with a UNIX socket pair' do before :each do @sock, @sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM) diff --git a/spec/ruby/library/socket/unixsocket/new_spec.rb b/spec/ruby/library/socket/unixsocket/new_spec.rb index 05a6b3eda2..6d8ea6dcfe 100644 --- a/spec/ruby/library/socket/unixsocket/new_spec.rb +++ b/spec/ruby/library/socket/unixsocket/new_spec.rb @@ -1,6 +1,14 @@ require_relative '../spec_helper' require_relative 'shared/new' -describe "UNIXSocket.new" do - it_behaves_like :unixsocket_new, :new +with_feature :unix_socket do + describe "UNIXSocket.new" do + it_behaves_like :unixsocket_new, :new + + it "does not use the given block and warns to use UNIXSocket::open" do + -> { + @client = UNIXSocket.new(@path) { raise } + }.should complain(/warning: UNIXSocket::new\(\) does not take block; use UNIXSocket::open\(\) instead/) + end + end end diff --git a/spec/ruby/library/socket/unixsocket/open_spec.rb b/spec/ruby/library/socket/unixsocket/open_spec.rb index 99ad151bb8..61def30abb 100644 --- a/spec/ruby/library/socket/unixsocket/open_spec.rb +++ b/spec/ruby/library/socket/unixsocket/open_spec.rb @@ -2,12 +2,12 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/new' -describe "UNIXSocket.open" do - it_behaves_like :unixsocket_new, :open -end +with_feature :unix_socket do + describe "UNIXSocket.open" do + it_behaves_like :unixsocket_new, :open + end -describe "UNIXSocket.open" do - platform_is_not :windows do + describe "UNIXSocket.open" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) diff --git a/spec/ruby/library/socket/unixsocket/pair_spec.rb b/spec/ruby/library/socket/unixsocket/pair_spec.rb index 0cdc55f998..9a66c56c10 100644 --- a/spec/ruby/library/socket/unixsocket/pair_spec.rb +++ b/spec/ruby/library/socket/unixsocket/pair_spec.rb @@ -2,9 +2,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative '../shared/partially_closable_sockets' -describe "UNIXSocket#pair" do - platform_is_not :windows do - +with_feature :unix_socket do + describe "UNIXSocket#pair" do it_should_behave_like :partially_closable_sockets before :each do diff --git a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb index 4f6ef8abd9..ef7d0f0b2a 100644 --- a/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb +++ b/spec/ruby/library/socket/unixsocket/partially_closable_spec.rb @@ -2,9 +2,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' require_relative '../shared/partially_closable_sockets' -platform_is_not :windows do +with_feature :unix_socket do describe "UNIXSocket partial closability" do - before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -20,6 +19,5 @@ platform_is_not :windows do end it_should_behave_like :partially_closable_sockets - end end diff --git a/spec/ruby/library/socket/unixsocket/path_spec.rb b/spec/ruby/library/socket/unixsocket/path_spec.rb index 317ffc0975..a608378e4f 100644 --- a/spec/ruby/library/socket/unixsocket/path_spec.rb +++ b/spec/ruby/library/socket/unixsocket/path_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#path" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#path" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -24,5 +23,4 @@ describe "UNIXSocket#path" do @client.path.should == "" end end - end diff --git a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb index 0b6b1ccf04..72bc96b1fe 100644 --- a/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb +++ b/spec/ruby/library/socket/unixsocket/peeraddr_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#peeraddr" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#peeraddr" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -26,5 +25,4 @@ describe "UNIXSocket#peeraddr" do }.should raise_error(Errno::ENOTCONN) end end - end diff --git a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb index 533f02a0fa..1dbc4538e3 100644 --- a/spec/ruby/library/socket/unixsocket/recv_io_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recv_io_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#recv_io" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#recv_io" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -41,9 +40,7 @@ describe "UNIXSocket#recv_io" do @io.should be_an_instance_of(File) end end -end -with_feature :unix_socket do describe 'UNIXSocket#recv_io' do before do @file = File.open('/dev/null', 'w') diff --git a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb index c0e1cf670b..fedf74bb2f 100644 --- a/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb +++ b/spec/ruby/library/socket/unixsocket/recvfrom_spec.rb @@ -1,8 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#recvfrom" do - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#recvfrom" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -42,10 +42,7 @@ describe "UNIXSocket#recvfrom" do sock.close end end -end - -with_feature :unix_socket do describe 'UNIXSocket#recvfrom' do describe 'using a socket pair' do before do diff --git a/spec/ruby/library/socket/unixsocket/send_io_spec.rb b/spec/ruby/library/socket/unixsocket/send_io_spec.rb index a2a7d26539..80f3550c6d 100644 --- a/spec/ruby/library/socket/unixsocket/send_io_spec.rb +++ b/spec/ruby/library/socket/unixsocket/send_io_spec.rb @@ -1,9 +1,8 @@ require_relative '../spec_helper' require_relative '../fixtures/classes' -describe "UNIXSocket#send_io" do - - platform_is_not :windows do +with_feature :unix_socket do + describe "UNIXSocket#send_io" do before :each do @path = SocketSpecs.socket_path @server = UNIXServer.open(@path) @@ -32,9 +31,7 @@ describe "UNIXSocket#send_io" do @io.read.should == File.read(@send_io_path) end end -end -with_feature :unix_socket do describe 'UNIXSocket#send_io' do before do @file = File.open('/dev/null', 'w') diff --git a/spec/ruby/library/socket/unixsocket/shared/new.rb b/spec/ruby/library/socket/unixsocket/shared/new.rb index bfb7ed3886..f075b03c5e 100644 --- a/spec/ruby/library/socket/unixsocket/shared/new.rb +++ b/spec/ruby/library/socket/unixsocket/shared/new.rb @@ -2,23 +2,21 @@ require_relative '../../spec_helper' require_relative '../../fixtures/classes' describe :unixsocket_new, shared: true do - platform_is_not :windows do - before :each do - @path = SocketSpecs.socket_path - @server = UNIXServer.open(@path) - end + before :each do + @path = SocketSpecs.socket_path + @server = UNIXServer.open(@path) + end - after :each do - @client.close if @client - @server.close - SocketSpecs.rm_socket @path - end + after :each do + @client.close if @client + @server.close + SocketSpecs.rm_socket @path + end - it "opens a unix socket on the specified file" do - @client = UNIXSocket.send(@method, @path) + it "opens a unix socket on the specified file" do + @client = UNIXSocket.send(@method, @path) - @client.addr[0].should == "AF_UNIX" - @client.should_not.closed? - end + @client.addr[0].should == "AF_UNIX" + @client.should_not.closed? end end diff --git a/spec/ruby/library/stringio/new_spec.rb b/spec/ruby/library/stringio/new_spec.rb index 328455134e..ec4b32aa11 100644 --- a/spec/ruby/library/stringio/new_spec.rb +++ b/spec/ruby/library/stringio/new_spec.rb @@ -2,7 +2,9 @@ require_relative '../../spec_helper' require 'stringio' describe "StringIO.new" do - it "warns when called with a block" do - -> { eval("StringIO.new {}") }.should complain(/StringIO::new\(\) does not take block; use StringIO::open\(\) instead/) + it "does not use the given block and warns to use StringIO::open" do + -> { + StringIO.new { raise } + }.should complain(/warning: StringIO::new\(\) does not take block; use StringIO::open\(\) instead/) end end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index aa632b963b..36437cc7b6 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -657,6 +657,20 @@ describe "C-API Encoding function" do end end + describe "rb_enc_raise" do + it "forces exception message encoding to the specified one" do + utf_8_incompatible_string = "\x81".b + + -> { + @s.rb_enc_raise(Encoding::UTF_8, RuntimeError, utf_8_incompatible_string) + }.should raise_error { |e| + e.message.encoding.should == Encoding::UTF_8 + e.message.valid_encoding?.should == false + e.message.bytes.should == utf_8_incompatible_string.bytes + } + end + end + describe "rb_uv_to_utf8" do it 'converts a Unicode codepoint to a UTF-8 C string' do str = ' ' * 6 @@ -674,6 +688,22 @@ describe "C-API Encoding function" do end end + describe "rb_enc_left_char_head" do + it 'returns the head position of a character' do + @s.rb_enc_left_char_head("é", 1).should == 0 + @s.rb_enc_left_char_head("éééé", 7).should == 6 + + @s.rb_enc_left_char_head("a", 0).should == 0 + + # unclear if this is intended to work + @s.rb_enc_left_char_head("a", 1).should == 1 + + # Works because for single-byte encodings rb_enc_left_char_head() just returns the pointer + @s.rb_enc_left_char_head("a".force_encoding(Encoding::US_ASCII), 88).should == 88 + @s.rb_enc_left_char_head("a".b, 88).should == 88 + end + end + describe "ONIGENC_MBC_CASE_FOLD" do it "returns the correct case fold for the given string" do @s.ONIGENC_MBC_CASE_FOLD("lower").should == ["l", 1] diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c index a454ae8395..0ebbc9d75a 100644 --- a/spec/ruby/optional/capi/ext/encoding_spec.c +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -271,6 +271,13 @@ static VALUE encoding_spec_rb_enc_str_asciionly_p(VALUE self, VALUE str) { } } +static VALUE encoding_spec_rb_enc_raise(VALUE self, VALUE encoding, VALUE exception_class, VALUE format) { + rb_encoding *e = rb_to_encoding(encoding); + const char *f = RSTRING_PTR(format); + + rb_enc_raise(e, exception_class, f); +} + static VALUE encoding_spec_rb_uv_to_utf8(VALUE self, VALUE buf, VALUE num) { int len = rb_uv_to_utf8(RSTRING_PTR(buf), NUM2INT(num)); RB_ENC_CODERANGE_CLEAR(buf); @@ -307,6 +314,12 @@ static VALUE encoding_spec_rb_enc_strlen(VALUE self, VALUE str, VALUE length, VA return LONG2FIX(rb_enc_strlen(p, e, rb_to_encoding(encoding))); } +static VALUE encoding_spec_rb_enc_left_char_head(VALUE self, VALUE str, VALUE offset) { + char *ptr = RSTRING_PTR(str); + char *result = rb_enc_left_char_head(ptr, ptr + NUM2INT(offset), RSTRING_END(str), rb_enc_get(str)); + return LONG2NUM(result - ptr); +} + void Init_encoding_spec(void) { VALUE cls; native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*)); @@ -362,8 +375,10 @@ void Init_encoding_spec(void) { rb_define_method(cls, "rb_enc_nth", encoding_spec_rb_enc_nth, 2); rb_define_method(cls, "rb_enc_codepoint_len", encoding_spec_rb_enc_codepoint_len, 1); rb_define_method(cls, "rb_enc_str_asciionly_p", encoding_spec_rb_enc_str_asciionly_p, 1); + rb_define_method(cls, "rb_enc_raise", encoding_spec_rb_enc_raise, 3); rb_define_method(cls, "rb_uv_to_utf8", encoding_spec_rb_uv_to_utf8, 2); rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1); + rb_define_method(cls, "rb_enc_left_char_head", encoding_spec_rb_enc_left_char_head, 2); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/util_spec.rb b/spec/ruby/optional/capi/util_spec.rb index 320527521b..2c16999cdc 100644 --- a/spec/ruby/optional/capi/util_spec.rb +++ b/spec/ruby/optional/capi/util_spec.rb @@ -127,6 +127,19 @@ describe "C-API Util function" do ScratchPad.recorded.should == [1, 7, 4] end + it "assigns optional arguments with no hash argument given" do + @o.rb_scan_args([1, 7], "02:", 3, @acc).should == 2 + ScratchPad.recorded.should == [1, 7, nil] + end + + it "assigns optional arguments with no hash argument given and rejects the use of optional nil argument as a hash" do + -> { + @o.rb_scan_args([1, nil], "02:", 3, @acc).should == 2 + }.should_not complain + + ScratchPad.recorded.should == [1, nil, nil] + end + it "assigns required, optional, splat, post-splat, Hash and block arguments" do h = {a: 1, b: 2} @o.rb_scan_args([1, 2, 3, 4, 5, h], "k11*1:&", 6, @acc, &@prc).should == 5 diff --git a/spec/ruby/shared/string/start_with.rb b/spec/ruby/shared/string/start_with.rb index 91fc50c4cd..4b947a3bbf 100644 --- a/spec/ruby/shared/string/start_with.rb +++ b/spec/ruby/shared/string/start_with.rb @@ -70,7 +70,13 @@ describe :start_with, shared: true do $1.should be_nil end - ruby_bug "#19784", ""..."3.3" do + ruby_version_is ""..."3.3" do + it "does not check that we are not matching part of a character" do + "\xC3\xA9".send(@method).should.start_with?("\xC3") + end + end + + ruby_version_is "3.3" do # #19784 it "checks that we are not matching part of a character" do "\xC3\xA9".send(@method).should_not.start_with?("\xC3") end |