Skip to content

Commit

Permalink
Test for AuxException cases
Browse files Browse the repository at this point in the history
Also make sure that AuxExceptions are thrown more often, where possible
  • Loading branch information
jakobnissen committed Oct 18, 2024
1 parent 4ba1617 commit 959c69a
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 12 deletions.
17 changes: 14 additions & 3 deletions src/XAMAuxData.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -180,6 +190,7 @@ const ELTYPE_DICT = Dict(
)

is_printable_char(x::UInt8) = in(x, UInt8('!'):UInt8('~'))
is_printable_char(c::Char) = '!' c '~'

"""
Errors
Expand Down
8 changes: 4 additions & 4 deletions src/auxtag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions src/bam.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/delimited.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 16 additions & 2 deletions src/sam.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
87 changes: 87 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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!"]
@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"])
Expand Down Expand Up @@ -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}()
Expand Down Expand Up @@ -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}()
Expand Down Expand Up @@ -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 <AuxTag>:<type tag>:, 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

0 comments on commit 959c69a

Please sign in to comment.