Skip to content

Commit

Permalink
Tests (#66)
Browse files Browse the repository at this point in the history
* Add devcontainer and Dependencies

* template for FMI2/3 tests

* s

* Unzip and Load

* switch container to 22.04

* add os and architecture distinction and check

* add passing test to os change

* Working Tests for Instantiate, Free, GetVersion and GetTypesPlatform

* create utils.jl

* os check in utils

* Get/SetStatus Tests, type checking

* typo

* SetDebugLogging

* Reset + Terminate

* FreeState

* serializedsize

* typo

* Getter and Setter

* get/set and directionalderivative

* ME-specific Functions

* move type check to utils

* Testsets

* some CS Tests

* logging?

* ähh

* working generic and ME-specific functions

* remove component typecast

* try internal callback

* fix terminate test with logger

* add os distinction to cblibpath

* add binaries for linux and mac

* add function to create callbacks

* stop logging

* CS Get/Set Tests

* Get and Set Real Input and Output Derivatives CS

* split files

* remove todo

* remove devcontainer

* add download for callbacks

* fix download dll on windows

* replace OS-Check Test with warning and skip

* add begin and end to testsets for 1.6 compatibility

* remove Type Checks and "fix" 32 Bit Tests

* Add Testing with IO FMU and Tests for fmi2String and fmi2Boolean

* move CS Get/Set OutputDerivatives to IO-FMU

* Supress expected Warnings and CVODE Stats

* point to right 32 bit binaries

* remove unnecessary DoStep that Errors on 32 Bit for some reason

* decrease dostep size

* change stepsize back

* only Test DoStep on x64

---------

Co-authored-by: TT <[email protected]>
Co-authored-by: ThummeTo <[email protected]>
  • Loading branch information
3 people authored Aug 27, 2024
1 parent 8ab6407 commit f07407f
Show file tree
Hide file tree
Showing 9 changed files with 474 additions and 19 deletions.
23 changes: 6 additions & 17 deletions src/FMI2/cfunc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,23 +91,12 @@ Source: FMISpec2.0.2[p.22]: 2.1.5 Creation, Destruction and Logging of FMU Insta
The function controls debug logging that is output via the logger function callback. If loggingOn = fmi2True, debug logging is enabled, otherwise it is switched off.
"""
function fmi2SetDebugLogging(
cfunc::Ptr{Cvoid},
c::fmi2Component,
logginOn::fmi2Boolean,
nCategories::Csize_t,
categories::Union{Ptr{fmi2String},AbstractArray{fmi2String}},
)::fmi2Status
status = ccall(
cfunc,
fmi2Status,
(fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String}),
c,
logginOn,
nCategories,
categories,
)
@debug "fmi2SetDebugLogging(c: $(c), logginOn: $(loggingOn), nCategories: $(nCategories), categories: $(categories)) → $(status)"
function fmi2SetDebugLogging(cfunc::Ptr{Cvoid}, c::fmi2Component, loggingOn::fmi2Boolean, nCategories::Csize_t, categories::Union{Ptr{fmi2String}, AbstractArray{fmi2String}})
status = ccall(cfunc,
fmi2Status,
(fmi2Component, fmi2Boolean, Csize_t, Ptr{fmi2String}),
c, loggingOn, nCategories, categories)
@debug "fmi2SetDebugLogging(c: $(c), loggingOn: $(loggingOn), nCategories: $(nCategories), categories: $(categories)) → $(status)"
return status
end
export fmi2SetDebugLogging
Expand Down
72 changes: 72 additions & 0 deletions test/FMI2/CS.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using Libdl, Suppressor

function test_CS(lib, cblibpath)
component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_cs"), fmi2TypeCoSimulation, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false))

startpoint = fmi2Real(0.0)
@test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(true), fmi2Real(0.01), startpoint, fmi2Boolean(false), fmi2Real(1.0))


fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component)
fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component)
if Sys.WORD_SIZE == 64
@test fmi2StatusOK == fmi2DoStep(dlsym(lib, :fmi2DoStep), component, fmi2Real(0.0), fmi2Real(0.01), fmi2False)
end


status = fmi2Real(0.0)
statusptr = pointer([status])
@test fmi2StatusOK == fmi2GetRealStatus!(dlsym(lib, :fmi2GetRealStatus), component, fmi2StatusKindLastSuccessfulTime, Ptr{fmi2Real}(statusptr))

@suppress begin
# Suppressing the IllegalFunctionCall Warnings, as they are expected here
# Async is not supported in this FMU, so the status should be fmi2StatusDiscard
status = fmi2Status(fmi2StatusOK)
statusptr = pointer([status])
@test fmi2StatusDiscard == fmi2GetStatus!(dlsym(lib, :fmi2GetStatus), component, fmi2StatusKindDoStepStatus, Ptr{fmi2Status}(statusptr))


status = fmi2Integer(0)
statusptr = pointer([status])
# Async is not supported in this FMU, so the status should be fmi2StatusDiscard
@test fmi2StatusDiscard == fmi2GetIntegerStatus!(dlsym(lib, :fmi2GetIntegerStatus), component, Cuint(2), Ptr{fmi2Integer}(statusptr))

status = fmi2Boolean(false)
statusptr = pointer([status])
# Async is not supported in this FMU, so the status should be fmi2StatusDiscard
@test fmi2StatusDiscard == fmi2GetBooleanStatus!(dlsym(lib, :fmi2GetBooleanStatus), component, Cuint(2), Ptr{fmi2Boolean}(statusptr))

status = "test"
statusptr = pointer(status)
# Async is not supported in this FMU, so the status should be fmi2StatusDiscard
@test fmi2StatusDiscard == fmi2GetStringStatus!(dlsym(lib, :fmi2GetStringStatus), component, Cuint(2), Ptr{fmi2String}(statusptr))
end

fmi2Terminate(dlsym(lib, :fmi2Terminate), component)

end

function test_CS_IO(lib, cblibpath)
component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic_io"), fmi2TypeCoSimulation, pointer("{95a6399d-38c5-4504-b3f3-98319bd94ce6}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false))
@test component != C_NULL
@test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0))

@test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component)
@test fmi2StatusOK == fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component)


fmireference = [fmi2ValueReference(352321536)]
@test fmi2StatusOK == fmi2SetRealInputDerivatives(dlsym(lib, :fmi2SetRealInputDerivatives), component, fmireference, Csize_t(1), [fmi2Integer(1)], fmi2Real.([1.0]))

fmireference = [fmi2ValueReference(335544320)]
values = zeros(fmi2Real, 1)
@test fmi2StatusOK == fmi2GetRealOutputDerivatives!(dlsym(lib, :fmi2GetRealOutputDerivatives), component, fmireference, Csize_t(1), [fmi2Integer(1)], values)




end
56 changes: 56 additions & 0 deletions test/FMI2/ME.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using Libdl

function test_ME(lib, cblibpath)
component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_me"), fmi2TypeModelExchange, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false))
@test component != C_NULL

fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component)
fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component)

@test fmi2StatusOK == fmi2EnterEventMode(dlsym(lib, :fmi2Instantiate), component)

eventInfo = fmi2EventInfo()
ptr = Ptr{fmi2EventInfo}(pointer_from_objref(eventInfo))

@test fmi2StatusOK == fmi2NewDiscreteStates!(dlsym(lib, :fmi2NewDiscreteStates), component, ptr)

@test fmi2StatusOK == fmi2EnterContinuousTimeMode(dlsym(lib, :fmi2EnterContinuousTimeMode), component)

enterEventMode = fmi2Boolean(false)
terminateSimulation = fmi2Boolean(false)
@test fmi2StatusOK == fmi2CompletedIntegratorStep!(dlsym(lib, :fmi2CompletedIntegratorStep), component, fmi2Boolean(false), pointer([enterEventMode]), pointer([terminateSimulation]))

@test fmi2StatusOK == fmi2SetTime(dlsym(lib, :fmi2SetTime), component, fmi2Real(0.0))

n_states = Csize_t(2)
state_arr = zeros(fmi2Real, 2)
@test fmi2StatusOK == fmi2GetContinuousStates!(dlsym(lib, :fmi2GetContinuousStates), component,state_arr, n_states)

state_arr[2] = 2.0
@test fmi2StatusOK == fmi2SetContinuousStates(dlsym(lib, :fmi2SetContinuousStates), component,state_arr, n_states)

state_arr = zeros(fmi2Real, 2)
@test fmi2StatusOK == fmi2GetContinuousStates!(dlsym(lib, :fmi2GetContinuousStates), component,state_arr, n_states)
@test state_arr[2] == 2.0

n_indicators = Csize_t(2)
indicator_arr = zeros(fmi2Real, 2)
@test fmi2StatusOK == fmi2GetEventIndicators!(dlsym(lib, :fmi2GetEventIndicators), component,indicator_arr, n_indicators)

nom_state_arr = zeros(fmi2Real, 2)
@test fmi2StatusOK == fmi2GetNominalsOfContinuousStates!(dlsym(lib, :fmi2GetNominalsOfContinuousStates), component, nom_state_arr, n_states)

der_arr = zeros(fmi2Real, 2)
@test fmi2StatusOK == fmi2GetDerivatives!(dlsym(lib, :fmi2GetDerivatives), component,der_arr, n_states)
# Acceleration should be equal to Gravity in this FMU
if Sys.WORD_SIZE == 64
# on 32 Bit this returns 9.81 * 10^16 which is not equal to -9.81
@test der_arr[2] -9.81
end

@test fmi2StatusOK == fmi2Terminate(dlsym(lib, :fmi2Terminate), component)
end
22 changes: 21 additions & 1 deletion test/FMI2/cfunc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,24 @@
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

# [ToDo] tests for FMI2
using Libdl

include.(["utils.jl", "ME.jl", "CS.jl", "generic.jl"])


binarypath, fmu_path, cblibpath = get_os_binaries()
iopath, io_fmu_path, iocblibpath = get_os_binaries("IO")
if binarypath != ""
lib = dlopen(binarypath)
libio = dlopen(iopath)
# Missing Tests for fmi2<Set, Get><Boolean, String> because the FMU we are testing with doesnt variables of these types
@testset "Generic Functions in ME Mode" begin test_generic(lib,cblibpath, fmi2TypeModelExchange) end
@testset "Generic Functions in ME Mode with IO FMU" begin test_generic_io(libio,iocblibpath, fmi2TypeModelExchange) end
@testset "Generic Functions in CS Mode with IO FMU" begin test_generic_io(libio,iocblibpath, fmi2TypeCoSimulation) end
@testset "Generic Functions in CS Mode" begin test_generic(lib,cblibpath, fmi2TypeCoSimulation) end
@testset "ME-specific Functions" begin test_ME(lib, cblibpath) end
@testset "CS-specific Functions" begin test_CS(lib, cblibpath) end
@testset "CS-specific Functions with IO FMU" begin test_CS_IO(libio, iocblibpath) end
else
@warn "No valid FMU binaries found for this OS. Skipping tests."
end
148 changes: 148 additions & 0 deletions test/FMI2/generic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using Libdl, Suppressor

function test_generic(lib, cblibpath, type::fmi2Type)
# Tests missing for fmi2<Set, Get><Boolean, String> because the FMU we are testing with doesnt have such variables

cGetTypesPlatform = dlsym(lib, :fmi2GetTypesPlatform)
test = fmi2GetTypesPlatform(cGetTypesPlatform)
@test unsafe_string(test) == "default"

## fmi2GetVersion TODO get Version from modelDescription.xml
vers = fmi2GetVersion(dlsym(lib, :fmi2GetVersion))
@test unsafe_string(vers) == "2.0"

# fmi2Instantiate

component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic"), type, pointer("{3c564ab6-a92a-48ca-ae7d-591f819b1d93}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false))
@test component != C_NULL

@test fmi2StatusOK == fmi2SetDebugLogging(dlsym(lib, :fmi2SetDebugLogging), component, fmi2False, Unsigned(0), AbstractArray{fmi2String}([]))
@test fmi2StatusOK == fmi2SetDebugLogging(dlsym(lib, :fmi2SetDebugLogging), component, fmi2True, Unsigned(0), AbstractArray{fmi2String}([]))

# fmi2SetupExperiment

@test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0))



# get, set and free State
state = fmi2FMUstate()
stateRef = Ref(state)

@test fmi2StatusOK == fmi2GetFMUstate!(dlsym(lib, :fmi2GetFMUstate), component, stateRef)
state = stateRef[]

@test typeof(state) == fmi2FMUstate

@test fmi2StatusOK == fmi2SetFMUstate(dlsym(lib, :fmi2SetFMUstate), component, state)
stateRef = Ref(state)

size_ptr = Ref{Csize_t}(0)
@test fmi2StatusOK == fmi2SerializedFMUstateSize!(dlsym(lib, :fmi2SerializedFMUstateSize), component, state, size_ptr)
size = size_ptr[]

serializedState = Array{fmi2Byte}(zeros(size))
@test fmi2StatusOK == fmi2SerializeFMUstate!(dlsym(lib,:fmi2SerializeFMUstate), component, state, serializedState, size)

@test fmi2StatusOK == fmi2DeSerializeFMUstate!(dlsym(lib, :fmi2DeSerializeFMUstate), component, serializedState, size, stateRef)

@test stateRef[] != C_NULL
@test fmi2StatusOK == fmi2FreeFMUstate(dlsym(lib,:fmi2FreeFMUstate), component, stateRef)
@test stateRef[] == C_NULL



# # Initialization Mode

@test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component)

fmireference = [fmi2ValueReference(16777220)]
@test fmi2StatusOK == fmi2SetReal(dlsym(lib, :fmi2SetReal), component, fmireference, Csize_t(1), fmi2Real.([0.8]))

fmireference = [fmi2ValueReference(16777220)]
value = zeros(fmi2Real, 1)
@test fmi2StatusOK == fmi2GetReal!(dlsym(lib, :fmi2GetReal), component, fmireference, Csize_t(1), value)
@test value == fmi2Real.([0.8])

fmireference = [fmi2ValueReference(637534208)]
value = zeros(fmi2Integer, 1)
@test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value)

fmireference = [fmi2ValueReference(637534208)]
@test fmi2StatusOK == fmi2SetInteger(dlsym(lib, :fmi2SetInteger), component, fmireference, Csize_t(1), fmi2Integer.([typemin(fmi2Integer)]))

fmireference = [fmi2ValueReference(637534208)]
value = zeros(fmi2Integer, 1)
@test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value)
@test value == fmi2Integer.([typemin(fmi2Integer)])

# calculate ∂p/∂p (x)
posreference = [fmi2ValueReference(33554432)]
delta_x = fmi2Real.([randn()])
result = zeros(fmi2Real, 1)
@test fmi2StatusOK == fmi2GetDirectionalDerivative!(dlsym(lib,:fmi2GetDirectionalDerivative), component, posreference, Csize_t(1), posreference, Csize_t(1), delta_x, result)
# ∂p/∂p(x) should be just x for any x
if Sys.WORD_SIZE == 64
# GetDirectionalDerivative behaves weirdly on 32 Bit
@test result delta_x
end

@test fmi2StatusOK == fmi2ExitInitializationMode(dlsym(lib, :fmi2ExitInitializationMode), component)
@suppress begin
# Suppressing the CVODE-Stats that are printed here in CS Mode
@test fmi2StatusOK == fmi2Reset(dlsym(lib, :fmi2Reset), component)
end

# # # fmi2FreeInstance
@test isnothing(fmi2FreeInstance(dlsym(lib, :fmi2FreeInstance), component))

end

function test_generic_io(lib, cblibpath, type::fmi2Type)
component = fmi2Instantiate(dlsym(lib, :fmi2Instantiate), pointer("test_generic_io"), type, pointer("{95a6399d-38c5-4504-b3f3-98319bd94ce6}"), pointer("file:///"), Ptr{fmi2CallbackFunctions}(pointer_from_objref(get_callbacks(cblibpath))), fmi2Boolean(false), fmi2Boolean(false))
@test component != C_NULL
@test fmi2StatusOK == fmi2SetupExperiment(dlsym(lib, :fmi2SetupExperiment),component, fmi2Boolean(false), fmi2Real(0.0), fmi2Real(0.0), fmi2Boolean(false), fmi2Real(1.0))

@test fmi2StatusOK == fmi2EnterInitializationMode(dlsym(lib, :fmi2EnterInitializationMode), component)

fmireference = [fmi2ValueReference(16777216)]
@test fmi2StatusOK == fmi2SetReal(dlsym(lib, :fmi2SetReal), component, fmireference, Csize_t(1), fmi2Real.([0.8]))

value = zeros(fmi2Real, 1)
@test fmi2StatusOK == fmi2GetReal!(dlsym(lib, :fmi2GetReal), component, fmireference, Csize_t(1), value)
@test value == fmi2Real.([0.8])

fmireference = [fmi2ValueReference(16777217)]
value = zeros(fmi2Integer, 1)
@test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value)

@test fmi2StatusOK == fmi2SetInteger(dlsym(lib, :fmi2SetInteger), component, fmireference, Csize_t(1), fmi2Integer.([typemin(fmi2Integer)]))

value = zeros(fmi2Integer, 1)
@test fmi2StatusOK == fmi2GetInteger!(dlsym(lib, :fmi2GetInteger), component, fmireference, Csize_t(1), value)
@test value == fmi2Integer.([typemin(fmi2Integer)])

fmireference = [fmi2ValueReference(16777218)]
@test fmi2StatusOK == fmi2SetBoolean(dlsym(lib, :fmi2SetBoolean), component, fmireference, Csize_t(1), fmi2Boolean.([false]))

value = zeros(fmi2Boolean, 1)
@test fmi2StatusOK == fmi2GetBoolean!(dlsym(lib, :fmi2GetBoolean), component, fmireference, Csize_t(1), value)
@test value == fmi2Boolean.([false])

fmireference = [fmi2ValueReference(134217728)]

value = ["anything"]
valueptr = pointer.(value)
@test fmi2StatusOK == fmi2SetString(dlsym(lib, :fmi2SetString), component, fmireference, Csize_t(1), valueptr)

value = Vector{fmi2String}(undef, 1)
values = string.(zeros(1))
@test fmi2StatusOK == fmi2GetString!(dlsym(lib, :fmi2GetString), component, fmireference, Csize_t(1), value)
values[:] = unsafe_string.(value)
@test values == ["anything"]

end
Loading

2 comments on commit f07407f

@ThummeTo
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/113938

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.0.2 -m "<description of version>" f07407f3b337e3b0d5feb9f8858528b54284d192
git push origin v1.0.2

Please sign in to comment.