Commit 2287ea81 authored by André Anjos's avatar André Anjos 💬

Fix tests on out-of-the-source builds, other minor incompatibilities

parent ff9b0c56
...@@ -47,6 +47,22 @@ from .. import utils ...@@ -47,6 +47,22 @@ from .. import utils
from .remote import RemoteExecutor from .remote import RemoteExecutor
def _which(program):
'''Pythonic version of the `which` command-line application'''
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath and is_exe(program): return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, fname)
if is_exe(exe_file): return exe_file
return None
class SubprocessExecutor(RemoteExecutor): class SubprocessExecutor(RemoteExecutor):
"""SubprocessExecutor runs the code given an execution block information, """SubprocessExecutor runs the code given an execution block information,
using a subprocess using a subprocess
...@@ -191,19 +207,22 @@ class SubprocessExecutor(RemoteExecutor): ...@@ -191,19 +207,22 @@ class SubprocessExecutor(RemoteExecutor):
'\n * '.join(self.errors)) '\n * '.join(self.errors))
# Check that the needed scripts are here # We need two apps to run this function: databases_provider and execute
bin_path = os.path.dirname(sys.argv[0]) EXECUTE_BIN = _which(os.path.join(os.path.dirname(sys.argv[0]),
'execute'))
DBPROVIDER_BIN = _which(os.path.join(os.path.dirname(sys.argv[0]),
'databases_provider'))
missing_scripts = [] missing_scripts = []
if (len(self.databases) > 0) and \ if (len(self.databases) > 0) and DBPROVIDER_BIN is None:
not os.path.exists(os.path.join(bin_path, 'databases_provider')):
missing_scripts.append('databases_provider') missing_scripts.append('databases_provider')
if not os.path.exists(os.path.join(bin_path, 'execute')): if EXECUTE_BIN is None:
missing_scripts.append('execute') missing_scripts.append('execute')
if missing_scripts: if missing_scripts:
raise RuntimeError("Scripts not found at path '%s': %s" % (bin_path, ', '.join(missing_scripts))) raise RuntimeError("Scripts not found at PATH (%s): %s" % \
(os.environ.get('PATH', ''), ', '.join(missing_scripts)))
# Creates the message handler # Creates the message handler
...@@ -229,7 +248,7 @@ class SubprocessExecutor(RemoteExecutor): ...@@ -229,7 +248,7 @@ class SubprocessExecutor(RemoteExecutor):
# Creation of the subprocess # Creation of the subprocess
# Note: we only support one databases image loaded at the same time # Note: we only support one databases image loaded at the same time
cmd = [ cmd = [
os.path.join(bin_path, 'databases_provider'), DBPROVIDER_BIN,
self.ip_address + ':51000', self.ip_address + ':51000',
databases_configuration_path, databases_configuration_path,
self.cache, self.cache,
...@@ -253,7 +272,7 @@ class SubprocessExecutor(RemoteExecutor): ...@@ -253,7 +272,7 @@ class SubprocessExecutor(RemoteExecutor):
# Command to execute # Command to execute
cmd = [ cmd = [
os.path.join(bin_path, 'execute'), EXECUTE_BIN,
'--cache=%s' % self.cache, '--cache=%s' % self.cache,
self.message_handler.address, self.message_handler.address,
configuration_path, configuration_path,
......
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.core module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
# Tests for experiment execution within Docker containers
from ..dock import Host
from ..execution import DockerExecutor
from .test_execution import BaseExecution
#----------------------------------------------------------
class TestDockerExecution(BaseExecution):
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
@classmethod
def tearDownClass(cls):
cls.host.teardown()
cleanup()
def tearDown(self):
super(TestDockerExecution, self).tearDown()
self.host.teardown()
def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache,
database_cache, algorithm_cache):
return DockerExecutor(self.host, prefix, configuration, tmp_prefix,
dataformat_cache, database_cache, algorithm_cache)
# NOT COMPATIBLE YET WITH THE NEW API
# @slow
# def test_cxx_double_1(self):
# assert self.execute('user/user/double/1/cxx_double', [{'out_data': 42}]) is None
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2017 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.core module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
# Tests for experiment execution
from ..dock import Host
from .test_worker import TestOneWorker, TestTwoWorkers
#----------------------------------------------------------
class TestOneWorkerDocker(TestOneWorker):
def __init__(self, methodName='runTest'):
super(TestOneWorkerDocker, self).__init__(methodName)
self.docker = True
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
#----------------------------------------------------------
class TestTwoWorkers(TestWorkerBase):
def setUp(self):
self.tearDown() # In case another test failed badly during its setUp()
super(TestTwoWorkers, self).setUp()
self.start_controller()
self.start_worker(WORKER1)
self.start_worker(WORKER2)
self.wait_for_worker_connection(WORKER1)
self.wait_for_worker_connection(WORKER2)
def _test_success_one_worker(self, worker_name):
self.controller.execute(worker_name, 1, CONFIGURATION1)
message = None
while message is None:
message = self.controller.process(100)
(worker, status, job_id, data) = message
self.assertEqual(worker, worker_name)
self.assertEqual(status, WorkerController.DONE)
self.assertEqual(job_id, 1)
result = simplejson.loads(data[0])
self.assertEqual(result['status'], 0)
def test_success_worker1(self):
self._test_success_one_worker(WORKER1)
def test_success_worker2(self):
self._test_success_one_worker(WORKER2)
def test_success_both_workers(self):
def _check(worker, status, job_id, data):
self.assertEqual(status, WorkerController.DONE)
if worker == WORKER1:
self.assertEqual(job_id, 1)
else:
self.assertEqual(worker, WORKER2)
self.assertEqual(job_id, 2)
result = simplejson.loads(data[0])
self.assertEqual(result['status'], 0)
self.controller.execute(WORKER1, 1, CONFIGURATION1)
self.controller.execute(WORKER2, 2, CONFIGURATION2)
message = None
while message is None:
message = self.controller.process(100)
(worker1, status, job_id, data) = message
_check(worker1, status, job_id, data)
message = None
while message is None:
message = self.controller.process(100)
(worker2, status, job_id, data) = message
_check(worker2, status, job_id, data)
self.assertNotEqual(worker1, worker2)
#----------------------------------------------------------
class TestTwoWorkersDocker(TestTwoWorkers):
def __init__(self, methodName='runTest'):
super(TestTwoWorkersDocker, self).__init__(methodName)
self.docker = True
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
...@@ -37,24 +37,23 @@ import numpy ...@@ -37,24 +37,23 @@ import numpy
import unittest import unittest
from ..experiment import Experiment from ..experiment import Experiment
from ..execution import DockerExecutor
from ..execution import LocalExecutor from ..execution import LocalExecutor
from ..execution import SubprocessExecutor from ..execution import SubprocessExecutor
from ..hash import hashFileContents from ..hash import hashFileContents
from ..hash import hashDataset from ..hash import hashDataset
from ..hash import toPath from ..hash import toPath
from ..data import CachedDataSource from ..data import CachedDataSource
from ..dock import Host
from . import prefix, tmp_prefix from . import prefix, tmp_prefix
from .utils import cleanup from .utils import cleanup
from .utils import slow from .utils import slow
import nose.tools
#----------------------------------------------------------
#----------------------------------------------------------
class TestExecution(unittest.TestCase): class BaseExecution(object):
def check_output(self, prefix, path): def check_output(self, prefix, path):
'''Checks if a given output exists, together with its indexes and checksums '''Checks if a given output exists, together with its indexes and checksums
...@@ -67,14 +66,14 @@ class TestExecution(unittest.TestCase): ...@@ -67,14 +66,14 @@ class TestExecution(unittest.TestCase):
indexchksums = glob.glob(finalpath + '*.index.checksum') indexchksums = glob.glob(finalpath + '*.index.checksum')
assert datafiles assert datafiles
self.assertEqual(len(datafiles), len(indexfiles)) nose.tools.eq_(len(datafiles), len(indexfiles))
for k in datafiles + indexfiles: for k in datafiles + indexfiles:
checksum_file = k + '.checksum' checksum_file = k + '.checksum'
assert checksum_file in datachksums + indexchksums assert checksum_file in datachksums + indexchksums
stored_checksum = None stored_checksum = None
with open(checksum_file, 'rt') as f: stored_checksum = f.read().strip() with open(checksum_file, 'rt') as f: stored_checksum = f.read().strip()
current_checksum = hashFileContents(k) current_checksum = hashFileContents(k)
self.assertEqual(current_checksum, stored_checksum) nose.tools.eq_(current_checksum, stored_checksum)
def load_result(self, executor): def load_result(self, executor):
...@@ -84,8 +83,8 @@ class TestExecution(unittest.TestCase): ...@@ -84,8 +83,8 @@ class TestExecution(unittest.TestCase):
assert f.setup(os.path.join(executor.cache, executor.data['result']['path'] + '.data'), assert f.setup(os.path.join(executor.cache, executor.data['result']['path'] + '.data'),
executor.prefix) executor.prefix)
data, start, end = f[0] data, start, end = f[0]
self.assertEqual(start, 0) nose.tools.eq_(start, 0)
self.assertTrue(end >= start) assert end >= start
f.close() f.close()
return data return data
...@@ -187,28 +186,28 @@ class TestExecution(unittest.TestCase): ...@@ -187,28 +186,28 @@ class TestExecution(unittest.TestCase):
def test_single_1_error(self): def test_single_1_error(self):
result = self.execute('user/user/single/1/single_error', [None]) result = self.execute('user/user/single/1/single_error', [None])
assert result assert result
self.assertEqual(result['status'], 1) nose.tools.eq_(result['status'], 1)
assert result['user_error'] assert result['user_error']
assert 'NameError' in result['user_error'] assert 'NameError' in result['user_error']
self.assertEqual(result['system_error'], '') nose.tools.eq_(result['system_error'], '')
@slow @slow
def test_single_1_crash(self): def test_single_1_crash(self):
result = self.execute('user/user/single/1/single_crash', [None]) result = self.execute('user/user/single/1/single_crash', [None])
assert result assert result
self.assertEqual(result['status'], 1) nose.tools.eq_(result['status'], 1)
assert result['user_error'] assert result['user_error']
assert 'NameError' in result['user_error'] assert 'NameError' in result['user_error']
self.assertEqual(result['system_error'], '') nose.tools.eq_(result['system_error'], '')
@slow @slow
def test_single_1_db_crash(self): def test_single_1_db_crash(self):
result = self.execute('user/user/single/1/single_db_crash', [None]) result = self.execute('user/user/single/1/single_db_crash', [None])
assert result assert result
self.assertTrue(result['status'] != 0) assert result['status'] != 0
assert result['user_error'] assert result['user_error']
assert 'a = b' in result['user_error'] assert 'a = b' in result['user_error']
self.assertEqual(result['system_error'], '') nose.tools.eq_(result['system_error'], '')
@slow @slow
def test_single_1_large(self): def test_single_1_large(self):
...@@ -226,7 +225,7 @@ class TestExecution(unittest.TestCase): ...@@ -226,7 +225,7 @@ class TestExecution(unittest.TestCase):
def test_too_many_nexts(self): def test_too_many_nexts(self):
result = self.execute('user/user/triangle/1/too_many_nexts', [None]) result = self.execute('user/user/triangle/1/too_many_nexts', [None])
assert result assert result
self.assertTrue(result['status'] != 0) assert result['status'] != 0
assert result['user_error'] assert result['user_error']
assert 'no more data' in result['user_error'] assert 'no more data' in result['user_error']
...@@ -291,40 +290,7 @@ class TestExecution(unittest.TestCase): ...@@ -291,40 +290,7 @@ class TestExecution(unittest.TestCase):
#---------------------------------------------------------- #----------------------------------------------------------
class TestDockerExecution(TestExecution): class TestLocalExecution(BaseExecution):
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
@classmethod
def tearDownClass(cls):
cls.host.teardown()
cleanup()
def tearDown(self):
super(TestDockerExecution, self).tearDown()
self.host.teardown()
def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache,
database_cache, algorithm_cache):
return DockerExecutor(self.host, prefix, configuration, tmp_prefix,
dataformat_cache, database_cache, algorithm_cache)
# NOT COMPATIBLE YET WITH THE NEW API
# @slow
# def test_cxx_double_1(self):
# assert self.execute('user/user/double/1/cxx_double', [{'out_data': 42}]) is None
#----------------------------------------------------------
class TestLocalExecution(TestExecution):
def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache, def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache,
database_cache, algorithm_cache): database_cache, algorithm_cache):
...@@ -335,16 +301,9 @@ class TestLocalExecution(TestExecution): ...@@ -335,16 +301,9 @@ class TestLocalExecution(TestExecution):
#---------------------------------------------------------- #----------------------------------------------------------
class TestSubprocessExecution(TestExecution): class TestSubprocessExecution(BaseExecution):
def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache, def create_executor(self, prefix, configuration, tmp_prefix, dataformat_cache,
database_cache, algorithm_cache): database_cache, algorithm_cache):
return SubprocessExecutor(prefix, configuration, tmp_prefix, return SubprocessExecutor(prefix, configuration, tmp_prefix,
dataformat_cache, database_cache, algorithm_cache) dataformat_cache, database_cache, algorithm_cache)
#----------------------------------------------------------
# Don't run tests from the base class
del TestExecution
...@@ -90,7 +90,8 @@ def test_plot_png(): ...@@ -90,7 +90,8 @@ def test_plot_png():
#with open('test.png', 'wb') as f: f.write(fig) #with open('test.png', 'wb') as f: f.write(fig)
def test_plot_jpeg(): def no_test_plot_jpeg():
# disabled test: matplotlib on 'defaults' is not compiled against libjpeg
fig = do_plot('image/jpeg') fig = do_plot('image/jpeg')
nose.tools.eq_(imghdr.what('test.jpg', fig), 'jpeg') nose.tools.eq_(imghdr.what('test.jpg', fig), 'jpeg')
#with open('test.jpg', 'wb') as f: f.write(fig) #with open('test.jpg', 'wb') as f: f.write(fig)
......
...@@ -45,7 +45,6 @@ from time import sleep ...@@ -45,7 +45,6 @@ from time import sleep
from ..scripts import worker from ..scripts import worker
from ..worker import WorkerController from ..worker import WorkerController
from ..dock import Host
from ..database import Database from ..database import Database
from . import prefix, tmp_prefix from . import prefix, tmp_prefix
...@@ -536,21 +535,6 @@ class TestOneWorker(TestWorkerBase): ...@@ -536,21 +535,6 @@ class TestOneWorker(TestWorkerBase):
#---------------------------------------------------------- #----------------------------------------------------------
class TestOneWorkerDocker(TestOneWorker):
def __init__(self, methodName='runTest'):
super(TestOneWorkerDocker, self).__init__(methodName)
self.docker = True
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
#----------------------------------------------------------
class TestTwoWorkers(TestWorkerBase): class TestTwoWorkers(TestWorkerBase):
def setUp(self): def setUp(self):
...@@ -623,18 +607,3 @@ class TestTwoWorkers(TestWorkerBase): ...@@ -623,18 +607,3 @@ class TestTwoWorkers(TestWorkerBase):
_check(worker2, status, job_id, data) _check(worker2, status, job_id, data)
self.assertNotEqual(worker1, worker2) self.assertNotEqual(worker1, worker2)
#----------------------------------------------------------
class TestTwoWorkersDocker(TestTwoWorkers):
def __init__(self, methodName='runTest'):
super(TestTwoWorkersDocker, self).__init__(methodName)
self.docker = True
@classmethod
def setUpClass(cls):
cls.host = Host(raise_on_errors=False)
...@@ -35,6 +35,7 @@ requirements: ...@@ -35,6 +35,7 @@ requirements:
- simplejson - simplejson
- six - six
- beat.backend.python - beat.backend.python
- matplotlib
test: test:
requires: requires:
...@@ -50,7 +51,7 @@ test: ...@@ -50,7 +51,7 @@ test:
commands: commands:
- worker --help - worker --help
- nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} --exclude=".*test_docker.*" --exclude=".*test_docker_execution.*" --exclude=".*test_docker_worker.*"
- sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx
- sphinx-build -aEb doctest {{ project_dir }}/doc sphinx - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx
- conda inspect linkages -p $PREFIX {{ name }} # [not win] - conda inspect linkages -p $PREFIX {{ name }} # [not win]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment