Skip to content

Commit

Permalink
Refs #4399. More Python algorithm tests.
Browse files Browse the repository at this point in the history
The traits/properties are split into two separate tests so to try
and make it clearer what is being tested.
  • Loading branch information
martyngigg committed Mar 5, 2012
1 parent 28725dc commit 4d4f603
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "MantidAPI/AlgorithmFactory.h"
#include "MantidAPI/Algorithm.h"
#include "MantidPythonInterface/kernel/PythonObjectInstantiator.h"
#include "MantidPythonInterface/api/PythonAlgorithm/AlgorithmWrapper.h"

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>

Expand Down Expand Up @@ -75,22 +77,16 @@ namespace
void registerAlgorithm(boost::python::object obj)
{
using Mantid::PythonInterface::PythonObjectInstantiator;
using Mantid::PythonInterface::AlgorithmWrapper;
using Mantid::API::Algorithm;
// The current frame should know what an Algorithm is, or it
// couldn't create one. Get the class object from the f_globals
PyObject *defs = PyEval_GetFrame()->f_globals;
PyObject *pyalgClass = PyDict_GetItemString(defs, "PythonAlgorithm");
if( !pyalgClass )
{
throw std::runtime_error("Unable to find Algorithm definition, cannot register algorithm.\nHas the definition been imported");
}
static PyObject * const pyAlgClass = (PyObject*)converter::registered<AlgorithmWrapper>::converters.to_python_target_type();
// obj could be or instance/class, check instance first
PyObject *classObject(NULL);
if( PyObject_IsInstance(obj.ptr(), pyalgClass) )
if( PyObject_IsInstance(obj.ptr(), pyAlgClass) )
{
classObject = PyObject_GetAttrString(obj.ptr(), "__class__");
}
else if(PyObject_IsSubclass(obj.ptr(), pyalgClass))
else if(PyObject_IsSubclass(obj.ptr(), pyAlgClass))
{
classObject = obj.ptr(); // We need to ensure the type of lifetime management so grab the raw pointer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ namespace
void export_BoundedValidator()
{
EXPORT_BOUNDEDVALIDATOR(double);
EXPORT_BOUNDEDVALIDATOR(int);
EXPORT_BOUNDEDVALIDATOR(long);
}

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace
void export_IValidators()
{
EXPORT_IVALIDATOR(double,double);
EXPORT_IVALIDATOR(int,double);
EXPORT_IVALIDATOR(long,long);
EXPORT_IVALIDATOR(std::string,std_string);
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ void export_Property()
;

class_<Property, boost::noncopyable>("Property", no_init)
.add_property("name", make_function(&Mantid::Kernel::Property::name, return_value_policy<copy_const_reference>()), "The name of the property")
.add_property("valueAsStr", &Mantid::Kernel::Property::value, "The value of the property as a string. "
"For some property types, e.g. Workspaces, it is useful to be able to refer to the string value directly")
.add_property("isValid", &Mantid::Kernel::Property::isValid, "An empty string if the property is valid, otherwise it contains an error message.")
.add_property("allowedValues", &Mantid::Kernel::Property::allowedValues, "A list of allowed values")
.add_property("name", make_function(&Mantid::Kernel::Property::name, return_value_policy<copy_const_reference>()),
"The name of the property")
.add_property("documentation", make_function(&Mantid::Kernel::Property::documentation, return_value_policy<copy_const_reference>()),
"The property's doc string")
.add_property("direction", &Mantid::Kernel::Property::direction, "Input, Output, InOut or Unknown. See the Direction enum")
.add_property("units", &Mantid::Kernel::Property::units, "The units attached to this property")
.add_property("isDefault", &Mantid::Kernel::Property::isDefault, "Is the property set at the default value")
.add_property("getGroup", make_function(&Mantid::Kernel::Property::getGroup, return_value_policy<copy_const_reference>()), "Return the 'group' of the property, that is, the header in the algorithm's list of properties.")
.add_property("valueAsStr", &Mantid::Kernel::Property::value, "The value of the property as a string. "
"For some property types, e.g. Workspaces, it is useful to be able to refer to the string value directly")
.def("isValid", &Mantid::Kernel::Property::isValid, "An empty string if the property is valid, otherwise it contains an error message.")
.def("allowedValues", &Mantid::Kernel::Property::allowedValues, "A list of allowed values")
.def("isDefault", &Mantid::Kernel::Property::isDefault, "Is the property set at the default value")
.def("getGroup", &Mantid::Kernel::Property::getGroup, return_value_policy<copy_const_reference>(),
"Return the 'group' of the property, that is, the header in the algorithm's list of properties.")
;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
void export_BasicPropertyWithValueTypes()
{
// See MantidPythonInterface/kernel/PropertyWithValue.h for macro definition
EXPORT_PROP_W_VALUE(int, int);
EXPORT_PROP_W_VALUE(std::vector<int>, vector_int);
EXPORT_PROP_W_VALUE(int32_t, int32_t);
EXPORT_PROP_W_VALUE(std::vector<int32_t>, vector_int32_t);
EXPORT_PROP_W_VALUE(int64_t, int64_t);
EXPORT_PROP_W_VALUE(std::vector<int64_t>, vector_int64_t);

EXPORT_PROP_W_VALUE(size_t, size_t);
EXPORT_PROP_W_VALUE(std::vector<size_t>, vector_size_t);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest

import testhelpers
from mantid.api import AlgorithmManager
from mantid.api import (IAlgorithm, Algorithm, AlgorithmProxy, PythonAlgorithm,
registerAlgorithm)
Expand All @@ -15,10 +15,7 @@ class NotAnAlgorithm(object):
class AlgorithmManagerTest(unittest.TestCase):

def test_create_default_version(self):
try:
alg = AlgorithmManager.Instance().create("ConvertUnits")
except RuntimeError:
self.fail(str(exc))
alg = testhelpers.assert_raises_nothing(self, AlgorithmManager.Instance().create, "ConvertUnits")
# Tests
self.assertNotEqual(alg, None)
self.assertEquals(alg.name(), "ConvertUnits")
Expand Down Expand Up @@ -46,20 +43,10 @@ def test_pyalg_isinstance_of_Algorithm(self):
self.assertTrue(isinstance(alg, IAlgorithm))

def test_algorithm_registration_with_valid_object_succeeds(self):
try:
registerAlgorithm(IsAnAlgorithm)
noerror = True
except Exception:
noerror = False
self.assertTrue(noerror)
testhelpers.assert_raises_nothing(self, registerAlgorithm, IsAnAlgorithm)

def test_algorithm_registration_with_invalid_object_throws(self):
try:
registerAlgorithm(NotAnAlgorithm)
error = False
except ValueError:
error = True
self.assertTrue(error)
self.assertRaises(ValueError, registerAlgorithm, NotAnAlgorithm)

if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import unittest
import testhelpers
from mantid import BoundedValidator

class BoundedValidatorTest(object):

def test_construction_does_not_raise_error_when_both_are_floats(self):
try:
BoundedValidator(1.0, 2.0)
except RuntimeError:
self.fail("BoundedValidator constructor should not raise an error with two floats.")
testhelpers.assert_raises_nothing(self, BoundedValidator, 1.0, 2.0)

def test_construction_does_not_raise_error_when_both_are_ints(self):
try:
BoundedValidator(1, 20)
except RuntimeError:
self.fail("BoundedValidator constructor should not raise an error with two ints.")
testhelpers.assert_raises_nothing(self, BoundedValidator, 1, 20)

def test_construction_raises_error_when_called_with_no_params(self):
self.assertRaises(TypeError, BoundedValidator())

def test_construction_with_lower_sets_only_lower(self):
validator = BoundedValidator(1)
validator = BoundedValidator(lower=1)
self.assertEquals(validator.hasLower(), True)
self.assertEquals(validator.hasUpper(), False)
self.assertEquals(validator.lower(), 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import unittest

import testhelpers
from mantid.api import FrameworkManager, AlgorithmProxy

class FrameworkManagerTest(unittest.TestCase):

def assertRaisesNothing(self, callable):
try:
callable()
except Exception, exc:
self.fail(str(exc))

def test_clear_functions_do_not_throw(self):
# Test they don't throw for now
self.assertRaisesNothing(FrameworkManager.Instance().clear)
self.assertRaisesNothing(FrameworkManager.Instance().clearData)
self.assertRaisesNothing(FrameworkManager.Instance().clearAlgorithms)
self.assertRaisesNothing(FrameworkManager.Instance().clearInstruments)
testhelpers.assert_raises_nothing(self, FrameworkManager.Instance().clear)
testhelpers.assert_raises_nothing(self, FrameworkManager.Instance().clearData)
testhelpers.assert_raises_nothing(self, FrameworkManager.Instance().clearAlgorithms)
testhelpers.assert_raises_nothing(self, FrameworkManager.Instance().clearInstruments)

def _is_managed_test(self, alg, version):
self.assertTrue(alg.isInitialized())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import testhelpers
from mantid.geometry import OrientedLattice, UnitCell
from mantid.kernel import V3D
import numpy as np
Expand All @@ -24,10 +25,7 @@ def test_simple_values(self):
def test_setu_matrix_from_vectors(self):
def run_test(v1, v2):
cell = OrientedLattice()
try:
cell.setUFromVectors(v1, v2)
except RuntimeError:
self.fail("The unit transformation should not raise an error")
testhelpers.assert_raises_nothing(self, cell.setUFromVectors, v1, v2)
rot = cell.getUB();
expected = np.array([(0,1.,0.), (0.,0.,1.), (1.,0.,0.)])
np.testing.assert_array_almost_equal(expected, rot, 8)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import unittest
import testhelpers

from mantid import PythonAlgorithm, Direction
from mantid import BoundedValidator


class BasicPropsAlg(PythonAlgorithm):

def PyInit(self):
self.declareProperty('SimpleInput', 1)
self.declareProperty('SimpleOutput', 1.0, Direction.Output)
self.declareProperty('InputString', "", Direction.Input)

def PyExec(self):
pass
Expand All @@ -15,16 +19,72 @@ def PyExec(self):

class PythonAlgorithmPropertiesTest(unittest.TestCase):

def test_simple_property_declarations_have_correct_attrs(self):
alg = BasicPropsAlg() #AlgorithmManager.Instance().createUnmanaged("TestPyAlgDeclaringProps")
def test_simple_property_declarations_have_correct_attrs(self):
"""
Test the basic property declarations without validators
"""
class BasicPropsAlg(PythonAlgorithm):

_testdocstring = "This is a doc string"
def PyInit(self):
self.declareProperty('SimpleInput', 1)
self.declareProperty('SimpleOutput', 1.0, Direction.Output)
self.declareProperty('InputString', "", Direction.Input)
self.declareProperty('PropWithDocDefaultDir', 1, self._testdocstring)
self.declareProperty('PropWithDocOutputDir', 1.0, self._testdocstring, Direction.Output)


def PyExec(self):
pass
##########################################################################
alg = BasicPropsAlg()
props = alg.getProperties()
self.assertEquals(0, len(props))
alg.initialize()
props = alg.getProperties()
self.assertEquals(2, len(props))
self.assertEquals(5, len(props))

input = alg.getProperty("SimpleInput")
self.assertEquals(input.direction, Direction.Input)
self.assertEquals(input.value, 1)
output = alg.getProperty("SimpleOutput")
self.assertEquals(output.direction, Direction.Output)

self.assertEquals(output.value, 1.0)
str_prop = alg.getProperty("InputString")
self.assertEquals(str_prop.direction, Direction.Input)
self.assertEquals(str_prop.value, "")

doc_prop_def_dir = alg.getProperty("PropWithDocDefaultDir")
self.assertEquals(doc_prop_def_dir.direction, Direction.Input)
self.assertEquals(doc_prop_def_dir.documentation, alg._testdocstring)
doc_prop_out_dir = alg.getProperty("PropWithDocOutputDir")
self.assertEquals(doc_prop_out_dir.direction, Direction.Output)
self.assertEquals(doc_prop_out_dir.documentation, alg._testdocstring)

def test_properties_obey_attached_validators(self):
"""
Test property declarations with validator.
The validators each have their own test.
"""
class PropertiesWithValidation(PythonAlgorithm):

def PyInit(self):
self.declareProperty('NumPropWithDefaultDir', -1, BoundedValidator(lower=0))
self.declareProperty('NumPropWithInOutDir', -1, BoundedValidator(lower=0),"doc string", Direction.InOut)

def PyExec(self):
pass
###################################################
alg = PropertiesWithValidation()
alg.initialize()
props = alg.getProperties()
self.assertEquals(2, len(props))

def_dir = alg.getProperty("NumPropWithDefaultDir")
self.assertEquals(def_dir.direction, Direction.Input)
self.assertNotEquals("", def_dir.isValid())
self.assertRaises(ValueError, alg.setProperty, "NumPropWithDefaultDir", -10)
testhelpers.assert_raises_nothing(self, alg.setProperty, "NumPropWithDefaultDir", 11)

if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest

import testhelpers
from mantid import (PythonAlgorithm, AlgorithmProxy, Algorithm, IAlgorithm,
AlgorithmManager, registerAlgorithm, Direction)

Expand Down Expand Up @@ -41,13 +41,7 @@ def setUp(self):
self.__class__._registered = True
registerAlgorithm(TestPyAlgDefaultAttrs)
registerAlgorithm(TestPyAlgOverriddenAttrs)

def raisesNothing(self, callable, *args): # unittest does not have this for some reason
try:
callable(*args)
except RuntimeError, exc:
self.fail(str(exc))


def test_managed_alg_is_descendent_of_AlgorithmProxy(self):
alg = AlgorithmManager.Instance().create("TestPyAlgDefaultAttrs")
self.assertTrue(isinstance(alg, AlgorithmProxy))
Expand All @@ -60,16 +54,16 @@ def test_unmanaged_alg_is_descendent_of_PythonAlgorithm(self):
self.assertTrue(isinstance(alg, IAlgorithm))

def test_alg_with_default_attrs(self):
self.raisesNothing(AlgorithmManager.Instance().createUnmanaged, "TestPyAlgDefaultAttrs")
testhelpers.assert_raises_nothing(self,AlgorithmManager.Instance().createUnmanaged, "TestPyAlgDefaultAttrs")
alg = AlgorithmManager.Instance().createUnmanaged("TestPyAlgDefaultAttrs")
self.raisesNothing(alg.initialize)
testhelpers.assert_raises_nothing(self,alg.initialize)

self.assertEquals(alg.name(), "TestPyAlgDefaultAttrs")
self.assertEquals(alg.version(), 1)
self.assertEquals(alg.category(), "PythonAlgorithms")

def test_alg_with_overridden_attrs(self):
self.raisesNothing(AlgorithmManager.Instance().createUnmanaged, "CoolAlgorithm")
testhelpers.assert_raises_nothing(self,AlgorithmManager.Instance().createUnmanaged, "CoolAlgorithm")
alg = AlgorithmManager.Instance().createUnmanaged("CoolAlgorithm")
self.assertEquals(alg.name(), "CoolAlgorithm")
self.assertEquals(alg.version(), 2)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import testhelpers
from mantid.geometry import UnitCell, AngleUnits
from mantid.kernel import V3D
import numpy as np
Expand Down Expand Up @@ -29,11 +30,7 @@ def test_numpy_array_conversion(self):
gstar = np.array( [row0,row1,row2] )

u = UnitCell()
try:
u.recalculateFromGstar(gstar)
except Exception:
self.fail("recalculateFromGstar should not raise an exception")

testhelpers.assert_raises_nothing(self, u.recalculateFromGstar, gstar)
self._check_cell(u)

def _check_cell(self, cell):
Expand Down
16 changes: 16 additions & 0 deletions Code/Mantid/Framework/PythonInterface/test/python/testhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ def run_algorithm(name, **kwargs):
alg.execute()
return alg

def assert_raises_nothing(testobj, callable, *args):
"""
unittest does not have an assertRaisesNothing. This
provides that functionality
Parameters:
testobj - A unittest object
callable - A callable object
*args - Positional arguments passed to the callable as they are
"""
try:
return callable(*args)
except Exception, exc:
testobj.fail("Assertion error. An exception was caught where none was expected in %s. Message: %s"
% (callable.__name__, str(exc)))

def can_be_instantiated(cls):
"""The Python unittest assertRaises does not
seem to catch the assertion raised by being unable
Expand Down

0 comments on commit 4d4f603

Please sign in to comment.