require "spec" require "spec/helpers/iterate" require "spec/helpers/string" private class BadSortingClass include Comparable(self) def <=>(other) 1 end end private class Spaceship getter value : Float64 def initialize(@value : Float64, @return_nil = false) end def <=>(other : Spaceship) return nil if @return_nil value <=> other.value end end private def is_stable_sort(mutable, &block) n = 42 # [Spaceship.new(0), ..., Spaceship.new(n - 1), Spaceship.new(0), ..., Spaceship.new(n - 1)] slice = Slice.new(n * 2) { |i| Spaceship.new((i % n).to_f) } # [Spaceship.new(0), Spaceship.new(0), ..., Spaceship.new(n - 1), Spaceship.new(n - 1)] expected = Slice.new(n * 2) { |i| slice[i % 2 * n + i // 2] } if mutable yield slice result = slice else result = yield slice result.should_not eq(slice) end result.size.should eq(expected.size) expected.zip(result) do |exp, res| res.should be(exp) # reference-equality is necessary to check sorting is stable. end end describe "Slice" do describe ".new" do describe "initializes with default value" do it "integer" do Slice(Int32).new(3, 0).should eq Slice[0, 0, 0] Slice(Int32).new(3, 1).should eq Slice[1, 1, 1] end it "float" do Slice(Float32).new(3, 0.0).should eq Slice[0.0, 0.0, 0.0] Slice(Float32).new(3, 1.0).should eq Slice[1.0, 1.0, 1.0] end it "pointer" do null = Pointer(Void).null Slice(Pointer(Void)).new(3, null).should eq Slice[null, null, null] value = Pointer(Void).new(0x123_u64) Slice(Pointer(Void)).new(3, value).should eq Slice[value, value, value] end end end it "gets pointer and size" do pointer = Pointer.malloc(1, 0) slice = Slice.new(pointer, 1) slice.to_unsafe.should eq(pointer) slice.size.should eq(1) end it "does []?" do slice = Slice.new(3) { |i| i + 1 } 3.times do |i| slice[i]?.should eq(i + 1) end slice[-1]?.should eq(3) slice[-2]?.should eq(2) slice[-3]?.should eq(1) slice[-4]?.should be_nil slice[3]?.should be_nil end it "does []" do slice = Slice.new(3) { |i| i + 1 } 3.times do |i| slice[i].should eq(i + 1) end slice[-1].should eq(3) slice[-2].should eq(2) slice[-3].should eq(1) expect_raises(IndexError) { slice[-4] } expect_raises(IndexError) { slice[3] } end it "does []=" do slice = Slice.new(3, 0) slice[0] = 1 slice[0].should eq(1) expect_raises(IndexError) { slice[-4] = 1 } expect_raises(IndexError) { slice[3] = 1 } end it "#+(Int)" do slice = Slice.new(3) { |i| i + 1 } slice1 = slice + 1 slice1.size.should eq(2) slice1[0].should eq(2) slice1[1].should eq(3) slice3 = slice + 3 slice3.size.should eq(0) expect_raises(IndexError) { slice + 4 } expect_raises(IndexError) { slice + (-1) } end it "does []? with start and count" do slice = Slice.new(4) { |i| i + 1 } slice1 = slice[1, 2]? slice1.should_not be_nil slice1 = slice1.not_nil! slice1.size.should eq(2) slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) slice2 = slice[-1, 1]? slice2.should_not be_nil slice2 = slice2.not_nil! slice2.size.should eq(1) slice2.to_unsafe.should eq(slice.to_unsafe + 3) slice[3, 2]?.should be_nil slice[0, 5]?.should be_nil expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1]? } end it "does []? with range" do slice = Slice.new(4) { |i| i + 1 } slice1 = slice[1..2]? slice1.should_not be_nil slice1 = slice1.not_nil! slice1.size.should eq(2) slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) slice[4..7]?.should be_nil slice[3..4]?.should be_nil slice[-2..4]?.should be_nil slice[-6..-5]?.should be_nil end it "does [] with start and count" do slice = Slice.new(4) { |i| i + 1 } slice1 = slice[1, 2] slice1.size.should eq(2) slice1.to_unsafe.should eq(slice.to_unsafe + 1) slice1[0].should eq(2) slice1[1].should eq(3) slice2 = slice[-1, 1] slice2.size.should eq(1) slice2.to_unsafe.should eq(slice.to_unsafe + 3) expect_raises(IndexError) { slice[3, 2] } expect_raises(IndexError) { slice[0, 5] } expect_raises(ArgumentError, "Negative count: -1") { slice[3, -1] } end it "does empty?" do Slice.new(0, 0).empty?.should be_true Slice.new(1, 0).empty?.should be_false end it "raises if size is negative on new" do expect_raises(ArgumentError) { Slice.new(-1, 0) } end it "does to_s" do slice = Slice.new(4) { |i| i + 1 } slice.to_s.should eq("Slice[1, 2, 3, 4]") end it "does to_s for bytes" do slice = Bytes[1, 2, 3] slice.to_s.should eq("Bytes[1, 2, 3]") end describe "#fill" do it "replaces values in a subrange" do slice = Slice[0, 1, 2, 3, 4] slice.fill(7) slice.should eq(Slice[7, 7, 7, 7, 7]) slice = Slice[0, 1, 2, 3, 4] slice.fill(7, 1, 2) slice.should eq(Slice[0, 7, 7, 3, 4]) slice.fill(8, 4, 10) slice.should eq(Slice[0, 7, 7, 3, 8]) slice = Slice[0, 1, 2, 3, 4] slice.fill(7, 2..3) slice.should eq(Slice[0, 1, 7, 7, 4]) slice.fill(8, -2..10) slice.should eq(Slice[0, 1, 7, 8, 8]) slice = Slice[0, 0, 0, 0, 0] slice.fill { |i| i + 7 } slice.should eq(Slice[7, 8, 9, 10, 11]) slice = Slice[0, 0, 0, 0, 0] slice.fill(offset: 2) { |i| i * i } slice.should eq(Slice[4, 9, 16, 25, 36]) slice = Slice[0, 0, 0, 0, 0] slice.fill(1, 2) { |i| i + 7 } slice.should eq(Slice[0, 8, 9, 0, 0]) slice = Slice[0, 0, 0, 0, 0] slice.fill(2..3) { |i| i + 7 } slice.should eq(Slice[0, 0, 9, 10, 0]) slice.fill(-2..10, &.itself) slice.should eq(Slice[0, 0, 9, 3, 4]) end it "works for bytes" do slice = Bytes[0, 1, 2, 3, 4] slice.fill(7) slice.should eq(Bytes[7, 7, 7, 7, 7]) slice = Bytes[0, 1, 2, 3, 4] slice.fill(7, 1, 2) slice.should eq(Bytes[0, 7, 7, 3, 4]) slice = Bytes[0, 1, 2, 3, 4] slice.fill(7, 2..3) slice.should eq(Bytes[0, 1, 7, 7, 4]) end end it "does copy_from pointer" do pointer = Pointer.malloc(4) { |i| i + 1 } slice = Slice.new(4, 0) slice.copy_from(pointer, 4) 4.times { |i| slice[i].should eq(i + 1) } expect_raises(IndexError) { slice.copy_from(pointer, 5) } end it "does copy_to pointer" do pointer = Pointer.malloc(4, 0) slice = Slice.new(4) { |i| i + 1 } slice.copy_to(pointer, 4) 4.times { |i| pointer[i].should eq(i + 1) } expect_raises(IndexError) { slice.copy_to(pointer, 5) } end describe ".copy_to(Slice)" do it "copies bytes" do src = Slice.new(4) { 'a' } dst = Slice.new(4) { 'b' } src.copy_to(dst) dst.should eq(src) end it "raises if dst is smaller" do src = Slice.new(8) { 'a' } dst = Slice.new(4) { 'b' } expect_raises(IndexError) { src.copy_to(dst) } end it "copies at most src.size" do src = Slice.new(4) { 'a' } dst = Slice.new(8) { 'b' } src.copy_to(dst) dst.should eq(Slice['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b']) end end describe ".copy_from(Slice)" do it "copies bytes" do src = Slice.new(4) { 'a' } dst = Slice.new(4) { 'b' } dst.copy_from(src) dst.should eq(src) end it "raises if dst is smaller" do src = Slice.new(8) { 'a' } dst = Slice.new(4) { 'b' } expect_raises(IndexError) { dst.copy_from(src) } end it "copies at most src.size" do src = Slice.new(4) { 'a' } dst = Slice.new(8) { 'b' } dst.copy_from(src) dst.should eq(Slice['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b']) end end describe ".move_to(Slice)" do it "moves bytes" do src = Slice.new(4) { 'a' } dst = Slice.new(4) { 'b' } src.move_to(dst) dst.should eq(src) end it "raises if dst is smaller" do src = Slice.new(8) { 'a' } dst = Slice.new(4) { 'b' } expect_raises(IndexError) { src.move_to(dst) } end it "moves most src.size" do src = Slice.new(4) { 'a' } dst = Slice.new(8) { 'b' } src.move_to(dst) dst.should eq(Slice['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b']) end it "handles intersecting ranges" do # Test with ranges offset by 0 to 8 bytes (0..8).each do |offset| buf = Slice.new(16) { |i| 'a' + i } dst = buf[0, 8] src = buf[offset, 8] src.move_to(dst) result = (0..7).map { |i| 'a' + i + offset } dst.should eq(Slice.new(result.to_unsafe, result.size)) end end end describe ".move_from(Slice)" do it "moves bytes" do src = Slice.new(4) { 'a' } dst = Slice.new(4) { 'b' } dst.move_from(src) dst.should eq(src) end it "raises if dst is smaller" do src = Slice.new(8) { 'a' } dst = Slice.new(4) { 'b' } expect_raises(IndexError) { dst.move_from(src) } end it "moves at most src.size" do src = Slice.new(4) { 'a' } dst = Slice.new(8) { 'b' } dst.move_from(src) dst.should eq(Slice['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b']) end it "handles intersecting ranges" do # Test with ranges offset by 0 to 8 bytes (0..8).each do |offset| buf = Slice.new(16) { |i| 'a' + i } dst = buf[0, 8] src = buf[offset, 8] dst.move_from(src) result = (0..7).map { |i| 'a' + i + offset } dst.should eq(Slice.new(result.to_unsafe, result.size)) end end end describe "#unsafe_slice_of" do it "reinterprets a slice's elements" do slice = Bytes.new(10) { |i| i.to_u8 + 1 } {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} slice.unsafe_slice_of(Int16).should eq(Int16.slice(0x0201, 0x0403, 0x0605, 0x0807, 0x0A09)) slice.unsafe_slice_of(Int32).should eq(Int32.slice(0x04030201, 0x08070605)) slice.unsafe_slice_of(UInt64)[0] = 0x1122_3344_5566_7788_u64 slice.should eq(Bytes[0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x09, 0x0A]) {% else %} slice.unsafe_slice_of(Int16).should eq(Int16.slice(0x0102, 0x0304, 0x0506, 0x0708, 0x090A)) slice.unsafe_slice_of(Int32).should eq(Int32.slice(0x01020304, 0x05060708)) slice.unsafe_slice_of(UInt64)[0] = 0x1122_3344_5566_7788_u64 slice.should eq(Bytes[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x09, 0x0A]) {% end %} end end describe "#to_unsafe_bytes" do it "reinterprets a slice's elements as bytes" do slice = Slice[0x01020304, -0x01020304] bytes = slice.to_unsafe_bytes {% if IO::ByteFormat::SystemEndian == IO::ByteFormat::LittleEndian %} bytes.should eq(Bytes[0x04, 0x03, 0x02, 0x01, 0xFC, 0xFC, 0xFD, 0xFE]) bytes[3] = 0x55 slice[0].should eq(0x55020304) {% else %} bytes.should eq(Bytes[0x01, 0x02, 0x03, 0x04, 0xFE, 0xFD, 0xFC, 0xFC]) bytes[3] = 0x55 slice[0].should eq(0x01020355) {% end %} end end describe "#hexstring" do it "works for Bytes" do slice = Bytes.new(4) { |i| i.to_u8 + 1 } slice.hexstring.should eq("01020304") end end describe "#hexdump" do it "works for empty slice" do Bytes.empty.hexdump.should eq("") io = IO::Memory.new Bytes.empty.hexdump(io).should eq(0) io.to_s.should eq("") end it "works for Bytes" do slice = Bytes.new(96) { |i| i.to_u8 + 32 } assert_prints slice.hexdump, <<-EOF 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.\n EOF plus = Bytes.new(101) { |i| i.to_u8 + 32 } assert_prints plus.hexdump, <<-EOF 00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ 00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? 00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO 00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ 00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno 00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. 00000060 80 81 82 83 84 .....\n EOF num = Bytes.new(10) { |i| i.to_u8 + 48 } assert_prints num.hexdump, <<-EOF 00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n EOF end end it_iterates "#each", [1, 2, 3], Slice[1, 2, 3].each it_iterates "#reverse_each", [3, 2, 1], Slice[1, 2, 3].reverse_each it_iterates "#each_index", [0, 1, 2], Slice[1, 2, 3].each_index it "does to_a" do slice = Slice.new(3) { |i| i } ary = slice.to_a ary.should eq([0, 1, 2]) end it "does rindex" do slice = "foobar".to_slice slice.rindex('o'.ord.to_u8).should eq(2) slice.rindex('z'.ord.to_u8).should be_nil end it "does bytesize" do slice = Slice(Int32).new(2) slice.bytesize.should eq(8) end describe "==" do it "does ==" do a = Slice.new(3) { |i| i } b = Slice.new(3) { |i| i } c = Slice.new(3) { |i| i + 1 } a.should eq(b) a.should_not eq(c) end it "does == with same type, different runtime instances" do a = Slice.new(3, &.to_s) b = Slice.new(3, &.to_s) a.should eq(b) end it "does == for bytes" do a = Bytes[1, 2, 3] b = Bytes[1, 2, 3] c = Bytes[1, 2, 4] a.should eq(b) a.should_not eq(c) end end it "#same?" do slice = Slice[1, 2, 3] slice.should be slice slice.should_not be slice.dup slice.should_not be Slice[1, 2, 3] (slice + 1).should be slice + 1 slice.should_not be slice + 1 (slice[0, 2]).should be slice[0, 2] slice.should_not be slice[0, 2] end it "does macro []" do slice = Slice[1, 'a', "foo"] slice.should be_a(Slice(Int32 | Char | String)) slice.size.should eq(3) slice[0].should eq(1) slice[1].should eq('a') slice[2].should eq("foo") end it "does macro [] with numbers (#3055)" do slice = Bytes[1, 2, 3] slice.should be_a(Bytes) slice.should eq(Bytes[1, 2, 3]) end it "does Bytes[]" do slice = Bytes[] slice.should be_a(Bytes) slice.should be_empty end it "uses percent vars in [] macro (#2954)" do slices = itself(Slice[1, 2], Slice[3]) slices[0].to_a.should eq([1, 2]) slices[1].to_a.should eq([3]) end it "reverses" do slice = Bytes[1, 2, 3] slice.reverse! slice.should eq(Bytes[3, 2, 1]) end it "shuffles" do a = Bytes[1, 2, 3] a.shuffle! b = [1, 2, 3] 3.times { a.should contain(b.shift) } end it "does map" do a = Slice[1, 2, 3] b = a.map { |x| x * 2 } b.should eq(Slice[2, 4, 6]) end it "does map!" do a = Slice[1, 2, 3] b = a.map! { |x| x * 2 } a.should eq(Slice[2, 4, 6]) a.to_unsafe.should eq(b.to_unsafe) end it "does map_with_index" do a = Slice[1, 1, 2, 2] b = a.map_with_index { |e, i| e + i } b.should eq(Slice[1, 2, 4, 5]) end it "does map_with_index, with offset" do a = Slice[1, 1, 2, 2] b = a.map_with_index(10) { |e, i| e + i } b.should eq(Slice[11, 12, 14, 15]) end it "does map_with_index!" do a = Slice[1, 1, 2, 2] b = a.map_with_index! { |e, i| e + i } a.should eq(Slice[1, 2, 4, 5]) a.to_unsafe.should eq(b.to_unsafe) end it "does map_with_index!, with offset" do a = Slice[1, 1, 2, 2] b = a.map_with_index!(10) { |e, i| e + i } a.should eq(Slice[11, 12, 14, 15]) a.to_unsafe.should eq(b.to_unsafe) end describe "rotate!" do it do a = Slice[1, 2, 3] a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[2, 3, 1]) a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[3, 1, 2]) a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[1, 2, 3]) a.rotate!.to_unsafe.should eq(a.to_unsafe); a.should eq(Slice[2, 3, 1]) end it { a = Slice[1, 2, 3]; a.rotate!(0); a.should eq(Slice[1, 2, 3]) } it { a = Slice[1, 2, 3]; a.rotate!(1); a.should eq(Slice[2, 3, 1]) } it { a = Slice[1, 2, 3]; a.rotate!(2); a.should eq(Slice[3, 1, 2]) } it { a = Slice[1, 2, 3]; a.rotate!(3); a.should eq(Slice[1, 2, 3]) } it { a = Slice[1, 2, 3]; a.rotate!(4); a.should eq(Slice[2, 3, 1]) } it { a = Slice[1, 2, 3]; a.rotate!(3001); a.should eq(Slice[2, 3, 1]) } it { a = Slice[1, 2, 3]; a.rotate!(-1); a.should eq(Slice[3, 1, 2]) } it { a = Slice[1, 2, 3]; a.rotate!(-3001); a.should eq(Slice[3, 1, 2]) } it do a = Slice(Int32).new(50) { |i| i } a.rotate!(5) a.should eq(Slice[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4]) end it do a = Slice(Int32).new(50) { |i| i } a.rotate!(-5) a.should eq(Slice[45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]) end it do a = Slice(Int32).new(50) { |i| i } a.rotate!(20) a.should eq(Slice[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) end it do a = Slice(Int32).new(50) { |i| i } a.rotate!(-20) a.should eq(Slice[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]) end end it "creates empty slice" do slice = Slice(Int32).empty slice.should be_empty end it "creates read-only slice" do slice = Slice.new(3, 0, read_only: true) slice.read_only?.should be_true expect_raises(Exception, "Can't write to read-only Slice") { slice[0] = 1 } expect_raises(Exception, "Can't write to read-only Slice") { slice.update(0, &.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.swap(0, 1) } expect_raises(Exception, "Can't write to read-only Slice") { slice.reverse! } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(0) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(0, 0, 0) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(0, 0..0) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(&.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(offset: 0, &.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(0, 0, &.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.fill(0..0, &.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.map!(&.itself) } expect_raises(Exception, "Can't write to read-only Slice") { slice.map_with_index! { |v, i| v } } expect_raises(Exception, "Can't write to read-only Slice") { slice.map_with_index!(offset: 0) { |v, i| v } } expect_raises(Exception, "Can't write to read-only Slice") { slice.shuffle! } expect_raises(Exception, "Can't write to read-only Slice") { slice.rotate!(0) } expect_raises(Exception, "Can't write to read-only Slice") { slice.copy_from(slice) } subslice = slice[0, 1] subslice.read_only?.should be_true expect_raises(Exception, "Can't write to read-only Slice") { subslice[0] = 1 } slice = Bytes[1, 2, 3, read_only: true] slice.read_only?.should be_true expect_raises(Exception, "Can't write to read-only Slice") { slice[0] = 0_u8 } end it "hashes each item in collection" do Slice[1, 2, 3].hash.should eq(Slice[1_u64, 2_u64, 3_u64].hash) end it "optimizes hash for Bytes" do Bytes[1, 2, 3].hash.should_not eq(Slice[1, 2, 3].hash) end it "#[]" do slice = Slice.new(6) { |i| i + 1 } subslice = slice[2..4] subslice.read_only?.should be_false subslice.size.should eq(3) subslice.to_unsafe.should eq(slice.to_unsafe + 2) subslice.should eq(Slice.new(3) { |i| i + 3 }) end it "#[] keeps read-only value" do slice = Slice.new(6, read_only: true) { |i| i + 1 } slice[2..4].read_only?.should be_true end describe "#clone" do it "clones primitive" do slice = Slice[1, 2] slice.clone.should eq slice end it "clones non-primitive" do slice = Slice["abc", "a"] slice.clone.should eq slice end it "buffer copy" do slice = Slice["foo"] copy = slice.clone slice[0] = "bar" copy.should_not eq slice end it "deep copy" do slice = Slice[["foo"]] copy = slice.clone slice[0] << "bar" copy.should_not eq slice end end describe "#dup" do it "buffer copy" do slice = Slice["foo"] copy = slice.dup slice[0] = "bar" copy.should_not eq slice end it "don't deep copy" do slice = Slice[["foo"]] copy = slice.dup slice[0] << "bar" copy.should eq slice end end describe "sort" do {% for sort in ["sort".id, "unstable_sort".id] %} describe {{ "##{sort}" }} do it "without block" do slice = Slice[3, 4, 1, 2, 5, 6] sorted_slice = slice.{{ sort }} sorted_slice.to_a.should eq([1, 2, 3, 4, 5, 6]) slice.should_not eq(sorted_slice) end it "with a block" do a = Slice["foo", "a", "hello"] b = a.{{ sort }} { |x, y| x.size <=> y.size } b.to_a.should eq(["a", "foo", "hello"]) a.should_not eq(b) end {% if sort == "sort" %} it "stable sort without a block" do is_stable_sort(mutable: false, &.sort) end it "stable sort with a block" do is_stable_sort(mutable: false, &.sort { |a, b| a.value <=> b.value }) end {% end %} end describe {{ "##{sort}!" }} do it "without block" do a = [3, 4, 1, 2, 5, 6] a.{{ sort }}! a.should eq([1, 2, 3, 4, 5, 6]) end it "with a block" do a = ["foo", "a", "hello"] a.{{ sort }}! { |x, y| x.size <=> y.size } a.should eq(["a", "foo", "hello"]) end it "sorts with invalid block (#4379)" do a = [1] * 17 b = a.{{ sort }} { -1 } a.should eq(b) end it "can sort! just by using <=> (#6608)" do spaceships = Slice[ Spaceship.new(2), Spaceship.new(0), Spaceship.new(1), Spaceship.new(3), ] spaceships.{{ sort }}! 4.times do |i| spaceships[i].value.should eq(i) end end it "raises if <=> returns nil" do spaceships = Slice[ Spaceship.new(2, return_nil: true), Spaceship.new(0, return_nil: true), ] expect_raises(ArgumentError) do spaceships.{{ sort }}! end end it "raises if sort! block returns nil" do expect_raises(ArgumentError) do Slice[1, 2].{{ sort }}! { nil } end end {% if sort == "sort" %} it "stable sort without a block" do is_stable_sort(mutable: true, &.sort!) end it "stable sort with a block" do is_stable_sort(mutable: true, &.sort! { |a, b| a.value <=> b.value }) end {% end %} end describe {{ "##{sort}_by" }} do it "sorts" do a = Slice["foo", "a", "hello"] b = a.{{ sort }}_by(&.size) b.to_a.should eq(["a", "foo", "hello"]) a.should_not eq(b) end {% if sort == "sort" %} it "stable sort" do is_stable_sort(mutable: false, &.sort_by(&.value)) end {% end %} end describe {{ "##{sort}_by" }} do it "sorts" do a = Slice["foo", "a", "hello"] a.{{ sort }}_by!(&.size) a.to_a.should eq(["a", "foo", "hello"]) end it "calls given block exactly once for each element" do calls = Hash(String, Int32).new(0) a = Slice["foo", "a", "hello"] a.{{ sort }}_by! { |e| calls[e] += 1; e.size } calls.should eq({"foo" => 1, "a" => 1, "hello" => 1}) end {% if sort == "sort" %} it "stable sort" do is_stable_sort(mutable: true, &.sort_by!(&.value)) end {% end %} end {% end %} end describe "<=>" do it "is comparable" do Bytes[1].is_a?(Comparable).should be_true end it "compares" do (Int32.slice(1, 2, 3) <=> Int32.slice(1, 2, 3)).should eq(0) (Int32.slice(1, 2, 3) <=> Int32.slice(1, 3, 3)).should be < 0 (Int32.slice(1, 3, 3) <=> Int32.slice(1, 2, 3)).should be > 0 (Int32.slice(1, 2, 3) <=> Int32.slice(1, 2, 3, 4)).should be < 0 (Int32.slice(1, 2, 3, 4) <=> Int32.slice(1, 2, 3)).should be > 0 end it "compares (UInt8)" do (Bytes[1, 2, 3] <=> Bytes[1, 2, 3]).should eq(0) (Bytes[1, 2, 3] <=> Bytes[1, 3, 3]).should be < 0 (Bytes[1, 3, 3] <=> Bytes[1, 2, 3]).should be > 0 (Bytes[1, 2, 3] <=> Bytes[1, 2, 3, 4]).should be < 0 (Bytes[1, 2, 3, 4] <=> Bytes[1, 2, 3]).should be > 0 end end describe "#+(Slice)" do it "concatenates two slices" do a = Slice[1, 2] b = a + Slice[3, 4, 5] b.should be_a(Slice(Int32)) b.should eq(Slice[1, 2, 3, 4, 5]) a.should eq(Slice[1, 2]) c = Slice[1, 2] + Slice['a', 'b', 'c'] c.should be_a(Slice(Int32 | Char)) c.should eq(Slice[1, 2, 'a', 'b', 'c']) end end describe ".join" do it "concatenates an indexable of slices" do a = Slice.join([Slice[1, 2], Slice[3, 4, 5]]) a.should be_a(Slice(Int32)) a.should eq(Slice[1, 2, 3, 4, 5]) b = Slice.join({Slice[1, 2], Slice['a', 'b', 'c']}) b.should be_a(Slice(Int32 | Char)) b.should eq(Slice[1, 2, 'a', 'b', 'c']) c = Slice.join(Deque{Slice[1, 2], Slice['a', 'b', 'c'], Slice["d", "e"], Slice[3, "f"]}) c.should be_a(Slice(Int32 | Char | String)) c.should eq(Slice[1, 2, 'a', 'b', 'c', "d", "e", 3, "f"]) end it "concatenates a slice of slices" do a = Slice[1] b = Slice['a'] c = Slice["xyz"] Slice.join(Slice[a, b, c]).should eq(Slice[1, 'a', "xyz"]) end it "concatenates an empty indexable of slices" do a = Slice.join(Array(Slice(Int32)).new) a.should be_a(Slice(Int32)) a.should be_empty b = Slice.join(Deque(Slice(Int32)).new) b.should be_a(Slice(Int32)) b.should be_empty end end describe ".additive_identity" do it "returns an empty slice" do a = Slice(Int32).additive_identity a.should be_a(Slice(Int32)) a.should be_empty end end end private def itself(*args) args end