diff --git a/.CI/Jenkinsfile b/.CI/Jenkinsfile index c7d2092..3323fb3 100644 --- a/.CI/Jenkinsfile +++ b/.CI/Jenkinsfile @@ -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') { @@ -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" @@ -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" @@ -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, @@ -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 @@ -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 @@ -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 @@ -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'" } @@ -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'" diff --git a/.CI/wasm-jit/Dockerfile b/.CI/testing/Dockerfile similarity index 59% rename from .CI/wasm-jit/Dockerfile rename to .CI/testing/Dockerfile index 30ab938..95dff69 100644 --- a/.CI/wasm-jit/Dockerfile +++ b/.CI/testing/Dockerfile @@ -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. 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/* @@ -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 diff --git a/.gitignore b/.gitignore index 409402b..388d96d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ __pycache__ -/.CI/wasm-jit/requirements.txt +/.CI/testing/requirements.txt !HelloWorld.mo !HelloWorld.mos /.venv/ diff --git a/README.md b/README.md index a2997d6..fd5284b 100644 --- a/README.md +++ b/README.md @@ -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,