From 762c8feebb6a245dfa4957a99f64691a3f596be0 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Mon, 4 Sep 2023 22:20:43 -0700 Subject: [PATCH] Clean up Dataspaces - define HDF5.UNLIMITED constant for unlimited values - improve printing of Dataspace objects - define Dataspace contructors, deprecate methods for dataspace functions --- src/HDF5.jl | 1 + src/api_midlevel.jl | 35 ------- src/dataspaces.jl | 245 ++++++++++++++++++++++++++------------------ src/deprecated.jl | 16 +++ src/show.jl | 56 ++++++---- test/dataspace.jl | 147 +++++++++++++++++--------- test/hyperslab.jl | 4 +- 7 files changed, 294 insertions(+), 210 deletions(-) diff --git a/src/HDF5.jl b/src/HDF5.jl index 783dd25b5..b533d84a0 100644 --- a/src/HDF5.jl +++ b/src/HDF5.jl @@ -42,6 +42,7 @@ export @read, create_property, group_info, object_info, + Dataspace, dataspace, datatype, Filters, diff --git a/src/api_midlevel.jl b/src/api_midlevel.jl index 4bbcac19f..9abbfceae 100644 --- a/src/api_midlevel.jl +++ b/src/api_midlevel.jl @@ -1,7 +1,6 @@ # This file defines midlevel api wrappers. We include name normalization for methods that are # applicable to different hdf5 api-layers. We still try to adhere close proximity to the underlying # method name in the hdf5-library. - """ HDF5.set_extent_dims(dset::HDF5.Dataset, new_dims::Dims) @@ -13,40 +12,6 @@ function set_extent_dims(dset::Dataset, size::Dims) API.h5d_set_extent(dset, API.hsize_t[reverse(size)...]) end -""" - HDF5.set_extent_dims(dspace::HDF5.Dataspace, new_dims::Dims, max_dims::Union{Dims,Nothing} = nothing) - -Change the dimensions of a dataspace `dspace` to `new_dims`, optionally with the maximum possible -dimensions `max_dims` different from the active size `new_dims`. If not given, `max_dims` is set equal -to `new_dims`. -""" -function set_extent_dims( - dspace::Dataspace, size::Dims, max_dims::Union{Dims,Nothing}=nothing -) - checkvalid(dspace) - rank = length(size) - current_size = API.hsize_t[reverse(size)...] - maximum_size = isnothing(max_dims) ? C_NULL : [reverse(max_dims .% API.hsize_t)...] - API.h5s_set_extent_simple(dspace, rank, current_size, maximum_size) - return nothing -end - -""" - HDF5.get_extent_dims(obj::Union{HDF5.Dataspace, HDF5.Dataset, HDF5.Attribute}) -> dims, maxdims - -Get the array dimensions from a dataspace, dataset, or attribute and return a tuple of `dims` and `maxdims`. -""" -function get_extent_dims(obj::Union{Dataspace,Dataset,Attribute}) - dspace = obj isa Dataspace ? checkvalid(obj) : dataspace(obj) - h5_dims, h5_maxdims = API.h5s_get_simple_extent_dims(dspace) - # reverse dimensions since hdf5 uses C-style order - N = length(h5_dims) - dims = ntuple(i -> @inbounds(Int(h5_dims[N - i + 1])), N) - maxdims = ntuple(i -> @inbounds(h5_maxdims[N - i + 1]) % Int, N) # allows max_dims to be specified as -1 without triggering an overflow - obj isa Dataspace || close(dspace) - return dims, maxdims -end - """ HDF5.get_chunk_offset(dataset_id, index) diff --git a/src/dataspaces.jl b/src/dataspaces.jl index c6a1c768d..ecc26e5e8 100644 --- a/src/dataspaces.jl +++ b/src/dataspaces.jl @@ -1,9 +1,28 @@ """ - HDF5.Dataspace + Dataspace -A dataspace defines the size and the shape of a dataset or an attribute. +A dataspace defines the size and the shape of a dataset or an attribute, and is +also used for selecting a subset of a dataset. -A dataspace is typically constructed by calling [`dataspace`](@ref). +# Constructors + + Dataspace(dims::Tuple; max_dims::Tuple=dims) + +Construct a simple array `Dataspace` for the given dimensions `dims`. The maximum +dimensions `max_dims` specifies the maximum possible size: `HDF5.UNLIMITED` can +be used to indicate unlimited dimensions. + + Dataspace(()) + +Construct a scalar `Dataspace`. This is a dataspace containing a single element. + + Dataspace() + +Construct a null `Dataspace` + +See also [`dataspace`](@ref). + +# Usage The following functions have methods defined for `Dataspace` objects - `==` @@ -15,6 +34,18 @@ The following functions have methods defined for `Dataspace` objects """ Dataspace # defined in types.jl +""" + HDF5.UNLIMITED + +A special value which can be used to indicate an unlimited dimension in a +[`Dataspace`](@ref). + +Can be used as the [`max_dims`](@ref) argument in the [`dataspace`](@ref) +constructor, or as the `count` argument in [`BlockRange`](@ref) when selecting +virtual dataset mappings. +""" +const UNLIMITED = reinterpret(Int64, API.H5S_UNLIMITED) + Base.:(==)(dspace1::Dataspace, dspace2::Dataspace) = API.h5s_extent_equal(checkvalid(dspace1), checkvalid(dspace2)) Base.hash(dspace::Dataspace, h::UInt) = hash(dspace.id, hash(Dataspace, h)) @@ -30,6 +61,34 @@ function Base.close(obj::Dataspace) nothing end +# null dataspace constructor +Dataspace() = Dataspace(API.h5s_create(API.H5S_NULL)) + +# reverese dims order +_to_h5_dims(dims::Dims{N}) where {N} = API.hsize_t[dims[i] for i in N:-1:1] +function _from_h5_dims(h5_dims::Vector{API.hsize_t}) + N = length(h5_dims) + ntuple(i -> @inbounds(Int(h5_dims[N - i + 1])), N) +end + +# reverse dims order and convert UNLIMITED to H5S_UNLIMITED +_to_h5_maxdims(max_dims::Dims{N}) where {N} = API.hsize_t[ + max_dims[i] == HDF5.UNLIMITED ? API.H5S_UNLIMITED : API.hsize_t(max_dims[i]) for + i in N:-1:1 +] +_to_h5_maxdims(::Nothing) = C_NULL +function _from_h5_maxdims(h5_maxdims::Vector{API.hsize_t}) + N = length(h5_maxdims) + ntuple(N) do i + d = @inbounds(h5_maxdims[N - i + 1]) + d == API.H5S_UNLIMITED ? HDF5.UNLIMITED : Int(d) + end +end + +function Dataspace(dims::Dims{N}; max_dims::Union{Dims{N},Nothing}=nothing) where {N} + return Dataspace(API.h5s_create_simple(N, _to_h5_dims(dims), _to_h5_maxdims(max_dims))) +end + """ dataspace(obj::Union{Attribute, Dataset, Dataspace}) @@ -41,65 +100,55 @@ dataspace(ds::Dataspace) = ds """ dataspace(data) -The default `Dataspace` used for representing a Julia object `data`: +Constructs an appropriate `Dataspace` for representing a Julia object `data`. + - strings or numbers: a scalar `Dataspace` - arrays: a simple `Dataspace` - `struct` types: a scalar `Dataspace` - `nothing` or an `EmptyArray`: a null dataspace """ -dataspace(x::T) where {T} = +function dataspace(x::T) where {T} if isstructtype(T) Dataspace(API.h5s_create(API.H5S_SCALAR)) else throw(MethodError(dataspace, x)) end +end dataspace(x::Union{T,Complex{T}}) where {T<:ScalarType} = Dataspace(API.h5s_create(API.H5S_SCALAR)) dataspace(::AbstractString) = Dataspace(API.h5s_create(API.H5S_SCALAR)) -function _dataspace(sz::Dims{N}, max_dims::Union{Dims{N},Tuple{}}=()) where {N} - dims = API.hsize_t[sz[i] for i in N:-1:1] - if isempty(max_dims) - maxd = dims - else - # This allows max_dims to be specified as -1 without triggering an overflow - # exception due to the signed -> unsigned conversion. - maxd = API.hsize_t[API.hssize_t(max_dims[i]) % API.hsize_t for i in N:-1:1] - end - return Dataspace(API.h5s_create_simple(length(dims), dims, maxd)) -end -dataspace(A::AbstractArray{T,N}; max_dims::Union{Dims{N},Tuple{}}=()) where {T,N} = - _dataspace(size(A), max_dims) +dataspace(A::AbstractArray{T,N}; max_dims::Union{Dims{N},Nothing}=nothing) where {T,N} = + Dataspace(size(A); max_dims) + # special array types -dataspace(v::VLen; max_dims::Union{Dims,Tuple{}}=()) = _dataspace(size(v.data), max_dims) -dataspace(A::EmptyArray) = Dataspace(API.h5s_create(API.H5S_NULL)) -dataspace(n::Nothing) = Dataspace(API.h5s_create(API.H5S_NULL)) +dataspace(v::VLen; max_dims::Union{Dims,Nothing}=nothing) = + Dataspace(size(v.data); max_dims) -# for giving sizes explicitly -""" - dataspace(dims::Tuple; max_dims::Tuple=dims) - dataspace(dims::Tuple, max_dims::Tuple) +dataspace(A::EmptyArray) = Dataspace() +dataspace(n::Nothing) = Dataspace() -Construct a simple `Dataspace` for the given dimensions `dims`. The maximum -dimensions `maxdims` specifies the maximum possible size: `-1` can be used to -indicate unlimited dimensions. -""" -dataspace(sz::Dims{N}; max_dims::Union{Dims{N},Tuple{}}=()) where {N} = - _dataspace(sz, max_dims) -dataspace(sz::Dims{N}, max_dims::Union{Dims{N},Tuple{}}) where {N} = - _dataspace(sz, max_dims) -dataspace(dims::Tuple{Dims{N},Dims{N}}) where {N} = _dataspace(first(dims), last(dims)) -dataspace(sz1::Int, sz2::Int, sz3::Int...; max_dims::Union{Dims,Tuple{}}=()) = - _dataspace(tuple(sz1, sz2, sz3...), max_dims) +# convenience function +function dataspace(fn, obj::Union{Dataset,Attribute}, args...) + dspace = dataspace(obj) + try + fn(dspace, args...) + finally + close(dspace) + end +end function Base.ndims(dspace::Dataspace) API.h5s_get_simple_extent_ndims(checkvalid(dspace)) end +Base.ndims(obj::Union{Dataset,Attribute}) = dataspace(ndims, obj) + function Base.size(dspace::Dataspace) h5_dims = API.h5s_get_simple_extent_dims(checkvalid(dspace), nothing) - N = length(h5_dims) - return ntuple(i -> @inbounds(Int(h5_dims[N - i + 1])), N) + return _from_h5_dims(h5_dims) end +Base.size(obj::Union{Dataset,Attribute}) = dataspace(size, obj) + function Base.size(dspace::Dataspace, d::Integer) d > 0 || throw(ArgumentError("invalid dimension d; must be positive integer")) N = ndims(dspace) @@ -107,12 +156,17 @@ function Base.size(dspace::Dataspace, d::Integer) h5_dims = API.h5s_get_simple_extent_dims(dspace, nothing) return @inbounds Int(h5_dims[N - d + 1]) end +Base.size(obj::Union{Dataset,Attribute}, d::Integer) = dataspace(ndims, obj, d) + function Base.length(dspace::Dataspace) isnull(dspace) && return 0 h5_dims = API.h5s_get_simple_extent_dims(checkvalid(dspace), nothing) return Int(prod(h5_dims)) end +Base.length(obj::Union{Dataset,Attribute}) = dataspace(length, obj) + Base.isempty(dspace::Dataspace) = length(dspace) == 0 +Base.isempty(obj::Union{Dataset,Attribute}) = dataspace(isempty, obj) """ isnull(dspace::Union{HDF5.Dataspace, HDF5.Dataset, HDF5.Attribute}) @@ -121,16 +175,57 @@ Determines whether the given object has no size (consistent with the `API.H5S_NU # Examples ```julia-repl -julia> HDF5.isnull(dataspace(HDF5.EmptyArray{Float64}())) +julia> HDF5.isnull(Dataspace()) +true + +julia> HDF5.isnull(Dataspace(())) true -julia> HDF5.isnull(dataspace((0,))) +julia> HDF5.isnull(Dataspace((0,))) false ``` """ function isnull(dspace::Dataspace) return API.h5s_get_simple_extent_type(checkvalid(dspace)) == API.H5S_NULL end +isnull(obj::Union{Dataset,Attribute}) = dataspace(isnull, obj) + +""" + HDF5.set_extent_dims(dspace::HDF5.Dataspace, new_dims::Dims, max_dims::Union{Dims,Nothing} = nothing) + +Change the dimensions of a dataspace `dspace` to `new_dims`, optionally with the maximum possible +dimensions `max_dims` different from the active size `new_dims`. If not given, `max_dims` is set equal +to `new_dims`. +""" +function set_extent_dims( + dspace::Dataspace, dims::Dims{N}, max_dims::Union{Dims{N},Nothing}=nothing +) where {N} + checkvalid(dspace) + API.h5s_set_extent_simple(dspace, N, _to_h5_dims(dims), _to_h5_maxdims(max_dims)) + return nothing +end + +""" + HDF5.get_extent_dims(obj::Union{HDF5.Dataspace, HDF5.Dataset, HDF5.Attribute}) -> dims, maxdims + +Get the array dimensions from a dataspace, dataset, or attribute and return a tuple of `dims` and `maxdims`. +""" +function get_extent_dims(dspace::Dataspace) + checkvalid(dspace) + h5_dims, h5_maxdims = API.h5s_get_simple_extent_dims(dspace) + return _from_h5_dims(h5_dims), _from_h5_maxdims(h5_maxdims) +end +get_extent_dims(obj::Union{Dataset,Attribute}) = dataspace(get_extent_dims, obj) + +# Selection +""" + HDF5.is_selection_valid(dspace::HDF5.Dataspace) + +Determines whether the selection is valid for the extent of the dataspace. +""" +function is_selection_valid(dspace::Dataspace) + return API.h5s_select_valid(checkvalid(dspace)) +end """ HDF5.get_regular_hyperslab(dspace)::Tuple @@ -161,8 +256,12 @@ hyperslab. It is similar to a Julia `range` object, with some extra features for selecting multiple contiguous blocks. - `start`: the index of the first element in the first block (1-based). + - `stride`: the step between the first element of each block (must be >0) -- `count`: the number of blocks (can be -1 for an unlimited number of blocks) + +- `count`: the number of blocks. Can be [`HDF5.UNLIMITED`](@ref) for an + unlimited number of blocks (e.g. for a virtual dataset mapping). + - `block`: the number of elements in each block. @@ -171,10 +270,11 @@ selecting multiple contiguous blocks. Convert `obj` to a `BlockRange` object. # External links -- [HDF5 User Guide, section 7.4.2.1 "Selecting Hyperslabs"](https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html#t=HDF5_Users_Guide%2FDataspaces%2FHDF5_Dataspaces_and_Partial_I_O.htm%23TOC_7_4_2_Programming_Modelbc-8&rhtocid=7.2.0_2) +- [HDF5 User Guide, section 7.4.2.1 "Selecting + Hyperslabs"](https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html#t=HDF5_Users_Guide%2FDataspaces%2FHDF5_Dataspaces_and_Partial_I_O.htm%23TOC_7_4_2_Programming_Modelbc-8&rhtocid=7.2.0_2) """ function BlockRange(; start::Integer, stride::Integer=1, count::Integer=1, block::Integer=1) - if count == -1 + if count == UNLIMITED count = API.H5S_UNLIMITED end BlockRange(start - 1, stride, count, block) @@ -265,61 +365,4 @@ function hyperslab(dspace::Dataspace, I::Tuple) select_hyperslab!(copy(dspace), I) end -# methods for Dataset/Attribute which operate on Dataspace -function Base.ndims(obj::Union{Dataset,Attribute}) - dspace = dataspace(obj) - try - return Base.ndims(dspace) - finally - close(dspace) - end -end -function Base.size(obj::Union{Dataset,Attribute}) - dspace = dataspace(obj) - try - return Base.size(dspace) - finally - close(dspace) - end -end -function Base.size(obj::Union{Dataset,Attribute}, d::Integer) - dspace = dataspace(obj) - try - return Base.size(dspace, d) - finally - close(dspace) - end -end -function Base.length(obj::Union{Dataset,Attribute}) - dspace = dataspace(obj) - try - return Base.length(dspace) - finally - close(dspace) - end -end -function Base.isempty(obj::Union{Dataset,Attribute}) - dspace = dataspace(obj) - try - return Base.isempty(dspace) - finally - close(dspace) - end -end -function isnull(obj::Union{Dataset,Attribute}) - dspace = dataspace(obj) - try - return isnull(dspace) - finally - close(dspace) - end -end - -function hyperslab(dset::Dataset, I::Union{AbstractRange{Int},Int}...) - dspace = dataspace(dset) - try - return hyperslab(dspace, I...) - finally - close(dspace) - end -end +hyperslab(dset::Dataset, I...) = dataspace(hyperslab, dset, I...) diff --git a/src/deprecated.jl b/src/deprecated.jl index 59879582e..6055c6469 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -84,3 +84,19 @@ import .Filters: ExternalFilter @deprecate set_track_order(p::Properties, val::Bool) set_track_order!( p::Properties, val::Bool ) false + +Base.@deprecate( + dataspace(sz::Dims{N}; max_dims::Union{Dims{N},Tuple{}}=()) where {N}, + Dataspace(sz; max_dims=max_dims == () ? nothing : max_dims) +) +Base.@deprecate( + dataspace(sz::Dims{N}, max_dims::Union{Dims{N},Tuple{}}) where {N}, + Dataspace(sz; max_dims=max_dims == () ? nothing : max_dims) +) +Base.@deprecate( + dataspace((dims, max_dims)::Tuple{Dims{N},Dims{N}}) where {N}, Dataspace(dims; max_dims) +) +Base.@deprecate( + dataspace(sz1::Int, sz2::Int, sz3::Int...; max_dims::Union{Dims,Tuple{}}=()), + Dataspace(tuple(sz1, sz2, sz3...); max_dims=max_dims == () ? nothing : max_dims) +) diff --git a/src/show.jl b/src/show.jl index 51f7b49c1..3ba7f21b1 100644 --- a/src/show.jl +++ b/src/show.jl @@ -137,13 +137,7 @@ function Base.show(io::IO, br::BlockRange) start = Int(br.start0 + 1) # choose the simplest possible representation if br.count == 1 - if br.block == 1 - # integer - r = start - else - # UnitRange - r = range(start; length=Int(br.block)) - end + r = range(start; length=Int(br.block)) elseif br.block == 1 && br.count != API.H5S_UNLIMITED # StepRange r = range(start; step=Int(br.stride), length=Int(br.count)) @@ -154,7 +148,11 @@ function Base.show(io::IO, br::BlockRange) print(io, ", stride=", Int(br.stride)) end if br.count != 1 - print(io, ", count=", br.count == API.API.H5S_UNLIMITED ? -1 : Int(br.count)) + print( + io, + ", count=", + br.count == API.API.H5S_UNLIMITED ? "HDF5.UNLIMITED" : Int(br.count) + ) end if br.block != 1 print(io, ", block=", Int(br.block)) @@ -169,43 +167,57 @@ function Base.show(io::IO, br::BlockRange) return nothing end +function print_dims_unlimited(io::IO, dims::Dims) + print(io, "(") + for ii in 1:length(dims) + if dims[ii] == HDF5.UNLIMITED + print(io, "HDF5.UNLIMITED") + else + print(io, dims[ii]) + end + if ii < length(dims) + print(io, ", ") + elseif ii == 1 + print(io, ",") + end + end + print(io, ")") + return nothing +end function Base.show(io::IO, dspace::Dataspace) if !isvalid(dspace) - print(io, "HDF5.Dataspace: (invalid)") + print(io, "HDF5.Dataspace(): (invalid)") return nothing end - print(io, "HDF5.Dataspace: ") type = API.h5s_get_simple_extent_type(dspace) if type == API.H5S_NULL - print(io, "H5S_NULL") + print(io, "HDF5.Dataspace(): null dataspace") return nothing elseif type == API.H5S_SCALAR - print(io, "H5S_SCALAR") + print(io, "HDF5.Dataspace(()): scalar dataspace") return nothing end # otherwise type == API.H5S_SIMPLE sz, maxsz = get_extent_dims(dspace) + print(io, "HDF5.Dataspace(", sz) + if maxsz != sz + print(io, "; max_dims=") + print_dims_unlimited(io, maxsz) + end + print(io, "): ") + print(io, Base.ndims(dspace), "-dimensional dataspace") sel = API.h5s_get_select_type(dspace) if sel == API.H5S_SEL_HYPERSLABS && API.h5s_is_regular_hyperslab(dspace) io_compact = IOContext(io, :compact => true) blockranges = get_regular_hyperslab(dspace) ndims = length(blockranges) - print(io_compact, "(") + print(io_compact, "\n hyperslab selection: (") for ii in 1:ndims print(io_compact, blockranges[ii]) ii != ndims && print(io_compact, ", ") end - print(io_compact, ") / (") - for ii in 1:ndims - print(io_compact, 1:maxsz[ii]) - ii != ndims && print(io_compact, ", ") - end print(io_compact, ")") else - print(io, sz) - if maxsz != sz - print(io, " / ", maxsz) - end if sel != API.H5S_SEL_ALL print(io, " [irregular selection]") end diff --git a/test/dataspace.jl b/test/dataspace.jl index 46d1a0f4b..e80874787 100644 --- a/test/dataspace.jl +++ b/test/dataspace.jl @@ -1,79 +1,144 @@ using HDF5 using Test -@testset "Dataspaces" begin - hsize_t = HDF5.API.hsize_t - # Reference objects without using high-level API - ds_null = HDF5.Dataspace(HDF5.API.h5s_create(HDF5.API.H5S_NULL)) - ds_scalar = HDF5.Dataspace(HDF5.API.h5s_create(HDF5.API.H5S_SCALAR)) - ds_zerosz = HDF5.Dataspace(HDF5.API.h5s_create_simple(1, hsize_t[0], hsize_t[0])) - ds_vector = HDF5.Dataspace(HDF5.API.h5s_create_simple(1, hsize_t[5], hsize_t[5])) - ds_matrix = HDF5.Dataspace(HDF5.API.h5s_create_simple(2, hsize_t[7, 5], hsize_t[7, 5])) - ds_maxdim = HDF5.Dataspace(HDF5.API.h5s_create_simple(2, hsize_t[7, 5], hsize_t[20, 20])) - ds_unlim = HDF5.Dataspace(HDF5.API.h5s_create_simple(1, hsize_t[1], [HDF5.API.H5S_UNLIMITED])) +@testset "null dataspace" begin + ds_null = HDF5.Dataspace() - # Testing basic property accessors of dataspaces + @test isvalid(ds_null) + @test HDF5.isnull(ds_null) + @test isempty(ds_null) + + @test length(ds_null) === 0 + @test ndims(ds_null) === 0 + @test size(ds_null) === () + @test size(ds_null, 5) === 1 + + @test HDF5.get_extent_dims(ds_null) === ((), ()) + + @test Dataspace() == ds_null + @test dataspace(nothing) == ds_null + @test dataspace(HDF5.EmptyArray{Bool}()) == ds_null + + @test repr(ds_null) == "HDF5.Dataspace(): null dataspace" +end + +@testset "scalar dataspace" begin + ds_scalar = HDF5.Dataspace(()) @test isvalid(ds_scalar) + @test !HDF5.isnull(ds_scalar) + @test !isempty(ds_scalar) - @test ndims(ds_null) === 0 + @test length(ds_scalar) === 1 @test ndims(ds_scalar) === 0 + @test size(ds_scalar) === () + @test size(ds_scalar, 5) === 1 + + @test HDF5.get_extent_dims(ds_scalar) === ((), ()) + + @test Dataspace() != ds_scalar + @test Dataspace(()) == ds_scalar + + @test dataspace(fill(1.0)) == ds_scalar + @test dataspace(1) == ds_scalar + @test dataspace(1 + 1im) == ds_scalar + @test dataspace("string") == ds_scalar + + @test repr(ds_scalar) == "HDF5.Dataspace(()): scalar dataspace" +end + +@testset "simple dataspaces" begin + # Reference objects without using high-level API + ds_zerosz = HDF5.Dataspace((0,)) + ds_vector = HDF5.Dataspace((5,)) + ds_matrix = HDF5.Dataspace((5, 7)) + ds_maxdim = HDF5.Dataspace((5, 7); max_dims=(20, 20)) + ds_unlim = HDF5.Dataspace((1,); max_dims=(HDF5.UNLIMITED,)) + + # Testing basic property accessors of dataspaces + @test isvalid(ds_zerosz) + @test isvalid(ds_vector) + @test isvalid(ds_matrix) + @test isvalid(ds_maxdim) + @test isvalid(ds_unlim) + @test ndims(ds_zerosz) === 1 @test ndims(ds_vector) === 1 @test ndims(ds_matrix) === 2 + @test ndims(ds_maxdim) === 2 + @test ndims(ds_unlim) === 1 # Test that properties of existing datasets can be extracted. # Note: Julia reverses the order of dimensions when using the high-level API versus # the dimensions used above to create the reference objects. - @test size(ds_null) === () - @test size(ds_scalar) === () @test size(ds_zerosz) === (0,) @test size(ds_vector) === (5,) @test size(ds_matrix) === (5, 7) @test size(ds_maxdim) === (5, 7) + @test size(ds_unlim) === (1,) + + @test size(ds_zerosz, 1) === 0 + @test size(ds_vector, 1) === 5 + @test size(ds_matrix, 1) === 5 + @test size(ds_maxdim, 1) === 5 + @test size(ds_unlim, 1) === 1 + + @test size(ds_zerosz, 2) === 1 + @test size(ds_vector, 2) === 1 + @test size(ds_matrix, 2) === 7 + @test size(ds_maxdim, 2) === 7 + @test size(ds_unlim, 2) === 1 - @test size(ds_null, 5) === 1 - @test size(ds_scalar, 5) === 1 @test size(ds_zerosz, 5) === 1 @test size(ds_vector, 5) === 1 @test size(ds_matrix, 5) === 1 @test size(ds_maxdim, 5) === 1 + @test size(ds_unlim, 5) === 1 + @test_throws ArgumentError("invalid dimension d; must be positive integer") size( - ds_null, 0 + ds_zerosz, 0 ) @test_throws ArgumentError("invalid dimension d; must be positive integer") size( - ds_scalar, -1 + ds_zerosz, -1 ) - @test length(ds_null) === 0 - @test length(ds_scalar) === 1 @test length(ds_zerosz) === 0 @test length(ds_vector) === 5 @test length(ds_matrix) === 35 @test length(ds_maxdim) === 35 + @test length(ds_unlim) === 1 - @test isempty(ds_null) - @test !isempty(ds_scalar) @test isempty(ds_zerosz) @test !isempty(ds_vector) + @test !isempty(ds_matrix) + @test !isempty(ds_maxdim) + @test !isempty(ds_unlim) - @test HDF5.isnull(ds_null) - @test !HDF5.isnull(ds_scalar) @test !HDF5.isnull(ds_zerosz) @test !HDF5.isnull(ds_vector) + @test !HDF5.isnull(ds_matrix) + @test !HDF5.isnull(ds_maxdim) + @test !HDF5.isnull(ds_unlim) - @test HDF5.get_extent_dims(ds_null) === ((), ()) - @test HDF5.get_extent_dims(ds_scalar) === ((), ()) @test HDF5.get_extent_dims(ds_zerosz) === ((0,), (0,)) @test HDF5.get_extent_dims(ds_vector) === ((5,), (5,)) @test HDF5.get_extent_dims(ds_matrix) === ((5, 7), (5, 7)) @test HDF5.get_extent_dims(ds_maxdim) === ((5, 7), (20, 20)) - @test HDF5.get_extent_dims(ds_unlim) === ((1,), (-1,)) + @test HDF5.get_extent_dims(ds_unlim) === ((1,), (HDF5.UNLIMITED,)) + + @test repr(ds_zerosz) == "HDF5.Dataspace((0,)): 1-dimensional dataspace" + @test repr(ds_vector) == "HDF5.Dataspace((5,)): 1-dimensional dataspace" + @test repr(ds_matrix) == "HDF5.Dataspace((5, 7)): 2-dimensional dataspace" + @test repr(ds_maxdim) == + "HDF5.Dataspace((5, 7); max_dims=(20, 20)): 2-dimensional dataspace" + @test repr(ds_unlim) == + "HDF5.Dataspace((1,); max_dims=(HDF5.UNLIMITED,)): 1-dimensional dataspace" # Can create new copies ds_tmp = copy(ds_maxdim) ds_tmp2 = HDF5.Dataspace(ds_tmp.id) # copy of ID, but new Julia object - @test ds_tmp.id == ds_tmp2.id != ds_maxdim.id + @test ds_tmp.id === ds_tmp2.id !== ds_maxdim.id + # Equality and hashing @test ds_tmp == ds_maxdim @test ds_tmp !== ds_maxdim @@ -98,30 +163,12 @@ using Test @test close(ds_tmp) === nothing # no error # Test ability to create explicitly-sized dataspaces - - @test dataspace(()) == ds_scalar - @test dataspace((5,)) == ds_vector - @test dataspace((5, 7)) == ds_matrix != ds_maxdim - @test dataspace((5, 7); max_dims=(20, 20)) == ds_maxdim != ds_matrix - @test dataspace((5, 7), (20, 20)) == ds_maxdim - @test dataspace(((5, 7), (20, 20))) == ds_maxdim - @test dataspace((1,); max_dims=(-1,)) == ds_unlim - @test dataspace((1,), (-1,)) == ds_unlim - @test dataspace(((1,), (-1,))) == ds_unlim - # for ≥ 2 numbers, same as single tuple argument - @test dataspace(5, 7) == ds_matrix - @test dataspace(5, 7, 1) == dataspace((5, 7, 1)) + @test Dataspace((5,)) == ds_vector + @test Dataspace((5, 7)) == ds_matrix != ds_maxdim + @test Dataspace((5, 7); max_dims=(20, 20)) == ds_maxdim != ds_matrix + @test Dataspace((1,); max_dims=(HDF5.UNLIMITED,)) == ds_unlim # Test dataspaces derived from data - - @test dataspace(nothing) == ds_null - @test dataspace(HDF5.EmptyArray{Bool}()) == ds_null - - @test dataspace(fill(1.0)) == ds_scalar - @test dataspace(1) == ds_scalar - @test dataspace(1 + 1im) == ds_scalar - @test dataspace("string") == ds_scalar - @test dataspace(zeros(0)) == ds_zerosz @test dataspace(zeros(0, 0)) != ds_zerosz @test dataspace(zeros(5, 7)) == ds_matrix diff --git a/test/hyperslab.jl b/test/hyperslab.jl index 5fad8f9a1..a6b110576 100644 --- a/test/hyperslab.jl +++ b/test/hyperslab.jl @@ -7,8 +7,8 @@ using Random, Test, HDF5 @test convert(AbstractRange, br) === 2:2 @test convert(UnitRange, br) === 2:2 @test convert(StepRange, br) === 2:1:2 - @test repr(br) == "HDF5.BlockRange(2)" - @test repr(br; context=:compact => true) == "2" + @test repr(br) == "HDF5.BlockRange(2:2)" + @test repr(br; context=:compact => true) == "2:2" br = HDF5.BlockRange(Base.OneTo(3)) @test length(br) == 3