Skip to content

Commit

Permalink
More tests and run multiple OMCSessions on Windows (#94)
Browse files Browse the repository at this point in the history
* Don't log omc zmq process

  - It get's stuck or breaks with permission errors on log file on Windows

* Adding simulation tests

* Simplifications

  - Base is always included, no need to add it explicitly
  - Simplify args for omc process

* Update CI 

  - Update test only Julia 1.9.
     Julia 1.8 get's stuck on some ZMQ recieve
  - Update AnHeuermann/[email protected]
  • Loading branch information
AnHeuermann authored Sep 5, 2023
1 parent 8b476a4 commit 7d5b841
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 88 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
julia-version: ['1.0.5', '1.8']
julia-version: ['1.9']
julia-arch: ['x64']
os: ['ubuntu-latest', 'windows-latest']
omc-version: ['stable']
Expand All @@ -22,11 +22,13 @@ jobs:
- uses: actions/checkout@v3

- name: "Set up OpenModelica Compiler"
uses: AnHeuermann/setup-openmodelica@v0.4
uses: AnHeuermann/setup-openmodelica@v0.6
with:
version: ${{ matrix.omc-version }}
packages: |
omc
libraries: |
'Modelica 4.0.0'
- run: "omc --version"

Expand All @@ -41,3 +43,5 @@ jobs:

- name: "Test OMJulia"
uses: julia-actions/julia-runtest@v1
with:
coverage: false
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Manifest.toml
.vscode/
*.cov
Manifest.toml
5 changes: 3 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ DataFrames = "0.19.4, 0.20, 0.21, 0.22, 1"
DataStructures = "0.9, 0.10, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16.1, 0.17, 0.18"
LightXML = "0.8, 0.9"
ZMQ = "1"
julia = "1"
julia = "1.9"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"

[targets]
test = ["Test"]
test = ["Test", "SafeTestsets"]
82 changes: 38 additions & 44 deletions src/OMJulia.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ using ZMQ
using DataStructures
using LightXML
using DataFrames
if (VERSION >= v"1.0")
using Random
end
using Random

export sendExpression, ModelicaSystem
# getMethods
Expand Down Expand Up @@ -109,21 +107,18 @@ mutable struct OMCSession
this.linearFlag = false
this.linearmodelname = ""
this.linearOptions = Dict("startTime" => "0.0", "stopTime" => "1.0", "stepSize" => "0.002", "tolerance" => "1e-6")
args2 = "--interactive=zmq"
args3 = "+z=julia."
if (VERSION >= v"1.0")
args4 = Random.randstring(10)
else
args4 = randstring(10)
end
if (Base.Sys.iswindows())
args1 = "--interactive=zmq"
randPortSuffix = Random.randstring(10)
args2 = "+z=julia.$(randPortSuffix)"

if (Sys.iswindows())
if (omc !== nothing)
ompath = replace(omc, r"[/\\]+" => "/")
dirpath = dirname(dirname(omc))
## create a omc process with OPENMODELICAHOME set to custom directory
@info("Setting environment variable OPENMODELICAHOME=\"$dirpath\" for this session.")
withenv("OPENMODELICAHOME" => dirpath) do
this.omcprocess = open(pipeline(`$omc $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`$omc $args1 $args2`))
end
else
omhome = ""
Expand All @@ -137,27 +132,27 @@ mutable struct OMCSession
# ompath=joinpath(omhome,"bin")
## create a omc process with default OPENMODELICAHOME set in environment variable
withenv("OPENMODELICAHOME" => omhome) do
this.omcprocess = open(pipeline(`$ompath $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`$ompath $args1 $args2`))
end
end
portfile = join(["openmodelica.port.julia.",args4])
portfile = join(["openmodelica.port.julia.",randPortSuffix])
else
if (Base.Sys.isapple())
if (Sys.isapple())
# add omc to path if not exist
ENV["PATH"] = ENV["PATH"] * "/opt/openmodelica/bin"
if (omc !== nothing)
this.omcprocess = open(pipeline(`$omc $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`$omc $args1 $args2`))
else
this.omcprocess = open(pipeline(`omc $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`omc $args1 $args2`))
end
else
if (omc !== nothing)
this.omcprocess = open(pipeline(`$omc $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`$omc $args1 $args2`))
else
this.omcprocess = open(pipeline(`omc $args2 $args3$args4`, stdout="stdout.log", stderr="stderr.log"))
this.omcprocess = open(pipeline(`omc $args1 $args2`))
end
end
portfile = join(["openmodelica.",ENV["USER"],".port.julia.",args4])
portfile = join(["openmodelica.", ENV["USER"], ".port.julia.", randPortSuffix])
end
fullpath = joinpath(tempdir(), portfile)
@info("Path to zmq file=\"$fullpath\"")
Expand All @@ -169,11 +164,7 @@ mutable struct OMCSession
end
# Catch omc error
if process_exited(this.omcprocess) && this.omcprocess.exitcode != 0
throw(OMCError(this.omcprocess.cmd, "stdout.log", "stderr.log"))
else
@debug read(e.stdout_file, String)
@debug read(e.stderr_file, String)
rm.(["stdout.log", "stderr.log"], force=true)
throw(OMCError(this.omcprocess.cmd))
end
if tries >= 100
throw(TimeoutError("ZMQ server port file \"$fullpath\" not created yet."))
Expand All @@ -186,17 +177,20 @@ mutable struct OMCSession
end
end

function sendExpression(omc, expr; parsed=true)
if (process_running(omc.omcprocess))
ZMQ.send(omc.socket, expr)
message = ZMQ.recv(omc.socket)
if parsed
return Parser.parseOM(unsafe_string(message))
else
return unsafe_string(message)
end
function sendExpression(omc::OMCSession, expr::String; parsed=true)
if (!process_running(omc.omcprocess))
return error("Process Exited, No connection with OMC. Create a new instance of OMCSession")
end

@debug "sending expression: $(expr)"
ZMQ.Sockets.send(omc.socket, expr)
@debug "Receiving message from ZMQ socket"
message = ZMQ.Sockets.recv(omc.socket)
@debug "Recieved message"
if parsed
return Parser.parseOM(unsafe_string(message))
else
return "Process Exited, No connection with OMC. Create a new instance of OMCSession"
return unsafe_string(message)
end
end

Expand Down Expand Up @@ -313,7 +307,7 @@ function buildModel(omc; variableFilter=nothing)
# println(buildmodelexpr)

buildModelmsg = sendExpression(omc, buildmodelexpr)
# parsebuilexp=Base.Meta.parse(buildModelmsg)
# parsebuilexp=Meta.parse(buildModelmsg)
if (!isempty(buildModelmsg[2]))
omc.xmlfile = replace(joinpath(omc.tempdir, buildModelmsg[2]), r"[/\\]+" => "/")
xmlparse(omc)
Expand Down Expand Up @@ -624,7 +618,7 @@ function simulate(omc; resultfile=nothing, simflags=nothing, verbose=true)
end

if (isfile(omc.xmlfile))
if (Base.Sys.iswindows())
if (Sys.iswindows())
getexefile = replace(joinpath(omc.tempdir, join([omc.modelname,".exe"])), r"[/\\]+" => "/")
else
getexefile = replace(joinpath(omc.tempdir, omc.modelname), r"[/\\]+" => "/")
Expand Down Expand Up @@ -657,7 +651,7 @@ function simulate(omc; resultfile=nothing, simflags=nothing, verbose=true)
# remove empty args in cmd objects
cmd = filter!(e -> e "", [getexefile,overridevar,csvinput,r,simflags])
# println(cmd)
if (Base.Sys.iswindows())
if (Sys.iswindows())
installPath = sendExpression(omc, "getInstallationDirectoryPath()")
envPath = ENV["PATH"]
newPath = "$(envPath);$(installPath)/bin/;$(installPath)/lib/omc;$(installPath)/lib/omc/cpp;$(installPath)/lib/omc/omsicpp"
Expand Down Expand Up @@ -738,7 +732,7 @@ function sensitivity(omc, Vp, Vv, Ve=[1e-2])
Ve = Ve[1:nVp] # truncates Ve to same length as Vp
end
# Nominal parameters p0
par0 = [Base.parse(Float64, pp) for pp in getParameters(omc, Vp)]
par0 = [parse(Float64, pp) for pp in getParameters(omc, Vp)]
# eXcitation parameters parX
parX = [par0[i] * (1 + Ve[i]) for i in 1:nVp]
# Combine parameter names and parameter values into vector of strings
Expand Down Expand Up @@ -917,7 +911,7 @@ function setInputs(omc, name)
name = strip_space(name)
value = split(name, "=")
if (haskey(omc.inputlist, value[1]))
newval = Base.Meta.parse(value[2])
newval = Meta.parse(value[2])
if (isa(newval, Expr))
omc.inputlist[value[1]] = [v.args for v in newval.args]
else
Expand All @@ -932,7 +926,7 @@ function setInputs(omc, name)
for var in name
value = split(var, "=")
if (haskey(omc.inputlist, value[1]))
newval = Base.Meta.parse(value[2])
newval = Meta.parse(value[2])
if (isa(newval, Expr))
omc.inputlist[value[1]] = [v.args for v in newval.args]
else
Expand Down Expand Up @@ -1092,7 +1086,7 @@ function linearize(omc; lintime = nothing, simflags= nothing, verbose=true)
end

if (isfile(omc.xmlfile))
if (Base.Sys.iswindows())
if (Sys.iswindows())
getexefile = replace(joinpath(omc.tempdir, join([omc.modelname,".exe"])), r"[/\\]+" => "/")
else
getexefile = replace(joinpath(omc.tempdir, omc.modelname), r"[/\\]+" => "/")
Expand All @@ -1111,7 +1105,7 @@ function linearize(omc; lintime = nothing, simflags= nothing, verbose=true)
# println(finalLinearizationexe)

cd(omc.tempdir)
if (Base.Sys.iswindows())
if (Sys.iswindows())
installPath = sendExpression(omc, "getInstallationDirectoryPath()")
envPath = ENV["PATH"]
newPath = "$(envPath);$(installPath)/bin/;$(installPath)/lib/omc;$(installPath)/lib/omc/cpp;$(installPath)/lib/omc/omsicpp"
Expand Down Expand Up @@ -1146,7 +1140,7 @@ function linearize(omc; lintime = nothing, simflags= nothing, verbose=true)
# to improve the performance by directly reading the matrices A, B, C and D from the julia code and avoid building the linearized modelica model
include("linearized_model.jl")
## to be evaluated at runtime, as Julia expects all functions should be known at the compilation time so efficient assembly code can be generated.
result = Base.invokelatest(linearized_model)
result = invokelatest(linearized_model)
(n, m, p, x0, u0, A, B, C, D, stateVars, inputVars, outputVars) = result
omc.linearstates = stateVars
omc.linearinputs = inputVars
Expand Down
19 changes: 14 additions & 5 deletions src/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,27 @@ omc process error
"""
struct OMCError <: Exception
cmd::Cmd
stdout_file::String
stderr_file::String
stdout_file::Union{String, Missing}
stderr_file::Union{String, Missing}

function OMCError(cmd, stdout_file=missing, stderr_file=missing)
new(cmd, stdout_file, stderr_file)
end
end
"""
Show error from log files
"""
function Base.showerror(io::IO, e::OMCError)
println(io, "OMCError ")
println(io, "Command $(e.cmd) failed")
println(io, read(e.stdout_file, String))
print(io, read(e.stderr_file, String))
rm.(["stdout.log", "stderr.log"], force=true)
if !ismissing(e.stdout_file)
println(io, read(e.stdout_file, String))
rm(e.stdout_file, force=true)
end
if !ismissing(e.stdout_file)
print(io, read(e.stderr_file, String))
rm(e.stderr_file, force=true)
end
end

"""
Expand Down
1 change: 1 addition & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/test-*/
98 changes: 98 additions & 0 deletions test/omcTest.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#=
This file is part of OpenModelica.
Copyright (c) 1998-2023, Open Source Modelica Consortium (OSMC),
c/o Linköpings universitet, Department of Computer and Information Science,
SE-58183 Linköping, Sweden.
All rights reserved.
THIS PROGRAM IS PROVIDED UNDER THE TERMS OF THE BSD NEW LICENSE OR THE
GPL VERSION 3 LICENSE OR THE OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2.
ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3,
ACCORDING TO RECIPIENTS CHOICE.
The OpenModelica software and the OSMC (Open Source Modelica Consortium)
Public License (OSMC-PL) are obtained from OSMC, either from the above
address, from the URLs: http://www.openmodelica.org or
http://www.ida.liu.se/projects/OpenModelica, and in the OpenModelica
distribution. GNU version 3 is obtained from:
http://www.gnu.org/copyleft/gpl.html. The New BSD License is obtained from:
http://www.opensource.org/licenses/BSD-3-Clause.
This program is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, EXCEPT AS
EXPRESSLY SET FORTH IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE
CONDITIONS OF OSMC-PL.
=#

using Test
import OMJulia

@testset "OpenModelica" begin
@testset "OMCSession" begin
workdir = abspath(joinpath(@__DIR__, "test-session"))
rm(workdir, recursive=true, force=true)
mkpath(workdir)

if Sys.iswindows()
workdir = replace(workdir, "\\" => "\\\\")
end

oldwd = pwd()
try
omc = OMJulia.OMCSession()
OMJulia.sendExpression(omc, "cd(\"$workdir\")")
version = OMJulia.sendExpression(omc, "getVersion()")
@test (startswith(version, "v1.") || startswith(version, "OpenModelica v1.") || startswith(version, "OpenModelica 1."))
a = OMJulia.sendExpression(omc, "model a end a;")
@test a == [:a]

classNames = OMJulia.sendExpression(omc, "getClassNames()")
@test classNames == [:a]
@test true == OMJulia.sendExpression(omc, "loadModel(Modelica)")
res = OMJulia.sendExpression(omc, "simulate(Modelica.Electrical.Analog.Examples.CauerLowPassAnalog)")
@test isfile(res["resultFile"])
@test occursin("The simulation finished successfully.", res["messages"])

@test 3 == OMJulia.sendExpression(omc, "1+2")
# TODO: sendExpression(quit()) and kill(omc.omcprocess) get stuck on Ubuntu
finally
cd(oldwd)
end
end

@testset "Multiple sessions" begin
workdir1 = abspath(joinpath(@__DIR__, "test-omc1"))
workdir2 = abspath(joinpath(@__DIR__, "test-omc2"))
rm(workdir1, recursive=true, force=true)
rm(workdir2, recursive=true, force=true)
mkpath(workdir1)
mkpath(workdir2)

if Sys.iswindows()
workdir1 = replace(workdir1, "\\" => "\\\\")
workdir2 = replace(workdir2, "\\" => "\\\\")
end

oldwd = pwd()
try
omc1 = OMJulia.OMCSession()
omc2 = OMJulia.OMCSession()

OMJulia.sendExpression(omc1, "cd(\"$workdir1\")")
@test true == OMJulia.sendExpression(omc1, "loadModel(Modelica)")
res = OMJulia.sendExpression(omc1, "simulate(Modelica.Blocks.Examples.PID_Controller)")
@test isfile(joinpath(@__DIR__, "test-omc1", "Modelica.Blocks.Examples.PID_Controller_res.mat"))

OMJulia.sendExpression(omc2, "cd(\"$workdir2\")")
@test true == OMJulia.sendExpression(omc2, "loadModel(Modelica)")
res = OMJulia.sendExpression(omc2, "simulate(Modelica.Blocks.Examples.PID_Controller)")
@test isfile(joinpath(@__DIR__, "test-omc2", "Modelica.Blocks.Examples.PID_Controller_res.mat"))

# TODO: sendExpression(quit()) and kill(omc.omcprocess) get stuck on Ubuntu
finally
cd(oldwd)
end
end
end
Loading

0 comments on commit 7d5b841

Please sign in to comment.