Skip to content

Commit

Permalink
Merge pull request #3067 from kinow/7.8.x-backport-pr-3060
Browse files Browse the repository at this point in the history
7.8.x backport pr 3060
  • Loading branch information
hjoliver authored Apr 3, 2019
2 parents 1bed60a + 272b61c commit 123bbc1
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 9 deletions.
16 changes: 9 additions & 7 deletions lib/cylc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2103,13 +2103,15 @@ def load_graph(self):
else:
try:
if not callable(get_func(xtrig.func_name, self.fdir)):
raise SuiteConfigError("ERROR, xtrigger function "
"not callable: "
"%s" % xtrig.func_name)
except ImportError:
raise SuiteConfigError("ERROR, xtrigger function "
"not found: "
"%s" % xtrig.func_name)
raise SuiteConfigError(
"ERROR, "
"xtrigger function not callable: %s" %
xtrig.func_name)
except (ImportError, AttributeError):
raise SuiteConfigError(
"ERROR, "
"xtrigger function not found: %s" %
xtrig.func_name)
self.xtrigger_mgr.add_trig(label, xtrig)
self.taskdefs[task_name].xtrig_labels.add(label)

Expand Down
142 changes: 142 additions & 0 deletions lib/cylc/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2019 NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import shutil
import unittest
from tempfile import mkdtemp

from cylc.config import SuiteConfig, SuiteConfigError


class TestSuiteConfig(unittest.TestCase):
"""Test class for the Cylc SuiteConfig object."""

def test_xfunction_imports(self):
"""Test for a suite configuration with valid xtriggers"""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
if not os.path.exists(python_dir):
os.makedirs(python_dir)
name_a_tree_file = os.path.join(python_dir, "name_a_tree.py")
with open(name_a_tree_file, mode="w") as f:
# NB: we are not returning a lambda, instead we have a scalar
f.write("""name_a_tree = lambda: 'jacaranda'""")
f.flush()
suite_rc = os.path.join(temp_dir, "suite.rc")
with open(suite_rc, mode="w") as f:
f.write("""
[scheduling]
initial cycle point = 2018-01-01
[[xtriggers]]
tree = name_a_tree()
[[dependencies]]
[[[R1]]]
graph = '@tree => qux'
""")
f.flush()
suite_config = SuiteConfig(suite="name_a_tree", fpath=f.name)
config = suite_config
self.assertTrue('tree' in config.xtriggers['qux'])
shutil.rmtree(temp_dir)

def test_xfunction_import_error(self):
"""Test for error when a xtrigger function cannot be imported."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
if not os.path.exists(python_dir):
os.makedirs(python_dir)
caiman_file = os.path.join(python_dir, "caiman.py")
with open(caiman_file, mode="w") as f:
# NB: we are not returning a lambda, instead we have a scalar
f.write("""caiman = lambda: True""")
f.flush()
suite_rc = os.path.join(temp_dir, "suite.rc")
with open(suite_rc, mode="w") as f:
f.write("""
[scheduling]
initial cycle point = 2018-01-01
[[xtriggers]]
oopsie = piranha()
[[dependencies]]
[[[R1]]]
graph = '@oopsie => qux'
""")
f.flush()
with self.assertRaises(SuiteConfigError) as ex:
SuiteConfig(suite="caiman_suite", fpath=f.name)
self.assertTrue("not found" in str(ex))
shutil.rmtree(temp_dir)

def test_xfunction_attribute_error(self):
"""Test for error when a xtrigger function cannot be imported."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
if not os.path.exists(python_dir):
os.makedirs(python_dir)
capybara_file = os.path.join(python_dir, "capybara.py")
with open(capybara_file, mode="w") as f:
# NB: we are not returning a lambda, instead we have a scalar
f.write("""toucan = lambda: True""")
f.flush()
suite_rc = os.path.join(temp_dir, "suite.rc")
with open(suite_rc, mode="w") as f:
f.write("""
[scheduling]
initial cycle point = 2018-01-01
[[xtriggers]]
oopsie = capybara()
[[dependencies]]
[[[R1]]]
graph = '@oopsie => qux'
""")
f.flush()
with self.assertRaises(SuiteConfigError) as ex:
SuiteConfig(suite="capybara_suite", fpath=f.name)
self.assertTrue("not found" in str(ex))
shutil.rmtree(temp_dir)

def test_xfunction_not_callable(self):
"""Test for error when a xtrigger function is not callable."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
if not os.path.exists(python_dir):
os.makedirs(python_dir)
not_callable_file = os.path.join(python_dir, "not_callable.py")
with open(not_callable_file, mode="w") as f:
# NB: we are not returning a lambda, instead we have a scalar
f.write("""not_callable = 42""")
f.flush()
suite_rc = os.path.join(temp_dir, "suite.rc")
with open(suite_rc, mode="w") as f:
f.write("""
[scheduling]
initial cycle point = 2018-01-01
[[xtriggers]]
oopsie = not_callable()
[[dependencies]]
[[[R1]]]
graph = '@oopsie => qux'
""")
f.flush()
with self.assertRaises(SuiteConfigError) as ex:
SuiteConfig(suite="suite_with_not_callable", fpath=f.name)
self.assertTrue("callable" in str(ex))
shutil.rmtree(temp_dir)


if __name__ == '__main__':
unittest.main()
69 changes: 67 additions & 2 deletions lib/cylc/tests/test_subprocpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from tempfile import NamedTemporaryFile, SpooledTemporaryFile, TemporaryFile
from tempfile import NamedTemporaryFile, SpooledTemporaryFile, TemporaryFile,\
mkdtemp
import unittest

import os
import shutil

from cylc.subprocctx import SubProcContext
from cylc.subprocpool import SubProcPool
from cylc.subprocpool import SubProcPool, _XTRIG_FUNCS, get_func


class TestSubProcPool(unittest.TestCase):
Expand Down Expand Up @@ -128,6 +132,67 @@ def test_run_command_with_stdin_from_paths(self):
for handle in handles:
handle.close()

def test_xfunction(self):
"""Test xtrigger function import."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
os.makedirs(python_dir)
the_answer_file = os.path.join(python_dir, "the_answer.py")
with open(the_answer_file, mode="w") as f:
f.write("""the_answer = lambda: 42""")
f.flush()
fn = get_func("the_answer", temp_dir)
result = fn()
self.assertEqual(42, result)
shutil.rmtree(temp_dir)

def test_xfunction_cache(self):
"""Test xtrigger function import cache."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
os.makedirs(python_dir)
amandita_file = os.path.join(python_dir, "amandita.py")
with open(amandita_file, mode="w") as f:
f.write("""amandita = lambda: 'chocolate'""")
f.flush()
fn = get_func("amandita", temp_dir)
result = fn()
self.assertEqual('chocolate', result)

# is in the cache
self.assertTrue('amandita' in _XTRIG_FUNCS)
# returned from cache
self.assertEqual(fn, get_func("amandita", temp_dir))
del _XTRIG_FUNCS['amandita']
# is not in the cache
self.assertFalse('amandita' in _XTRIG_FUNCS)
shutil.rmtree(temp_dir)

def test_xfunction_import_error(self):
"""Test for error on importing a xtrigger function.
To prevent the test eventually failing if the test function is added
and successfully imported, we use an invalid module name as per Python
spec.
"""
temp_dir = mkdtemp()
with self.assertRaises(ImportError):
get_func("invalid-module-name", temp_dir)
shutil.rmtree(temp_dir)

def test_xfunction_attribute_error(self):
"""Test for error on looking for an attribute in a xtrigger script."""
temp_dir = mkdtemp()
python_dir = os.path.join(temp_dir, "lib", "python")
os.makedirs(python_dir)
the_answer_file = os.path.join(python_dir, "the_sword.py")
with open(the_answer_file, mode="w") as f:
f.write("""the_droid = lambda: 'excalibur'""")
f.flush()
with self.assertRaises(AttributeError):
get_func("the_sword", temp_dir)
shutil.rmtree(temp_dir)


if __name__ == '__main__':
unittest.main()

0 comments on commit 123bbc1

Please sign in to comment.