This commit is contained in:
Charles Oliver Nutter 2025-11-18 19:38:20 -06:00 committed by Benoit Daloze
parent bbb4c7b88b
commit 85cd08e4f9
Notes: git 2025-11-19 11:37:12 +00:00
49 changed files with 2411 additions and 91 deletions

View File

@ -24,7 +24,7 @@ describe 'Comparable#clamp' do
c.clamp(two, three).should equal(c)
end
it 'returns the min parameter if smaller than it' do
it 'returns the min parameter if less than it' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
two = ComparableSpecs::WithOnlyCompareDefined.new(2)
c = ComparableSpecs::Weird.new(0)
@ -52,7 +52,7 @@ describe 'Comparable#clamp' do
c.clamp(two..three).should equal(c)
end
it 'returns the minimum value of the range parameters if smaller than it' do
it 'returns the minimum value of the range parameters if less than it' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
two = ComparableSpecs::WithOnlyCompareDefined.new(2)
c = ComparableSpecs::Weird.new(0)
@ -75,4 +75,70 @@ describe 'Comparable#clamp' do
-> { c.clamp(one...two) }.should raise_error(ArgumentError)
end
context 'with endless range' do
it 'returns minimum value of the range parameters if less than it' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
zero = ComparableSpecs::WithOnlyCompareDefined.new(0)
c = ComparableSpecs::Weird.new(0)
c.clamp(one..).should equal(one)
c.clamp(zero..).should equal(c)
end
it 'always returns self if greater than minimum value of the range parameters' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
two = ComparableSpecs::WithOnlyCompareDefined.new(2)
c = ComparableSpecs::Weird.new(2)
c.clamp(one..).should equal(c)
c.clamp(two..).should equal(c)
end
it 'works with exclusive range' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
c = ComparableSpecs::Weird.new(2)
c.clamp(one...).should equal(c)
end
end
context 'with beginless range' do
it 'returns maximum value of the range parameters if greater than it' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
c = ComparableSpecs::Weird.new(2)
c.clamp(..one).should equal(one)
end
it 'always returns self if less than maximum value of the range parameters' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
zero = ComparableSpecs::WithOnlyCompareDefined.new(0)
c = ComparableSpecs::Weird.new(0)
c.clamp(..one).should equal(c)
c.clamp(..zero).should equal(c)
end
it 'raises an Argument error if the range parameter is exclusive' do
one = ComparableSpecs::WithOnlyCompareDefined.new(1)
c = ComparableSpecs::Weird.new(0)
-> { c.clamp(...one) }.should raise_error(ArgumentError)
end
end
context 'with beginless-and-endless range' do
it 'always returns self' do
c = ComparableSpecs::Weird.new(1)
c.clamp(nil..nil).should equal(c)
end
it 'works with exclusive range' do
c = ComparableSpecs::Weird.new(2)
c.clamp(nil...nil).should equal(c)
end
end
end

View File

@ -103,7 +103,7 @@ describe :enumerable_inject, shared: true do
it "without inject arguments(legacy rubycon)" do
# no inject argument
EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 } .should == 2
EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 }.should == 2
EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| acc }.should == 2
EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2

View File

@ -35,7 +35,7 @@ describe "Hash#compact" do
hash.compact.default_proc.should == pr
end
it "retains compare_by_identity_flag" do
it "retains compare_by_identity flag" do
hash = {}.compare_by_identity
hash.compact.compare_by_identity?.should == true
hash[:a] = 1

View File

@ -103,14 +103,14 @@ describe "Hash.[]" do
HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash)
end
it "removes the default value" do
it "does not retain the default value" do
hash = Hash.new(1)
Hash[hash].default.should be_nil
hash[:a] = 1
Hash[hash].default.should be_nil
end
it "removes the default_proc" do
it "does not retain the default_proc" do
hash = Hash.new { |h, k| h[k] = [] }
Hash[hash].default_proc.should be_nil
hash[:a] = 1
@ -118,10 +118,11 @@ describe "Hash.[]" do
end
ruby_version_is '3.3' do
it "does not retain compare_by_identity_flag" do
hash = {}.compare_by_identity
it "does not retain compare_by_identity flag" do
hash = { a: 1 }.compare_by_identity
Hash[hash].compare_by_identity?.should == false
hash[:a] = 1
hash = {}.compare_by_identity
Hash[hash].compare_by_identity?.should == false
end
end

View File

@ -19,14 +19,24 @@ describe "Hash#except" do
@hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 }
end
it "always returns a Hash without a default" do
klass = Class.new(Hash)
h = klass.new(:default)
h[:bar] = 12
h[:foo] = 42
r = h.except(:foo)
r.should == {bar: 12}
r.class.should == Hash
r.default.should == nil
it "does not retain the default value" do
h = Hash.new(1)
h.except(:a).default.should be_nil
h[:a] = 1
h.except(:a).default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.except(:a).default_proc.should be_nil
h[:a] = 1
h.except(:a).default_proc.should be_nil
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.except(:a)
h2.compare_by_identity?.should == true
end
end

View File

@ -24,4 +24,25 @@ describe "Hash#invert" do
HashSpecs::MyHash[1 => 2, 3 => 4].invert.class.should == Hash
HashSpecs::MyHash[].invert.class.should == Hash
end
it "does not retain the default value" do
h = Hash.new(1)
h.invert.default.should be_nil
h[:a] = 1
h.invert.default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.invert.default_proc.should be_nil
h[:a] = 1
h.invert.default_proc.should be_nil
end
it "does not retain compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.invert
h2.compare_by_identity?.should == false
end
end

View File

@ -93,6 +93,29 @@ describe "Hash#merge" do
merged.should eql(hash)
merged.should_not equal(hash)
end
it "retains the default value" do
h = Hash.new(1)
h.merge(b: 1, d: 2).default.should == 1
end
it "retains the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.merge(b: 1, d: 2).default_proc.should == pr
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.merge(b: 1, d: 2)
h2.compare_by_identity?.should == true
end
it "ignores compare_by_identity flag of an argument" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = { b: 1, d: 2 }.merge(h)
h2.compare_by_identity?.should == false
end
end
describe "Hash#merge!" do

View File

@ -44,6 +44,27 @@ describe "Hash#reject" do
reject_pairs.should == reject_bang_pairs
end
it "does not retain the default value" do
h = Hash.new(1)
h.reject { false }.default.should be_nil
h[:a] = 1
h.reject { false }.default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.reject { false }.default_proc.should be_nil
h[:a] = 1
h.reject { false }.default_proc.should be_nil
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.reject { |k, _| k == :a }
h2.compare_by_identity?.should == true
end
it_behaves_like :hash_iteration_no_block, :reject
it_behaves_like :enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 }
end

View File

@ -23,39 +23,48 @@ describe "Hash#replace" do
h.should == { 1 => 2 }
end
it "transfers the compare_by_identity flag" do
hash_a = { a: 1 }
hash_b = { b: 2 }
hash_b.compare_by_identity
hash_a.should_not.compare_by_identity?
hash_a.replace(hash_b)
hash_a.should.compare_by_identity?
hash_a = { a: 1 }
hash_b = { b: 2 }
hash_a.compare_by_identity
hash_a.should.compare_by_identity?
hash_a.replace(hash_b)
hash_a.should_not.compare_by_identity?
it "does not retain the default value" do
hash = Hash.new(1)
hash.replace(b: 2).default.should be_nil
end
it "does not transfer default values" do
hash_a = {}
hash_b = Hash.new(5)
hash_a.replace(hash_b)
hash_a.default.should == 5
it "transfers the default value of an argument" do
hash = Hash.new(1)
{ a: 1 }.replace(hash).default.should == 1
end
hash_a = {}
hash_b = Hash.new { |h, k| k * 2 }
hash_a.replace(hash_b)
hash_a.default(5).should == 10
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
hash = Hash.new(&pr)
hash.replace(b: 2).default_proc.should be_nil
end
it "transfers the default_proc of an argument" do
pr = proc { |h, k| h[k] = [] }
hash = Hash.new(&pr)
{ a: 1 }.replace(hash).default_proc.should == pr
end
it "does not call the default_proc of an argument" do
hash_a = Hash.new { |h, k| k * 5 }
hash_b = Hash.new(-> { raise "Should not invoke lambda" })
hash_a.replace(hash_b)
hash_a.default.should == hash_b.default
end
it "transfers compare_by_identity flag of an argument" do
h = { a: 1, c: 3 }
h2 = { b: 2, d: 4 }.compare_by_identity
h.replace(h2)
h.compare_by_identity?.should == true
end
it "does not retain compare_by_identity flag" do
h = { a: 1, c: 3 }.compare_by_identity
h.replace(b: 2, d: 4)
h.compare_by_identity?.should == false
end
it "raises a FrozenError if called on a frozen instance that would not be modified" do
-> do
HashSpecs.frozen_hash.replace(HashSpecs.frozen_hash)

View File

@ -40,6 +40,27 @@ describe :hash_select, shared: true do
@empty.send(@method).should be_an_instance_of(Enumerator)
end
it "does not retain the default value" do
h = Hash.new(1)
h.send(@method) { true }.default.should be_nil
h[:a] = 1
h.send(@method) { true }.default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.send(@method) { true }.default_proc.should be_nil
h[:a] = 1
h.send(@method) { true }.default_proc.should be_nil
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.send(@method) { |k, _| k == :a }
h2.compare_by_identity?.should == true
end
it_should_behave_like :hash_iteration_no_block
before :each do

View File

@ -50,4 +50,25 @@ describe "Hash#slice" do
ScratchPad.recorded.should == []
end
it "does not retain the default value" do
h = Hash.new(1)
h.slice(:a).default.should be_nil
h[:a] = 1
h.slice(:a).default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.slice(:a).default_proc.should be_nil
h[:a] = 1
h.slice(:a).default_proc.should be_nil
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.slice(:a)
h2.compare_by_identity?.should == true
end
end

View File

@ -19,17 +19,22 @@ describe "Hash#to_h" do
@h[:foo].should == :bar
end
it "copies the default" do
it "retains the default" do
@h.default = 42
@h.to_h.default.should == 42
@h[:hello].should == 42
end
it "copies the default_proc" do
it "retains the default_proc" do
@h.default_proc = prc = Proc.new{ |h, k| h[k] = 2 * k }
@h.to_h.default_proc.should == prc
@h[42].should == 84
end
it "retains compare_by_identity flag" do
@h.compare_by_identity
@h.to_h.compare_by_identity?.should == true
end
end
context "with block" do
@ -78,5 +83,24 @@ describe "Hash#to_h" do
{ a: 1 }.to_h { |k| x }
end.should raise_error(TypeError, /wrong element type MockObject/)
end
it "does not retain the default value" do
h = Hash.new(1)
h2 = h.to_h { |k, v| [k.to_s, v*v]}
h2.default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h2 = h.to_h { |k, v| [k.to_s, v*v]}
h2.default_proc.should be_nil
end
it "does not retain compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.to_h { |k, v| [k.to_s, v*v]}
h2.compare_by_identity?.should == false
end
end
end

View File

@ -54,6 +54,27 @@ describe "Hash#transform_keys" do
it "allows a combination of hash and block argument" do
@hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 }
end
it "does not retain the default value" do
h = Hash.new(1)
h.transform_keys(&:succ).default.should be_nil
h[:a] = 1
h.transform_keys(&:succ).default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.transform_values(&:succ).default_proc.should be_nil
h[:a] = 1
h.transform_values(&:succ).default_proc.should be_nil
end
it "does not retain compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.transform_keys(&:succ)
h2.compare_by_identity?.should == false
end
end
describe "Hash#transform_keys!" do

View File

@ -39,6 +39,27 @@ describe "Hash#transform_values" do
r[:foo].should == 84
r.class.should == Hash
end
it "does not retain the default value" do
h = Hash.new(1)
h.transform_values(&:succ).default.should be_nil
h[:a] = 1
h.transform_values(&:succ).default.should be_nil
end
it "does not retain the default_proc" do
pr = proc { |h, k| h[k] = [] }
h = Hash.new(&pr)
h.transform_values(&:succ).default_proc.should be_nil
h[:a] = 1
h.transform_values(&:succ).default_proc.should be_nil
end
it "retains compare_by_identity flag" do
h = { a: 9, c: 4 }.compare_by_identity
h2 = h.transform_values(&:succ)
h2.compare_by_identity?.should == true
end
end
describe "Hash#transform_values!" do

View File

@ -0,0 +1,29 @@
require_relative '../../../spec_helper'
require_relative 'shared/null_and_empty'
describe "IO::Buffer#empty?" do
after :each do
@buffer&.free
@buffer = nil
end
it_behaves_like :io_buffer_null_and_empty, :empty?
it "is true for a 0-length String-backed buffer created with .for" do
@buffer = IO::Buffer.for("")
@buffer.empty?.should be_true
end
ruby_version_is "3.3" do
it "is true for a 0-length String-backed buffer created with .string" do
IO::Buffer.string(0) do |buffer|
buffer.empty?.should be_true
end
end
end
it "is true for a 0-length slice of a buffer with size > 0" do
@buffer = IO::Buffer.new(4)
@buffer.slice(3, 0).empty?.should be_true
end
end

View File

@ -0,0 +1,108 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#external?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is false for an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.external?.should be_false
end
it "is false for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.external?.should be_false
end
end
context "with a file-backed buffer created with .map" do
it "is true for a regular mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.external?.should be_true
end
end
ruby_version_is "3.3" do
it "is false for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.external?.should be_false
end
end
end
end
context "with a String-backed buffer created with .for" do
it "is true for a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.external?.should be_true
end
it "is true for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.external?.should be_true
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "is true" do
IO::Buffer.string(4) do |buffer|
buffer.external?.should be_true
end
end
end
end
# Always false for slices
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.external?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.external?.should be_false
end
end
context "created with .map" do
it "is false" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.external?.should be_false
end
end
end
context "created with .for" do
it "is false when slicing a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.slice.external?.should be_false
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.external?.should be_false
end
end
end
ruby_version_is "3.3" do
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.external?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,104 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#free" do
context "with a buffer created with .new" do
it "frees internal memory and nullifies the buffer" do
buffer = IO::Buffer.new(4)
buffer.free
buffer.null?.should be_true
end
it "frees mapped memory and nullifies the buffer" do
buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
buffer.free
buffer.null?.should be_true
end
end
context "with a file-backed buffer created with .map" do
it "frees mapped memory and nullifies the buffer" do
File.open(__FILE__, "r") do |file|
buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
buffer.free
buffer.null?.should be_true
end
end
end
context "with a String-backed buffer created with .for" do
context "without a block" do
it "disassociates the buffer from the string and nullifies the buffer" do
string = +"test"
buffer = IO::Buffer.for(string)
# Read-only buffer, can't modify the string.
buffer.free
buffer.null?.should be_true
end
end
context "with a block" do
it "disassociates the buffer from the string and nullifies the buffer" do
string = +"test"
IO::Buffer.for(string) do |buffer|
buffer.set_string("meat")
buffer.free
buffer.null?.should be_true
end
string.should == "meat"
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "disassociates the buffer from the string and nullifies the buffer" do
string =
IO::Buffer.string(4) do |buffer|
buffer.set_string("meat")
buffer.free
buffer.null?.should be_true
end
string.should == "meat"
end
end
end
it "can be called repeatedly without an error" do
buffer = IO::Buffer.new(4)
buffer.free
buffer.null?.should be_true
buffer.free
buffer.null?.should be_true
end
it "is disallowed while locked, raising IO::Buffer::LockedError" do
buffer = IO::Buffer.new(4)
buffer.locked do
-> { buffer.free }.should raise_error(IO::Buffer::LockedError, "Buffer is locked!")
end
buffer.free
buffer.null?.should be_true
end
context "with a slice of a buffer" do
it "nullifies the slice, not touching the buffer" do
buffer = IO::Buffer.new(4)
slice = buffer.slice(0, 2)
slice.free
slice.null?.should be_true
buffer.null?.should be_false
buffer.free
end
it "nullifies buffer, invalidating the slice" do
buffer = IO::Buffer.new(4)
slice = buffer.slice(0, 2)
buffer.free
slice.null?.should be_false
slice.valid?.should be_false
end
end
end

View File

@ -0,0 +1,103 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#initialize" do
after :each do
@buffer&.free
@buffer = nil
end
it "creates a new zero-filled buffer with default size" do
@buffer = IO::Buffer.new
@buffer.size.should == IO::Buffer::DEFAULT_SIZE
@buffer.each(:U8).should.all? { |_offset, value| value.eql?(0) }
end
it "creates a buffer with default state" do
@buffer = IO::Buffer.new
@buffer.should_not.shared?
@buffer.should_not.readonly?
@buffer.should_not.empty?
@buffer.should_not.null?
# This is run-time state, set by #locked.
@buffer.should_not.locked?
end
context "with size argument" do
it "creates a new internal buffer if size is less than IO::Buffer::PAGE_SIZE" do
size = IO::Buffer::PAGE_SIZE - 1
@buffer = IO::Buffer.new(size)
@buffer.size.should == size
@buffer.should.internal?
@buffer.should_not.mapped?
@buffer.should_not.empty?
end
it "creates a new mapped buffer if size is greater than or equal to IO::Buffer::PAGE_SIZE" do
size = IO::Buffer::PAGE_SIZE
@buffer = IO::Buffer.new(size)
@buffer.size.should == size
@buffer.should_not.internal?
@buffer.should.mapped?
@buffer.should_not.empty?
end
it "creates a null buffer if size is 0" do
@buffer = IO::Buffer.new(0)
@buffer.size.should.zero?
@buffer.should_not.internal?
@buffer.should_not.mapped?
@buffer.should.null?
@buffer.should.empty?
end
it "raises TypeError if size is not an Integer" do
-> { IO::Buffer.new(nil) }.should raise_error(TypeError, "not an Integer")
-> { IO::Buffer.new(10.0) }.should raise_error(TypeError, "not an Integer")
end
it "raises ArgumentError if size is negative" do
-> { IO::Buffer.new(-1) }.should raise_error(ArgumentError, "Size can't be negative!")
end
end
context "with size and flags arguments" do
it "forces mapped buffer with IO::Buffer::MAPPED flag" do
@buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE - 1, IO::Buffer::MAPPED)
@buffer.should.mapped?
@buffer.should_not.internal?
@buffer.should_not.empty?
end
it "forces internal buffer with IO::Buffer::INTERNAL flag" do
@buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::INTERNAL)
@buffer.should.internal?
@buffer.should_not.mapped?
@buffer.should_not.empty?
end
it "raises IO::Buffer::AllocationError if neither IO::Buffer::MAPPED nor IO::Buffer::INTERNAL is given" do
-> { IO::Buffer.new(10, IO::Buffer::READONLY) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!")
-> { IO::Buffer.new(10, 0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!")
end
ruby_version_is "3.3" do
it "raises ArgumentError if flags is negative" do
-> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!")
end
end
ruby_version_is ""..."3.3" do
it "raises IO::Buffer::AllocationError with non-Integer flags" do
-> { IO::Buffer.new(10, 0.0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!")
end
end
ruby_version_is "3.3" do
it "raises TypeError with non-Integer flags" do
-> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer")
end
end
end
end

View File

@ -0,0 +1,108 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#internal?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is true for an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.internal?.should be_true
end
it "is false for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.internal?.should be_false
end
end
context "with a file-backed buffer created with .map" do
it "is false for a regular mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.internal?.should be_false
end
end
ruby_version_is "3.3" do
it "is false for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.internal?.should be_false
end
end
end
end
context "with a String-backed buffer created with .for" do
it "is false for a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.internal?.should be_false
end
it "is false for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.internal?.should be_false
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.internal?.should be_false
end
end
end
end
# Always false for slices
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.internal?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.internal?.should be_false
end
end
context "created with .map" do
it "is false" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.internal?.should be_false
end
end
end
context "created with .for" do
it "is false when slicing a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.slice.internal?.should be_false
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.internal?.should be_false
end
end
end
ruby_version_is "3.3" do
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.internal?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,75 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#locked" do
after :each do
@buffer&.free
@buffer = nil
end
context "when buffer is locked" do
it "allows reading and writing operations on the buffer" do
@buffer = IO::Buffer.new(4)
@buffer.set_string("test")
@buffer.locked do
@buffer.get_string.should == "test"
@buffer.set_string("meat")
end
@buffer.get_string.should == "meat"
end
it "disallows operations changing buffer itself, raising IO::Buffer::LockedError" do
@buffer = IO::Buffer.new(4)
@buffer.locked do
# Just an example, each method is responsible for checking the lock state.
-> { @buffer.resize(8) }.should raise_error(IO::Buffer::LockedError)
end
end
end
it "disallows reentrant locking, raising IO::Buffer::LockedError" do
@buffer = IO::Buffer.new(4)
@buffer.locked do
-> { @buffer.locked {} }.should raise_error(IO::Buffer::LockedError, "Buffer already locked!")
end
end
it "does not propagate to buffer's slices" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(0, 2)
@buffer.locked do
@buffer.locked?.should be_true
slice.locked?.should be_false
slice.locked { slice.locked?.should be_true }
end
end
it "does not propagate backwards from buffer's slices" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(0, 2)
slice.locked do
slice.locked?.should be_true
@buffer.locked?.should be_false
@buffer.locked { @buffer.locked?.should be_true }
end
end
end
describe "IO::Buffer#locked?" do
after :each do
@buffer&.free
@buffer = nil
end
it "is false by default" do
@buffer = IO::Buffer.new(4)
@buffer.locked?.should be_false
end
it "is true only inside of #locked block" do
@buffer = IO::Buffer.new(4)
@buffer.locked do
@buffer.locked?.should be_true
end
@buffer.locked?.should be_false
end
end

View File

@ -0,0 +1,108 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#mapped?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is false for an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.mapped?.should be_false
end
it "is true for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.mapped?.should be_true
end
end
context "with a file-backed buffer created with .map" do
it "is true for a regular mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.mapped?.should be_true
end
end
ruby_version_is "3.3" do
it "is true for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.mapped?.should be_true
end
end
end
end
context "with a String-backed buffer created with .for" do
it "is false for a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.mapped?.should be_false
end
it "is false for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.mapped?.should be_false
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.mapped?.should be_false
end
end
end
end
# Always false for slices
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.mapped?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.mapped?.should be_false
end
end
context "created with .map" do
it "is false" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.mapped?.should be_false
end
end
end
context "created with .for" do
it "is false when slicing a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.slice.mapped?.should be_false
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.mapped?.should be_false
end
end
end
ruby_version_is "3.3" do
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.mapped?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,29 @@
require_relative '../../../spec_helper'
require_relative 'shared/null_and_empty'
describe "IO::Buffer#null?" do
after :each do
@buffer&.free
@buffer = nil
end
it_behaves_like :io_buffer_null_and_empty, :null?
it "is false for a 0-length String-backed buffer created with .for" do
@buffer = IO::Buffer.for("")
@buffer.null?.should be_false
end
ruby_version_is "3.3" do
it "is false for a 0-length String-backed buffer created with .string" do
IO::Buffer.string(0) do |buffer|
buffer.null?.should be_false
end
end
end
it "is false for a 0-length slice of a buffer with size > 0" do
@buffer = IO::Buffer.new(4)
@buffer.slice(3, 0).null?.should be_false
end
end

View File

@ -0,0 +1,111 @@
require_relative '../../../spec_helper'
ruby_version_is "3.3" do
describe "IO::Buffer#private?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is false for an internal buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL)
@buffer.private?.should be_false
end
it "is false for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.private?.should be_false
end
end
context "with a file-backed buffer created with .map" do
it "is false for a regular mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.private?.should be_false
end
end
it "is true for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.private?.should be_true
end
end
end
context "with a String-backed buffer created with .for" do
it "is false for a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.private?.should be_false
end
it "is false for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.private?.should be_false
end
end
end
context "with a String-backed buffer created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.private?.should be_false
end
end
end
# Always false for slices
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.private?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.private?.should be_false
end
end
context "created with .map" do
it "is false when slicing a regular file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.private?.should be_false
end
end
it "is false when slicing a private file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.slice.private?.should be_false
end
end
end
context "created with .for" do
it "is false when slicing a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.slice.private?.should be_false
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.private?.should be_false
end
end
end
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.private?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,143 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#readonly?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is false for an internal buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL)
@buffer.readonly?.should be_false
end
it "is false for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.readonly?.should be_false
end
end
context "with a file-backed buffer created with .map" do
it "is false for a writable mapping" do
File.open(__FILE__, "r+") do |file|
@buffer = IO::Buffer.map(file)
@buffer.readonly?.should be_false
end
end
it "is true for a readonly mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.readonly?.should be_true
end
end
ruby_version_is "3.3" do
it "is false for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
@buffer.readonly?.should be_false
end
end
end
end
context "with a String-backed buffer created with .for" do
it "is true for a buffer created without a block" do
@buffer = IO::Buffer.for(+"test")
@buffer.readonly?.should be_true
end
it "is false for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.readonly?.should be_false
end
end
it "is true for a buffer created with a block from a frozen string" do
IO::Buffer.for(-"test") do |buffer|
buffer.readonly?.should be_true
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.readonly?.should be_false
end
end
end
end
# This seems to be the only flag propagated from the source buffer to the slice.
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.readonly?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.readonly?.should be_false
end
end
context "created with .map" do
it "is false when slicing a read-write file-backed buffer" do
File.open(__FILE__, "r+") do |file|
@buffer = IO::Buffer.map(file)
@buffer.slice.readonly?.should be_false
end
end
it "is true when slicing a readonly file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.readonly?.should be_true
end
end
ruby_version_is "3.3" do
it "is false when slicing a private file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
@buffer.slice.readonly?.should be_false
end
end
end
end
context "created with .for" do
it "is true when slicing a buffer created without a block" do
@buffer = IO::Buffer.for(+"test")
@buffer.slice.readonly?.should be_true
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.readonly?.should be_false
end
end
it "is true when slicing a buffer created with a block from a frozen string" do
IO::Buffer.for(-"test") do |buffer|
buffer.slice.readonly?.should be_true
end
end
end
ruby_version_is "3.3" do
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.readonly?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,155 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#resize" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "resizes internal buffer, preserving type" do
@buffer = IO::Buffer.new(4)
@buffer.resize(IO::Buffer::PAGE_SIZE)
@buffer.size.should == IO::Buffer::PAGE_SIZE
@buffer.internal?.should be_true
@buffer.mapped?.should be_false
end
platform_is :linux do
it "resizes mapped buffer, preserving type" do
@buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED)
@buffer.resize(4)
@buffer.size.should == 4
@buffer.internal?.should be_false
@buffer.mapped?.should be_true
end
end
platform_is_not :linux do
it "resizes mapped buffer, changing type to internal" do
@buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED)
@buffer.resize(4)
@buffer.size.should == 4
@buffer.internal?.should be_true
@buffer.mapped?.should be_false
end
end
end
context "with a file-backed buffer created with .map" do
it "disallows resizing shared buffer, raising IO::Buffer::AccessError" do
File.open(__FILE__, "r+") do |file|
@buffer = IO::Buffer.map(file)
-> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!")
end
end
ruby_version_is "3.3" do
it "resizes private buffer, discarding excess contents" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
@buffer.resize(10)
@buffer.size.should == 10
@buffer.get_string.should == "require_re"
@buffer.resize(12)
@buffer.size.should == 12
@buffer.get_string.should == "require_re\0\0"
end
end
end
end
context "with a String-backed buffer created with .for" do
context "without a block" do
it "disallows resizing, raising IO::Buffer::AccessError" do
@buffer = IO::Buffer.for(+"test")
-> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!")
end
end
context "with a block" do
it "disallows resizing, raising IO::Buffer::AccessError" do
IO::Buffer.for(+'test') do |buffer|
-> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!")
end
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "disallows resizing, raising IO::Buffer::AccessError" do
IO::Buffer.string(4) do |buffer|
-> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!")
end
end
end
end
context "with a null buffer" do
it "allows resizing a 0-sized buffer, creating a regular buffer according to new size" do
@buffer = IO::Buffer.new(0)
@buffer.resize(IO::Buffer::PAGE_SIZE)
@buffer.size.should == IO::Buffer::PAGE_SIZE
@buffer.internal?.should be_false
@buffer.mapped?.should be_true
end
it "allows resizing after a free, creating a regular buffer according to new size" do
@buffer = IO::Buffer.for("test")
@buffer.free
@buffer.resize(10)
@buffer.size.should == 10
@buffer.internal?.should be_true
@buffer.mapped?.should be_false
end
end
it "allows resizing to 0, freeing memory" do
@buffer = IO::Buffer.new(4)
@buffer.resize(0)
@buffer.null?.should be_true
end
it "can be called repeatedly" do
@buffer = IO::Buffer.new(4)
@buffer.resize(10)
@buffer.resize(27)
@buffer.resize(1)
@buffer.size.should == 1
end
it "always clears extra memory" do
@buffer = IO::Buffer.new(4)
@buffer.set_string("test")
# This should not cause a re-allocation, just a technical resizing,
# even with very aggressive memory allocation.
@buffer.resize(2)
@buffer.resize(4)
@buffer.get_string.should == "te\0\0"
end
it "is disallowed while locked, raising IO::Buffer::LockedError" do
@buffer = IO::Buffer.new(4)
@buffer.locked do
-> { @buffer.resize(10) }.should raise_error(IO::Buffer::LockedError, "Cannot resize locked buffer!")
end
end
it "raises ArgumentError if size is negative" do
@buffer = IO::Buffer.new(4)
-> { @buffer.resize(-1) }.should raise_error(ArgumentError, "Size can't be negative!")
end
it "raises TypeError if size is not an Integer" do
@buffer = IO::Buffer.new(4)
-> { @buffer.resize(nil) }.should raise_error(TypeError, "not an Integer")
-> { @buffer.resize(10.0) }.should raise_error(TypeError, "not an Integer")
end
context "with a slice of a buffer" do
# Current behavior of slice resizing seems unintended (it's undocumented, too).
# It either creates a completely new buffer, or breaks the slice on size 0.
it "needs to be reviewed for spec completeness"
end
end

View File

@ -0,0 +1,59 @@
describe :io_buffer_null_and_empty, shared: true do
it "is false for a buffer with size > 0" do
@buffer = IO::Buffer.new(1)
@buffer.send(@method).should be_false
end
it "is false for a slice with length > 0" do
@buffer = IO::Buffer.new(4)
@buffer.slice(1, 2).send(@method).should be_false
end
it "is false for a file-mapped buffer" do
File.open(__FILE__, "rb") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.send(@method).should be_false
end
end
it "is false for a non-empty String-backed buffer created with .for" do
@buffer = IO::Buffer.for("test")
@buffer.send(@method).should be_false
end
ruby_version_is "3.3" do
it "is false for a non-empty String-backed buffer created with .string" do
IO::Buffer.string(4) do |buffer|
buffer.send(@method).should be_false
end
end
end
it "is true for a 0-sized buffer" do
@buffer = IO::Buffer.new(0)
@buffer.send(@method).should be_true
end
it "is true for a slice of a 0-sized buffer" do
@buffer = IO::Buffer.new(0)
@buffer.slice(0, 0).send(@method).should be_true
end
it "is true for a freed buffer" do
@buffer = IO::Buffer.new(1)
@buffer.free
@buffer.send(@method).should be_true
end
it "is true for a buffer resized to 0" do
@buffer = IO::Buffer.new(1)
@buffer.resize(0)
@buffer.send(@method).should be_true
end
it "is true for a buffer whose memory was transferred" do
buffer = IO::Buffer.new(1)
@buffer = buffer.transfer
buffer.send(@method).should be_true
end
end

View File

@ -0,0 +1,117 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#shared?" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "is false for an internal buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL)
@buffer.shared?.should be_false
end
it "is false for a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.shared?.should be_false
end
end
context "with a file-backed buffer created with .map" do
it "is true for a regular mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.shared?.should be_true
end
end
ruby_version_is "3.3" do
it "is false for a private mapping" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.shared?.should be_false
end
end
end
end
context "with a String-backed buffer created with .for" do
it "is false for a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.shared?.should be_false
end
it "is false for a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.shared?.should be_false
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.shared?.should be_false
end
end
end
end
# Always false for slices
context "with a slice of a buffer" do
context "created with .new" do
it "is false when slicing an internal buffer" do
@buffer = IO::Buffer.new(4)
@buffer.slice.shared?.should be_false
end
it "is false when slicing a mapped buffer" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
@buffer.slice.shared?.should be_false
end
end
context "created with .map" do
it "is false when slicing a regular file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.slice.shared?.should be_false
end
end
ruby_version_is "3.3" do
it "is false when slicing a private file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE)
@buffer.slice.shared?.should be_false
end
end
end
end
context "created with .for" do
it "is false when slicing a buffer created without a block" do
@buffer = IO::Buffer.for("test")
@buffer.slice.shared?.should be_false
end
it "is false when slicing a buffer created with a block" do
IO::Buffer.for(+"test") do |buffer|
buffer.slice.shared?.should be_false
end
end
end
ruby_version_is "3.3" do
context "created with .string" do
it "is false" do
IO::Buffer.string(4) do |buffer|
buffer.slice.shared?.should be_false
end
end
end
end
end
end

View File

@ -0,0 +1,118 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#transfer" do
after :each do
@buffer&.free
@buffer = nil
end
context "with a buffer created with .new" do
it "transfers internal memory to a new buffer, nullifying the original" do
buffer = IO::Buffer.new(4)
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
it "transfers mapped memory to a new buffer, nullifying the original" do
buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
end
context "with a file-backed buffer created with .map" do
it "transfers mapped memory to a new buffer, nullifying the original" do
File.open(__FILE__, "r") do |file|
buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
end
end
context "with a String-backed buffer created with .for" do
context "without a block" do
it "transfers memory to a new buffer, nullifying the original" do
buffer = IO::Buffer.for("test")
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
end
context "with a block" do
it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do
IO::Buffer.for(+"test") do |buffer|
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
@buffer.null?.should be_false
end
end
end
ruby_version_is "3.3" do
context "with a String-backed buffer created with .string" do
it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do
IO::Buffer.string(4) do |buffer|
info = buffer.to_s
@buffer = buffer.transfer
@buffer.to_s.should == info
buffer.null?.should be_true
end
@buffer.null?.should be_false
end
end
end
it "allows multiple transfers" do
buffer_1 = IO::Buffer.new(4)
buffer_2 = buffer_1.transfer
@buffer = buffer_2.transfer
buffer_1.null?.should be_true
buffer_2.null?.should be_true
@buffer.null?.should be_false
end
it "is disallowed while locked, raising IO::Buffer::LockedError" do
@buffer = IO::Buffer.new(4)
@buffer.locked do
-> { @buffer.transfer }.should raise_error(IO::Buffer::LockedError, "Cannot transfer ownership of locked buffer!")
end
end
context "with a slice of a buffer" do
it "transfers source to a new slice, not touching the buffer" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(0, 2)
@buffer.set_string("test")
new_slice = slice.transfer
slice.null?.should be_true
new_slice.null?.should be_false
@buffer.null?.should be_false
new_slice.set_string("ea")
@buffer.get_string.should == "east"
end
it "nullifies buffer, invalidating the slice" do
buffer = IO::Buffer.new(4)
slice = buffer.slice(0, 2)
@buffer = buffer.transfer
slice.null?.should be_false
slice.valid?.should be_false
-> { slice.get_string }.should raise_error(IO::Buffer::InvalidatedError, "Buffer has been invalidated!")
end
end
end

View File

@ -0,0 +1,119 @@
require_relative '../../../spec_helper'
describe "IO::Buffer#valid?" do
after :each do
@buffer&.free
@buffer = nil
end
# Non-slices are always valid
context "with a non-slice buffer" do
it "is true for a regular buffer" do
@buffer = IO::Buffer.new(4)
@buffer.valid?.should be_true
end
it "is true for a 0-size buffer" do
@buffer = IO::Buffer.new(0)
@buffer.valid?.should be_true
end
it "is true for a freed buffer" do
@buffer = IO::Buffer.new(4)
@buffer.free
@buffer.valid?.should be_true
end
it "is true for a freed file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
@buffer.valid?.should be_true
@buffer.free
@buffer.valid?.should be_true
end
end
it "is true for a freed string-backed buffer" do
@buffer = IO::Buffer.for("hello")
@buffer.valid?.should be_true
@buffer.free
@buffer.valid?.should be_true
end
end
# "A buffer becomes invalid if it is a slice of another buffer (or string)
# which has been freed or re-allocated at a different address."
context "with a slice" do
it "is true for a slice of a live buffer" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(0, 2)
slice.valid?.should be_true
end
context "when buffer is resized" do
platform_is_not :windows do
it "is true when slice is still inside the buffer" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(1, 2)
@buffer.resize(3)
slice.valid?.should be_true
end
end
it "is false when slice becomes outside the buffer" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(2, 2)
@buffer.resize(3)
slice.valid?.should be_false
end
platform_is_not :linux do
# This test does not cause a copy-resize on Linux.
# `#resize` MAY cause the buffer to move, but there is no guarantee.
it "is false when buffer is copied on resize" do
@buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
slice = @buffer.slice(0, 2)
@buffer.resize(8)
slice.valid?.should be_false
end
end
end
it "is false for a slice of a transferred buffer" do
buffer = IO::Buffer.new(4)
slice = buffer.slice(0, 2)
@buffer = buffer.transfer
slice.valid?.should be_false
end
it "is false for a slice of a freed buffer" do
@buffer = IO::Buffer.new(4)
slice = @buffer.slice(0, 2)
@buffer.free
slice.valid?.should be_false
end
it "is false for a slice of a freed file-backed buffer" do
File.open(__FILE__, "r") do |file|
@buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)
slice = @buffer.slice(0, 2)
slice.valid?.should be_true
@buffer.free
slice.valid?.should be_false
end
end
it "is true for a slice of a freed string-backed buffer while string is alive" do
@buffer = IO::Buffer.for("alive")
slice = @buffer.slice(0, 2)
slice.valid?.should be_true
@buffer.free
slice.valid?.should be_true
end
# There probably should be a test with a garbage-collected string,
# but it's not clear how to force that.
it "needs to be reviewed for spec completeness"
end
end

View File

@ -231,6 +231,10 @@ describe :kernel_float, shared: true do
@object.send(:Float, "0x0f").should == 15.0
end
it "interprets negative hex value" do
@object.send(:Float, "-0x10").should == -16.0
end
it "accepts embedded _ if the number does not contain a-f" do
@object.send(:Float, "0x1_0").should == 16.0
end

View File

@ -7,7 +7,9 @@ require_relative 'fixtures/classes'
autoload :KSAutoloadA, "autoload_a.rb"
autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb")
autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb"
define_autoload_KSAutoloadCallsRequire = -> {
autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb"
}
def check_autoload(const)
autoload? const
@ -43,6 +45,7 @@ describe "Kernel#autoload" do
end
it "calls main.require(path) to load the file" do
define_autoload_KSAutoloadCallsRequire.call
main = TOPLEVEL_BINDING.eval("self")
main.should_receive(:require).with("main_autoload_not_exist.rb")
# The constant won't be defined since require is mocked to do nothing

View File

@ -0,0 +1,132 @@
require_relative '../../spec_helper'
ruby_version_is "3.4" do
describe "MatchData#bytebegin" do
context "when passed an integer argument" do
it "returns the byte-based offset of the start of the nth element" do
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.bytebegin(0).should == 1
match_data.bytebegin(2).should == 2
end
it "returns nil when the nth match isn't found" do
match_data = /something is( not)? (right)/.match("something is right")
match_data.bytebegin(1).should be_nil
end
it "returns the byte-based offset for multi-byte strings" do
match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
match_data.bytebegin(0).should == 1
match_data.bytebegin(2).should == 3
end
not_supported_on :opal do
it "returns the byte-based offset for multi-byte strings with unicode regexp" do
match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
match_data.bytebegin(0).should == 1
match_data.bytebegin(2).should == 3
end
end
it "tries to convert the passed argument to an Integer using #to_int" do
obj = mock('to_int')
obj.should_receive(:to_int).and_return(2)
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.bytebegin(obj).should == 2
end
it "raises IndexError if index is out of bounds" do
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
match_data.bytebegin(-1)
}.should raise_error(IndexError, "index -1 out of matches")
-> {
match_data.bytebegin(3)
}.should raise_error(IndexError, "index 3 out of matches")
end
end
context "when passed a String argument" do
it "return the byte-based offset of the start of the named capture" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.bytebegin("a").should == 1
match_data.bytebegin("b").should == 3
end
it "returns the byte-based offset for multi byte strings" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
match_data.bytebegin("a").should == 1
match_data.bytebegin("b").should == 4
end
not_supported_on :opal do
it "returns the byte-based offset for multi byte strings with unicode regexp" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
match_data.bytebegin("a").should == 1
match_data.bytebegin("b").should == 4
end
end
it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do
match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
match_data.bytebegin("a").should == 3
end
it "returns the byte-based offset for multi-byte names" do
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.bytebegin("æ").should == 1
end
it "raises IndexError if there is no group with the provided name" do
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
match_data.bytebegin("y")
}.should raise_error(IndexError, "undefined group name reference: y")
end
end
context "when passed a Symbol argument" do
it "return the byte-based offset of the start of the named capture" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.bytebegin(:a).should == 1
match_data.bytebegin(:b).should == 3
end
it "returns the byte-based offset for multi byte strings" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
match_data.bytebegin(:a).should == 1
match_data.bytebegin(:b).should == 4
end
not_supported_on :opal do
it "returns the byte-based offset for multi byte strings with unicode regexp" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
match_data.bytebegin(:a).should == 1
match_data.bytebegin(:b).should == 4
end
end
it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do
match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
match_data.bytebegin(:a).should == 3
end
it "returns the byte-based offset for multi-byte names" do
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.bytebegin(:æ).should == 1
end
it "raises IndexError if there is no group with the provided name" do
match_data = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
match_data.bytebegin(:y)
}.should raise_error(IndexError, "undefined group name reference: y")
end
end
end
end

View File

@ -0,0 +1,104 @@
require_relative '../../spec_helper'
ruby_version_is "3.4" do
describe "MatchData#byteend" do
context "when passed an integer argument" do
it "returns the byte-based offset of the end of the nth element" do
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.byteend(0).should == 7
match_data.byteend(2).should == 3
end
it "returns nil when the nth match isn't found" do
match_data = /something is( not)? (right)/.match("something is right")
match_data.byteend(1).should be_nil
end
it "returns the byte-based offset for multi-byte strings" do
match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
match_data.byteend(0).should == 8
match_data.byteend(2).should == 4
end
not_supported_on :opal do
it "returns the byte-based offset for multi-byte strings with unicode regexp" do
match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
match_data.byteend(0).should == 8
match_data.byteend(2).should == 4
end
end
it "tries to convert the passed argument to an Integer using #to_int" do
obj = mock('to_int')
obj.should_receive(:to_int).and_return(2)
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.byteend(obj).should == 3
end
end
context "when passed a String argument" do
it "return the byte-based offset of the start of the named capture" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.byteend("a").should == 2
match_data.byteend("b").should == 6
end
it "returns the byte-based offset for multi byte strings" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
match_data.byteend("a").should == 3
match_data.byteend("b").should == 7
end
not_supported_on :opal do
it "returns the byte-based offset for multi byte strings with unicode regexp" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
match_data.byteend("a").should == 3
match_data.byteend("b").should == 7
end
end
it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do
match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
match_data.byteend("a").should == 6
end
it "returns the byte-based offset for multi-byte names" do
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.byteend("æ").should == 2
end
end
context "when passed a Symbol argument" do
it "return the byte-based offset of the start of the named capture" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.byteend(:a).should == 2
match_data.byteend(:b).should == 6
end
it "returns the byte-based offset for multi byte strings" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.")
match_data.byteend(:a).should == 3
match_data.byteend(:b).should == 7
end
not_supported_on :opal do
it "returns the byte-based offset for multi byte strings with unicode regexp" do
match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.")
match_data.byteend(:a).should == 3
match_data.byteend(:b).should == 7
end
end
it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do
match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.")
match_data.byteend(:a).should == 6
end
it "returns the byte-based offset for multi-byte names" do
match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.")
match_data.byteend(:æ).should == 2
end
end
end
end

View File

@ -1,30 +1,102 @@
# -*- encoding: utf-8 -*-
require_relative '../../spec_helper'
describe "MatchData#offset" do
it "returns a two element array with the begin and end of the nth match" do
match_data = /(.)(.)(\d+)(\d)/.match("THX1138.")
match_data.offset(0).should == [1, 7]
match_data.offset(4).should == [6, 7]
it "returns beginning and ending character offset of whole matched substring for 0 element" do
m = /(.)(.)(\d+)(\d)/.match("THX1138.")
m.offset(0).should == [1, 7]
end
it "returns [nil, nil] when the nth match isn't found" do
match_data = /something is( not)? (right)/.match("something is right")
match_data.offset(1).should == [nil, nil]
it "returns beginning and ending character offset of n-th match, all the subsequent elements are capturing groups" do
m = /(.)(.)(\d+)(\d)/.match("THX1138.")
m.offset(2).should == [2, 3]
m.offset(3).should == [3, 6]
m.offset(4).should == [6, 7]
end
it "returns the offset for multi byte strings" do
match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.")
match_data.offset(0).should == [1, 7]
match_data.offset(4).should == [6, 7]
it "accepts String as a reference to a named capture" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
m.offset("f").should == [0, 3]
m.offset("b").should == [3, 6]
end
it "accepts Symbol as a reference to a named capture" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
m.offset(:f).should == [0, 3]
m.offset(:b).should == [3, 6]
end
it "returns [nil, nil] if a capturing group is optional and doesn't match" do
m = /(?<x>q..)?/.match("foobarbaz")
m.offset("x").should == [nil, nil]
m.offset(1).should == [nil, nil]
end
it "returns correct beginning and ending character offset for multi-byte strings" do
m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
m.offset(1).should == [1, 2]
m.offset(3).should == [2, 3]
end
not_supported_on :opal do
it "returns the offset for multi byte strings with unicode regexp" do
match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.")
match_data.offset(0).should == [1, 7]
match_data.offset(4).should == [6, 7]
it "returns correct character offset for multi-byte strings with unicode regexp" do
m = /\A\u3042(.)(.)?(.)\z/u.match("\u3042\u3043\u3044")
m.offset(1).should == [1, 2]
m.offset(3).should == [2, 3]
end
end
it "returns [nil, nil] if a capturing group is optional and doesn't match for multi-byte string" do
m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
m.offset(2).should == [nil, nil]
end
it "converts argument into integer if is not String nor Symbol" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
obj = Object.new
def obj.to_int; 2; end
m.offset(1r).should == [0, 3]
m.offset(1.1).should == [0, 3]
m.offset(obj).should == [3, 6]
end
it "raises IndexError if there is no group with the provided name" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
m.offset("y")
}.should raise_error(IndexError, "undefined group name reference: y")
-> {
m.offset(:y)
}.should raise_error(IndexError, "undefined group name reference: y")
end
it "raises IndexError if index is out of bounds" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
m.offset(-1)
}.should raise_error(IndexError, "index -1 out of matches")
-> {
m.offset(3)
}.should raise_error(IndexError, "index 3 out of matches")
end
it "raises TypeError if can't convert argument into Integer" do
m = /(?<f>foo)(?<b>bar)/.match("foobar")
-> {
m.offset([])
}.should raise_error(TypeError, "no implicit conversion of Array into Integer")
end
end

View File

@ -117,6 +117,7 @@ describe "Module#const_added" do
end
ScratchPad.recorded.should == [:A, :B]
ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule
end
it "is called when a new class is defined under self" do
@ -158,6 +159,7 @@ describe "Module#const_added" do
end
ScratchPad.recorded.should == [:A, :B]
ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB
end
it "is called when an autoload is defined" do

View File

@ -245,6 +245,14 @@ describe "Module#const_source_location" do
@line = __LINE__ - 1
end
before :each do
@loaded_features = $".dup
end
after :each do
$".replace @loaded_features
end
it 'returns the autoload location while not resolved' do
ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line]
end
@ -265,6 +273,8 @@ describe "Module#const_source_location" do
ConstantSpecs.const_source_location(:ConstSource).should == autoload_location
ConstantSpecs::ConstSource::LOCATION.should == ConstantSpecs.const_source_location(:ConstSource)
ConstantSpecs::BEFORE_DEFINE_LOCATION.should == autoload_location
ConstantSpecs.send :remove_const, :ConstSource
ConstantSpecs.send :remove_const, :BEFORE_DEFINE_LOCATION
end
end
end

View File

@ -190,6 +190,7 @@ describe "Module#name" do
ScratchPad.recorded.should.one?(/#<Module.+>::A$/)
ScratchPad.recorded.should.one?(/#<Module.+>::A::B$/)
ModuleSpecs::NameSpecs.send :remove_const, :NamedModule
end
it "returns a frozen String" do

View File

@ -86,6 +86,7 @@ ruby_version_is "3.3" do
ModuleSpecs::SetTemporaryNameSpec::M = m
m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N"
ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M
end
it "can update the name when assigned to a constant" do

View File

@ -11,7 +11,7 @@ describe "Random.new" do
it "returns Random instances initialized with different seeds" do
first = Random.new
second = Random.new
(0..20).map { first.rand } .should_not == (0..20).map { second.rand }
(0..20).map { first.rand }.should_not == (0..20).map { second.rand }
end
it "accepts an Integer seed value as an argument" do

View File

@ -39,7 +39,11 @@ describe "Regexps with encoding modifiers" do
end
it "warns when using /n with a match string with non-ASCII characters and an encoding other than ASCII-8BIT" do
-> { /./n.match("\303\251".dup.force_encoding('utf-8')) }.should complain(%r{historical binary regexp match /.../n against UTF-8 string})
-> {
eval <<~RUBY
/./n.match("\303\251".dup.force_encoding('utf-8'))
RUBY
}.should complain(%r{historical binary regexp match /.../n against UTF-8 string})
end
it 'uses US-ASCII as /n encoding if all chars are 7-bit' do

View File

@ -136,10 +136,14 @@ describe "The rescue keyword" do
it 'captures successfully at the top-level' do
ScratchPad.record []
loaded_features = $".dup
begin
require_relative 'fixtures/rescue/top_level'
require_relative 'fixtures/rescue/top_level'
ScratchPad.recorded.should == ["message"]
ScratchPad.recorded.should == ["message"]
ensure
$".replace loaded_features
end
end
end

View File

@ -3,10 +3,10 @@ require 'net/http'
describe "Net::HTTPServerException" do
it "is a subclass of Net::ProtoServerError and is warned as deprecated" do
-> { Net::HTTPServerException.should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
-> { eval("Net::HTTPServerException").should < Net::ProtoServerError }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
end
it "includes the Net::HTTPExceptions module and is warned as deprecated" do
-> { Net::HTTPServerException.should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
-> { eval("Net::HTTPServerException").should < Net::HTTPExceptions }.should complain(/warning: constant Net::HTTPServerException is deprecated/)
end
end

View File

@ -1,6 +0,0 @@
require_relative '../../spec_helper'
require 'tempfile'
describe "Tempfile.callback" do
it "needs to be reviewed for spec completeness"
end

View File

@ -0,0 +1,176 @@
require_relative '../../spec_helper'
require 'tempfile'
describe "Tempfile.create" do
after :each do
if @tempfile
@tempfile.close
File.unlink(@tempfile.path) if File.file?(@tempfile.path)
end
end
it "returns a new, open regular File instance placed in tmpdir" do
@tempfile = Tempfile.create
# Unlike Tempfile.open this returns a true File,
# but `.should be_an_instance_of(File)` would be true either way.
@tempfile.instance_of?(File).should be_true
@tempfile.should_not.closed?
File.file?(@tempfile.path).should be_true
@tempfile.path.should.start_with?(Dir.tmpdir)
@tempfile.path.should_not == "#{Dir.tmpdir}/"
end
it "returns file in w+ mode" do
@tempfile = Tempfile.create
@tempfile << "Test!\nMore test!"
@tempfile.rewind
@tempfile.read.should == "Test!\nMore test!"
# Not "a+" mode, which would write at the end of the file.
@tempfile.rewind
@tempfile.print "Trust"
@tempfile.rewind
@tempfile.read.should == "Trust\nMore test!"
end
platform_is_not :windows do
it "returns a private, readable and writable file" do
@tempfile = Tempfile.create
stat = @tempfile.stat
stat.should.readable?
stat.should.writable?
stat.should_not.executable?
stat.should_not.world_readable?
stat.should_not.world_writable?
end
end
platform_is :windows do
it "returns a public, readable and writable file" do
@tempfile = Tempfile.create
stat = @tempfile.stat
stat.should.readable?
stat.should.writable?
stat.should_not.executable?
stat.should.world_readable?
stat.should.world_writable?
end
end
context "when called with a block" do
it "returns the value of the block" do
value = Tempfile.create do |tempfile|
tempfile << "Test!"
"return"
end
value.should == "return"
end
it "closes and unlinks file after block execution" do
Tempfile.create do |tempfile|
@tempfile = tempfile
@tempfile.should_not.closed?
File.exist?(@tempfile.path).should be_true
end
@tempfile.should.closed?
File.exist?(@tempfile.path).should be_false
end
end
context "when called with a single positional argument" do
it "uses a String as a prefix for the filename" do
@tempfile = Tempfile.create("create_spec")
@tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec")
@tempfile.path.should_not == "#{Dir.tmpdir}/create_spec"
end
it "uses an array of one String as a prefix for the filename" do
@tempfile = Tempfile.create(["create_spec"])
@tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec")
@tempfile.path.should_not == "#{Dir.tmpdir}/create_spec"
end
it "uses an array of two Strings as a prefix and suffix for the filename" do
@tempfile = Tempfile.create(["create_spec", ".temp"])
@tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec")
@tempfile.path.should.end_with?(".temp")
end
it "ignores excessive array elements after the first two" do
@tempfile = Tempfile.create(["create_spec", ".temp", :".txt"])
@tempfile.path.should.start_with?("#{Dir.tmpdir}/create_spec")
@tempfile.path.should.end_with?(".temp")
end
it "raises ArgumentError if passed something else than a String or an array of Strings" do
-> { Tempfile.create(:create_spec) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec")
-> { Tempfile.create([:create_spec]) }.should raise_error(ArgumentError, "unexpected prefix: :create_spec")
-> { Tempfile.create(["create_spec", :temp]) }.should raise_error(ArgumentError, "unexpected suffix: :temp")
end
end
context "when called with a second positional argument" do
it "uses it as a directory for the tempfile" do
@tempfile = Tempfile.create("create_spec", "./")
@tempfile.path.should.start_with?("./create_spec")
end
it "raises TypeError if argument can not be converted to a String" do
-> { Tempfile.create("create_spec", :temp) }.should raise_error(TypeError, "no implicit conversion of Symbol into String")
end
end
context "when called with a mode option" do
it "ORs it with the default mode, forcing it to be readable and writable" do
@tempfile = Tempfile.create(mode: File::RDONLY)
@tempfile.puts "test"
@tempfile.rewind
@tempfile.read.should == "test\n"
end
it "raises NoMethodError if passed a String mode" do
-> { Tempfile.create(mode: "wb") }.should raise_error(NoMethodError, /undefined method ['`]|' for .+String/)
end
end
ruby_version_is "3.4" do
context "when called with anonymous: true" do
it "returns an already unlinked File without a proper path" do
@tempfile = Tempfile.create(anonymous: true)
@tempfile.should_not.closed?
@tempfile.path.should == "#{Dir.tmpdir}/"
File.file?(@tempfile.path).should be_false
end
it "unlinks file before calling the block" do
Tempfile.create(anonymous: true) do |tempfile|
@tempfile = tempfile
@tempfile.should_not.closed?
@tempfile.path.should == "#{Dir.tmpdir}/"
File.file?(@tempfile.path).should be_false
end
@tempfile.should.closed?
end
end
context "when called with anonymous: false" do
it "returns a usual File with a path" do
@tempfile = Tempfile.create(anonymous: false)
@tempfile.should_not.closed?
@tempfile.path.should.start_with?(Dir.tmpdir)
File.file?(@tempfile.path).should be_true
end
end
end
context "when called with other options" do
it "passes them along to File.open" do
@tempfile = Tempfile.create(encoding: "IBM037:IBM037", binmode: true)
@tempfile.external_encoding.should == Encoding.find("IBM037")
@tempfile.binmode?.should be_true
end
end
end

View File

@ -724,14 +724,16 @@ describe "C-API Encoding function" do
end
describe "rb_define_dummy_encoding" do
run = 0
it "defines the dummy encoding" do
@s.rb_define_dummy_encoding("FOO")
enc = Encoding.find("FOO")
@s.rb_define_dummy_encoding("FOO#{run += 1}")
enc = Encoding.find("FOO#{run}")
enc.should.dummy?
end
it "returns the index of the dummy encoding" do
index = @s.rb_define_dummy_encoding("BAR")
index = @s.rb_define_dummy_encoding("BAR#{run += 1}")
index.should == Encoding.list.size - 1
end

View File

@ -62,6 +62,10 @@ static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st) {
return rb_struct_size(st);
}
static VALUE struct_spec_rb_struct_initialize(VALUE self, VALUE st, VALUE values) {
return rb_struct_initialize(st, values);
}
#if defined(RUBY_VERSION_IS_3_3)
/* Only allow setting three attributes, should be sufficient for testing. */
static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass,
@ -90,6 +94,7 @@ void Init_struct_spec(void) {
rb_define_method(cls, "rb_struct_define_under", struct_spec_rb_struct_define_under, 5);
rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4);
rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1);
rb_define_method(cls, "rb_struct_initialize", struct_spec_rb_struct_initialize, 2);
#if defined(RUBY_VERSION_IS_3_3)
rb_define_method(cls, "rb_data_define", struct_spec_rb_data_define, 4);
#endif

View File

@ -41,14 +41,19 @@ describe "CApiGlobalSpecs" do
@f.sb_get_global_value.should == "XYZ"
end
run = 0
it "rb_define_readonly_variable should define a new readonly global variable" do
name = "ro_gvar#{run += 1}"
eval <<~RUBY
# Check the gvar doesn't exist and ensure rb_gv_get doesn't implicitly declare the gvar,
# otherwise the rb_define_readonly_variable call will conflict.
suppress_warning { @f.sb_gv_get("ro_gvar") } .should == nil
suppress_warning { @f.sb_gv_get("#{name}") }.should == nil
@f.rb_define_readonly_variable("ro_gvar", 15)
$ro_gvar.should == 15
-> { $ro_gvar = 10 }.should raise_error(NameError)
@f.rb_define_readonly_variable("#{name}", 15)
$#{name}.should == 15
-> { $#{name} = 10 }.should raise_error(NameError)
RUBY
end
it "rb_define_hooked_variable should define a C hooked global variable" do

View File

@ -38,7 +38,7 @@ describe "CApiModule" do
CApiModuleSpecs::C.const_set(:_INVALID, 1)
}.should raise_error(NameError, /wrong constant name/)
@m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2)
suppress_warning { @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) }
@m.rb_const_get(CApiModuleSpecs::C, :_INVALID).should == 2
# Ruby-level should still not allow access

View File

@ -208,20 +208,48 @@ describe "C-API Struct function" do
@s.rb_struct_size(@struct).should == 3
end
end
describe "rb_struct_initialize" do
it "sets all members" do
@s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil
@struct.a.should == 1
@struct.b.should == 2
@struct.c.should == 3
end
it "does not freeze the Struct instance" do
@s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil
@struct.should_not.frozen?
@s.rb_struct_initialize(@struct, [4, 5, 6]).should == nil
@struct.a.should == 4
@struct.b.should == 5
@struct.c.should == 6
end
it "raises ArgumentError if too many values" do
-> { @s.rb_struct_initialize(@struct, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs")
end
it "treats missing values as nil" do
@s.rb_struct_initialize(@struct, [1, 2]).should == nil
@struct.a.should == 1
@struct.b.should == 2
@struct.c.should == nil
end
end
end
ruby_version_is "3.3" do
describe "C-API Data function" do
before :each do
before :all do
@s = CApiStructSpecs.new
@klass = @s.rb_data_define(nil, "a", "b", "c")
end
describe "rb_data_define" do
it "returns a subclass of Data class when passed nil as the first argument" do
klass = @s.rb_data_define(nil, "a", "b", "c")
klass.should.is_a? Class
klass.superclass.should == Data
@klass.should.is_a? Class
@klass.superclass.should == Data
end
it "returns a subclass of a class when passed as the first argument" do
@ -233,8 +261,7 @@ ruby_version_is "3.3" do
end
it "creates readers for the members" do
klass = @s.rb_data_define(nil, "a", "b", "c")
obj = klass.new(1, 2, 3)
obj = @klass.new(1, 2, 3)
obj.a.should == 1
obj.b.should == 2
@ -242,8 +269,7 @@ ruby_version_is "3.3" do
end
it "returns the member names as Symbols" do
klass = @s.rb_data_define(nil, "a", "b", "c")
obj = klass.new(0, 0, 0)
obj = @klass.new(0, 0, 0)
obj.members.should == [:a, :b, :c]
end
@ -256,5 +282,35 @@ ruby_version_is "3.3" do
-> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)")
end
end
describe "rb_struct_initialize" do
it "sets all members for a Data instance" do
data = @klass.allocate
@s.rb_struct_initialize(data, [1, 2, 3]).should == nil
data.a.should == 1
data.b.should == 2
data.c.should == 3
end
it "freezes the Data instance" do
data = @klass.allocate
@s.rb_struct_initialize(data, [1, 2, 3]).should == nil
data.should.frozen?
-> { @s.rb_struct_initialize(data, [1, 2, 3]) }.should raise_error(FrozenError)
end
it "raises ArgumentError if too many values" do
data = @klass.allocate
-> { @s.rb_struct_initialize(data, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs")
end
it "treats missing values as nil" do
data = @klass.allocate
@s.rb_struct_initialize(data, [1, 2]).should == nil
data.a.should == 1
data.b.should == 2
data.c.should == nil
end
end
end
end