From 157ca46e5b4f9914c9ac7a96210d901b4aadf8d8 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Mon, 23 Mar 2026 17:36:28 +0200 Subject: [PATCH 1/5] add solvable to summary level, rm blobentries --- src/DistributedFactorGraphs.jl | 6 ++++-- src/entities/DFGFactor.jl | 16 ++++++++++++++-- src/entities/DFGVariable.jl | 19 +++++++++++++------ src/services/CompareUtils.jl | 6 ++---- test/iifInterfaceTests.jl | 18 ++++++++++++++---- test/testBlocks.jl | 18 ++++++++++++++---- test/testSerializingVariables.jl | 2 +- 7 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/DistributedFactorGraphs.jl b/src/DistributedFactorGraphs.jl index d5f0ba0a..f0091560 100644 --- a/src/DistributedFactorGraphs.jl +++ b/src/DistributedFactorGraphs.jl @@ -80,11 +80,11 @@ export AbstractStateType, StateType #TODO types are not yet stable - also, we might not export all types # Variables export VariableDFG -export VariableSummary +# export VariableSummary #TODO not finalized yet. export VariableSkeleton # Factors export FactorDFG -export FactorSummary +# export FactorSummary TODO not finalized yet. export FactorSkeleton export Blobentry @@ -417,6 +417,8 @@ public pack, unpack # list of unstable functions not exported any more # will move to public or deprecate over time const unstable_functions::Vector{Symbol} = [ + :VariableSummary, + :FactorSummary, :listNeighborhood, :listNeighbors, :InMemoryBlobstore, diff --git a/src/entities/DFGFactor.jl b/src/entities/DFGFactor.jl index 8d31ecdc..f228487c 100644 --- a/src/entities/DFGFactor.jl +++ b/src/entities/DFGFactor.jl @@ -311,6 +311,10 @@ $(TYPEDFIELDS) """Variable timestamp. Accessors: [`getTimestamp`](@ref)""" timestamp::TimeDateZone + """Solvable flag for the factor. + Accessors: [`getSolvable`](@ref), [`setSolvable!`](@ref)""" + solvable::Base.RefValue{Int} = Ref{Int}(1) #& (lower = getindex, lift = Ref) + #TODO factorkind for isKind(Pose2Pose2) like queries and filters. end function FactorSummary( @@ -318,8 +322,9 @@ function FactorSummary( variableorder::Union{Vector{Symbol}, Tuple}; timestamp::TimeDateZone = now_tdz(), tags::Set{Symbol} = Set{Symbol}(), + solvable::Int = 1, ) - return FactorSummary(label, tags, Tuple(variableorder), timestamp) + return FactorSummary(label, tags, Tuple(variableorder), timestamp, Ref(solvable)) end ##------------------------------------------------------------------------------ @@ -362,7 +367,13 @@ end ##============================================================================== function FactorSummary(f::FactorDFG) - return FactorSummary(f.label, copy(f.tags), f.variableorder, f.timestamp) + return FactorSummary( + f.label, + copy(f.tags), + f.variableorder, + f.timestamp, + deepcopy(f.solvable), + ) end function FactorSkeleton(f::AbstractGraphFactor) @@ -389,6 +400,7 @@ function patch!(dest::FactorSummary, src::FactorSummary) ), ) union!(dest.tags, src.tags) + dest.solvable[] = src.solvable[] return dest end diff --git a/src/entities/DFGVariable.jl b/src/entities/DFGVariable.jl index 6b20359d..6e185c74 100644 --- a/src/entities/DFGVariable.jl +++ b/src/entities/DFGVariable.jl @@ -269,7 +269,7 @@ $(TYPEDFIELDS) $variable_timestamp_note Accessors: [`getTimestamp`](@ref)""" timestamp::TimeDateZone = now_tdz() #NOTE changed to TimeDateZone in v0.29 - # """Nanoseconds since a user-understood epoch (i.e unix epoch, robot boot time, etc.)""" + # """Nanoseconds since a user-understood epoch (e.g unix epoch, robot boot time, etc.)""" # nstime::String = "0" #NOTE deprecated field in v0.29 """Variable tags, e.g [:POSE, :VARIABLE, and :LANDMARK]. Accessors: [`listTags`](@ref), [`mergeTags!`](@ref), and [`deleteTags!`](@ref)""" @@ -446,11 +446,11 @@ $(TYPEDFIELDS) """Variable tags, e.g [:POSE, :VARIABLE, and :LANDMARK]. Accessors: [`listTags`](@ref), [`mergeTags!`](@ref), and [`deleteTags!`](@ref)""" tags::Set{Symbol} + """Solvable flag for the variable. + Accessors: [`getSolvable`](@ref), [`setSolvable!`](@ref)""" + solvable::Base.RefValue{Int} """Symbol for the state type for the underlying variable.""" statekind::AbstractStateType - """Dictionary of large data associated with this variable. - Accessors: [`addBlobentry!`](@ref), [`getBlobentry`](@ref), [`mergeBlobentry!`](@ref), and [`deleteBlobentry!`](@ref)""" - blobentries::Blobentries end ##------------------------------------------------------------------------------ @@ -483,7 +483,7 @@ end ##============================================================================== function VariableSummary(v::VariableDFG{T}) where {T} - return VariableSummary(v.label, v.timestamp, copy(v.tags), T(), copy(v.blobentries)) + return VariableSummary(v.label, v.timestamp, copy(v.tags), deepcopy(v.solvable), T()) end function VariableSkeleton(v::AbstractGraphVariable) @@ -504,8 +504,15 @@ function patch!(dest::VariableSummary, src::VariableSummary) "Variables has different timestamps: $(dest.timestamp) vs $(src.timestamp).", ), ) + dest.statekind !== src.statekind && throw( + MergeConflictError( + "Variables has different statekinds: $(dest.statekind) vs $(src.statekind).", + ), + ) + union!(dest.tags, src.tags) - merge!(dest.blobentries, src.blobentries) + dest.solvable[] = src.solvable[] + return dest end diff --git a/src/services/CompareUtils.jl b/src/services/CompareUtils.jl index 1732a784..b0ccb3b0 100644 --- a/src/services/CompareUtils.jl +++ b/src/services/CompareUtils.jl @@ -23,9 +23,7 @@ const GeneratedCompareUnion = Union{ State, Blobentry, Bloblet, - VariableSummary, VariableSkeleton, - FactorSummary, FactorSkeleton, Recipehyper, Recipestate, @@ -35,7 +33,7 @@ const GeneratedCompareUnion = Union{ return mapreduce(n -> :(x.$n == y.$n), (a, b) -> :($a && $b), fieldnames(x)) end -function ==(x::FactorDFG, y::FactorDFG) +function ==(x::T, y::T) where {T <: AbstractGraphFactor} ignored = [:solvercache, :solvable] tp = mapreduce( n -> getproperty(x, n) == getproperty(y, n), @@ -45,7 +43,7 @@ function ==(x::FactorDFG, y::FactorDFG) return tp && getSolvable(x) == getSolvable(y) end -function ==(x::VariableDFG, y::VariableDFG) +function ==(x::T, y::T) where {T <: AbstractGraphVariable} ignored = [:solvable] tp = mapreduce( n -> getproperty(x, n) == getproperty(y, n), diff --git a/test/iifInterfaceTests.jl b/test/iifInterfaceTests.jl index ae2aee74..934ff02f 100644 --- a/test/iifInterfaceTests.jl +++ b/test/iifInterfaceTests.jl @@ -397,14 +397,24 @@ end # Check all fields are equal for all variables for v in ls(summaryGraph) for field in variableFields - @test getproperty(getVariable(dfg, v), field) == - getfield(getVariable(summaryGraph, v), field) + a = getproperty(getVariable(dfg, v), field) + b = getproperty(getVariable(summaryGraph, v), field) + if field == :solvable + @test a[] == b[] + else + @test a == b + end end end for f in lsf(summaryGraph) for field in factorFields - @test getproperty(getFactor(dfg, f), field) == - getfield(getFactor(summaryGraph, f), field) + a = getproperty(getFactor(dfg, f), field) + b = getproperty(getFactor(summaryGraph, f), field) + if field == :solvable + @test a[] == b[] + else + @test a == b + end end end end diff --git a/test/testBlocks.jl b/test/testBlocks.jl index 99c58aed..e9508d01 100644 --- a/test/testBlocks.jl +++ b/test/testBlocks.jl @@ -1580,14 +1580,24 @@ function Summaries(testDFGAPI) # Check all fields are equal for all variables for v in ls(summaryGraph) for field in variableFields - @test getproperty(getVariable(dfg, v), field) == - getproperty(getVariable(summaryGraph, v), field) + a = getproperty(getVariable(dfg, v), field) + b = getproperty(getVariable(summaryGraph, v), field) + if field == :solvable + @test a[] == b[] + else + @test a == b + end end end for f in lsf(summaryGraph) for field in factorFields - @test getproperty(getFactor(dfg, f), field) == - getproperty(getFactor(summaryGraph, f), field) + a = getproperty(getFactor(dfg, f), field) + b = getproperty(getFactor(summaryGraph, f), field) + if field == :solvable + @test a[] == b[] + else + @test a == b + end end end end diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index 84847d82..1f8fac45 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -93,7 +93,7 @@ end vs = VariableSummary(v) @test vs.label == :x1 @test vs.tags == v.tags - @test vs.blobentries == v.blobentries + # @test vs.blobentries == v.blobentries end @testset "VariableSkeleton from VariableDFG" begin From deec82c79fc14d3a93a690cc8d60d531ae18c455 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Mon, 23 Mar 2026 19:03:07 +0200 Subject: [PATCH 2/5] Enhance Blob handling and MIME type integration with new functions and tests --- Project.toml | 2 + ext/BlobArrow.jl | 7 +- src/DataBlobs/services/BlobPacking.jl | 127 +++++++----- src/DataBlobs/services/BlobWrappers.jl | 59 +++++- src/Deprecated.jl | 62 ++++++ src/DistributedFactorGraphs.jl | 4 + src/services/AbstractDFG.jl | 19 -- src/services/find.jl | 36 ---- test/runtests.jl | 8 + test/testBlobPacking.jl | 179 +++++++++++++++++ test/testBlobStoresAndWrappers.jl | 265 +++++++++++++++++++++++++ 11 files changed, 662 insertions(+), 106 deletions(-) create mode 100644 test/testBlobPacking.jl create mode 100644 test/testBlobStoresAndWrappers.jl diff --git a/Project.toml b/Project.toml index 6f3c24b5..29752ab6 100644 --- a/Project.toml +++ b/Project.toml @@ -14,6 +14,7 @@ Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MIMEs = "6c6e2e6c-3030-632d-7369-2d6c69616d65" ManifoldsBase = "3362f125-f0bb-47a3-aa74-596ffd7ef2fb" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" @@ -55,6 +56,7 @@ InteractiveUtils = "1.11" JSON = "1.0.0" LieGroups = "0.1" LinearAlgebra = "1.11" +MIMEs = "1.1" ManifoldsBase = "1, 2" OrderedCollections = "1.4" Pkg = "1.4, 1.5" diff --git a/ext/BlobArrow.jl b/ext/BlobArrow.jl index 47545d59..126fbad6 100644 --- a/ext/BlobArrow.jl +++ b/ext/BlobArrow.jl @@ -2,11 +2,12 @@ module BlobArrow using Arrow using DistributedFactorGraphs -using DistributedFactorGraphs: _MIMETypes +using DistributedFactorGraphs: _MIMEOverrides, format_to_mime function __init__() @info "Including Arrow blobs support in DFG." - return push!(_MIMETypes, MIME("application/vnd.apache.arrow.file") => format"Arrow") # see issue #507 + push!(_MIMEOverrides, format"Arrow" => MIME("application/vnd.apache.arrow.file")) + return nothing end # kwargs: compress = :lz4, @@ -14,7 +15,7 @@ function DFG.packBlob(::Type{format"Arrow"}, data; kwargs...) io = IOBuffer() Arrow.write(io, data; kwargs...) blob = take!(io) - mimetype = findfirst(==(format"Arrow"), _MIMETypes) + mimetype = format_to_mime(format"Arrow") return blob, mimetype end diff --git a/src/DataBlobs/services/BlobPacking.jl b/src/DataBlobs/services/BlobPacking.jl index 9ce13f9a..db5e750b 100644 --- a/src/DataBlobs/services/BlobPacking.jl +++ b/src/DataBlobs/services/BlobPacking.jl @@ -1,49 +1,91 @@ -# using FileIO -# using ImageIO -# using LasIO -# using BSON -# using OrderedCollections - -# 2 types for now with MIME type -# 1. JSON - application/octet-stream/json -# 2. FileIO - application/octet-stream -# - application/bson -# - image/jpeg -# - image/png -# - application/vnd.apache.arrow.file - -const _MIMETypes = OrderedDict{MIME, DataType}() -push!(_MIMETypes, MIME("application/octet-stream/json") => format"JSON") -push!(_MIMETypes, MIME("application/bson") => format"BSON") -push!(_MIMETypes, MIME("image/png") => format"PNG") -push!(_MIMETypes, MIME("image/jpeg") => format"JPG") -push!(_MIMETypes, MIME("application/vnd.las") => format"LAS") -push!(_MIMETypes, MIME("application/vnd.apache.parque") => format"Parquet") # Provided by FileIO with ParquetFiles +##============================================================================== +## BlobPacking: format <-> MIME type bridging and blob serialization +##============================================================================== + +# Override dictionary for formats not covered by MIMEs.jl + FileIO auto-detection. +# Standard types (PNG, JPEG, CSV, etc.) are auto-detected and don't need entries here. +const _MIMEOverrides = OrderedDict{DataType, MIME}( + format"JSON" => MIME("application/json"), + format"BSON" => MIME("application/bson"), + format"LAS" => MIME("application/vnd.las"), + format"Parquet" => MIME("application/vnd.apache.parquet"), +) + +""" + format_to_mime(::Type{DataFormat{S}}) -> MIME + +Get the MIME type for a FileIO `DataFormat`. Uses FileIO's extension registry +and MIMEs.jl for standard types, falls back to `_MIMEOverrides` for +domain-specific formats. + +# Examples +```julia +format_to_mime(format"PNG") # MIME("image/png") +format_to_mime(format"JSON") # MIME("application/json") +``` +""" +function format_to_mime(::Type{DataFormat{S}}) where {S} + T = DataFormat{S} + haskey(_MIMEOverrides, T) && return _MIMEOverrides[T] + try + finfo = FileIO.info(T) + ext = finfo[2] + ext = ext isa AbstractVector ? first(ext) : ext + m = mime_from_extension(ext) + !isnothing(m) && return m + catch + end + return MIME("application/octet-stream") +end + +""" + mime_to_format(::MIME) -> Union{Type{DataFormat{S}}, Nothing} + +Get the FileIO `DataFormat` for a MIME type. Uses MIMEs.jl and FileIO's extension +registry, falls back to `_MIMEOverrides`. + +Returns `nothing` if no matching format is found. + +# Examples +```julia +mime_to_format(MIME("image/png")) # format"PNG" +mime_to_format(MIME("application/json")) # format"JSON" +``` +""" +function mime_to_format(m::MIME) + for (fmt, mime) in _MIMEOverrides + mime == m && return fmt + end + ext = extension_from_mime(m) + sym = get(FileIO.ext2sym, ext, nothing) + !isnothing(sym) && return DataFormat{sym} + return nothing +end """ packBlob -Convert a file (JSON, JPG, PNG, BSON, LAS) to Vector{UInt8} for use as a Blob. -Returns the blob and MIME type. +Convert data to `Vector{UInt8}` for use as a Blob. Returns `(blob, mimetype)`. +The MIME type is automatically determined from the DataFormat. """ function packBlob end + """ unpackBlob -Convert a Blob back to the origanal typ using the MIME type or DataFormat type. +Convert a Blob back to the original type using the MIME type or DataFormat type. """ function unpackBlob end unpackBlob(mime::String, blob) = unpackBlob(MIME(mime), blob) function unpackBlob(T::MIME, blob) - dataformat = get(_MIMETypes, T, nothing) + dataformat = mime_to_format(T) isnothing(dataformat) && error("Format not found for MIME type $(T)") return unpackBlob(dataformat, blob) end # 1. JSON strings are saved as is function packBlob(::Type{format"JSON"}, json_str::String) - mimetype = findfirst(==(format"JSON"), _MIMETypes) - # blob = codeunits(json_str) + mimetype = format_to_mime(format"JSON") blob = Vector{UInt8}(json_str) return blob, mimetype end @@ -55,16 +97,12 @@ end unpackBlob(entry::Blobentry, blob::Vector{UInt8}) = unpackBlob(entry.mimetype, blob) unpackBlob(eb::Pair{<:Blobentry, Vector{UInt8}}) = unpackBlob(eb[1], eb[2]) -# 2/ FileIO +# 2. FileIO formats (PNG, JPEG, BSON, LAS, Parquet, etc.) function packBlob(::Type{T}, data::Any; kwargs...) where {T <: DataFormat} io = IOBuffer() save(Stream{T}(io), data; kwargs...) blob = take!(io) - mimetype = findfirst(==(T), _MIMETypes) - if isnothing(mimetype) - @warn "No MIME type found for format $T" - mimetype = MIME"application/octet-stream" - end + mimetype = format_to_mime(T) return blob, mimetype end @@ -73,18 +111,13 @@ function unpackBlob(::Type{T}, blob::Vector{UInt8}) where {T <: DataFormat} return load(Stream{T}(io)) end -# if false -# json_str = "{\"name\":\"John\"}" -# blob, mimetype = packBlob(format"JSON", json_str) -# @assert json_str == unpackBlob(format"JSON", blob) -# @assert json_str == unpackBlob(MIME("application/octet-stream/json"), blob) -# @assert json_str == unpackBlob("application/octet-stream/json", blob) - -# blob,mime = packBlob(format"PNG", img) -# up_img = unpackBlob(format"PNG", blob) - -# #TODO BSON does not work yet, can extend [un]packBlob(::Type{format"BSON"}, ...) -# packBlob(format"BSON", Dict("name"=>"John")) -# unpackBlob(format"BSON", Dict("name"=>"John")) +""" + getMimetype(io::IO) -> MIME -# end +Detect the MIME type of data in an IO stream using FileIO's format detection. +""" +function getMimetype(io::IO) + _getFormat(s::FileIO.Stream{T}) where {T} = T + stream = FileIO.query(io) + return format_to_mime(_getFormat(stream)) +end diff --git a/src/DataBlobs/services/BlobWrappers.jl b/src/DataBlobs/services/BlobWrappers.jl index b7d68dc6..07a45adf 100644 --- a/src/DataBlobs/services/BlobWrappers.jl +++ b/src/DataBlobs/services/BlobWrappers.jl @@ -63,6 +63,27 @@ $(METHODLIST) """ function deleteBlob_Agent! end +""" +Convenience wrapper to load a Blob for a given factor and Blobentry label. + +$(METHODLIST) +""" +function loadBlob_Factor end + +""" +Convenience wrapper to save a Blob to a Blobstore and a Blobentry to a factor. + +$(METHODLIST) +""" +function saveBlob_Factor! end + +""" +Convenience wrapper to delete a Blob from a Blobstore and its Blobentry from a factor. + +$(METHODLIST) +""" +function deleteBlob_Factor! end + function loadBlob_Variable( dfg::AbstractDFG, variable_label::Symbol, @@ -166,6 +187,42 @@ function deleteBlob_Agent!(dfg::AbstractDFG, entry_label::Symbol) return 2 end +function loadBlob_Factor(dfg::AbstractDFG, factor_label::Symbol, entry_label::Symbol) + entry = getFactorBlobentry(dfg, factor_label, entry_label) + blob = getBlob(dfg, entry) + return entry, blob +end + +function saveBlob_Factor!( + dfg::AbstractDFG, + factor_label::Symbol, + blob::Vector{UInt8}, + entry::Blobentry, +) + addFactorBlobentry!(dfg, factor_label, entry) + addBlob!(dfg, entry, blob) + return entry +end + +function saveBlob_Factor!( + dfg::AbstractDFG, + factor_label::Symbol, + blob::Vector{UInt8}, + entry_label::Symbol, + blobstore::Symbol = :default; + blobentry_kwargs..., +) + entry = Blobentry(entry_label, blobstore; blobentry_kwargs...) + return saveBlob_Factor!(dfg, factor_label, blob, entry) +end + +function deleteBlob_Factor!(dfg::AbstractDFG, factor_label::Symbol, entry_label::Symbol) + entry = getFactorBlobentry(dfg, factor_label, entry_label) + deleteFactorBlobentry!(dfg, factor_label, entry_label) + deleteBlob!(dfg, entry) + return 2 +end + function saveImage_Variable!( dfg::AbstractDFG, variable_label::Symbol, @@ -175,7 +232,7 @@ function saveImage_Variable!( entry_kwargs..., ) mimeType = get(entry_kwargs, :mimeType, MIME("image/png")) - format = _MIMETypes[mimeType] + format = mime_to_format(mimeType) blob, mimeType = packBlob(format, img) diff --git a/src/Deprecated.jl b/src/Deprecated.jl index 63d2e946..e1ea62a5 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -627,3 +627,65 @@ function mergeGraph!( end @deprecate buildSubgraph(args...; kwargs...) getSubgraph(args...; kwargs...) + +#TODO deprecate +# - the verb is not correct and should be `list` as the function returns a list of factors +# - The noun is also not correct and algorithm details are used in the name +# - A better noun is maybe Path or simply listFactors with a fancy filter, something like: +# - [list/get]Path(dfg, from, to; algorithm...) +# the `search` verb can also come ito play, but it is more for knn search type functions. +""" + $SIGNATURES + +Relatively naive function counting linearly from-to + +DevNotes +- Convert to using Graphs shortest path methods instead. +""" +function findFactorsBetweenNaive( + dfg::AbstractDFG, + from::Symbol, + to::Symbol, + assertSingles::Bool = false, +) + # + @info "findFactorsBetweenNaive is naive linear number method -- improvements welcome" + SRT = getVariableLabelNumber(from) + STP = getVariableLabelNumber(to) + prefix = string(from)[1] + @assert prefix == string(to)[1] "from-to prefixes must match, one is $prefix, other $(string(to)[1])" + prev = from + fctlist = Symbol[] + for num = (SRT + 1):STP + next = Symbol(prefix, num) + fct = intersect(ls(dfg, prev), ls(dfg, next)) + if assertSingles + @assert length(fct) == 1 "assertSingles=true, won't return multiple factors joining variables at this time" + end + union!(fctlist, fct) + prev = next + end + + return fctlist +end + +#TODO deprecate `is` is the correct verb, but rather isHomogeneous(path::Path) the form is isAdjective +""" + $SIGNATURES +Return (::Bool,::Vector{TypeName}) of types between two nodes in the factor graph + +DevNotes +- Only works on LigthDFG at the moment. + +Related + +[`findShortestPathDijkstra`](@ref) +""" +# +function isPathFactorsHomogeneous(dfg::AbstractDFG, from::Symbol, to::Symbol) + # FIXME, must consider all paths, not just shortest... + pth = intersect(findShortestPathDijkstra(dfg, from, to), lsf(dfg)) + types = getObservation.(dfg, pth) .|> typeof .|> x -> (x).name #TODO this might not be correct in julia 1.6 + utyp = unique(types) + return (length(utyp) == 1), utyp +end diff --git a/src/DistributedFactorGraphs.jl b/src/DistributedFactorGraphs.jl index f0091560..dab089c8 100644 --- a/src/DistributedFactorGraphs.jl +++ b/src/DistributedFactorGraphs.jl @@ -31,6 +31,7 @@ using TensorCast using ProgressMeter using SHA using FileIO +using MIMEs: mime_from_extension, extension_from_mime import Distributions #TODO this was unused before (if we move SerializingDistributions.jl out we can maybe remove the Distributions dependency?) import Tar @@ -478,6 +479,9 @@ const unstable_functions::Vector{Symbol} = [ :packBlob, :hasTags, :unpackBlob, + :format_to_mime, + :mime_to_format, + :getMimetype, :emptyTags!, :ls, :lsf, diff --git a/src/services/AbstractDFG.jl b/src/services/AbstractDFG.jl index 1ce7b1a5..79c61d5b 100644 --- a/src/services/AbstractDFG.jl +++ b/src/services/AbstractDFG.jl @@ -455,25 +455,6 @@ end ## Subgraphs and Neighborhoods ##============================================================================== -""" - $SIGNATURES -Return (::Bool,::Vector{TypeName}) of types between two nodes in the factor graph - -DevNotes -- Only works on LigthDFG at the moment. - -Related - -[`findShortestPathDijkstra`](@ref) -""" -function isPathFactorsHomogeneous(dfg::AbstractDFG, from::Symbol, to::Symbol) - # FIXME, must consider all paths, not just shortest... - pth = intersect(findShortestPathDijkstra(dfg, from, to), lsf(dfg)) - types = getObservation.(dfg, pth) .|> typeof .|> x -> (x).name #TODO this might not be correct in julia 1.6 - utyp = unique(types) - return (length(utyp) == 1), utyp -end - #TODO add pruning filters that is applied during traversal. """ $(SIGNATURES) diff --git a/src/services/find.jl b/src/services/find.jl index 9c317c43..4309fecb 100644 --- a/src/services/find.jl +++ b/src/services/find.jl @@ -130,39 +130,3 @@ Related [`findFactorsBetweenNaive`](@ref), `Graphs.dijkstra_shortest_paths` """ function findShortestPathDijkstra end - -#TODO deprecate -""" - $SIGNATURES - -Relatively naive function counting linearly from-to - -DevNotes -- Convert to using Graphs shortest path methods instead. -""" -function findFactorsBetweenNaive( - dfg::AbstractDFG, - from::Symbol, - to::Symbol, - assertSingles::Bool = false, -) - # - @info "findFactorsBetweenNaive is naive linear number method -- improvements welcome" - SRT = getVariableLabelNumber(from) - STP = getVariableLabelNumber(to) - prefix = string(from)[1] - @assert prefix == string(to)[1] "from-to prefixes must match, one is $prefix, other $(string(to)[1])" - prev = from - fctlist = Symbol[] - for num = (SRT + 1):STP - next = Symbol(prefix, num) - fct = intersect(ls(dfg, prev), ls(dfg, next)) - if assertSingles - @assert length(fct) == 1 "assertSingles=true, won't return multiple factors joining variables at this time" - end - union!(fctlist, fct) - prev = next - end - - return fctlist -end diff --git a/test/runtests.jl b/test/runtests.jl index 775c1cdc..5881b6b0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -53,6 +53,14 @@ DFG.@usingDFG true include("consol_DataEntryBlobTests.jl") end + @testset "BlobPacking Tests" begin + include("testBlobPacking.jl") + end + + @testset "BlobStores and Wrappers Tests" begin + include("testBlobStoresAndWrappers.jl") + end + @testset "GraphsDFG subtype tests" begin for type in [ (var = VariableSummary, fac = FactorSummary), diff --git a/test/testBlobPacking.jl b/test/testBlobPacking.jl new file mode 100644 index 00000000..685762b6 --- /dev/null +++ b/test/testBlobPacking.jl @@ -0,0 +1,179 @@ +using Test +using DistributedFactorGraphs +using DistributedFactorGraphs: format_to_mime, mime_to_format, _MIMEOverrides +using FileIO + +@testset "BlobPacking" begin + + ##========================================================================== + ## format_to_mime + ##========================================================================== + @testset "format_to_mime" begin + # Standard types auto-detected via FileIO + MIMEs.jl + @test format_to_mime(format"PNG") == MIME("image/png") + @test format_to_mime(format"JPEG") == MIME("image/jpeg") + + # Override types from _MIMEOverrides + @test format_to_mime(format"JSON") == MIME("application/json") + @test format_to_mime(format"BSON") == MIME("application/bson") + @test format_to_mime(format"LAS") == MIME("application/vnd.las") + @test format_to_mime(format"Parquet") == MIME("application/vnd.apache.parquet") + + # Unknown format falls back to application/octet-stream + @test format_to_mime(DataFormat{:SomeUnknownFormat12345}) == + MIME("application/octet-stream") + end + + ##========================================================================== + ## mime_to_format + ##========================================================================== + @testset "mime_to_format" begin + # Standard types auto-detected via MIMEs.jl + FileIO + @test mime_to_format(MIME("image/png")) == format"PNG" + @test mime_to_format(MIME("image/jpeg")) == format"JPEG" + + # Override types + @test mime_to_format(MIME("application/json")) == format"JSON" + @test mime_to_format(MIME("application/bson")) == format"BSON" + @test mime_to_format(MIME("application/vnd.las")) == format"LAS" + @test mime_to_format(MIME("application/vnd.apache.parquet")) == format"Parquet" + + # Unknown MIME returns nothing + @test mime_to_format(MIME("application/x-totally-unknown-12345")) === nothing + end + + ##========================================================================== + ## _MIMEOverrides extensibility + ##========================================================================== + @testset "_MIMEOverrides extensibility" begin + # Extensions (like BlobArrow) can add to _MIMEOverrides + push!(_MIMEOverrides, DataFormat{:TestFormat} => MIME("application/x-test-format")) + @test format_to_mime(DataFormat{:TestFormat}) == MIME("application/x-test-format") + @test mime_to_format(MIME("application/x-test-format")) == DataFormat{:TestFormat} + delete!(_MIMEOverrides, DataFormat{:TestFormat}) + end + + ##========================================================================== + ## packBlob / unpackBlob - JSON + ##========================================================================== + @testset "packBlob/unpackBlob JSON" begin + json_str = """{"name":"John","age":30}""" + + # packBlob + blob, mimetype = DFG.packBlob(format"JSON", json_str) + @test blob isa Vector{UInt8} + @test mimetype == MIME("application/json") + @test length(blob) == length(json_str) + + # unpackBlob via DataFormat + result = DFG.unpackBlob(format"JSON", blob) + @test result == json_str + @test result isa String + + # unpackBlob via MIME + result2 = DFG.unpackBlob(MIME("application/json"), blob) + @test result2 == json_str + + # unpackBlob via String MIME + result3 = DFG.unpackBlob("application/json", blob) + @test result3 == json_str + + # unpackBlob via Blobentry + entry = Blobentry(:test_json; mimetype = MIME("application/json")) + result5 = DFG.unpackBlob(entry, blob) + @test result5 == json_str + + # unpackBlob via Pair{Blobentry, Vector{UInt8}} + result6 = DFG.unpackBlob(entry => blob) + @test result6 == json_str + + # Edge case: empty JSON + blob_empty, mime_empty = DFG.packBlob(format"JSON", "{}") + @test DFG.unpackBlob(format"JSON", blob_empty) == "{}" + @test mime_empty == MIME("application/json") + + # Edge case: JSON array + json_arr = """[1,2,3]""" + blob_arr, _ = DFG.packBlob(format"JSON", json_arr) + @test DFG.unpackBlob(format"JSON", blob_arr) == json_arr + + # Edge case: unicode + json_unicode = """{"emoji":"🎉"}""" + blob_u, _ = DFG.packBlob(format"JSON", json_unicode) + @test DFG.unpackBlob(format"JSON", blob_u) == json_unicode + + # unpackBlob returns a copy (not aliased to input) + blob_copy = copy(blob) + result_copy = DFG.unpackBlob(format"JSON", blob_copy) + blob_copy[1] = 0x00 + @test result_copy == json_str + end + + ##========================================================================== + ## packBlob / unpackBlob - unknown MIME error + ##========================================================================== + @testset "unpackBlob unknown MIME error" begin + blob = Vector{UInt8}("test") + @test_throws ErrorException DFG.unpackBlob( + MIME("application/x-totally-unknown-12345"), + blob, + ) + end + + ##========================================================================== + ## Blobentry mimetype integration + ##========================================================================== + @testset "Blobentry mimetype integration" begin + # Default MIME type + entry = Blobentry(:default_mime) + @test entry.mimetype == MIME("application/octet-stream") + + # Custom MIME type + entry_json = Blobentry(:json_data; mimetype = MIME("application/json")) + @test entry_json.mimetype == MIME("application/json") + + # JSON round-trip through Blobentry + json_str = """{"key":"value"}""" + blob, mimetype = DFG.packBlob(format"JSON", json_str) + entry_with_mime = Blobentry(:my_json; mimetype = mimetype) + @test DFG.unpackBlob(entry_with_mime, blob) == json_str + end + + ##========================================================================== + ## FileIO generic packBlob/unpackBlob + ##========================================================================== + @testset "FileIO generic packBlob/unpackBlob" begin + # Test that the generic FileIO method dispatches and returns correct MIME + # We define a custom DataFormat for testing without needing external packages + # The generic method calls save(Stream{T}(io), data) and load(Stream{T}(io)) + # Test that format_to_mime is called correctly in the generic path + # by verifying the returned mimetype for a known format + @test DFG.format_to_mime(format"JPEG") == MIME("image/jpeg") + @test DFG.format_to_mime(format"PNG") == MIME("image/png") + @test DFG.format_to_mime(format"BSON") == MIME("application/bson") + + # Test the generic path uses format_to_mime + # by adding a custom format to overrides and round-tripping JSON through generic + # (JSON has its own specialization, but BSON/LAS/Parquet would use generic) + + # The generic unpackBlob(::Type{T}, blob) where T<:DataFormat creates an IOBuffer + # and calls load(Stream{T}(io)) - test that it creates the correct stream + blob = Vector{UInt8}("test data") + # This will fail with a load error (no loader for the format) but that + # validates the code path enters the function + @test_throws Exception DFG.unpackBlob(DataFormat{:SomeTestFormat}, blob) + end + + ##========================================================================== + ## getMimetype + ##========================================================================== + @testset "getMimetype" begin + # PNG magic bytes + png_header = UInt8[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] + @test DFG.getMimetype(IOBuffer(png_header)) == MIME("image/png") + + # JPEG magic bytes + jpeg_header = UInt8[0xff, 0xd8, 0xff, 0xe0] + @test DFG.getMimetype(IOBuffer(jpeg_header)) == MIME("image/jpeg") + end +end diff --git a/test/testBlobStoresAndWrappers.jl b/test/testBlobStoresAndWrappers.jl new file mode 100644 index 00000000..9fa96fa1 --- /dev/null +++ b/test/testBlobStoresAndWrappers.jl @@ -0,0 +1,265 @@ +using Test +using UUIDs +using DistributedFactorGraphs: Tables + +testDFGAPI = GraphsDFG + +##============================================================================== +## LinkStore +##============================================================================== +@testset "LinkStore" begin + csvfile = joinpath(tempdir(), "linkstore_test_$(uuid4()).csv") + + # Create new LinkStore (file does not exist) + ls = DFG.LinkStore(:links, csvfile) + @test ls.label == :links + @test ls.csvfile == csvfile + @test isempty(ls.cache) + @test isfile(csvfile) + + # Write a temporary data file to link to + datafile = joinpath(tempdir(), "linkstore_data_$(uuid4()).bin") + test_data = rand(UInt8, 100) + write(datafile, test_data) + + # addBlob! with a link + blobid = uuid4() + @test addBlob!(ls, blobid, datafile) == blobid + @test haskey(ls.cache, blobid) + + # addBlob! duplicate throws + @test_throws DFG.IdExistsError addBlob!(ls, blobid, datafile) + + # getBlob reads through the link + retrieved = getBlob(ls, blobid) + @test retrieved == test_data + + # getBlob for missing id throws + @test_throws DFG.IdNotFoundError getBlob(ls, uuid4()) + + # deleteBlob! is not supported + @test_throws ErrorException deleteBlob!(ls) + @test_throws ErrorException deleteBlob!(ls, uuid4()) + @test_throws ErrorException deleteBlob!(ls, Blobentry(:test)) + + # Re-open existing CSV to test loading from file + ls2 = DFG.LinkStore(:links, csvfile) + @test haskey(ls2.cache, blobid) + @test ls2.cache[blobid] == datafile + @test getBlob(ls2, blobid) == test_data + + # Multiple entries + datafile2 = joinpath(tempdir(), "linkstore_data2_$(uuid4()).bin") + test_data2 = rand(UInt8, 50) + write(datafile2, test_data2) + blobid2 = uuid4() + addBlob!(ls, blobid2, datafile2) + + # Re-open and verify both entries are loaded + ls3 = DFG.LinkStore(:links, csvfile) + @test length(ls3.cache) == 2 + @test getBlob(ls3, blobid) == test_data + @test getBlob(ls3, blobid2) == test_data2 + + # Cleanup + rm(csvfile; force = true) + rm(datafile; force = true) + rm(datafile2; force = true) +end + +##============================================================================== +## RowBlobstore +##============================================================================== +@testset "RowBlobstore" begin + NT = @NamedTuple{a::Vector{Int}, b::Vector{Int}} + + @testset "Construction" begin + store = DFG.RowBlobstore(:test_rows, NT) + @test store.label == :test_rows + @test isempty(store.blobs) + @test store isa DFG.RowBlobstore{NT} + end + + @testset "CRUD operations" begin + store = DFG.RowBlobstore(:test_rows, NT) + + # addBlob! + id1 = uuid4() + blob1 = (a = [1, 2], b = [3, 4]) + @test addBlob!(store, id1, blob1) == id1 + + id2 = uuid4() + blob2 = (a = [5, 6], b = [7, 8]) + addBlob!(store, id2, blob2) + + # addBlob! duplicate throws + @test_throws DFG.IdExistsError addBlob!(store, id1, blob1) + + # getBlob + @test getBlob(store, id1) == blob1 + @test getBlob(store, id2) == blob2 + @test_throws DFG.IdNotFoundError getBlob(store, uuid4()) + + # hasBlob + @test hasBlob(store, id1) + @test !hasBlob(store, uuid4()) + + # listBlobs + ids = listBlobs(store) + @test length(ids) == 2 + @test id1 in ids + @test id2 in ids + + # deleteBlob! + @test deleteBlob!(store, id1) == 1 + @test !hasBlob(store, id1) + @test length(listBlobs(store)) == 1 + @test deleteBlob!(store, uuid4()) == 0 + end + + @testset "RowBlob Tables interface" begin + rb = DFG.RowBlob(uuid4(), (a = [1, 2], b = [3, 4])) + @test Tables.columnnames(rb) == (:id, :a, :b) + @test Tables.getcolumn(rb, 1) isa UUID + @test Tables.getcolumn(rb, 2) == [1, 2] + @test Tables.getcolumn(rb, :id) isa UUID + @test Tables.getcolumn(rb, :a) == [1, 2] + @test Tables.getcolumn(rb, :b) == [3, 4] + end + + @testset "Tables integration" begin + store = DFG.RowBlobstore(:test_rows, NT) + addBlob!(store, uuid4(), (a = [1, 2], b = [3, 4])) + addBlob!(store, uuid4(), (a = [5, 6], b = [7, 8])) + addBlob!(store, uuid4(), (a = [9, 10], b = [11, 12])) + + @test Tables.istable(typeof(store)) + @test Tables.rowaccess(typeof(store)) + + rowtbl = Tables.rowtable(store) + @test length(rowtbl) == 3 + + coltbl = Tables.columntable(rowtbl) + @test length(coltbl.id) == 3 + @test length(coltbl.a) == 3 + end + + @testset "Construct from table" begin + # Build a table, then construct RowBlobstore from it + ids = [uuid4(), uuid4()] + source = + [(id = ids[1], a = [1, 2], b = [3, 4]), (id = ids[2], a = [5, 6], b = [7, 8])] + store = DFG.RowBlobstore(:from_table, NT, source) + @test length(listBlobs(store)) == 2 + @test getBlob(store, ids[1]) == (a = [1, 2], b = [3, 4]) + @test getBlob(store, ids[2]) == (a = [5, 6], b = [7, 8]) + end +end + +##============================================================================== +## loadBlob / saveBlob / deleteBlob wrappers for Factors +##============================================================================== +@testset "loadBlob/saveBlob/deleteBlob Factor" begin + dfg, _, _ = connectivityTestGraph(testDFGAPI, VariableDFG, FactorDFG) + ds = InMemoryBlobstore(:default) + addBlobstore!(dfg, ds) + + dataset = rand(UInt8, 200) + + # saveBlob_Factor! with entry_label + entry = DFG.saveBlob_Factor!(dfg, :x1x2f1, dataset, :factor_data, :default) + @test entry isa Blobentry + @test entry.label == :factor_data + + # loadBlob_Factor + loaded_entry, loaded_blob = DFG.loadBlob_Factor(dfg, :x1x2f1, :factor_data) + @test loaded_entry == entry + @test loaded_blob == dataset + + # saveBlob_Factor! with explicit Blobentry + entry2 = Blobentry(:factor_data_2, :default) + DFG.saveBlob_Factor!(dfg, :x1x2f1, dataset, entry2) + loaded_entry2, loaded_blob2 = DFG.loadBlob_Factor(dfg, :x1x2f1, :factor_data_2) + @test loaded_entry2.label == :factor_data_2 + @test loaded_blob2 == dataset + + # deleteBlob_Factor! + @test DFG.deleteBlob_Factor!(dfg, :x1x2f1, :factor_data) == 2 + @test !hasFactorBlobentry(dfg, :x1x2f1, :factor_data) + + # Multiple factors can have blobs + DFG.saveBlob_Factor!(dfg, :x2x3f1, dataset, :another_blob, :default) + e, b = DFG.loadBlob_Factor(dfg, :x2x3f1, :another_blob) + @test b == dataset + @test DFG.deleteBlob_Factor!(dfg, :x2x3f1, :another_blob) == 2 + + # Cleanup + DFG.deleteBlob_Factor!(dfg, :x1x2f1, :factor_data_2) +end + +##============================================================================== +## loadBlob / saveBlob / deleteBlob wrappers - Variable (expanded) +##============================================================================== +@testset "loadBlob/saveBlob/deleteBlob Variable (expanded)" begin + dfg, _, _ = connectivityTestGraph(testDFGAPI, VariableDFG, FactorDFG) + ds = InMemoryBlobstore(:default) + addBlobstore!(dfg, ds) + + dataset = rand(UInt8, 300) + + # saveBlob_Variable! with entry_label and kwargs + entry = DFG.saveBlob_Variable!( + dfg, + :x1, + dataset, + :var_blob, + :default; + description = "test blob", + ) + @test entry.label == :var_blob + @test entry.description == "test blob" + + # loadBlob_Variable + loaded_entry, loaded_blob = DFG.loadBlob_Variable(dfg, :x1, :var_blob) + @test loaded_entry == entry + @test loaded_blob == dataset + + # saveBlob_Variable! with explicit Blobentry + entry2 = Blobentry(:var_blob_2, :default) + DFG.saveBlob_Variable!(dfg, :x1, dataset, entry2) + _, blob2 = DFG.loadBlob_Variable(dfg, :x1, :var_blob_2) + @test blob2 == dataset + + # deleteBlob_Variable! + @test DFG.deleteBlob_Variable!(dfg, :x1, :var_blob) == 2 + @test DFG.deleteBlob_Variable!(dfg, :x1, :var_blob_2) == 2 +end + +##============================================================================== +## saveImage_Variable! / loadImage_Variable +##============================================================================== +@testset "saveImage_Variable! / loadImage_Variable" begin + dfg, _, _ = connectivityTestGraph(testDFGAPI, VariableDFG, FactorDFG) + ds = InMemoryBlobstore(:default) + addBlobstore!(dfg, ds) + + # Create a small test "image" (matrix of floats, like a grayscale image) + # Use N0f8-like values via simple UInt8 matrix to avoid needing Images.jl + # saveImage_Variable! calls packBlob which needs FileIO save support + # We test that the interface works by checking that it calls through correctly + # For a real image test we'd need ImageIO/PNGFiles, so test the error path + img = rand(Float64, 4, 4) + @test_throws Exception DFG.saveImage_Variable!(dfg, :x1, img, :test_img, :default) + + # Test loadImage_Variable with a JSON blob that has image mimetype set + # (tests the dispatch path through unpackBlob(entry, blob)) + json_str = """{"px":[1,2,3]}""" + blob, _ = DFG.packBlob(format"JSON", json_str) + entry = Blobentry(:json_as_img, :default; mimetype = MIME("application/json")) + DFG.saveBlob_Variable!(dfg, :x1, blob, entry) + loaded_entry, loaded_data = DFG.loadImage_Variable(dfg, :x1, :json_as_img) + @test loaded_entry.label == :json_as_img + @test loaded_data == json_str + + DFG.deleteBlob_Variable!(dfg, :x1, :json_as_img) +end From d537d5acd80e95cd14575eab19c83be742b1537d Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Mon, 23 Mar 2026 20:45:06 +0200 Subject: [PATCH 3/5] fix docs --- src/Deprecated.jl | 1 + src/services/find.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Deprecated.jl b/src/Deprecated.jl index e1ea62a5..a0fdee71 100644 --- a/src/Deprecated.jl +++ b/src/Deprecated.jl @@ -642,6 +642,7 @@ Relatively naive function counting linearly from-to DevNotes - Convert to using Graphs shortest path methods instead. """ +# function findFactorsBetweenNaive( dfg::AbstractDFG, from::Symbol, diff --git a/src/services/find.jl b/src/services/find.jl index 4309fecb..b3bf7cbe 100644 --- a/src/services/find.jl +++ b/src/services/find.jl @@ -127,6 +127,6 @@ DevNotes Related -[`findFactorsBetweenNaive`](@ref), `Graphs.dijkstra_shortest_paths` +`Graphs.dijkstra_shortest_paths` """ function findShortestPathDijkstra end From 4f56398ebda670821c6308ea0e2692948e08b6e0 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche <6612981+Affie@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:24:58 +0200 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/testBlobPacking.jl | 9 ++- test/testBlobStoresAndWrappers.jl | 112 +++++++++++++++--------------- test/testSerializingVariables.jl | 2 +- 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/test/testBlobPacking.jl b/test/testBlobPacking.jl index 685762b6..7a86c453 100644 --- a/test/testBlobPacking.jl +++ b/test/testBlobPacking.jl @@ -48,9 +48,12 @@ using FileIO @testset "_MIMEOverrides extensibility" begin # Extensions (like BlobArrow) can add to _MIMEOverrides push!(_MIMEOverrides, DataFormat{:TestFormat} => MIME("application/x-test-format")) - @test format_to_mime(DataFormat{:TestFormat}) == MIME("application/x-test-format") - @test mime_to_format(MIME("application/x-test-format")) == DataFormat{:TestFormat} - delete!(_MIMEOverrides, DataFormat{:TestFormat}) + try + @test format_to_mime(DataFormat{:TestFormat}) == MIME("application/x-test-format") + @test mime_to_format(MIME("application/x-test-format")) == DataFormat{:TestFormat} + finally + delete!(_MIMEOverrides, DataFormat{:TestFormat}) + end end ##========================================================================== diff --git a/test/testBlobStoresAndWrappers.jl b/test/testBlobStoresAndWrappers.jl index 9fa96fa1..221d87d2 100644 --- a/test/testBlobStoresAndWrappers.jl +++ b/test/testBlobStoresAndWrappers.jl @@ -8,63 +8,63 @@ testDFGAPI = GraphsDFG ## LinkStore ##============================================================================== @testset "LinkStore" begin - csvfile = joinpath(tempdir(), "linkstore_test_$(uuid4()).csv") - - # Create new LinkStore (file does not exist) - ls = DFG.LinkStore(:links, csvfile) - @test ls.label == :links - @test ls.csvfile == csvfile - @test isempty(ls.cache) - @test isfile(csvfile) - - # Write a temporary data file to link to - datafile = joinpath(tempdir(), "linkstore_data_$(uuid4()).bin") - test_data = rand(UInt8, 100) - write(datafile, test_data) - - # addBlob! with a link - blobid = uuid4() - @test addBlob!(ls, blobid, datafile) == blobid - @test haskey(ls.cache, blobid) - - # addBlob! duplicate throws - @test_throws DFG.IdExistsError addBlob!(ls, blobid, datafile) - - # getBlob reads through the link - retrieved = getBlob(ls, blobid) - @test retrieved == test_data - - # getBlob for missing id throws - @test_throws DFG.IdNotFoundError getBlob(ls, uuid4()) - - # deleteBlob! is not supported - @test_throws ErrorException deleteBlob!(ls) - @test_throws ErrorException deleteBlob!(ls, uuid4()) - @test_throws ErrorException deleteBlob!(ls, Blobentry(:test)) - - # Re-open existing CSV to test loading from file - ls2 = DFG.LinkStore(:links, csvfile) - @test haskey(ls2.cache, blobid) - @test ls2.cache[blobid] == datafile - @test getBlob(ls2, blobid) == test_data - - # Multiple entries - datafile2 = joinpath(tempdir(), "linkstore_data2_$(uuid4()).bin") - test_data2 = rand(UInt8, 50) - write(datafile2, test_data2) - blobid2 = uuid4() - addBlob!(ls, blobid2, datafile2) - - # Re-open and verify both entries are loaded - ls3 = DFG.LinkStore(:links, csvfile) - @test length(ls3.cache) == 2 - @test getBlob(ls3, blobid) == test_data - @test getBlob(ls3, blobid2) == test_data2 + tmpdir = mktempdir() + try + csvfile = joinpath(tmpdir, "linkstore_test_$(uuid4()).csv") + + # Create new LinkStore (file does not exist) + ls = DFG.LinkStore(:links, csvfile) + @test ls.label == :links + @test ls.csvfile == csvfile + @test isempty(ls.cache) + @test isfile(csvfile) + + # Write a temporary data file to link to + datafile = joinpath(tmpdir, "linkstore_data_$(uuid4()).bin") + test_data = rand(UInt8, 100) + write(datafile, test_data) + + # addBlob! with a link + blobid = uuid4() + @test addBlob!(ls, blobid, datafile) == blobid + @test haskey(ls.cache, blobid) - # Cleanup - rm(csvfile; force = true) - rm(datafile; force = true) - rm(datafile2; force = true) + # addBlob! duplicate throws + @test_throws DFG.IdExistsError addBlob!(ls, blobid, datafile) + + # getBlob reads through the link + retrieved = getBlob(ls, blobid) + @test retrieved == test_data + + # getBlob for missing id throws + @test_throws DFG.IdNotFoundError getBlob(ls, uuid4()) + + # deleteBlob! is not supported + @test_throws ErrorException deleteBlob!(ls) + @test_throws ErrorException deleteBlob!(ls, uuid4()) + @test_throws ErrorException deleteBlob!(ls, Blobentry(:test)) + + # Re-open existing CSV to test loading from file + ls2 = DFG.LinkStore(:links, csvfile) + @test haskey(ls2.cache, blobid) + @test ls2.cache[blobid] == datafile + @test getBlob(ls2, blobid) == test_data + + # Multiple entries + datafile2 = joinpath(tmpdir, "linkstore_data2_$(uuid4()).bin") + test_data2 = rand(UInt8, 50) + write(datafile2, test_data2) + blobid2 = uuid4() + addBlob!(ls, blobid2, datafile2) + + # Re-open and verify both entries are loaded + ls3 = DFG.LinkStore(:links, csvfile) + @test length(ls3.cache) == 2 + @test getBlob(ls3, blobid) == test_data + @test getBlob(ls3, blobid2) == test_data2 + finally + rm(tmpdir; force = true, recursive = true) + end end ##============================================================================== diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index 1f8fac45..cd6bb3d8 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -93,7 +93,7 @@ end vs = VariableSummary(v) @test vs.label == :x1 @test vs.tags == v.tags - # @test vs.blobentries == v.blobentries + @test vs.solvable == v.solvable end @testset "VariableSkeleton from VariableDFG" begin From fc5ccb6816f5e120a89b455c898240577b756ae0 Mon Sep 17 00:00:00 2001 From: Johannes Terblanche Date: Mon, 23 Mar 2026 21:53:31 +0200 Subject: [PATCH 5/5] fixes and formatting --- ext/BlobArrow.jl | 4 +- src/Common.jl | 2 +- src/DataBlobs/entities/BlobEntry.jl | 14 +++---- src/DataBlobs/services/BlobPacking.jl | 24 ++++++------ src/DataBlobs/services/BlobWrappers.jl | 14 +++---- src/DistributedFactorGraphs.jl | 4 +- src/FileDFG/services/FileDFG.jl | 6 +-- src/GraphsDFG/services/GraphsDFG.jl | 8 ++-- src/entities/DFGFactor.jl | 6 +-- src/services/AbstractDFG.jl | 4 +- test/testBlobPacking.jl | 53 +++++++++++++------------- test/testBlobStoresAndWrappers.jl | 4 +- test/testBlocks.jl | 4 +- test/testSerializingVariables.jl | 2 +- 14 files changed, 76 insertions(+), 73 deletions(-) diff --git a/ext/BlobArrow.jl b/ext/BlobArrow.jl index 126fbad6..de973054 100644 --- a/ext/BlobArrow.jl +++ b/ext/BlobArrow.jl @@ -2,7 +2,7 @@ module BlobArrow using Arrow using DistributedFactorGraphs -using DistributedFactorGraphs: _MIMEOverrides, format_to_mime +using DistributedFactorGraphs: _MIMEOverrides, getMimetype function __init__() @info "Including Arrow blobs support in DFG." @@ -15,7 +15,7 @@ function DFG.packBlob(::Type{format"Arrow"}, data; kwargs...) io = IOBuffer() Arrow.write(io, data; kwargs...) blob = take!(io) - mimetype = format_to_mime(format"Arrow") + mimetype = getMimetype(format"Arrow") return blob, mimetype end diff --git a/src/Common.jl b/src/Common.jl index ea66bd7c..7ced5ad1 100644 --- a/src/Common.jl +++ b/src/Common.jl @@ -214,7 +214,7 @@ function Timestamp(epoch::Val{:unix}, t::Float64, zone = tz"UTC") end Timestamp(t::Float64, zone = tz"UTC") = Timestamp(Val(:unix), t, zone) function Timestamp(epoch::Val{:rata}, t::Float64, zone = tz"UTC") - return TimeDateZone(convert(DateTime, Millisecond(t*10^3)), zone) + return TimeDateZone(convert(DateTime, Millisecond(t * 10^3)), zone) end function now_tdz(zone = tz"UTC") diff --git a/src/DataBlobs/entities/BlobEntry.jl b/src/DataBlobs/entities/BlobEntry.jl index 01b6c1d1..f89cff66 100644 --- a/src/DataBlobs/entities/BlobEntry.jl +++ b/src/DataBlobs/entities/BlobEntry.jl @@ -20,23 +20,23 @@ StructUtils.@kwarg struct Blobentry """ (Optional) crc32c hash value to ensure data consistency which must correspond to the stored hash upon retrieval.""" crchash::Union{UInt32, Nothing} = nothing & ( - json=( - lower = h->isnothing(h) ? nothing : string(h, base = 16), - lift = s->isnothing(s) ? nothing : parse(UInt32, s; base = 16), + json = ( + lower = h -> isnothing(h) ? nothing : string(h; base = 16), + lift = s -> isnothing(s) ? nothing : parse(UInt32, s; base = 16), ) ) """ (Optional) sha256 hash value to ensure data consistency which must correspond to the stored hash upon retrieval.""" shahash::Union{Vector{UInt8}, Nothing} = nothing & ( - json=( - lower = h->isnothing(h) ? nothing : bytes2hex(h), - lift = s->isnothing(s) ? nothing : hex2bytes(s), + json = ( + lower = h -> isnothing(h) ? nothing : bytes2hex(h), + lift = s -> isnothing(s) ? nothing : hex2bytes(s), ) ) """ Source system or application where the blob was created (e.g., webapp, sdk, robot)""" origin::String = "" """Number of bytes in blob serialized as a string""" - size::Int64 = -1 & (json=(lower = string, lift = x->parse(Int64, x))) + size::Int64 = -1 & (json = (lower = string, lift = x -> parse(Int64, x))) """ Additional information that can help a different user of the Blob. """ description::String = "" """ MIME description describing the format of binary data in the `Blob`, e.g. 'image/png' or 'application/json'. """ diff --git a/src/DataBlobs/services/BlobPacking.jl b/src/DataBlobs/services/BlobPacking.jl index db5e750b..bd28858f 100644 --- a/src/DataBlobs/services/BlobPacking.jl +++ b/src/DataBlobs/services/BlobPacking.jl @@ -12,7 +12,7 @@ const _MIMEOverrides = OrderedDict{DataType, MIME}( ) """ - format_to_mime(::Type{DataFormat{S}}) -> MIME + getMimetype(::Type{DataFormat{S}}) -> MIME Get the MIME type for a FileIO `DataFormat`. Uses FileIO's extension registry and MIMEs.jl for standard types, falls back to `_MIMEOverrides` for @@ -20,11 +20,11 @@ domain-specific formats. # Examples ```julia -format_to_mime(format"PNG") # MIME("image/png") -format_to_mime(format"JSON") # MIME("application/json") +getMimetype(format"PNG") # MIME("image/png") +getMimetype(format"JSON") # MIME("application/json") ``` """ -function format_to_mime(::Type{DataFormat{S}}) where {S} +function getMimetype(::Type{DataFormat{S}}) where {S} T = DataFormat{S} haskey(_MIMEOverrides, T) && return _MIMEOverrides[T] try @@ -39,7 +39,7 @@ function format_to_mime(::Type{DataFormat{S}}) where {S} end """ - mime_to_format(::MIME) -> Union{Type{DataFormat{S}}, Nothing} + getDataFormat(::MIME) -> Union{Type{DataFormat{S}}, Nothing} Get the FileIO `DataFormat` for a MIME type. Uses MIMEs.jl and FileIO's extension registry, falls back to `_MIMEOverrides`. @@ -48,11 +48,11 @@ Returns `nothing` if no matching format is found. # Examples ```julia -mime_to_format(MIME("image/png")) # format"PNG" -mime_to_format(MIME("application/json")) # format"JSON" +getDataFormat(MIME("image/png")) # format"PNG" +getDataFormat(MIME("application/json")) # format"JSON" ``` """ -function mime_to_format(m::MIME) +function getDataFormat(m::MIME) for (fmt, mime) in _MIMEOverrides mime == m && return fmt end @@ -78,14 +78,14 @@ function unpackBlob end unpackBlob(mime::String, blob) = unpackBlob(MIME(mime), blob) function unpackBlob(T::MIME, blob) - dataformat = mime_to_format(T) + dataformat = getDataFormat(T) isnothing(dataformat) && error("Format not found for MIME type $(T)") return unpackBlob(dataformat, blob) end # 1. JSON strings are saved as is function packBlob(::Type{format"JSON"}, json_str::String) - mimetype = format_to_mime(format"JSON") + mimetype = getMimetype(format"JSON") blob = Vector{UInt8}(json_str) return blob, mimetype end @@ -102,7 +102,7 @@ function packBlob(::Type{T}, data::Any; kwargs...) where {T <: DataFormat} io = IOBuffer() save(Stream{T}(io), data; kwargs...) blob = take!(io) - mimetype = format_to_mime(T) + mimetype = getMimetype(T) return blob, mimetype end @@ -119,5 +119,5 @@ Detect the MIME type of data in an IO stream using FileIO's format detection. function getMimetype(io::IO) _getFormat(s::FileIO.Stream{T}) where {T} = T stream = FileIO.query(io) - return format_to_mime(_getFormat(stream)) + return getMimetype(_getFormat(stream)) end diff --git a/src/DataBlobs/services/BlobWrappers.jl b/src/DataBlobs/services/BlobWrappers.jl index 07a45adf..63cf55fa 100644 --- a/src/DataBlobs/services/BlobWrappers.jl +++ b/src/DataBlobs/services/BlobWrappers.jl @@ -231,19 +231,19 @@ function saveImage_Variable!( blobstore::Symbol = :default; entry_kwargs..., ) - mimeType = get(entry_kwargs, :mimeType, MIME("image/png")) - format = mime_to_format(mimeType) + mimetype = get(entry_kwargs, :mimeType, MIME("image/png")) + format = getDataFormat(mimetype) + isnothing(format) && + throw(ArgumentError("Unsupported MIME type for image: $(mimetype)")) + blob, mimetype = packBlob(format, img) - blob, mimeType = packBlob(format, img) - - size = string(length(blob)) entry = Blobentry( entry_label, blobstore; blobid = uuid4(), entry_kwargs..., - size, - mimeType = string(mimeType), + size = length(blob), + mimetype, ) return saveBlob_Variable!(dfg, variable_label, blob, entry) diff --git a/src/DistributedFactorGraphs.jl b/src/DistributedFactorGraphs.jl index dab089c8..a3852d05 100644 --- a/src/DistributedFactorGraphs.jl +++ b/src/DistributedFactorGraphs.jl @@ -479,8 +479,8 @@ const unstable_functions::Vector{Symbol} = [ :packBlob, :hasTags, :unpackBlob, - :format_to_mime, - :mime_to_format, + :getMimetype, + :getDataFormat, :getMimetype, :emptyTags!, :ls, diff --git a/src/FileDFG/services/FileDFG.jl b/src/FileDFG/services/FileDFG.jl index 7e1fe25c..01cdbfeb 100644 --- a/src/FileDFG/services/FileDFG.jl +++ b/src/FileDFG/services/FileDFG.jl @@ -119,7 +119,7 @@ function loadDFG!( variablefiles = readdir(joinpath(loaddir, "variables"); sort = false, join = true) # type instability on `variables` as either `::Vector{Variable}` or `::Vector{VariableDFG{<:}}` (vector of abstract) - variables = @showprogress dt=1 desc = "loading variables" asyncmap( + variables = @showprogress dt = 1 desc = "loading variables" asyncmap( variablefiles, ) do file v = JSON.parsefile(file, V; style = DFGJSONStyle()) @@ -130,7 +130,7 @@ function loadDFG!( factorfiles = readdir(joinpath(loaddir, "factors"); sort = false, join = true) - factors = @showprogress dt=1 desc = "loading factors" asyncmap(factorfiles) do file + factors = @showprogress dt = 1 desc = "loading factors" asyncmap(factorfiles) do file f = JSON.parsefile(file, F; style = DFGJSONStyle()) return addFactor!(dfgLoadInto, f) end @@ -139,7 +139,7 @@ function loadDFG!( if isa(dfgLoadInto, GraphsDFG) && getTypeDFGFactors(dfgLoadInto) <: FactorDFG # Finally, rebuild the CCW's for the factors to completely reinflate them - @showprogress dt=1 desc = "Rebuilding factor solver cache" for factor in factors + @showprogress dt = 1 desc = "Rebuilding factor solver cache" for factor in factors rebuildFactorCache!(dfgLoadInto, factor) end end diff --git a/src/GraphsDFG/services/GraphsDFG.jl b/src/GraphsDFG/services/GraphsDFG.jl index 7d8462a2..bbe167e0 100644 --- a/src/GraphsDFG/services/GraphsDFG.jl +++ b/src/GraphsDFG/services/GraphsDFG.jl @@ -239,8 +239,8 @@ function listNeighbors( # Additional filtering # solvable != 0 && filter!(lbl -> _isSolvable(dfg, lbl, solvable), neighbors_ll) - filterDFG!(neighbors_ll, solvableFilter, l->getSolvable(dfg, l)) - filterDFG!(neighbors_ll, tagsFilter, l->listTags(dfg, l)) + filterDFG!(neighbors_ll, solvableFilter, l -> getSolvable(dfg, l)) + filterDFG!(neighbors_ll, tagsFilter, l -> listTags(dfg, l)) # Variable sorting (order is important) if haskey(dfg.g.factors, label) @@ -278,8 +278,8 @@ function listNeighborhood( allvarfacs = [dfg.g.labels[id] for id in nbhood] - filterDFG!(allvarfacs, solvableFilter, l->getSolvable(dfg, l)) - filterDFG!(allvarfacs, tagsFilter, l->listTags(dfg, l)) + filterDFG!(allvarfacs, solvableFilter, l -> getSolvable(dfg, l)) + filterDFG!(allvarfacs, tagsFilter, l -> listTags(dfg, l)) variableLabels = intersect(listVariables(dfg), allvarfacs) factorLabels = intersect(listFactors(dfg), allvarfacs) diff --git a/src/entities/DFGFactor.jl b/src/entities/DFGFactor.jl index f228487c..ffbebe72 100644 --- a/src/entities/DFGFactor.jl +++ b/src/entities/DFGFactor.jl @@ -65,7 +65,7 @@ StructUtils.@kwarg struct FactorDFG{T <: AbstractObservation, N} <: AbstractGrap tags::Set{Symbol} = Set{Symbol}([:FACTOR]) """Ordered list of the neighbor variables. Accessors: [`getVariableOrder`](@ref)""" - variableorder::NTuple{N, Symbol} & (choosetype = x->NTuple{length(x), Symbol},) # NOTE v0.29 renamed from _variableOrderSymbols + variableorder::NTuple{N, Symbol} & (choosetype = x -> NTuple{length(x), Symbol},) # NOTE v0.29 renamed from _variableOrderSymbols """Variable timestamp. Accessors: [`getTimestamp`](@ref)""" timestamp::TimeDateZone = now_tdz() # NOTE v0.29 changed from ZonedDateTime @@ -307,7 +307,7 @@ $(TYPEDFIELDS) tags::Set{Symbol} """Ordered list of the neighbor variables. Accessors: [`getVariableOrder`](@ref)""" - variableorder::Tuple{Vararg{Symbol}} & (choosetype = x->NTuple{length(x), Symbol},) #TODO changed to NTuple + variableorder::Tuple{Vararg{Symbol}} & (choosetype = x -> NTuple{length(x), Symbol},) #TODO changed to NTuple """Variable timestamp. Accessors: [`getTimestamp`](@ref)""" timestamp::TimeDateZone @@ -348,7 +348,7 @@ $(TYPEDFIELDS) tags::Set{Symbol} """Ordered list of the neighbor variables. Accessors: [`getVariableOrder`](@ref)""" - variableorder::Tuple{Vararg{Symbol}} & (choosetype = x->NTuple{length(x), Symbol},) + variableorder::Tuple{Vararg{Symbol}} & (choosetype = x -> NTuple{length(x), Symbol},) end ##------------------------------------------------------------------------------ diff --git a/src/services/AbstractDFG.jl b/src/services/AbstractDFG.jl index 79c61d5b..b95b1373 100644 --- a/src/services/AbstractDFG.jl +++ b/src/services/AbstractDFG.jl @@ -276,7 +276,7 @@ Implement `mergeVariable!(dfg::AbstractDFG, variable::AbstractGraphVariable)` function mergeVariable! end function mergeVariables!(dfg::AbstractDFG, variables::Vector{<:AbstractGraphVariable}) - counts = asyncmap(v->mergeVariable!(dfg, v), variables) + counts = asyncmap(v -> mergeVariable!(dfg, v), variables) return sum(counts; init = 0) end @@ -289,7 +289,7 @@ Implement `mergeFactor!(dfg::AbstractDFG, factor::AbstractGraphFactor)` function mergeFactor! end function mergeFactors!(dfg::AbstractDFG, factors::Vector{<:AbstractGraphFactor}) - counts = asyncmap(f->mergeFactor!(dfg, f), factors) + counts = asyncmap(f -> mergeFactor!(dfg, f), factors) return sum(counts; init = 0) end diff --git a/test/testBlobPacking.jl b/test/testBlobPacking.jl index 7a86c453..4cb36429 100644 --- a/test/testBlobPacking.jl +++ b/test/testBlobPacking.jl @@ -1,45 +1,45 @@ using Test using DistributedFactorGraphs -using DistributedFactorGraphs: format_to_mime, mime_to_format, _MIMEOverrides +using DistributedFactorGraphs: getMimetype, getDataFormat, _MIMEOverrides using FileIO @testset "BlobPacking" begin ##========================================================================== - ## format_to_mime + ## getMimetype ##========================================================================== - @testset "format_to_mime" begin + @testset "getMimetype" begin # Standard types auto-detected via FileIO + MIMEs.jl - @test format_to_mime(format"PNG") == MIME("image/png") - @test format_to_mime(format"JPEG") == MIME("image/jpeg") + @test getMimetype(format"PNG") == MIME("image/png") + @test getMimetype(format"JPEG") == MIME("image/jpeg") # Override types from _MIMEOverrides - @test format_to_mime(format"JSON") == MIME("application/json") - @test format_to_mime(format"BSON") == MIME("application/bson") - @test format_to_mime(format"LAS") == MIME("application/vnd.las") - @test format_to_mime(format"Parquet") == MIME("application/vnd.apache.parquet") + @test getMimetype(format"JSON") == MIME("application/json") + @test getMimetype(format"BSON") == MIME("application/bson") + @test getMimetype(format"LAS") == MIME("application/vnd.las") + @test getMimetype(format"Parquet") == MIME("application/vnd.apache.parquet") # Unknown format falls back to application/octet-stream - @test format_to_mime(DataFormat{:SomeUnknownFormat12345}) == + @test getMimetype(DataFormat{:SomeUnknownFormat12345}) == MIME("application/octet-stream") end ##========================================================================== - ## mime_to_format + ## getDataFormat ##========================================================================== - @testset "mime_to_format" begin + @testset "getDataFormat" begin # Standard types auto-detected via MIMEs.jl + FileIO - @test mime_to_format(MIME("image/png")) == format"PNG" - @test mime_to_format(MIME("image/jpeg")) == format"JPEG" + @test getDataFormat(MIME("image/png")) == format"PNG" + @test getDataFormat(MIME("image/jpeg")) == format"JPEG" # Override types - @test mime_to_format(MIME("application/json")) == format"JSON" - @test mime_to_format(MIME("application/bson")) == format"BSON" - @test mime_to_format(MIME("application/vnd.las")) == format"LAS" - @test mime_to_format(MIME("application/vnd.apache.parquet")) == format"Parquet" + @test getDataFormat(MIME("application/json")) == format"JSON" + @test getDataFormat(MIME("application/bson")) == format"BSON" + @test getDataFormat(MIME("application/vnd.las")) == format"LAS" + @test getDataFormat(MIME("application/vnd.apache.parquet")) == format"Parquet" # Unknown MIME returns nothing - @test mime_to_format(MIME("application/x-totally-unknown-12345")) === nothing + @test getDataFormat(MIME("application/x-totally-unknown-12345")) === nothing end ##========================================================================== @@ -49,8 +49,9 @@ using FileIO # Extensions (like BlobArrow) can add to _MIMEOverrides push!(_MIMEOverrides, DataFormat{:TestFormat} => MIME("application/x-test-format")) try - @test format_to_mime(DataFormat{:TestFormat}) == MIME("application/x-test-format") - @test mime_to_format(MIME("application/x-test-format")) == DataFormat{:TestFormat} + @test getMimetype(DataFormat{:TestFormat}) == MIME("application/x-test-format") + @test getDataFormat(MIME("application/x-test-format")) == + DataFormat{:TestFormat} finally delete!(_MIMEOverrides, DataFormat{:TestFormat}) end @@ -149,13 +150,13 @@ using FileIO # Test that the generic FileIO method dispatches and returns correct MIME # We define a custom DataFormat for testing without needing external packages # The generic method calls save(Stream{T}(io), data) and load(Stream{T}(io)) - # Test that format_to_mime is called correctly in the generic path + # Test that getMimetype is called correctly in the generic path # by verifying the returned mimetype for a known format - @test DFG.format_to_mime(format"JPEG") == MIME("image/jpeg") - @test DFG.format_to_mime(format"PNG") == MIME("image/png") - @test DFG.format_to_mime(format"BSON") == MIME("application/bson") + @test DFG.getMimetype(format"JPEG") == MIME("image/jpeg") + @test DFG.getMimetype(format"PNG") == MIME("image/png") + @test DFG.getMimetype(format"BSON") == MIME("application/bson") - # Test the generic path uses format_to_mime + # Test the generic path uses getMimetype # by adding a custom format to overrides and round-tripping JSON through generic # (JSON has its own specialization, but BSON/LAS/Parquet would use generic) diff --git a/test/testBlobStoresAndWrappers.jl b/test/testBlobStoresAndWrappers.jl index 221d87d2..a7d60fc7 100644 --- a/test/testBlobStoresAndWrappers.jl +++ b/test/testBlobStoresAndWrappers.jl @@ -249,7 +249,9 @@ end # We test that the interface works by checking that it calls through correctly # For a real image test we'd need ImageIO/PNGFiles, so test the error path img = rand(Float64, 4, 4) - @test_throws Exception DFG.saveImage_Variable!(dfg, :x1, img, :test_img, :default) + entry = DFG.saveImage_Variable!(dfg, :x1, img, :test_img, :default) + @test entry.label == :test_img + @test entry.mimetype == MIME("image/png") # Test loadImage_Variable with a JSON blob that has image mimetype set # (tests the dispatch path through unpackBlob(entry, blob)) diff --git a/test/testBlocks.jl b/test/testBlocks.jl index e9508d01..c9978868 100644 --- a/test/testBlocks.jl +++ b/test/testBlocks.jl @@ -1200,8 +1200,8 @@ function testGroup!(fg, v1, v2, f0, f1) @test lsf(fg; tagsFilter = ⊇([:PRIOR])) == [:af1] # Regexes - @test ls(fg, labelFilter = contains(r"a")) == [v1.label] - @test lsf(fg, labelFilter = contains(r"abf*")) == [f1.label] + @test ls(fg; labelFilter = contains(r"a")) == [v1.label] + @test lsf(fg; labelFilter = contains(r"abf*")) == [f1.label] #TODO test filters and options # regexFilter::Union{Nothing, Regex}=nothing; diff --git a/test/testSerializingVariables.jl b/test/testSerializingVariables.jl index cd6bb3d8..d3c5fe97 100644 --- a/test/testSerializingVariables.jl +++ b/test/testSerializingVariables.jl @@ -93,7 +93,7 @@ end vs = VariableSummary(v) @test vs.label == :x1 @test vs.tags == v.tags - @test vs.solvable == v.solvable + @test vs.solvable[] == v.solvable[] end @testset "VariableSkeleton from VariableDFG" begin