Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 5428233710b752fa243b118f71ef7801850b581f)
+++ sasmodels/model_test.py	(revision 5428233710b752fa243b118f71ef7801850b581f)
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Feb 17 11:43:56 2015
+
+@author: David
+"""
+
+import unittest
+import warnings
+import numpy as np
+
+from os.path import basename, dirname, join as joinpath
+from glob import glob
+
+try:
+    from .kernelcl import load_model
+except ImportError,exc:
+    warnings.warn(str(exc))
+    warnings.warn("using ctypes instead")
+    from .kerneldll import load_model
+
+def load_kernel(model, dtype='single'):   
+    kernel = load_model(model, dtype=dtype)
+    kernel.info['defaults'] = dict((p[0],p[2]) for p in kernel.info['parameters'])
+    return kernel
+
+def get_weights(model, pars, name):
+    from . import weights
+    
+    relative = name in model.info['partype']['pd-rel']
+    disperser = pars.get(name+"_pd_type", "gaussian")
+    value = pars.get(name, model.info['defaults'][name])
+    width = pars.get(name+"_pd", 0.0)
+    npts = pars.get(name+"_pd_n", 30)
+    nsigma = pars.get(name+"_pd_nsigma", 3.0)
+    v,w = weights.get_weights(
+            disperser, npts, width, nsigma,
+            value, model.info['limits'][name], relative)
+    return v,w/np.sum(w)
+
+def eval_kernel(kernel, q, pars, cutoff=1e-5):
+    input = kernel.make_input(q)
+    finput = kernel(input)
+
+    fixed_pars = [pars.get(name, finput.info['defaults'][name])
+                  for name in finput.fixed_pars]
+    pd_pars = [get_weights(finput, pars, p) for p in finput.pd_pars]
+    return finput(fixed_pars, pd_pars, cutoff)
+
+def annotate_exception(exc, msg):
+    """
+    Add an annotation to the current exception, which can then be forwarded
+    to the caller using a bare "raise" statement to reraise the annotated
+    exception.
+    Example::
+        >>> D = {}
+        >>> try: 
+        ...    print D['hello']
+        ... except Exception,exc: 
+        ...    annotate_exception(exc, "while accessing 'D'")
+        ...    raise
+        Traceback (most recent call last):
+            ...
+        KeyError: "hello while accessing 'D'"
+    """
+    args = exc.args
+    if not args:
+        arg0 = msg
+    else:
+        arg0 = " ".join((args[0],msg))
+    exc.args = tuple([arg0] + list(args[1:]))
+    
+def suite():
+    root = dirname(__file__)
+    files = sorted(glob(joinpath(root, 'models', "[a-zA-Z]*.py")))
+    models_names = [basename(f)[:-3] for f in files]
+    
+    suite = unittest.TestSuite()
+    
+    for model_name in models_names:
+        module = __import__('sasmodels.models.' + model_name)
+        module = getattr(module, 'models', None)
+
+        model = getattr(module, model_name, None)
+        tests = getattr(model, 'tests', [])
+        
+        if tests:
+            #print '------'
+            #print 'found tests in', model_name
+            #print '------'
+    
+            kernel = load_kernel(model)
+            suite.addTest(ModelTestCase(model_name, kernel, tests))
+
+    return suite
+
+class ModelTestCase(unittest.TestCase):
+    
+    def __init__(self, model_name, kernel, tests):
+        unittest.TestCase.__init__(self)
+        
+        self.model_name = model_name
+        self.kernel = kernel
+        self.tests = tests
+
+    def runTest(self):
+        #print '------'
+        #print self.model_name
+        #print '------'
+        try:
+            for test in self.tests:
+                params = test[0]
+                Q = test[1]
+                I = test[2]
+                      
+                if not isinstance(Q, list):
+                    Q = [Q]
+                if not isinstance(I, list):
+                    I = [I]
+                    
+                if isinstance(Q[0], tuple):
+                    npQ = [np.array([Qi[d] for Qi in Q]) for d in xrange(len(Q[0]))]
+                else:
+                    npQ = [np.array(Q)]
+
+                self.assertTrue(Q)
+                self.assertEqual(len(I), len(Q))    
+            
+                Iq = eval_kernel(self.kernel, npQ, params)
+            
+                self.assertGreater(len(Iq), 0)    
+                self.assertEqual(len(I), len(Iq))              
+                
+                for q, i, iq in zip(Q, I, Iq):
+                    err = np.abs(i - iq)
+                    nrm = np.abs(i)
+            
+                    self.assertLess(err * 10**5, nrm, 'q:%s; expected:%s; actual:%s' % (q, i, iq))
+                    
+        except Exception,exc: 
+            annotate_exception(exc, '\r\nModel: %s' % self.model_name)
+            raise
+
+def main():
+    #unittest.main()
+    runner = unittest.TextTestRunner()
+    runner.run(suite())
+
+if __name__ == "__main__":
+    main()
Index: sasmodels/models/cylinder.py
===================================================================
--- sasmodels/models/cylinder.py	(revision a503bfddd742ec35fe2b801f1d75a0a5f11eccfa)
+++ sasmodels/models/cylinder.py	(revision 5428233710b752fa243b118f71ef7801850b581f)
@@ -169,2 +169,22 @@
 oldpars=dict(theta='cyl_theta', phi='cyl_phi', sld='sldCyl', solvent_sld='sldSolv')
 
+
+# test values:
+# [
+#   [ {parameters}, q, I(q)],
+#   [ {parameters}, [q], [I(q)] ],
+#   [ {parameters}, [q1, q2, ...], [I(q1), I(q2), ...]],
+
+#   [ {parameters}, (qx, qy), I(qx, Iqy)],
+#   [ {parameters}, [(qx1, qy1), (qx2, qy2), ...], [I(qx1,qy1), I(qx2,qy2), ...],
+#   ...
+# ]
+# Precision defaults to 7 digits (relative) for single, 14 for double
+# May want a limited precision version that checks for 8-n or 15-n digits respectively
+tests = [
+    [{},  0.2, 0.041761386790780453],
+    [{'theta':10.0, 'phi':10.0}, [0.2], [0.041761386790780453]],
+    [{'theta':10.0, 'phi':10.0}, (0.2, 2.5), 0.038176446608393366],
+    [{'theta':10.0, 'phi':10.0}, [(0.2, 2.5)], [0.038176446608393366]],
+    ]
+
