Skip to content

Commit

Permalink
Refactor and simplify src/utils.jl (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Mar 19, 2024
1 parent 58c4d5c commit b4ffcf2
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 152 deletions.
2 changes: 0 additions & 2 deletions src/Xpress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const libxprs = joinpath(
)

include("Lib/Lib.jl")

include("utils.jl")
include("helper.jl")
include("api.jl")
include("xprs_callbacks.jl")
Expand Down
90 changes: 85 additions & 5 deletions src/helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,91 @@ Base.cconvert(::Type{Ptr{Cvoid}}, prob::XpressProblem) = prob

Base.unsafe_convert(::Type{Ptr{Cvoid}}, prob::XpressProblem) = prob.ptr

function _invoke(f::Function, pos::Int, ::Type{Int}, args...)
out = Ref{Cint}(0)
args = collect(args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return out[]
end

function _invoke(f::Function, pos::Int, ::Type{Float64}, args...)
out = Ref{Float64}(0.0)
args = collect(args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return out[]
end

function _invoke(f::Function, pos::Int, ::Type{String}, args...)
buffer = Array{Cchar}(undef, 1024)
GC.@preserve buffer begin
out = Cstring(pointer(buffer))
args = collect(Any, args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return unsafe_string(out)
end
end

macro _invoke(expr)
@assert Meta.isexpr(expr, :(::)) "macro argument must have return type declaration"
f, return_type = expr.args
@assert Meta.isexpr(f, :call) "macro argument must have a function call"
invoke_expr = Expr(:call, _invoke, f.args[1], 0, return_type)
for i in 2:length(f.args)
if f.args[i] == :_
invoke_expr.args[3] = i - 1
else
push!(invoke_expr.args, esc(f.args[i]))
end
end
return invoke_expr
end

"""
@checked f(prob)
Lets you invoke a lower level `Lib` function and check that Xpress does not
error.
Use this macro to minimize repetition and increase readability.
The first argument must be a object that can be cast into an Xpress pointer,
e.g., `Ptr{XpressProblem}`.
This is passed to `get_xpress_error_message(xprs_ptr)` to get the error message.
## Example
```julia
@checked Lib.XPRSsetprobname(prob, name)
```
"""
macro checked(expr)
@assert expr.head == :call "Can only use @checked on function calls"
@assert (expr.args[1].head == :(.)) && (expr.args[1].args[1] == :Lib) "Can only use @checked on Lib.\$function"
@assert length(expr.args) >= 2 "Lib.\$function must be contain atleast one argument and the first argument must be of type XpressProblem"
prob = expr.args[2]
return quote
val = $(esc(expr))::Cint
if val != 0
e = get_xpress_error_message($(esc(prob)))
throw(XpressError(val, "Xpress internal error:\n\n$e.\n"))
end
end
end

function get_xpress_error_message(prob)
return lstrip(@_invoke(Lib.XPRSgetlasterror(prob, _)::String), ['?'])
end

function getattribute(prob::XpressProblem, name::String)
p_id, p_type = Ref{Cint}(), Ref{Cint}()
Lib.XPRSgetattribinfo(prob, name, p_id, p_type)
Expand Down Expand Up @@ -128,11 +213,6 @@ get_banner() = @_invoke Lib.XPRSgetbanner(_)::String

get_version() = VersionNumber(@_invoke Lib.XPRSgetversion(_)::String)

function get_xpress_error_message(prob::XpressProblem)
last_error = @_invoke Lib.XPRSgetlasterror(prob, _)::String
return lstrip(last_error, ['?'])
end

"""
show(io::IO, prob::XpressProblem)
Expand Down
135 changes: 0 additions & 135 deletions src/utils.jl

This file was deleted.

10 changes: 0 additions & 10 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,3 @@ println("Optimizer version: $(Xpress.get_version())")
@testset "$f" for f in filter!(f -> startswith(f, "test_"), readdir(@__DIR__))
include(f)
end

@testset "Xpress tests" begin
prob = Xpress.XpressProblem()
@test Xpress.getcontrol(prob, "HEURTHREADS") == 0
r = Xpress.Lib.XPRSreadprob(prob, "", "")
if Xpress.get_version() >= v"41.0.0"
msg = "Xpress internal error:\n\n85 Error: File not found: .\n"
@test_throws Xpress.XpressError(85, msg) Xpress._check(prob, r)
end
end
36 changes: 36 additions & 0 deletions test/test_helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module TestHelper
using Xpress
using Test

import Xpress.Lib as Lib

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
Expand Down Expand Up @@ -83,6 +85,40 @@ function test_XpressProblem_show()
return
end

function test_checked()
if Xpress.get_version() < v"41.0.0"
return
end
prob = Xpress.XpressProblem()
msg = "Xpress internal error:\n\n85 Error: File not found: .\n"
@test_throws(
Xpress.XpressError(85, msg),
Xpress.@checked(Lib.XPRSreadprob(prob, "", "")),
)
return
end

function test_invoke_errors()
prob = C_NULL
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(
Lib.XPRSgetintattrib(prob, Lib.XPRS_ORIGINALCOLS, _)::Int,
),
)
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(
Lib.XPRSgetdblattrib(prob, Lib.XPRS_OBJSENSE, _)::Float64,
),
)
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(Lib.XPRSgetlasterror(prob, _)::String)
)
return
end

end # TestHelper

TestHelper.runtests()

0 comments on commit b4ffcf2

Please sign in to comment.