Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions .CI/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,7 @@ pipeline {
}
steps {
runRegressiontest('master', 'wasm-jit', 'setCommandLineOptions("--simCodeTarget=wasm-jit")', '', false, '', '--wasmjitrunner=sim,me,cs', false, false, 0, 'configs/conf.json',
'-DOM_OMC_ENABLE_RUST=ON -DRUST_OMC_CI=ON -DRUST_OMC_THREADS=4 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache',
'.CI/wasm-jit')
'-DOM_OMC_ENABLE_RUST=ON -DRUST_OMC_CI=ON -DRUST_OMC_THREADS=4 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache')
}
}
stage('generateSymbolicJacobian') {
Expand Down Expand Up @@ -702,7 +701,12 @@ def numLogicalCPU() {
def omsimulatorHash() {
return 'master'
}
def installLibraries(boolean removePackageOrder, boolean conversionScript, name, String omhomeTestedOMC) {
/**
* Installs the libraries a run tests, with the node's own omc, and returns the home directory
* holding them. `runSh` is how the conversion script is run: that one uses the omc that was just
* built, which is a binary of the image when the job has one.
*/
def installLibraries(boolean removePackageOrder, boolean conversionScript, name, String omhomeTestedOMC, Closure runSh) {
sh "rm -rf '${env.HOME}/saved_omc/libraries/.openmodelica/libraries'"
sh "mkdir -p '${env.HOME}/saved_omc/libraries/'"
sh "HOME='${env.HOME}/saved_omc/libraries/' /usr/bin/omc OpenModelicaLibraryTesting/.CI/installLibraries.mos"
Expand All @@ -714,11 +718,11 @@ def installLibraries(boolean removePackageOrder, boolean conversionScript, name,
sh "cp -ai /mnt/ReferenceFiles/ExtraLibs/packaged/* '${env.HOME}/saved_omc/libraries/'"
echo "installLibraries removePackageOrder: ${removePackageOrder} conversionScript: ${conversionScript} name: ${name}"
if (conversionScript) {
sh """
runSh("""
cd '${WORKSPACE}/OpenModelicaLibraryTesting'
OPENMODELICAHOME="${omhomeTestedOMC}" ./conversionscript.py --diff --allowErrorsInDiff '${env.HOME}/saved_omc/libraries/.openmodelica/libraries'
scp converted-libraries/.openmodelica/libraries/*.diff 'libraries.openmodelica.org:/var/www/libraries.openmodelica.org/branches/${name}'
"""
""")
return "${WORKSPACE}/OpenModelicaLibraryTesting/converted-libraries"
} else {
return "${env.HOME}/saved_omc/libraries"
Expand Down Expand Up @@ -808,9 +812,10 @@ def sccachePreamble() {
* This file specifies which libraries to test and what options to use for them.
* @param cmakeFlags: Target-specific cmake flags, e.g. `-DOM_OMC_ENABLE_RUST=ON`. If non-empty, omc is
* built with cmake instead of autotools; the shared release flags are added here.
* @param dockerfile: Directory with a Dockerfile, relative to the testing repository, e.g.
* `.CI/wasm-jit`. If non-empty, the omc build and test.py run in that image
* instead of on the node; everything using the node's own omc stays outside.
* @param dockerfile: Directory with a Dockerfile, relative to the testing repository. Defaults to
* `.CI/testing`, the image every job runs in: the omc build, the OMSimulator
* build and test.py happen inside it, and only the steps using the node's own
* omc stay outside. Passing `''` runs the job on the node itself.
*/
/* The FMI simulators a job runs, from the parameters that used to start one job each.
* Both ticked is one job that builds every FMU once and simulates it with both,
Expand All @@ -822,7 +827,7 @@ def fmiSimulators(boolean omsimulator, boolean fmpy) {
return simulators
}

def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimflags, testFlags, boolean removePackageOrder, boolean conversionScript, int jobs=0, libs_config_file = 'configs/conf.json', cmakeFlags = '', dockerfile = '', fmiSimulators = null) {
def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimflags, testFlags, boolean removePackageOrder, boolean conversionScript, int jobs=0, libs_config_file = 'configs/conf.json', cmakeFlags = '', dockerfile = '.CI/testing', fmiSimulators = null) {
sh '''
find /tmp -name "*openmodelica.hudson*" -exec rm {} ";" || true

Expand All @@ -846,7 +851,10 @@ def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimfla
if (dockerfile) {
// Build context is the Dockerfile directory; the workspace has a directory per tested model.
sh "cp OpenModelicaLibraryTesting/requirements.txt OpenModelicaLibraryTesting/${dockerfile}/"
image = docker.build("openmodelica-library-testing:${name}", "--pull OpenModelicaLibraryTesting/${dockerfile}")
// Tagged after the Dockerfile rather than after the job, so that jobs sharing an image share
// its tag as well and a node keeps one of them instead of one per job it has ever run.
image = docker.build("openmodelica-library-testing:${dockerfile.tokenize('/').last()}",
"--pull OpenModelicaLibraryTesting/${dockerfile}")
}
// --init reaps the omc processes test.py orphans. ssh refuses to run for a uid it cannot look
// up, so the node's passwd entry is needed to publish the results. The home holds the cached omc
Expand Down Expand Up @@ -885,7 +893,9 @@ def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimfla
}
FMI_TESTING_FLAG = ""
if (simulators.contains('OMSimulator') && omsHash) {
sh """
// In the image rather than on the node: test.py runs this binary from inside it, and one built
// against the node's libraries need not load there.
runSh("""
if ! test -d OMSimulator; then
git clone --recursive https://openmodelica.org/git-readonly/OMSimulator.git || exit 1
fi
Expand Down Expand Up @@ -916,16 +926,14 @@ def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimfla
fi
echo OMSimulator version:
${env.HOME}/saved_omc/OMSimulator/install/bin/OMSimulator --version
"""
""")
FMI_TESTING_FLAG = " --fmisimulator=${env.HOME}/saved_omc/OMSimulator/install/bin/OMSimulator"
}

if (simulators.contains('fmpy')) {
sh """
# update fmpy
pip install FMPy || true
python3 -m fmpy -h || exit 1
"""
// The version is the image's; check it in the interpreter test.py will reach rather than
// installing one over it, which in a container would be thrown away with the container.
runSh('python3 -m fmpy -h > /dev/null || exit 1')
FMI_TESTING_FLAG += " --fmisimulator='python3 -m fmpy'"
}

Expand Down Expand Up @@ -1117,7 +1125,7 @@ def runRegressiontest(branch, name, extraFlags, omsHash, omcompiler, extrasimfla
mkdir -p "/var/www/libraries.openmodelica.org/branches/${name}/"
"""

def libraryPath = installLibraries(removePackageOrder, conversionScript, name, "${WORKSPACE}/OpenModelica/${OMCPATH}/build")
def libraryPath = installLibraries(removePackageOrder, conversionScript, name, "${WORKSPACE}/OpenModelica/${OMCPATH}/build", runSh)

sh "test -d '${libraryPath}/.openmodelica/libraries/Modelica trunk'"

Expand Down
20 changes: 17 additions & 3 deletions .CI/wasm-jit/Dockerfile → .CI/testing/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
# Image for the targets needing the Rust omc (wasm-jit); the nodes run an Ubuntu
# too old to build it. Built by .CI/Jenkinsfile, with this directory as the
# context (the job copies requirements.txt into it).
# The image every library testing job runs in: it builds omc, OMSimulator and runs test.py.
# Built by .CI/Jenkinsfile, with this directory as the context (the job copies requirements.txt
# into it).
#
# One image for all of them rather than one per target: what a node then needs is docker, git and
# an ssh key instead of a set of packages kept up to date by hand, and a job moved to another
# machine tests what it tested before. The base is the one the wasm-jit target needs - the OMDev
# image the OpenModelica repository builds against, in the variant carrying the Rust toolchain,
# since a Rust omc cannot be built on the Ubuntu the nodes run, and its sccache keys only hit for
# the toolchain that job used.
FROM docker.openmodelica.org/build-deps:ubuntu-26.04-rust

# rsync/ssh publish the results; time and killall are used around test.py.
# libcomedi-dev libx11-dev is used by Modelica_DeviceDrivers
# Python 3.8 and 3.12 are used by Buildings
# autoconf/automake/libtool and omniORB build the targets that do not ask for a cmake omc: those
# still go through `autoreconf && ./configure --with-omniORB`. The base image carries what the
# cmake build needs, which is not quite the same set.
Comment thread
AnHeuermann marked this conversation as resolved.
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -qy software-properties-common \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get install -qy --no-install-recommends openssh-client psmisc rsync time \
&& apt-get install -qy --no-install-recommends libcomedi-dev libx11-dev \
&& apt-get install -qy --no-install-recommends autoconf automake libtool omniidl libomniorb4-dev \
&& apt-get install -qy python3.8 python3.12 libpython3.8-dev libpython3.12-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
Expand All @@ -23,6 +34,9 @@ RUN python3 -m venv /opt/libtest-venv \
&& /opt/libtest-venv/bin/pip install --no-cache-dir -r /tmp/requirements.txt \
# Python dependencies of library install scripts (e.g. Buildings EnergyPlus)
&& /opt/libtest-venv/bin/pip install --no-cache-dir jinja2 \
# The simulator of the -fmi-fmpy targets, which test.py runs as `python3 -m fmpy`. Its version
# is the image's: a job cannot pip install one over it that outlives the container.
&& /opt/libtest-venv/bin/pip install --no-cache-dir FMPy \
&& rm /tmp/requirements.txt
ENV PATH=/opt/libtest-venv/bin:$PATH

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__pycache__
/.CI/wasm-jit/requirements.txt
/.CI/testing/requirements.txt
!HelloWorld.mo
!HelloWorld.mos
/.venv/
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ OpenModelica
[issue tracker](https://github.com/OpenModelica/OpenModelica/issues/new/choose)
and ask us to do it for you.

### The image the OSMC jobs run in

Every job of [.CI/Jenkinsfile](.CI/Jenkinsfile) runs in one docker image, built
from [.CI/testing/Dockerfile](.CI/testing/Dockerfile) at the start of the job
and with `--pull`, so a rebuilt base is picked up on its own. What a node needs
is then docker, git and an ssh key to publish the results with; the compilers,
the python environment, the `omc` that generates the mos files, FMPy and the
packages the tested libraries need are the image's, not the machine's, and a job
moved to another machine tests what it tested before.

The omc build, the OMSimulator build and `test.py` run inside the image; the
steps that use the node's own `omc` - installing the libraries and preparing the
reference files - stay outside it. The node's home directory is mounted, so the
cached omc build in `~/saved_omc`, the installed libraries and the ssh key are
the files they always were, and so is `/mnt/ReferenceFiles`, where the
maintenance job installs the reference results: the container reads the same
directory the node does, and writes the hash `test.py` keeps next to a reference
file back into it. The container is capped at 85% of the node's memory, which a
job running on the node itself was not.

A dependency a library needs is therefore a line in the Dockerfile rather than
an `apt-get install` repeated on every machine:

```bash
cp requirements.txt .CI/testing/ # the build context has to carry it
docker build -t openmodelica-library-testing:testing .CI/testing
docker run --rm -it openmodelica-library-testing:testing bash
```

## Running the library testing infrastructure on your own server

The scripts from this repository can be used to run regression tests for public,
Expand Down
Loading