Skip to content

Commit 73eaa27

Browse files
committed
Add tests for calling OpenModelica via ZMQ
We now create a process group so we can kill OMC properly including all child processes. This makes the tests run more stable.
1 parent e2439ba commit 73eaa27

File tree

4 files changed

+107
-40
lines changed

4 files changed

+107
-40
lines changed

Jenkinsfile

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,46 @@
11
pipeline {
2-
agent any
2+
agent {
3+
docker {
4+
// Large image with full OpenModelica build dependencies; lacks omc and OMPython
5+
image 'openmodelica/build-deps'
6+
}
7+
}
38
stages {
9+
stage('setup') {
10+
steps {
11+
sh '''
12+
# Install the omc package; should only take a few seconds
13+
apt-get update
14+
apt-get install -qy gnupg wget ca-certificates apt-transport-https sudo
15+
echo "deb https://build.openmodelica.org/apt `lsb_release -sc` release" > /etc/apt/sources.list.d/openmodelica.list
16+
wget https://build.openmodelica.org/apt/openmodelica.asc -O- | apt-key add -
17+
apt-get update
18+
apt-get install -qy --no-install-recommends omc
19+
'''
20+
}
21+
}
422
stage('build') {
523
parallel {
624
stage('python2') {
7-
agent {
8-
docker {
9-
image 'python:2'
10-
}
11-
}
1225
steps {
13-
sh 'cat /etc/resolv.conf'
14-
sh 'python2 setup.py build'
15-
sh 'python2 setup.py test'
16-
sh 'python2 setup.py install'
26+
timeout(1) {
27+
// OpenModelica does not like running as root
28+
sh 'chown -R nobody .'
29+
sh 'sudo -u nobody python2 setup.py build'
30+
sh 'sudo -u nobody python2 setup.py test'
31+
sh 'python2 setup.py install'
32+
}
1733
}
1834
}
1935
stage('python3') {
20-
agent {
21-
docker {
22-
image 'python:3'
23-
}
24-
}
2536
steps {
26-
sh 'cat /etc/resolv.conf'
27-
sh 'python3 setup.py build'
28-
sh 'python3 setup.py test'
29-
sh 'python3 setup.py install'
37+
timeout(1) {
38+
// OpenModelica does not like running as root
39+
sh 'chown -R nobody .'
40+
sh 'sudo -u nobody python3 setup.py build'
41+
sh 'sudo -u nobody python3 setup.py test'
42+
sh 'python3 setup.py install'
43+
}
3044
}
3145
}
3246
}

OMPython/__init__.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class which means it will use OMCSessionZMQ by default. If you want to use
4141
import logging
4242
import os
4343
import platform
44+
import signal
4445
import subprocess
4546
import sys
4647
import tempfile
@@ -120,11 +121,28 @@ def __init__(self, readonly=False):
120121
self._omc_log_file = None
121122

122123
def __del__(self):
123-
self.sendExpression("quit()")
124+
try:
125+
self.sendExpression("quit()")
126+
except:
127+
pass
124128
self._omc_log_file.close()
129+
if sys.version_info.major >= 3:
130+
self._omc_process.wait(timeout=1.0)
131+
else:
132+
for i in range(0,100):
133+
time.sleep(0.01)
134+
if self._omc_process.poll() is not None:
135+
break
125136
# kill self._omc_process process if it is still running/exists
126137
if self._omc_process.returncode is None:
127-
self._omc_process.kill()
138+
print("OMC did not exit after being sent the quit() command; killing the process with pid=%s" % str(self._omc_process.pid))
139+
if sys.platform=="win32":
140+
self._omc_process.kill()
141+
self._omc_process.wait()
142+
else:
143+
os.killpg(os.getpgid(self._omc_process.pid), signal.SIGTERM)
144+
self._omc_process.kill()
145+
self._omc_process.wait()
128146

129147
def _create_omc_log_file(self, suffix):
130148
if sys.platform == 'win32':
@@ -143,7 +161,8 @@ def _start_omc_process(self):
143161
my_env["PATH"] = omhome_bin + os.pathsep + my_env["PATH"]
144162
self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, env=my_env)
145163
else:
146-
self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file)
164+
# Because we spawned a shell, and we need to be able to kill OMC, create a new process group for this
165+
self._omc_process = subprocess.Popen(self._omc_command, shell=True, stdout=self._omc_log_file, stderr=self._omc_log_file, preexec_fn=os.setsid)
147166
return self._omc_process
148167

149168
def _set_omc_command(self, omc_path, args):
@@ -513,24 +532,21 @@ def _connect_to_omc(self, timeout):
513532
self._port_file = os.path.join(self._temp_dir, self._port_file).replace("\\", "/")
514533
self._omc_zeromq_uri = "file:///" + self._port_file
515534
# See if the omc server is running
516-
if os.path.isfile(self._port_file):
517-
logger.info("OMC Server is up and running at {0}".format(self._omc_zeromq_uri))
518-
else:
519-
attempts = 0
520-
while True:
521-
if not os.path.isfile(self._port_file):
522-
time.sleep(timeout)
523-
attempts += 1
524-
if attempts == 10:
525-
name = self._omc_log_file.name
526-
self._omc_log_file.close()
527-
logger.error("OMC Server is down. Please start it! Log-file says:\n%s" % open(name).read())
528-
raise Exception
529-
else:
530-
continue
535+
attempts = 0
536+
while True:
537+
if not os.path.isfile(self._port_file):
538+
time.sleep(timeout)
539+
attempts += 1
540+
if attempts == 10:
541+
name = self._omc_log_file.name
542+
self._omc_log_file.close()
543+
logger.error("OMC Server is down. Please start it! Log-file says:\n%s" % open(name).read())
544+
raise Exception("OMC Server is down. Could not open file %s" % self._port_file)
531545
else:
532-
logger.info("OMC Server is up and running at {0}".format(self._omc_zeromq_uri))
533-
break
546+
continue
547+
else:
548+
logger.info("OMC Server is up and running at {0} pid={1}".format(self._omc_zeromq_uri, self._omc_process.pid))
549+
break
534550

535551
# Read the port file
536552
with open(self._port_file, 'r') as f_p:

tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__all__ = ['tests.test_OMParser']
1+
__all__ = ['tests.test_OMParser', 'tests.test_ZMQ']

tests/test_ZMQ.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import OMPython
2+
import unittest
3+
import tempfile, shutil, os
4+
5+
class ZMQTester(unittest.TestCase):
6+
def __init__(self, *args, **kwargs):
7+
super(ZMQTester, self).__init__(*args, **kwargs)
8+
self.simpleModel = """model M
9+
Real r = time;
10+
end M;"""
11+
self.tmp = tempfile.mkdtemp(prefix='tmpOMPython.tests')
12+
self.origDir = os.getcwd()
13+
os.chdir(self.tmp)
14+
self.om = OMPython.OMCSessionZMQ()
15+
os.chdir(self.origDir)
16+
def __del__(self):
17+
shutil.rmtree(self.tmp, ignore_errors=True)
18+
del(self.om)
19+
def clean(self):
20+
del(self.om)
21+
self.om = None
22+
23+
def testHelloWorld(self):
24+
self.assertEqual("HelloWorld!", self.om.sendExpression('"HelloWorld!"'))
25+
self.clean()
26+
def testTranslate(self):
27+
self.assertEqual(("M",), self.om.sendExpression(self.simpleModel))
28+
self.assertEqual(True, self.om.sendExpression('translateModel(M)'))
29+
self.clean()
30+
def testSimulate(self):
31+
self.assertEqual(True, self.om.sendExpression('loadString("%s")' % self.simpleModel))
32+
self.om.sendExpression('res:=simulate(M, stopTime=2.0)')
33+
self.assertNotEqual("", self.om.sendExpression('res.resultFile'))
34+
self.clean()
35+
36+
if __name__ == '__main__':
37+
unittest.main()

0 commit comments

Comments
 (0)