# This file is a part of Julia. License is MIT: https://julialang.org/license using Base.Test ######## Utilities ########### # Generate an array similar to A[indx1, indx2, ...], but only call # getindex with scalar-valued indexes. This will be safe even if # `getindex` someday calls `view`. # The "nodrop" variant does not drop any dimensions (not even trailing ones) function Agen_nodrop(A::AbstractArray, I...) irep = replace_colon(A, I) _Agen(A, ensure_iterable(irep)...) end # This drops scalar dimensions function Agen_slice(A::AbstractArray, I...) irep = replace_colon(A, I) B = _Agen(A, ensure_iterable(irep)...) sd = Int[] for i = 1:length(I) if isa(I[i], Real) push!(sd, i) end end squeeze(B, sd) end _Agen(A, i1) = [A[j1] for j1 in i1] _Agen(A, i1, i2) = [A[j1,j2] for j1 in i1, j2 in i2] _Agen(A, i1, i2, i3) = [A[j1,j2,j3] for j1 in i1, j2 in i2, j3 in i3] _Agen(A, i1, i2, i3, i4) = [A[j1,j2,j3,j4] for j1 in i1, j2 in i2, j3 in i3, j4 in i4] function replace_colon(A::AbstractArray, I) Iout = Array{Any}(length(I)) for d = 1:length(I)-1 Iout[d] = isa(I[d], Colon) ? (1:size(A,d)) : I[d] end d = length(I) Iout[d] = isa(I[d], Colon) ? (1:prod(size(A)[d:end])) : I[d] (Iout...,) end ensure_iterable(::Tuple{}) = () ensure_iterable(t::Tuple{Union{Number, CartesianIndex}, Vararg{Any}}) = ((t[1],), ensure_iterable(Base.tail(t))...) ensure_iterable(t::Tuple{Any, Vararg{Any}}) = (t[1], ensure_iterable(Base.tail(t))...) index_ndims(t::Tuple) = tup2val(Base.index_ndims(t)) tup2val{N}(::NTuple{N}) = Val{N} # To avoid getting confused by manipulations that are implemented for SubArrays, # it's good to copy the contents to an Array. This version protects against # `similar` ever changing its meaning. function copy_to_array(A::AbstractArray) Ac = Array{eltype(A)}(size(A)) copy!(Ac, A) end # Discover the highest dimension along which the values in A are # separated by a single increment. If A was extracted via getindex # from reshape(1:N, ...), this is equivalent to finding the highest # dimension of the SubArray consistent with a single stride in the # parent array. function single_stride_dim(A::Array) ld = 0 while ld < ndims(A) # Collapse all dimensions up to & including ld+1 into the first dimension shp = [prod(size(A)[1:ld+1])] for j = ld+2:ndims(A) push!(shp, size(A,j)) end Ar = reshape(A, shp...) # Compute the diff along dimension 1 if size(Ar, 1) > 1 indexes = map(d->1:size(Ar,d), [1:ndims(Ar);]) indexesp = copy(indexes); indexesp[1] = 2:size(Ar,1) indexesm = copy(indexes); indexesm[1] = 1:size(Ar,1)-1 dA = Ar[indexesp...] - Ar[indexesm...] ustride = unique(dA[:]) if length(ustride) == 1 # is it a single stride? ld += 1 else break end else ld += 1 end end ld end single_stride_dim(A::ANY) = single_stride_dim(copy_to_array(A)) # Testing equality of AbstractArrays, using several different methods to access values function test_cartesian(A::ANY, B::ANY) isgood = true for (IA, IB) in zip(eachindex(A), eachindex(B)) if A[IA] != B[IB] @show IA IB A[IA] B[IB] isgood = false break end end if !isgood @show A @show B error("Mismatch") end end function test_linear(A::ANY, B::ANY) length(A) == length(B) || error("length mismatch") isgood = true for (iA, iB) in zip(1:length(A), 1:length(B)) if A[iA] != B[iB] isgood = false break end end if !isgood @show A @show A.indexes @show B error("Mismatch") end end # "mixed" means 2 indexes even for N-dimensional arrays test_mixed{T}(::AbstractArray{T,1}, ::Array) = nothing test_mixed{T}(::AbstractArray{T,2}, ::Array) = nothing test_mixed(A, B::Array) = _test_mixed(A, reshape(B, size(A))) function _test_mixed(A::ANY, B::ANY) m = size(A, 1) n = size(A, 2) isgood = true for j = 1:n, i = 1:m if A[i,j] != B[i,j] isgood = false break end end if !isgood @show A @show B error("Mismatch") end nothing end function test_bounds(A::ANY) @test_throws BoundsError A[0] @test_throws BoundsError A[end+1] @test_throws BoundsError A[1, 0] @test_throws BoundsError A[1, end+1] @test_throws BoundsError A[1, 1, 0] @test_throws BoundsError A[1, 1, end+1] @test_throws BoundsError A[0, 1] @test_throws BoundsError A[end+1, 1] @test_throws BoundsError A[0, 1, 1] @test_throws BoundsError A[end+1, 1, 1] @test_throws BoundsError A[1, 0, 1] @test_throws BoundsError A[1, end+1, 1] end function dim_break_linindex(I) i = 1 while i <= length(I) && !isa(I[i], Vector{Int}) i += 1 end i - 1 end function runsubarraytests(A::Array, I...) # Direct test of linear indexing inference C = Agen_nodrop(A, I...) ld = min(single_stride_dim(C), dim_break_linindex(I)) S = view(A, I...) if Base.iscontiguous(S) @test S.stride1 == 1 end test_linear(S, C) test_cartesian(S, C) test_mixed(S, C) end function runsubarraytests(A::ANY, I...) # When A was created with view, we have to check bounds, since some # of the "residual" dimensions have size 1. It's possible that we # need dedicated tests for view. for d = 1:length(I)-1 if !isa(I[d], Colon) && any(I[d] .> size(A,d)) return nothing end end if !isa(I[end], Colon) && any(I[end] .> prod(size(A)[length(I):end])) return nothing end AA = copy_to_array(A) # Direct test of linear indexing inference C = Agen_nodrop(AA, I...) Cld = ld = min(single_stride_dim(C), dim_break_linindex(I)) Cdim = AIindex = 0 while Cdim <= Cld && AIindex < length(A.indexes) AIindex += 1 if isa(A.indexes[AIindex], Real) ld += 1 else Cdim += 1 end end local S try S = view(A, I...) catch err @show typeof(A) @show A.indexes @show I rethrow(err) end test_linear(S, C) test_cartesian(S, C) test_mixed(S, C) end # indexN is a cartesian index, indexNN is a linear index for 2 dimensions, and indexNNN is a linear index for 3 dimensions function runviews(SB::AbstractArray, indexN, indexNN, indexNNN) @assert ndims(SB) > 2 for i3 in indexN, i2 in indexN, i1 in indexN ndims(SB) > 3 && i3 isa Colon && continue # TODO: Re-enable once Colon no longer spans partial trailing dimensions runsubarraytests(SB, i1, i2, i3) end for i2 in indexN, i1 in indexN i2 isa Colon && continue # TODO: Re-enable once Colon no longer spans partial trailing dimensions runsubarraytests(SB, i1, i2) end for i1 in indexNNN runsubarraytests(SB, i1) end end function runviews{T}(SB::AbstractArray{T,2}, indexN, indexNN, indexNNN) for i2 in indexN, i1 in indexN runsubarraytests(SB, i1, i2) end for i1 in indexNN runsubarraytests(SB, i1) end end function runviews{T}(SB::AbstractArray{T,1}, indexN, indexNN, indexNNN) for i1 in indexN runsubarraytests(SB, i1) end end runviews{T}(SB::AbstractArray{T,0}, indexN, indexNN, indexNNN) = nothing ######### Tests ######### testfull = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) ### Views from Arrays ### index5 = (1, :, 2:5, [4,1,5], reshape([2]), view(1:5,[2 3 4 1])) # all work with at least size 5 index25 = (3, :, 2:11, [19,9,7], reshape([10]), view(1:25,[19 15; 4 24])) index125 = (113, :, 85:121, [99,14,103], reshape([72]), view(1:125,reshape([25,4,102,67], 1, 2, 2))) if testfull let A = copy(reshape(1:5*7*11, 11, 7, 5)) runviews(A, index5, index25, index125) end end ### Views from views ### # "outer" indexes create snips that have at least size 5 along each dimension, # with the exception of Int-slicing oindex = (:, 6, 3:7, reshape([12]), [8,4,6,12,5,7], [3:7 1:5 2:6 4:8 5:9]) _ndims{T,N}(::AbstractArray{T,N}) = N _ndims(x) = 1 if testfull let B = copy(reshape(1:13^3, 13, 13, 13)) for o3 in oindex, o2 in oindex, o1 in oindex if (o3 isa Colon && (_ndims(o1) + _ndims(o2) != 2)) continue # TODO: remove once Colon no longer spans partial trailing dimensions end viewB = view(B, o1, o2, o3) runviews(viewB, index5, index25, index125) end end end if !testfull let B = copy(reshape(1:13^3, 13, 13, 13)) for oind in ((:,:,:), (:,:,6), (:,6,:), (6,:,:), (:,3:7,:), (3:7,:,:), (3:7,6,:), (3:7,6,0x6), (6,UInt(3):UInt(7),3:7), (13:-2:1,:,:), ([8,4,6,12,5,7],:,3:7), (6,CartesianIndex.(6,[8,4,6,12,5,7])), (CartesianIndex(13,6),[8,4,6,12,5,7]), (1,:,view(1:13,[9,12,4,13,1])), (view(1:13,[9,12,4,13,1]),2:6,4), ([1:5 2:6 3:7 4:8 5:9], :, 3)) runsubarraytests(B, oind...) viewB = view(B, oind...) runviews(viewB, index5, index25, index125) end end end # issue #11289 x11289 = randn(5,5) @test isempty(view(x11289, Int[], :)) @test isempty(view(x11289, [2,5], Int[])) @test isempty(view(x11289, Int[], 2)) ####### "Classical" tests ####### # Tests where non-trailing dimensions are preserved A = copy(reshape(1:120, 3, 5, 8)) sA = view(A, 2:2, 1:5, :) @test strides(sA) == (1, 3, 15) @test parent(sA) == A @test parentindexes(sA) == (2:2, 1:5, Base.Slice(1:8)) @test Base.parentdims(sA) == [1:3;] @test size(sA) == (1, 5, 8) @test indices(sA) === (Base.OneTo(1), Base.OneTo(5), Base.OneTo(8)) @test sA[1, 2, 1:8][:] == [5:15:120;] sA[2:5:end] = -1 @test all(sA[2:5:end] .== -1) @test all(A[5:15:120] .== -1) @test strides(sA) == (1,3,15) @test stride(sA,3) == 15 @test stride(sA,4) == 120 test_bounds(sA) sA = view(A, 1:3, 1:5, 5) @test Base.parentdims(sA) == [1:2;] sA[1:3,1:5] = -2 @test all(A[:,:,5] .== -2) sA[:] = -3 @test all(A[:,:,5] .== -3) @test strides(sA) == (1,3) test_bounds(sA) sA = view(A, 1:3, 3:3, 2:5) @test Base.parentdims(sA) == [1:3;] @test size(sA) == (3,1,4) @test indices(sA) === (Base.OneTo(3), Base.OneTo(1), Base.OneTo(4)) @test sA == A[1:3,3:3,2:5] @test sA[:] == A[1:3,3,2:5][:] test_bounds(sA) sA = view(A, 1:2:3, 1:3:5, 1:2:8) @test Base.parentdims(sA) == [1:3;] @test strides(sA) == (2,9,30) @test sA[:] == A[1:2:3, 1:3:5, 1:2:8][:] # issue #8807 @test view(view([1:5;], 1:5), 1:5) == [1:5;] # Test with mixed types @test sA[:, Int16[1,2], big(2)] == [31 40; 33 42] test_bounds(sA) sA = view(A, 1:1, 1:5, [1 3; 4 2]) @test ndims(sA) == 4 @test indices(sA) === (Base.OneTo(1), Base.OneTo(5), Base.OneTo(2), Base.OneTo(2)) sA = view(A, 1:2, 3, [1 3; 4 2]) @test ndims(sA) == 3 @test indices(sA) === (Base.OneTo(2), Base.OneTo(2), Base.OneTo(2)) # logical indexing #4763 A = view([1:10;], 5:8) @test A[A.<7] == view(A, A.<7) == [5, 6] @test Base.unsafe_getindex(A, A.<7) == [5, 6] B = reshape(1:16, 4, 4) sB = view(B, 2:3, 2:3) @test sB[sB.>8] == view(sB, sB.>8) == [10, 11] @test Base.unsafe_getindex(sB, sB.>8) == [10, 11] # Tests where dimensions are dropped A = copy(reshape(1:120, 3, 5, 8)) sA = view(A, 2, :, 1:8) @test parent(sA) == A @test parentindexes(sA) == (2, Base.Slice(1:5), 1:8) @test Base.parentdims(sA) == [2:3;] @test size(sA) == (5, 8) @test indices(sA) === (Base.OneTo(5), Base.OneTo(8)) @test strides(sA) == (3,15) @test sA[2, 1:8][:] == [5:15:120;] @test sA[:,1] == [2:3:14;] @test sA[2:5:end] == [5:15:110;] sA[2:5:end] = -1 @test all(sA[2:5:end] .== -1) @test all(A[5:15:120] .== -1) test_bounds(sA) sA = view(A, 1:3, 1:5, 5) @test Base.parentdims(sA) == [1:2;] @test size(sA) == (3,5) @test indices(sA) === (Base.OneTo(3),Base.OneTo(5)) @test strides(sA) == (1,3) test_bounds(sA) sA = view(A, 1:2:3, 3, 1:2:8) @test Base.parentdims(sA) == [1,3] @test size(sA) == (2,4) @test indices(sA) === (Base.OneTo(2), Base.OneTo(4)) @test strides(sA) == (2,30) @test sA[:] == A[sA.indexes...][:] test_bounds(sA) a = [5:8;] @test parent(a) == a @test parentindexes(a) == (1:4,) # issue #6218 - logical indexing A = rand(2, 2, 3) msk = ones(Bool, 2, 2) msk[2,1] = false sA = view(A, :, :, 1) sA[msk] = 1.0 @test sA[msk] == ones(countnz(msk)) # bounds checking upon construction; see #4044, #10296 @test_throws BoundsError view(1:10, 8:11) A = reshape(1:20, 5, 4) sA = view(A, 1:2, 1:3) @test_throws BoundsError view(sA, 1:3, 1:3) @test_throws BoundsError view(sA, 1:2, 1:4) view(sA, 1:2, 1:2) @test_throws BoundsError view(A, 17:23) view(A, 17:20) # Linear indexing by one multidimensional array: A = reshape(1:120, 3, 5, 8) sA = view(A, :, :, :) @test sA[[72 17; 107 117]] == [72 17; 107 117] @test sA[[99 38 119 14 76 81]] == [99 38 119 14 76 81] @test sA[[ones(Int, 2, 2, 2); 2ones(Int, 2, 2, 2)]] == [ones(Int, 2, 2, 2); 2ones(Int, 2, 2, 2)] sA = view(A, 1:2, 2:3, 3:4) @test sA[(1:8)'] == [34 35 37 38 49 50 52 53] @test sA[[1 2 4 4; 6 1 1 4]] == [34 35 38 38; 50 34 34 38] # issue #11871 let a = ones(Float64, (2,2)), b = view(a, 1:2, 1:2) b[2] = 2 @test b[2] === 2.0 end # issue #15138 let a = [1,2,3], b = view(a, UInt(1):UInt(2)) @test b == view(a, UInt(1):UInt(2)) == view(view(a, :), UInt(1):UInt(2)) == [1,2] end let A = reshape(1:4, 2, 2) B = view(A, :, :) @test parent(B) === A @test parent(view(B, 0x1, :)) === parent(view(B, 0x1, :)) === A end # issue #15168 let A = rand(10), sA = view(copy(A), :) @test sA[Int16(1)] === sA[Int32(1)] === sA[Int64(1)] === A[1] permute!(sA, collect(Int16, 1:10)) @test A == sA end # the following segfaults with LLVM 3.8 on Windows, ref #15417 @test collect(view(view(reshape(1:13^3, 13, 13, 13), 3:7, 6:6, :), 1:2:5, :, 1:2:5)) == cat(3,[68,70,72],[406,408,410],[744,746,748]) # tests @view (and replace_ref_end!) X = reshape(1:24,2,3,4) Y = 4:-1:1 @test isa(@view(X[1:3]), SubArray) @test X[1:end] == @.(@view X[1:end]) # test compatibility of @. and @view @test X[1:end-3] == @view X[1:end-3] @test X[1:end,2,2] == @view X[1:end,2,2] # @test X[1,1:end-2] == @view X[1,1:end-2] # TODO: Re-enable after partial linear indexing deprecation @test X[1,2,1:end-2] == @view X[1,2,1:end-2] @test X[1,2,Y[2:end]] == @view X[1,2,Y[2:end]] @test X[1:end,2,Y[2:end]] == @view X[1:end,2,Y[2:end]] u = (1,2:3) @test X[u...,2:end] == @view X[u...,2:end] @test X[(1,)...,(2,)...,2:end] == @view X[(1,)...,(2,)...,2:end] # test macro hygiene let size=(x,y)-> error("should not happen"), Base=nothing @test X[1:end,2,2] == @view X[1:end,2,2] end # test that side effects occur only once let foo = [X] @test X[2:end-1] == @view (push!(foo,X)[1])[2:end-1] @test foo == [X, X] end # test @views macro @views let f!(x) = x[1:end-1] .+= x[2:end].^2 x = [1,2,3,4] f!(x) @test x == [5,11,19,4] @test x[1:3] isa SubArray @test x[2] === 11 @test Dict((1:3) => 4)[1:3] === 4 x[1:2] = 0 @test x == [0,0,19,4] x[1:2] .= 5:6 @test x == [5,6,19,4] f!(x[3:end]) @test x == [5,6,35,4] x[Y[2:3]] .= 7:8 @test x == [5,8,7,4] @. x[(3,)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views @test x == [5,8,10,4] i = Int[] # test that lhs expressions in update operations are evaluated only once: x[push!(i,4)[1]] += 5 @test x == [5,8,10,9] && i == [4] x[push!(i,3)[end]] += 2 @test x == [5,8,12,9] && i == [4,3] @. x[3:end] = 0 # make sure @. works with end expressions in @views @test x == [5,8,0,0] end @views @test isa(X[1:3], SubArray) @test X[1:end] == @views X[1:end] @test X[1:end-3] == @views X[1:end-3] @test X[1:end,2,2] == @views X[1:end,2,2] # @test X[1,1:end-2] == @views X[1,1:end-2] # TODO: Re-enable after partial linear indexing deprecation @test X[1,2,1:end-2] == @views X[1,2,1:end-2] @test X[1,2,Y[2:end]] == @views X[1,2,Y[2:end]] @test X[1:end,2,Y[2:end]] == @views X[1:end,2,Y[2:end]] @test X[u...,2:end] == @views X[u...,2:end] @test X[(1,)...,(2,)...,2:end] == @views X[(1,)...,(2,)...,2:end] # test macro hygiene let size=(x,y)-> error("should not happen"), Base=nothing @test X[1:end,2,2] == @views X[1:end,2,2] end # issue #18034 # ensure that it is possible to create an isbits, IndexLinear view of an immutable Array let struct ImmutableTestArray{T, N} <: Base.DenseArray{T, N} end Base.size(::Union{ImmutableTestArray, Type{ImmutableTestArray}}) = (0, 0) Base.IndexStyle(::Union{ImmutableTestArray, Type{ImmutableTestArray}}) = Base.IndexLinear() a = ImmutableTestArray{Float64, 2}() @test Base.IndexStyle(view(a, :, :)) == Base.IndexLinear() @test isbits(view(a, :, :)) end # Issue #17351 let x = rand(10) u = rand(10, 3) su = view(u, :, 1) @test size(@inferred(xcorr(x, su))) == (19,) end