From 959c69a8f77259d56c100c6b917790914ab510a3 Mon Sep 17 00:00:00 2001 From: Jakob Nybo Nissen Date: Fri, 18 Oct 2024 13:20:28 +0200 Subject: [PATCH] Test for AuxException cases Also make sure that AuxExceptions are thrown more often, where possible --- src/XAMAuxData.jl | 17 +++++++-- src/auxtag.jl | 8 ++--- src/bam.jl | 5 +-- src/delimited.jl | 2 +- src/sam.jl | 18 ++++++++-- test/runtests.jl | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/XAMAuxData.jl b/src/XAMAuxData.jl index 1f3c970..fd79655 100644 --- a/src/XAMAuxData.jl +++ b/src/XAMAuxData.jl @@ -11,6 +11,7 @@ public Errors, Error # These are the numerical types supported by the BAM format. const AUX_NUMBER_TYPES = Union{Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32} +const BIT_INTEGERS = Union{UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64, UInt128, Int128} include("auxtag.jl") @@ -80,11 +81,20 @@ function as_aux_value end as_sam_aux_value(x) = as_aux_value(x) as_bam_aux_value(x) = as_aux_value(x) -as_aux_value(x::Real) = Float32(x)::Float32 +as_aux_value(x::Float32) = x +as_aux_value(x::Float16) = Float32(x) -function as_aux_value(x::AbstractChar)::Union{Char, Error} +function as_aux_value(x::Real) + f = Float32(x) + if !isinf(x) & isinf(f) + throw(AuxException(Errors.InvalidFloat)) + end + f +end + +function as_aux_value(x::AbstractChar)::Char c = Char(x)::Char - isascii(c) ? c : throw(AuxException(Errors.InvalidChar)) + is_printable_char(c) ? c : throw(AuxException(Errors.InvalidChar)) end as_aux_value(x::Hex) = x @@ -180,6 +190,7 @@ const ELTYPE_DICT = Dict( ) is_printable_char(x::UInt8) = in(x, UInt8('!'):UInt8('~')) +is_printable_char(c::Char) = '!' ≤ c ≤ '~' """ Errors diff --git a/src/auxtag.jl b/src/auxtag.jl index d1364cf..9852f36 100644 --- a/src/auxtag.jl +++ b/src/auxtag.jl @@ -71,10 +71,10 @@ function try_auxtag(x::Union{String, SubString{String}}) try_auxtag(cu1, cu2) end -function try_auxtag(x::AbstractString) - (x, s) = @something iterate(x) return nothing - (y, s) = @something iterate(x, s) return nothing - isnothing(iterate(x, s)) || return nothing +function try_auxtag(str::AbstractString) + (x, s) = @something iterate(str) return nothing + (y, s) = @something iterate(str, s) return nothing + isnothing(iterate(str, s)) || return nothing try_auxtag(Char(x)::Char, Char(y)::Char) end diff --git a/src/bam.jl b/src/bam.jl index 14371c8..24fc6ee 100644 --- a/src/bam.jl +++ b/src/bam.jl @@ -266,8 +266,9 @@ function bytes_needed(x::AbstractVector{<:AUX_NUMBER_TYPES}) end as_bam_aux_value(x::AUX_NUMBER_TYPES) = x -as_bam_aux_value(x::Signed) = Int32(x) -as_bam_aux_value(x::Unsigned) = UInt32(x) + +as_bam_aux_value(x::Signed) = Int32(x)::Int32 +as_bam_aux_value(x::Unsigned) = UInt32(x)::UInt32 function Base.setindex!(aux::MutableAuxiliary, val, k) key = convert(AuxTag, k) diff --git a/src/delimited.jl b/src/delimited.jl index 7676d25..84ab126 100644 --- a/src/delimited.jl +++ b/src/delimited.jl @@ -11,7 +11,7 @@ end function DelimitedIterator(x, d::T) where {T} v = ImmutableMemoryView(x) - eltype(v) == T || error("MemoryView(x) must be of eltype T") # TODO + eltype(v) == T || error("MemoryView element type is different from delimiter type") DelimitedIterator{T}(v, d) end diff --git a/src/sam.jl b/src/sam.jl index 9764fc8..e8890f5 100644 --- a/src/sam.jl +++ b/src/sam.jl @@ -13,7 +13,7 @@ import ..AuxTag, ..AbstractAuxiliary, ..ELTYPE_DICT, ..is_printable_char, ..is_p import ..DelimitedIterator, ..get_type_tag, ..Error, ..Errors, ..load_hex, ..validate_hex import ..try_auxtag, ..Unsafe, ..as_sam_aux_value, ..AUX_NUMBER_TYPES, ..hexencode! import ..iter_encodings, ..AbstractEncodedIterator, ..AuxException, ..striptype -import ..is_well_formed +import ..is_well_formed, ..BIT_INTEGERS public Auxiliary, AuxTag, Hex, Errors, Error @@ -230,7 +230,21 @@ function load_auxvalue(type_tag::UInt8, mem::ImmutableMemoryView{UInt8}) end end -as_sam_aux_value(x::Integer) = Int32(x)::Int32 +function as_sam_aux_value(x::Integer)::Int32 + try + return Int32(x)::Int32 + catch + throw(AuxException(Errors.InvalidInt)) + end +end + +function as_sam_aux_value(x::BIT_INTEGERS)::Int32 + if (x < typemin(Int32)) | (x > typemax(Int32)) + throw(AuxException(Errors.InvalidInt)) + else + x % Int32 + end +end function setindex_nonexisting!(aux::MutableAuxiliary, val, key::AuxTag) v = as_sam_aux_value(val) diff --git a/test/runtests.jl b/test/runtests.jl index 8891010..d5edc82 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,9 +35,11 @@ end @test AuxTag("ac") == AuxTag('a', 'c') == AuxTag(0x61, 0x63) for good in ["AB", "A1", "a9", "w5"] @test try_auxtag(good) isa AuxTag + @test try_auxtag(Test.GenericString(good)) isa AuxTag end for bad in ["", "A", "ABC", "A11", "5A", "AØ", "a!"] @test try_auxtag(bad) === nothing + @test try_auxtag(Test.GenericString(bad)) === nothing end @test sort!(AuxTag.(["XA", "X1", "ab", "AB"])) == AuxTag.(["AB", "X1", "XA", "ab"]) @@ -83,6 +85,19 @@ end @test aux["hc"] == Errors.InvalidTypeTag end +@testset "Display" begin + aux = SAM.Auxiliary("AB:i:242\tkV:Z:abcdef gh \tJJ:A:!\tzZ:f:-1.2") + buf = IOBuffer() + show(buf, MIME"text/plain"(), aux) + str = String(take!(buf)) + @test str == """ + 4-element XAMAuxData.SAM.Auxiliary{MemoryViews.ImmutableMemoryView{UInt8}}: + "AB" => 242 + "kV" => "abcdef gh " + "JJ" => '!' + "zZ" => -1.2f0""" +end + @testset "Mutating" begin aux = SAM.Auxiliary(UInt8[], 1) d = Dict{AuxTag, Any}() @@ -340,6 +355,19 @@ end # SAM end end +@testset "Display" begin + aux = BAM.Auxiliary("ABS\0\xf2kVZabcdef gh \0JJA!zZf\x9a\x99\x99\xbf") + buf = IOBuffer() + show(buf, MIME"text/plain"(), aux) + str = String(take!(buf)) + @test str == """ + 4-element XAMAuxData.BAM.Auxiliary{MemoryViews.ImmutableMemoryView{UInt8}}: + "AB" => 0xf200 + "kV" => "abcdef gh " + "JJ" => '!' + "zZ" => -1.2f0""" +end + @testset "Mutating" begin aux = BAM.Auxiliary(UInt8[], 1) d = Dict{AuxTag, Any}() @@ -632,4 +660,63 @@ end end end +@testset "Exception types" begin + @test_throws( + "Not enough bytes remaining in Aux data to encode a full field", + first(values(SAM.Auxiliary("\t"))), + ) + + @test_throws( + "Invalid AuxTag. Tags must conform to r\"^[A-Za-z][A-Za-z0-9]\$\".", + first(values(SAM.Auxiliary("11:i:1"))), + ) + + @test_throws( + "Invalid SAM tag header. Expected ::, but found no colons.", + first(values(SAM.Auxiliary("ABi111"))), + ) + + @test_throws( + "BAM string or Hex type not terminated by null byte", + BAM.Auxiliary("ABZabc")["AB"], + ) + + @test_throws( + "BAM string or Hex type not terminated by null byte", + BAM.Auxiliary("ABH1234")["AB"], + ) + + @test_throws( + "Unknown type tag in aux value.", + BAM.Auxiliary("ABW123")["AB"], + ) + + @test_throws( + "Invalid array element type tag. Valid values are CcSsIif.", + BAM.Auxiliary("ABBW1234")["AB"], + ) + + @test_throws( + "Auxiliary Char (type 'A') must be in '!':'~'.", + SAM.Auxiliary(UInt8[], 1)["AB"] = '\n', + ) + + @test_throws( + "Data in SAM Auxiliary cannot be parsed as base-ten Int32.", + SAM.Auxiliary(UInt8[], 1)["AB"] = typemax(Int64) + ) + + @test_throws( + "Data in SAM Auxiliary cannot be parsed as Float32.", + SAM.Auxiliary(UInt8[], 1)["AB"] = 22e304, + ) + @test SAM.Auxiliary(UInt8[], 1)["AB"] = Inf64 isa Any + @test SAM.Auxiliary(UInt8[], 1)["AB"] = -Inf64 isa Any + + @test_throws( + "Auxiliary String (type 'Z') can only contain bytes in re\"[ !-~]\".", + SAM.Auxiliary(UInt8[], 1)["AB"] = "Rødgrød med fløde" + ) +end + end # module