diff --git a/.gitattributes b/.gitattributes index 65491dbe12c9bd96e4cb43db929505908bca7251..b29d576159d02102eab2bcc15f6a86eaa41d69e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -822,6 +822,31 @@ CEP/Pipeline/docs/sphinx/source/user/usage/running.rst -text CEP/Pipeline/docs/sphinx/source/user/usage/tasks.rst -text CEP/Pipeline/mac/src/Echo_Protocol.prot -text CEP/Pipeline/mac/test/Observation6129 -text +CEP/Pipeline/recipes/sip/master/__init__.py -text +CEP/Pipeline/recipes/sip/nodes/__init__.py -text +CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py -text +CEP/Pipeline/test/CMakeLists.txt -text +CEP/Pipeline/test/__init__.py -text +CEP/Pipeline/test/cuisine/__init__.py -text +CEP/Pipeline/test/cuisine/lofaringredient.py -text +CEP/Pipeline/test/pipeline_test.run -text +CEP/Pipeline/test/pipeline_test.sh -text +CEP/Pipeline/test/recipes/__init__.py -text +CEP/Pipeline/test/recipes/master/__init__.py -text +CEP/Pipeline/test/recipes/nodes/__init__.py -text +CEP/Pipeline/test/recipes/nodes/imager_create_dbs_test.py -text +CEP/Pipeline/test/support/__init__.py -text +CEP/Pipeline/test/test_framework/CMakeLists.txt -text +CEP/Pipeline/test/test_framework/__init__.py -text +CEP/Pipeline/test/test_framework/fixture/__init__.py -text +CEP/Pipeline/test/test_framework/fixture/gsmutils.py -text +CEP/Pipeline/test/test_framework/fixture/logger.py -text +CEP/Pipeline/test/test_framework/fixture/monetdb/__init__.py -text +CEP/Pipeline/test/test_framework/fixture/monetdb/sql.py -text +CEP/Pipeline/test/test_framework/fixture/pyrap/__init__.py -text +CEP/Pipeline/test/test_framework/fixture/pyrap/tables.py -text +CEP/Pipeline/test/test_framework/fixture/read.me -text +CEP/Pipeline/test/test_framework/unittest_runner.py -text CEP/pyparmdb/src/gsmutils.py -text CEP/pyparmdb/src/lsm.py -text CMake/FindAskapSoft.cmake -text diff --git a/CEP/Pipeline/CMakeLists.txt b/CEP/Pipeline/CMakeLists.txt index 3f840d30676f232eb592425808da27fd9e10c6eb..5b1dcbff6a83b2d264f2262bc8dd33e20643b7eb 100644 --- a/CEP/Pipeline/CMakeLists.txt +++ b/CEP/Pipeline/CMakeLists.txt @@ -1,5 +1,7 @@ # $Id$ lofar_add_package(Pipeline-Framework framework) -#lofar_add_package(Pipeline-MAC mac) lofar_add_package(Pipeline-Recipes recipes) +#lofar_add_package(Pipeline-MAC mac) + +add_subdirectory(test) diff --git a/CEP/Pipeline/recipes/sip/CMakeLists.txt b/CEP/Pipeline/recipes/sip/CMakeLists.txt index 211866f1bd937a0c7e4eab293001eb8d6fcb29f8..79d78ab5d51b3cb75976c1141e7ff3b41b71511f 100644 --- a/CEP/Pipeline/recipes/sip/CMakeLists.txt +++ b/CEP/Pipeline/recipes/sip/CMakeLists.txt @@ -24,6 +24,7 @@ python_install( master/target_pipeline.py master/vdsmaker.py master/vdsreader.py + master/__init__.py nodes/bbs.py nodes/cimager.py nodes/count_timesteps.py @@ -43,6 +44,8 @@ python_install( nodes/demix/shiftphasecenter.py nodes/demix/smoothdemix.py nodes/demix/subtract_from_averaged.py + nodes/imager_create_dbs.py + nodes/__init__.py DESTINATION lofarpipe/recipes) install(FILES diff --git a/CEP/Pipeline/framework/lofarpipe/tests/__init__.py b/CEP/Pipeline/recipes/sip/master/__init__.py similarity index 100% rename from CEP/Pipeline/framework/lofarpipe/tests/__init__.py rename to CEP/Pipeline/recipes/sip/master/__init__.py diff --git a/CEP/Pipeline/recipes/sip/nodes/__init__.py b/CEP/Pipeline/recipes/sip/nodes/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py b/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py new file mode 100644 index 0000000000000000000000000000000000000000..6126c350f20fd5cfb54ec8df2df031394fdb9fdc --- /dev/null +++ b/CEP/Pipeline/recipes/sip/nodes/imager_create_dbs.py @@ -0,0 +1,258 @@ +## LOFAR AWIMAGER RECIPE +## Wouter Klijn 2012 +## klijn@astron.nl +## ----------------------------------------------------------------------------- +# +from __future__ import with_statement +import sys +import subprocess +import math + +import pyrap.tables as pt #@UnresolvedImport +import monetdb.sql as db #@UnresolvedImport +import gsmutils as gsm #@UnresolvedImport + +from lofarpipe.support.lofarnode import LOFARnodeTCP +from lofarpipe.support.pipelinelogging import log_process_output +#from lofar.parameterset import parameterset #@UnresolvedImport #marcel new im + +#TODO: de template moet mischien toch ergens anders vandaan worden gehaalt. +template_parmdb = """ +create tablename="{0}" +adddef Gain:0:0:Ampl values=1.0 +adddef Gain:1:1:Ampl values=1.0 +adddef Gain:0:0:Real values=1.0 +adddef Gain:1:1:Real values=1.0 +adddef DirectionalGain:0:0:Ampl values=1.0 +adddef DirectionalGain:1:1:Ampl values=1.0 +adddef DirectionalGain:0:0:Real values=1.0 +adddef DirectionalGain:1:1:Real values=1.0 +adddef AntennaOrientation values=5.497787144 +quit +""" + + +class imager_create_dbs(LOFARnodeTCP): + def run(self, concatenated_measurement_set, sourcedb_target_path, + monet_db_hostname, monet_db_port, monet_db_name, monet_db_user, + monet_db_password, assoc_theta, parmdb_executable, slice_paths, + parmdb_suffix): + """ + run() creates a source db for the concatenated measurement set. For each + of the individual time slices a paramdb is created. + All inputs are path wise, and no information regarding the number of + slices is needed + """ + self.logger.info("Starting imager_create_dbs Node") + + # create the source db + # returns zero on succes + if self._create_bbs_sky_model(concatenated_measurement_set, + sourcedb_target_path, monet_db_hostname, monet_db_port, + monet_db_name, monet_db_user, monet_db_password, assoc_theta): + return 1 + + # for each slice_set, create a parmdb in the target path + # slice_paths is string representation of am array, convert + slice_paths = eval(slice_paths) + if self._create_parmdb_for_timeslices(parmdb_executable, slice_paths, + parmdb_suffix): + return 1 + + return 0 + + + def _field_of_view(self, measurement_set, alpha_one = None): + """ + _field_of_view calculates the fov, which is dependend on the + station type, location and mode: + For details see: + (1) http://www.astron.nl/radio-observatory/astronomers/lofar-imaging-capabilities-sensitivity/lofar-imaging-capabilities/lofa + + """ + # Open the ms + t = pt.table(measurement_set) + + # Get antenna name and observation mode + antenna = pt.table(t.getkeyword("ANTENNA")) + antenna_name = antenna.getcell('NAME', 0) + antenna.close() + + observation = pt.table(t.getkeyword("OBSERVATION")) + antenna_set = observation.getcell('LOFAR_ANTENNA_SET', 0) + observation.close + + #static parameters for the station diameters ref (1) + hba_core_diameter = 30.8 + hba_remote_diameter = 41.1 + lba_inner = 32.3 + lba_outer = 81.3 + + #use measurement set information to assertain antenna diameter + station_diameter = None + if antenna_name.count('HBA'): + if antenna_name.count('CS'): + station_diameter = hba_core_diameter + elif antenna_name.count('RS'): + station_diameter = hba_remote_diameter + elif antenna_name.count('LBA'): + if antenna_set.count('INNER'): + station_diameter = lba_inner + elif antenna_set.count('OUTER'): + station_diameter = lba_outer + + #raise exception if the antenna is not of a supported type + if station_diameter == None: + self.logger.error('Unknown antenna type for antenna: {0} , {1}'.format(\ + antenna_name, antenna_set)) + raise Exception("Unknown antenna type encountered in Measurement set") + + #Get the wavelength + spectral_window_table = pt.table(t.getkeyword("SPECTRAL_WINDOW")) + freq = float(spectral_window_table.getcell("REF_FREQUENCY", 0)) + wave_length = pt.taql('CALC C()') / freq + + # Now calculate the FOV see ref (1) + # alpha_one is a magic parameter: The value 1.3 is representative for a + # WSRT dish, where it depends on the dish illumination + alpha_one = 1.3 + + #alpha_one is in radians so transform to degrees for output + fwhm = alpha_one * (wave_length / station_diameter) * (180 / math.pi) + fov = fwhm / 2.0 + t.close() + + return fov + + + def _create_parmdb(self, parmdb_executable, target_dir_path): + """ + _create_parmdb, creates a parmdb_executable at the target_dir_path using the + suplied executable. Does not test for existence of target parent dir + returns 1 if parmdb_executable failed 0 otherwise + """ + # Format the template string by inserting the target dir + formatted_template = template_parmdb.format(target_dir_path) + try: + # Spawn a subprocess and connect the pipelines + parmdbm_process = subprocess.Popen( + parmdb_executable, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE + ) + # Send formatted template on stdin + sout, serr = parmdbm_process.communicate(formatted_template) + + # Log the output + log_process_output("parmdbm", sout, serr, self.logger) + except OSError, e: + self.logger.error("Failed to spawn parmdbm: {0}".format(str(e))) + return 1 + + return 0 + + + def _create_parmdb_for_timeslices(self, parmdb_executable, slice_paths, + suffix): + """ + _create_parmdb_for_timeslices creates a paramdb for each of the + supplied time slices. The paramdb path = input path + suffix. + returns 0 on succes 1 on failure: + """ + for slice_path in slice_paths: + #Create the paths based on the 'source ms' + ms_parmdb_path = slice_path + suffix + #call parmdb return failure if a single create failed + if self._create_parmdb(parmdb_executable, ms_parmdb_path) != 0: + return 1 + + return 0 + + + def _create_monet_db_connection(self, hostname, database, username, password, + port): + """ + Create and return a monat db connection. Return None if the creation + failed and log the error. Returns the connection if succeed. + """ + try: + conn = db.connect(hostname = hostname, database = database, + username = username, password = password, + port = port) + except db.Error, e: + self.logger.error("failed to create a monetDB connection: " + "{0}".format(str(e))) + return None + + return conn + + + def _get_ra_and_decl_from_ms(self, measurement_set): + """ + This function uses pyrap to read the ra and declanation from a + measurement set (used by exprected_fluxes_in_fov). This is a position + in the sky. These values are stored in the field.phase_dir in the first + row. All exceptions thrown are caught and logged, return None if reading + failed + """ + try: + # open the ms, get the phase direction + t = pt.table(measurement_set) + t1 = pt.table(t.getkeyword("FIELD")) + ra_and_decl = t1.getcell("PHASE_DIR", 0) + t1.close() + t.close() + except Exception, e: + #catch all exceptions and log + self.logger.error("Error loading FIELD/PHASE_DIR from " + "measurementset {0} : {1}".format(measurement_set, + str(e))) + return None + + # Return the ra and decl + if len(ra_and_decl) != 2: + self.logger.error("returned PHASE_DIR data did not contain two values") + return None + return (ra_and_decl[0], ra_and_decl[1]) + + + def _create_bbs_sky_model(self, measurement_set, path_output_skymap, + monet_db_host, monet_db_port, monet_db_name, + monet_db_user, monet_db_password, + assoc_theta = None): + """ + Create a bbs sky model. Based on the measurement (set) suplied + The skymap is created at the path_output_skymap + """ + + # Create monetdb connection + conn = self._create_monet_db_connection(monet_db_host, monet_db_name, + monet_db_user, monet_db_password, monet_db_port) + + # get position of the target in the sky + (ra_c, decl_c) = self._get_ra_and_decl_from_ms(measurement_set) + + # Get the Fov: sources in this fov should be included in the skumodel + fov_radius = self._field_of_view(measurement_set) + + # !!magic constant!! This value is calculated based on communications with Bart Sheers + if assoc_theta == None: + assoc_theta = 90.0 / 3600 + + try: + gsm.expected_fluxes_in_fov(conn, ra_c, decl_c, fov_radius, assoc_theta, + path_output_skymap, storespectraplots = False) + except Exception, e: + self.logger.error("expected_fluxes_in_fov raise exception: " + + str(e)) + return 1 + + return 0 + + +if __name__ == "__main__": + # args contain information regarding to the logging server + jobid, jobhost, jobport = sys.argv[1:4] + sys.exit(imager_create_dbs(jobid, jobhost, jobport).run_with_stored_arguments()) + diff --git a/CEP/Pipeline/test/CMakeLists.txt b/CEP/Pipeline/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c0697c51d251e7eb8e90d99f1235165692b567a --- /dev/null +++ b/CEP/Pipeline/test/CMakeLists.txt @@ -0,0 +1,6 @@ +# $Id: CMakeLists.txt 18904 2011-09-28 12:15:43Z diepen $ +include(LofarCTest) + +lofar_add_test(pipeline_test) + + diff --git a/CEP/Pipeline/test/__init__.py b/CEP/Pipeline/test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/cuisine/__init__.py b/CEP/Pipeline/test/cuisine/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/framework/lofarpipe/tests/lofaringredient.py b/CEP/Pipeline/test/cuisine/lofaringredient.py similarity index 95% rename from CEP/Pipeline/framework/lofarpipe/tests/lofaringredient.py rename to CEP/Pipeline/test/cuisine/lofaringredient.py index 0a41c79ed0ff8c0f98a2914ed82d0eb94efb7df1..7540bb86cf788ecb424398e783b1d37902dc7e75 100644 --- a/CEP/Pipeline/framework/lofarpipe/tests/lofaringredient.py +++ b/CEP/Pipeline/test/cuisine/lofaringredient.py @@ -1,217 +1,217 @@ -# LOFAR PIPELINE FRAMEWORK -# -# Tests: ingredients -# John Swinbank, 2010 -# swinbank@transientskp.org -# ------------------------------------------------------------------------------ -import xmlrunner -import unittest -import os - -class StringFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.StringField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import StringField - self.stringfield = StringField(default="a string") - - def test_validator(self): - """ - Check that strings are correctly regarded as valid, and integers - aren't. - """ - self.assertFalse(self.stringfield.is_valid(1)) - self.assertTrue(self.stringfield.is_valid("1")) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.stringfield.default, "a string") - -class IntFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.IntField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import IntField - self.intfield = IntField(default=1) - - def test_validator(self): - """ - Check that integers are correctly regarded as valid, and strings - aren't. - """ - self.assertFalse(self.intfield.is_valid("1")) - self.assertTrue(self.intfield.is_valid(1)) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.intfield.default, 1) - - def test_coerce(self): - """ - Check that a string is correctly coerced to an integer. - """ - self.assertEqual(self.intfield.coerce("1"), 1) - -class FloatFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.FloatField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import FloatField - self.floatfield = FloatField(default=1.0) - - def test_validator(self): - """ - Check that floats are correctly regarded as valid, and strings - aren't. - """ - self.assertFalse(self.floatfield.is_valid("1")) - self.assertTrue(self.floatfield.is_valid(1.0)) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.floatfield.default, 1.0) - - def test_coerce(self): - """ - Check that a string is correctly coerced to an float. - """ - self.assertEqual(self.floatfield.coerce("1"), 1.0) - -class FileFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.FileField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import FileField - self.filefield = FileField(default='/') - - def test_validator(self): - """ - Integers are not valid as filenames, and certainly don't exist on - disk. - - ``/`` should, though. - """ - self.assertFalse(self.filefield.is_valid(1)) - self.assertTrue(self.filefield.is_valid("/")) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.filefield.default, "/") - -class ExecFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.ExecField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import ExecField - self.execfield = ExecField(default='/bin/ls') - - def test_validator(self): - """ - ``/etc/passwd`` should always exist as a file on disk, but not be - executable. - - ``/bin/ls`` should always exist, and must be executable. - """ - self.assertFalse(self.execfield.is_valid("/etc/passwd")) - self.assertTrue(self.execfield.is_valid("/bin/ls")) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.execfield.default, "/bin/ls") - -class DirectoryFieldTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.DirectoryField` - """ - def setUp(self): - from lofarpipe.support.lofaringredient import DirectoryField - self.directoryfield = DirectoryField(default='/tmp') - - def test_validator(self): - """ - An integer is not a valid directory. - - ``/tmp`` should always be valid. - """ - self.assertFalse(self.directoryfield.is_valid(1)) - self.assertTrue(self.directoryfield.is_valid("/tmp")) - - def test_default(self): - """ - Check that default is correctly set. - """ - self.assertEqual(self.directoryfield.default, "/tmp") - - def test_coerce(self): - """ - Coercing a create-able directory name should cause it to exist. We - should always be able to write in ``/tmp``. - """ - self.directoryfield.coerce("/tmp/foo") - self.assertTrue(os.path.exists("/tmp/foo")) - -class LOFARIngredientTest(unittest.TestCase): - """ - Tests for :class:`lofarpipe.support.lofaringredient.LOFARingredient` - """ - def setUp(self): - """ - An instance of - :class:`~lofarpipe.support.lofaringredient.LOFARingredient` is defined - which contains three instances of - :class:`~lofarpipe.support.lofaringredient.StringField`. - """ - from lofarpipe.support.lofaringredient import StringField - from lofarpipe.support.lofaringredient import LOFARingredient - f = StringField(default="foo") - g = StringField() - h = StringField(default=1) - self.lofaringredient = LOFARingredient({"f": f, "g": g, "h": h}) - - def test_keys(self): - """ - ``self.lofaringredient`` should contain keys for the two fields - which have default parameters, but not for the one which is unset. - """ - self.assertEqual(len(self.lofaringredient.keys()), 2) - self.assertRaises(KeyError, lambda: self.lofaringredient['g']) - - def test_values(self): - """ - Prior to setting, the value of the fields should be equal to - the default value. - """ - self.assertEqual(self.lofaringredient['f'], "foo") - - def test_set(self): - """ - When set, the value of the fields should be equal to the new value. - """ - self.lofaringredient['g'] = "bar" - self.assertEqual(self.lofaringredient['g'], "bar") - - def test_bad_values(self): - """ - Unacceptable values should raise an exception. - """ - self.assertRaises(TypeError, lambda: self.lofaringredient['h']) - self.lofaringredient['h'] = "bar" - self.assertEqual(self.lofaringredient['h'], "bar") - -if __name__ == "__main__": - unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports.xml')) +# LOFAR PIPELINE FRAMEWORK +# +# Tests: ingredients +# John Swinbank, 2010 +# swinbank@transientskp.org +# ------------------------------------------------------------------------------ +import unittest +import os + +class StringFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.StringField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import StringField + self.stringfield = StringField(default="a string") + + def test_validator(self): + """ + Check that strings are correctly regarded as valid, and integers + aren't. + """ + self.assertFalse(self.stringfield.is_valid(1)) + self.assertTrue(self.stringfield.is_valid("1")) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.stringfield.default, "a string") + +class IntFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.IntField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import IntField + self.intfield = IntField(default=1) + + def test_validator(self): + """ + Check that integers are correctly regarded as valid, and strings + aren't. + """ + self.assertFalse(self.intfield.is_valid("1")) + self.assertTrue(self.intfield.is_valid(1)) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.intfield.default, 1) + + def test_coerce(self): + """ + Check that a string is correctly coerced to an integer. + """ + self.assertEqual(self.intfield.coerce("1"), 1) + +class FloatFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.FloatField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import FloatField + self.floatfield = FloatField(default=1.0) + + def test_validator(self): + """ + Check that floats are correctly regarded as valid, and strings + aren't. + """ + self.assertFalse(self.floatfield.is_valid("1")) + self.assertTrue(self.floatfield.is_valid(1.0)) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.floatfield.default, 1.0) + + def test_coerce(self): + """ + Check that a string is correctly coerced to an float. + """ + self.assertEqual(self.floatfield.coerce("1"), 1.0) + +class FileFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.FileField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import FileField + self.filefield = FileField(default='/') + + def test_validator(self): + """ + Integers are not valid as filenames, and certainly don't exist on + disk. + + ``/`` should, though. + """ + self.assertFalse(self.filefield.is_valid(1)) + self.assertTrue(self.filefield.is_valid("/")) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.filefield.default, "/") + +class ExecFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.ExecField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import ExecField + self.execfield = ExecField(default='/bin/ls') + + def test_validator(self): + """ + ``/etc/passwd`` should always exist as a file on disk, but not be + executable. + + ``/bin/ls`` should always exist, and must be executable. + """ + self.assertFalse(self.execfield.is_valid("/etc/passwd")) + self.assertTrue(self.execfield.is_valid("/bin/ls")) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.execfield.default, "/bin/ls") + +class DirectoryFieldTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.DirectoryField` + """ + def setUp(self): + from lofarpipe.support.lofaringredient import DirectoryField + self.directoryfield = DirectoryField(default='/tmp') + + def test_validator(self): + """ + An integer is not a valid directory. + + ``/tmp`` should always be valid. + """ + self.assertFalse(self.directoryfield.is_valid(1)) + self.assertTrue(self.directoryfield.is_valid("/tmp")) + + def test_default(self): + """ + Check that default is correctly set. + """ + self.assertEqual(self.directoryfield.default, "/tmp") + + def test_coerce(self): + """ + Coercing a create-able directory name should cause it to exist. We + should always be able to write in ``/tmp``. + """ + self.directoryfield.coerce("/tmp/foo") + self.assertTrue(os.path.exists("/tmp/foo")) + +class LOFARIngredientTest(unittest.TestCase): + """ + Tests for :class:`lofarpipe.support.lofaringredient.LOFARingredient` + """ + def setUp(self): + """ + An instance of + :class:`~lofarpipe.support.lofaringredient.LOFARingredient` is defined + which contains three instances of + :class:`~lofarpipe.support.lofaringredient.StringField`. + """ + from lofarpipe.support.lofaringredient import StringField + from lofarpipe.support.lofaringredient import LOFARingredient + f = StringField(default="foo") + g = StringField() + h = StringField(default=1) + self.lofaringredient = LOFARingredient({"f": f, "g": g, "h": h}) + + def test_keys(self): + """ + ``self.lofaringredient`` should contain keys for the two fields + which have default parameters, but not for the one which is unset. + """ + self.assertEqual(len(self.lofaringredient.keys()), 2) + self.assertRaises(KeyError, lambda: self.lofaringredient['g']) + + def test_values(self): + """ + Prior to setting, the value of the fields should be equal to + the default value. + """ + self.assertEqual(self.lofaringredient['f'], "foo") + + def test_set(self): + """ + When set, the value of the fields should be equal to the new value. + """ + self.lofaringredient['g'] = "bar" + self.assertEqual(self.lofaringredient['g'], "bar") + + def test_bad_values(self): + """ + Unacceptable values should raise an exception. + """ + self.assertRaises(TypeError, lambda: self.lofaringredient['h']) + self.lofaringredient['h'] = "bar" + self.assertEqual(self.lofaringredient['h'], "bar") + +if __name__ == "__main__": + import xmlrunner + unittest.main(testRunner=xmlrunner.XMLTestRunner(output='result.xml')) diff --git a/CEP/Pipeline/test/pipeline_test.run b/CEP/Pipeline/test/pipeline_test.run new file mode 100755 index 0000000000000000000000000000000000000000..7afad961b81abe0b40919b7641574134888235ff --- /dev/null +++ b/CEP/Pipeline/test/pipeline_test.run @@ -0,0 +1,14 @@ +#!/bin/sh + +#add correct path for lofarPipe python files +#**WORKAROUND** for incorrect path setting in testframework code +# For standalone unittest the path should only contain the code to be tested and +# the testframework containing fake externals + +#run the actual python script performing the tests +# Run all test collected in the current directory (including possible self tests!) + +PYTHONPATH=${srcdir}/test_framework/fixture:${PYTHONPATH} +export PYTHONPATH + +python ${srcdir}/test_framework/unittest_runner.py -x results.xml -p ${srcdir} diff --git a/CEP/Pipeline/test/pipeline_test.sh b/CEP/Pipeline/test/pipeline_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..861679c24a454f27073f4fe17be0697dce9ce56e --- /dev/null +++ b/CEP/Pipeline/test/pipeline_test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./runctest.sh pipeline_test > pipeline_test.log 2>&1 diff --git a/CEP/Pipeline/test/recipes/__init__.py b/CEP/Pipeline/test/recipes/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/recipes/master/__init__.py b/CEP/Pipeline/test/recipes/master/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/recipes/nodes/__init__.py b/CEP/Pipeline/test/recipes/nodes/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/recipes/nodes/imager_create_dbs_test.py b/CEP/Pipeline/test/recipes/nodes/imager_create_dbs_test.py new file mode 100644 index 0000000000000000000000000000000000000000..1982c3f5483a97a48d31873db09b3c33c58dad99 --- /dev/null +++ b/CEP/Pipeline/test/recipes/nodes/imager_create_dbs_test.py @@ -0,0 +1,369 @@ +from __future__ import with_statement +import os +import errno +import unittest +import shutil +import numpy +import tempfile + + +import pyrap.tables as tb #@UnresolvedImport +from lofarpipe.support.utilities import create_directory #@UnresolvedImport +from lofarpipe.recipes.nodes.imager_create_dbs import imager_create_dbs #@UnresolvedImport +from logger import logger + + +class ImagerCreateDBsTestWrapper(imager_create_dbs): + """ + Wrapper for the imager_create_dbs allows overwriting of + """ + def __init__(self): + """ + Overloaded __init__ function, hiding the original __init__ on + LOFARnodeTCP. + """ + self.logger = logger() + +class ImagerCreateDBsTest(unittest.TestCase): + """ + Tests for ImagerCreateDBs class + """ + test_path = "/data/scratch/python_unittest" + + def __init__(self, arg): #todo deze moet toch in de setUp + super(ImagerCreateDBsTest, self).__init__(arg) + + def setUp(self): + self.imager_create_dbs = ImagerCreateDBsTestWrapper() + create_directory(self.test_path) + + def tearDown(self): + shutil.rmtree(self.test_path) + + + def test_field_of_view_HBA_120_CS(self): + """ + Test the calcultaion of the FOV for lowest freq on a hba core station + """ + variable_dictionary = {'NAME':["CS--HBA--"], + 'REF_FREQUENCY':["120E6"]} + tb.table.variable_dictionary = variable_dictionary + fov = self.imager_create_dbs._field_of_view("MS_name") + self.assertAlmostEqual(fov, 3.02, 2, "Incorrect FOV Value") + + + def test_field_of_view_HBA_240_RS(self): + """ + Test the calcultaion of the FOV for lowest freq on a hba core station + """ + variable_dictionary = {'NAME':["RS--HBA--"], + 'REF_FREQUENCY':["240E6"]} + tb.table.variable_dictionary = variable_dictionary + fov = self.imager_create_dbs._field_of_view("MS_name") + self.assertAlmostEqual(fov, 1.13, 2, "Incorrect FOV Value") + + def test_field_of_view_LBA_15_INNER(self): + """ + Test the calcultaion of the FOV for lowest freq on a hba core station + """ + variable_dictionary = {'NAME':["--LBA--"], + 'REF_FREQUENCY':["15E6"], + 'LOFAR_ANTENNA_SET':["--INNER--"]} + tb.table.variable_dictionary = variable_dictionary + fov = self.imager_create_dbs._field_of_view("MS_name") + self.assertAlmostEqual(fov, 23.04, 2, "Incorrect FOV Value") + + + def test_field_of_view_LBA_75_OUTER(self): + """ + Test the calcultaion of the FOV for lowest freq on a hba core station + """ + variable_dictionary = {'NAME':["--LBA--"], + 'REF_FREQUENCY':["75E6"], + 'LOFAR_ANTENNA_SET':["--OUTER--"]} + tb.table.variable_dictionary = variable_dictionary + fov = self.imager_create_dbs._field_of_view("MS_name") + self.assertAlmostEqual(fov, 1.83, 2, "Incorrect FOV Value") + + + def test_field_of_view_incorrect_antenna_name(self): + """ + When the measurement set is from an antenna with a name NOT + containing either LBA or HBA en Exception should be trown + """ + variable_dictionary = {'NAME':["--error--"]} + tb.table.variable_dictionary = variable_dictionary + self.assertRaises(Exception, self.imager_create_dbs._field_of_view, "MS_name") + + def test__create_parmdb(self): + """ + Test the correct functioning of the create parmdbs function + 1. test if dir is created + 2. test if dir contains files (content tests omitted: thats a parmdbs + unit test. + 3. correct return value + + """ + path_to_create = os.path.join(self.test_path, "testParmdb") + create_directory(path_to_create) + + parmdb_output = os.path.join(path_to_create, "parmdbs") + parmdb_executable = "/opt/cep/LofIm/daily/lofar/bin/parmdbm" #TODO: static + self.assertTrue(0 == self.imager_create_dbs._create_parmdb(parmdb_executable, + parmdb_output), + self.imager_create_dbs.logger._log[-1]) + + self.assertTrue(os.path.exists(parmdb_output), "targer dir to be" + "created by parmdb does not exist") + table_data_file_path = os.path.join(parmdb_output, "table.dat") + self.assertTrue(os.path.exists(table_data_file_path), + "Creation of table.dat failed") + + + shutil.rmtree(path_to_create) + + def test__create_parmdb_missing_exec(self): + """ + Test the correct functioning of the create parmdbs function + + """ + path_to_create = os.path.join(self.test_path, "testParmdb") + create_directory(path_to_create) + + parmdb_output = os.path.join(path_to_create, "parmdbs") + parmdb_executable = "/opt/cep/LofIm/daily/lofar/bin/incorrectExecutable" + self.assertTrue(1 == self.imager_create_dbs._create_parmdb(parmdb_executable, + parmdb_output), + self.imager_create_dbs.logger.last()) + + + self.assertFalse(os.path.exists(parmdb_output), "target dir to be" + "created by parmdb does exist, while it should not") + + shutil.rmtree(path_to_create) + + def test__create_parmdb_for_timeslices(self): + """ + Test the correct functioning of the _create_parmdb_for_timeslices + Creating paramdbs for multiple measurement sets + """ + path_to_create = os.path.join(self.test_path, "testParmdb") + parmdb_ms_output = os.path.join(path_to_create, "parmdbs") + create_directory(parmdb_ms_output) + parmdb_executable = "/opt/cep/LofIm/daily/lofar/bin/parmdbm" + + #Create a number of paths to supply to the create function + ms_paths = [] + for idx in range(5): + ms_paths.append(os.path.join(parmdb_ms_output, str(idx))) + + + #test output + self.assertTrue( + 0 == self.imager_create_dbs._create_parmdb_for_timeslices(parmdb_executable, + ms_paths, ".parmdb"), + self.imager_create_dbs.logger.last()) + + #test creation of parmdb + final_ms_path = os.path.join(parmdb_ms_output, "4.parmdb") + self.assertTrue(os.path.exists(final_ms_path)) + final_ms_table = os.path.join(final_ms_path, "table.dat") + self.assertTrue(os.path.exists(final_ms_table)) + + def test__create_parmdb_for_timeslices_except(self): + """ + Test the errorous functioning of the _create_parmdb_for_timeslices + with missing executable should return 1 and no created directories + """ + path_to_create = os.path.join(self.test_path, "testParmdb") + parmdb_ms_output = os.path.join(path_to_create, "parmdbs") + create_directory(parmdb_ms_output) + parmdb_executable = "/opt/cep/LofIm/daily/lofar/bin/missingExcecutable" + + #Create a number of paths to supply to the create function + ms_paths = [] + for idx in range(5): + ms_paths.append(os.path.join(parmdb_ms_output, str(idx))) + + + self.assertTrue( + 1 == self.imager_create_dbs._create_parmdb_for_timeslices(parmdb_executable, + ms_paths, ".parmdb"), + self.imager_create_dbs.logger.last()) + final_ms_path = os.path.join(parmdb_ms_output, "time_slice_8.dppp.ms.parmdb") + self.assertFalse(os.path.exists(final_ms_path)) + + def test__create_monet_db_connection(self): + """ + Tests the correct creation of a monetdb connection + Monat db is mucked!! and returns: "connection" + """ + db_host = "hostname" + db_dbase = "spam" + db_user = "spam" + db_passwd = "spam" + db_port = 1 + self.assertTrue("connection" == + self.imager_create_dbs._create_monet_db_connection(db_host, + db_dbase, db_user, db_passwd, db_port), + "_create_monat_db_connection() did not return the" + " string 'connection'") + + + def test__create_monet_db_connection_fail(self): + """ + Tests the monetdb connection that failse: Altough internally db.Error + exceptions could be trown these should be caught the function return None + When no conenction could be established + + """ + db_host = "except" + db_dbase = "spam" + db_user = "spam" + db_passwd = "spam" + db_port = 1 + self.assertTrue(None == + self.imager_create_dbs._create_monet_db_connection(db_host, + db_dbase, db_user, db_passwd, db_port), + "_create_monat_db_connection() did not return the" + " string 'connection'") + + + def test__get_ra_and_decl_from_ms(self): + """ + Test the extraction of the beam direction from the measurement set + 1. insert temp values is muck db + 2. call extract function + """ + ra = 123 + decl = 456 + variable_dictionary = {'PHASE_DIR':[numpy.array([ra, decl])]} + tb.table.variable_dictionary = variable_dictionary + + ret_ra, ret_decl = \ + self.imager_create_dbs._get_ra_and_decl_from_ms("measurementset") + + self.assertTrue((ra == ret_ra) and (decl == ret_decl) , + "_get_ra_and_decl_from_ms dir not return the expected" + " values for the re and decl") + + + def test__get_ra_and_decl_from_ms_pyrap_raised_except(self): + """ + Test correct raising of exceptions + """ + error_message = "test__get_ra_and_decl_from_ms_pyrap_raised_except" + variable_dictionary = {'FIELD': error_message} + tb.table.variable_dictionary = variable_dictionary + + self.assertTrue( + None == self.imager_create_dbs._get_ra_and_decl_from_ms('except'), + "_get_ra_and_decl_from_ms should return None when exception" + "thrown in pyrap") + self.assertTrue(self.imager_create_dbs.logger.last()[1].count(error_message) > 0, + "The last logged message is incorrect") + + + def test__get_ra_and_decl_from_ms_pyrap_incorrect_data(self): + """ + Test correct return value on non correct values (but none exceptionaly) + """ + error_message = "returned PHASE_DIR data did not contain two values" + variable_dictionary = {'PHASE_DIR': [numpy.array([1])]} + tb.table.variable_dictionary = variable_dictionary + + self.assertTrue( + None == self.imager_create_dbs._get_ra_and_decl_from_ms('ms'), + "_get_ra_and_decl_from_ms should return None when retreived" + "data has not 2 entries") + self.assertTrue(self.imager_create_dbs.logger.last()[1].count(error_message) > 0, + "The last logged message is incorrect") + + + def test__create_bbs_sky_model_no_theta(self): + """ + Test correct functioning of _create_bbs_sky_model. + The inner workings of nested funtions is not tested + """ + + # create the muck db with location + ra = 123 + decl = 456 + + variable_dictionary = {'NAME':["--LBA--"], + 'REF_FREQUENCY':["75E6"], + 'LOFAR_ANTENNA_SET':["--OUTER--"], + 'PHASE_DIR':[numpy.array([ra, decl])]} + tb.table.variable_dictionary = variable_dictionary + + #Create temp location to save the output!! + tempdir = tempfile.mkdtemp() + output_skymodel_name = "bbs.skymodel.test" + test_skymodel_path = os.path.join(tempdir, output_skymodel_name) + + + #test correct return value + self.assertTrue(0 == self.imager_create_dbs._create_bbs_sky_model( + "measurement_set", test_skymodel_path, "host", "db_port", "db_name", + "db_user", "db_password")) + + #assert creation of output file + self.assertTrue(os.path.exists(test_skymodel_path)) + + #assert correct creation of theta + fp = open(test_skymodel_path) + theta = fp.readline() + self.assertTrue(theta == "0.025\n") + #clean up the created file and dir + try: + os.remove(test_skymodel_path) + except: + pass + + os.rmdir(tempdir) + + def test__create_bbs_sky_model_theta(self): + """ + Test correct functioning of _create_bbs_sky_model. + The inner workings of nested funtions is not tested + """ + theta = "20" + + # create the muck db with location + ra = 123 + decl = 456 + variable_dictionary = {'NAME':["--LBA--"], + 'REF_FREQUENCY':["75E6"], + 'LOFAR_ANTENNA_SET':["--OUTER--"], + 'PHASE_DIR':[numpy.array([ra, decl])]} + tb.table.variable_dictionary = variable_dictionary + + #Create temp location to save the output!! + tempdir = tempfile.mkdtemp() + output_skymodel_name = "bbs.skymodel.test" + test_skymodel_path = os.path.join(tempdir, output_skymodel_name) + + + #test correct return value + self.assertTrue(0 == self.imager_create_dbs._create_bbs_sky_model( + "measurement_set", test_skymodel_path, "host", "db_port", + "db_name", "db_user", "db_password", theta)) + + #assert creation of output file + self.assertTrue(os.path.exists(test_skymodel_path)) + + #assert correct creation of theta + fp = open(test_skymodel_path) + theta_red = fp.readline() + self.assertTrue(theta_red == theta + "\n") + #clean up the created file and dir + try: + os.remove(test_skymodel_path) + except: + pass + + os.rmdir(tempdir) + + +if __name__ == "__main__": + unittest.main() diff --git a/CEP/Pipeline/test/support/__init__.py b/CEP/Pipeline/test/support/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/test_framework/CMakeLists.txt b/CEP/Pipeline/test/test_framework/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a141ed21c5de46216c274f91623faad520e0ba88 --- /dev/null +++ b/CEP/Pipeline/test/test_framework/CMakeLists.txt @@ -0,0 +1,3 @@ +# $Id: CMakeLists.txt 18904 2011-09-28 12:15:43Z diepen $ + + diff --git a/CEP/Pipeline/test/test_framework/__init__.py b/CEP/Pipeline/test/test_framework/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/test_framework/fixture/__init__.py b/CEP/Pipeline/test/test_framework/fixture/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/test_framework/fixture/gsmutils.py b/CEP/Pipeline/test/test_framework/fixture/gsmutils.py new file mode 100644 index 0000000000000000000000000000000000000000..4df8c7d64bad64d9c7f76a9fd06c3f7b7f85f62b --- /dev/null +++ b/CEP/Pipeline/test/test_framework/fixture/gsmutils.py @@ -0,0 +1,11 @@ + +def expected_fluxes_in_fov(conn, ra_c, decl_c, fov_radius, assoc_theta, + output_path, storespectraplots = False): + """ + Muck skymodel creation: Only creates the file on output_path. + The file is filled with the supplied assoc_theta + """ + fp = open(output_path, "w") + fp.write(str(assoc_theta)) + fp.write("\nbbs.skymodel.test") + fp.close() diff --git a/CEP/Pipeline/test/test_framework/fixture/logger.py b/CEP/Pipeline/test/test_framework/fixture/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..851b77d8cabae2e437d6b483eb85d1de4c34e46d --- /dev/null +++ b/CEP/Pipeline/test/test_framework/fixture/logger.py @@ -0,0 +1,28 @@ +#test fixture for pipeline framework + +class logger(): + """ + muck logger object, allows logging of info, debug and error function + new entries are appended to a list with the type of logger error and + the suplied input + """ + def __init__(self): + self._log = [] + + def info(self, input_string): + self._log.append(("info", input_string)) + + def debug(self, input_string): + self._log.append(("debug", input_string)) + + def error(self, input_string): + self._log.append(("error", input_string)) + + def warn(self, input_string): + self._log.append(("warn", input_string)) + + def last(self): + """ + return that last error + """ + return self._log[-1] diff --git a/CEP/Pipeline/test/test_framework/fixture/monetdb/__init__.py b/CEP/Pipeline/test/test_framework/fixture/monetdb/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/test_framework/fixture/monetdb/sql.py b/CEP/Pipeline/test/test_framework/fixture/monetdb/sql.py new file mode 100644 index 0000000000000000000000000000000000000000..a0f847c465bba0a04e2e29c712b675407aba477c --- /dev/null +++ b/CEP/Pipeline/test/test_framework/fixture/monetdb/sql.py @@ -0,0 +1,20 @@ +class Error(Exception): + """ + Exception returned by sql.connect() + """ + def __str__(self): + return "Error" + +def connect(hostname, database, username, password, port): + """ + Muck sql.connect: returns the string connection upon a call of connect with + an int as port and the string 'hostname' as hostname. + Will return an Error of the type Exception when the hostname is 'except'. + Otherwise the function return the string 'connection Failed' + """ + if hostname == "hostname" and isinstance(port, int): + return "connection" + if hostname == "except": + raise Error() + + return "connection Failed" diff --git a/CEP/Pipeline/test/test_framework/fixture/pyrap/__init__.py b/CEP/Pipeline/test/test_framework/fixture/pyrap/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/CEP/Pipeline/test/test_framework/fixture/pyrap/tables.py b/CEP/Pipeline/test/test_framework/fixture/pyrap/tables.py new file mode 100644 index 0000000000000000000000000000000000000000..6a9d4337eeec4d11985c641b69b3a9b2c8d3b99f --- /dev/null +++ b/CEP/Pipeline/test/test_framework/fixture/pyrap/tables.py @@ -0,0 +1,68 @@ +from numpy.ma import extras + +class table(): + """ + Muck table object. + A minimal implementation of the pyrapl.table object: + It allows getting of keywords and cells from a previously initialed static + dictionary of keyword -> value pairs. Currently a global dictionary is used + + """ + # TODO: global dict prevent parallen running of unit tests (fragile code smell) + variable_dictionary = {} + + def __init__(self, measurement_set): + """ + Initialize the table, give it a name. If the name is except all calls + to getters will return an exception + """ + if measurement_set == 'except': + self.exception = True + else: + self.exception = False + self.measurement_set = measurement_set + + def getkeyword(self, keyword, default = None): + """ + Returns the value found in the variable dict. + Returns exception if set + """ + if self.exception: + raise Exception(table.variable_dictionary[keyword]) + if table.variable_dictionary.has_key(keyword): + return table.variable_dictionary[keyword] + + return default + + def getcell(self, keyword, idx, default = None): + """ + Returns the value found in the variable dict + Returns exception if set + """ + if self.exception: + raise Exception(table.variable_dictionary[keyword]) + if table.variable_dictionary.has_key(keyword): + cell_data = table.variable_dictionary[keyword] + if idx < len(cell_data): + return cell_data[idx] + + return default + + def set_variable_dictionary(self, variable_dictionary): + """ + Sets the class member variable_dictionary. + """ + table.variable_dictionary = variable_dictionary + + def close(self): + return + +def taql(input_string): + """ + Return taql commands: + 1. 'CALC C()' -> return c (float) + """ + if input_string.strip() == 'CALC C()': + return 299792458.0 + raise Exception("command not know in this muck implementation!") + diff --git a/CEP/Pipeline/test/test_framework/fixture/read.me b/CEP/Pipeline/test/test_framework/fixture/read.me new file mode 100644 index 0000000000000000000000000000000000000000..f9804a32cb9d7b0aa8f3f034f11118b20f8b7090 --- /dev/null +++ b/CEP/Pipeline/test/test_framework/fixture/read.me @@ -0,0 +1,7 @@ +The files in this directory are MUCK implementations: +They mimic limited framework functionality in a reproducible manner allowing for unit testing. + +The file are NOT functional. + +2012 +klijn@astron.nl \ No newline at end of file diff --git a/CEP/Pipeline/test/test_framework/unittest_runner.py b/CEP/Pipeline/test/test_framework/unittest_runner.py new file mode 100644 index 0000000000000000000000000000000000000000..2abbf275e0d36b3ef8b7968518a582624c701d45 --- /dev/null +++ b/CEP/Pipeline/test/test_framework/unittest_runner.py @@ -0,0 +1,167 @@ +import os +import unittest +import getopt +import string +import inspect +import sys +import re + +class Discover(): + """ + Discover class collects all unit test case in <path> and recursive directories + Collects them in a single large suite. + Start at supplied <path> an add all tests in files matching the supplied expression and + all individual tests matching the expression + """ + #TODO: ordering of suites is not controlled atm. + #TODO: maybe it should be expanded with multi path input?? + suite = unittest.TestSuite() + + def __init__(self,path,pattern): + + #match with all (used for a filename matches with expression: all individual test must be loaded + allMatcher = re.compile(".*") + #matcher for the expression + patternMatcher = re.compile(pattern) + #matcher for hidden dirs + hiddenMatcher = re.compile(".*/\..*") + + for root, dirs, files in os.walk(path): + #skip hidden directories + if hiddenMatcher.match(root): + continue + + dirSuite = unittest.TestSuite() + for name in files: + fileNameParts = name.split('.') + #assert correct file extention + if (len(fileNameParts) == 1) or (fileNameParts[1] != 'py'): + continue + + #try loading as a module + try: + module = self.import_path(root, fileNameParts[0]) #use import including path + except BaseException: + continue + + #the expression mechanism + testMatcher = None + if patternMatcher.match(name): + testMatcher = allMatcher #if current dir matches with expression include all tests + else: + testMatcher = patternMatcher + #create a test suite + fileSuite = unittest.TestSuite() + testnames = dir(module) + + #add all cases ending with test and match the regexp search string + for testName in testnames: + if testName.endswith('Test') or testName.endswith('test'): + try: + testClass = getattr(module, testName) #load attribute + if inspect.isclass(testClass): #if class + if not testMatcher.match(testName): #Continue of current testname does not match supplied expression + continue + fileSuite.addTest(unittest.makeSuite(testClass)) + except: + pass + + #if tests found add the file suite to the directory suite + if fileSuite.countTestCases() != 0: + dirSuite.addTest(fileSuite) + + #add to top level suite + if dirSuite.countTestCases() != 0: + self.suite.addTest(dirSuite) + + + def import_path(self, path, filename): + """ + Import a file with full path specification. Allows one to + import from anywhere, something __import__ does not do. + """ + filename, ext = os.path.splitext(filename) + sys.path.append(path) + module = __import__(filename) + reload(module) # Might be out of date + del sys.path[-1] + return module + + +class UnitTesterTest(unittest.TestCase): + """ + Self test for the UnitTester + """ + #TODO: Add propper test suite, creating come files and try laoding it (multiple directories and depths) + def setUp(self): + self.tester = "A test string" + + def test_validator(self): + """ + Check that current testClass is loaded and performed + """ + self.assertTrue(self.tester == "A test string") + + +def usage(): + """ + Display a short overview of available arguments + """ + usage = r"""Usage: python UnitTester [-p <path = '.'> -e <expression = *> -h -x] + Recursively look in path for unit test classes matching expression. + Collect in a single suite and run them + -p, --path <path> to start looking. Default is '.' + -e, --exp <expresion> to match with found classes to perform a subset of the tests + or + -m, --matchword match with found classes to perform a subset of tests (shorthand for .*arg.* expression + -h, --help Display this usage + -x, --xml <filename> Export resuls to xml (results are overwritten) + """ + print usage + +if __name__ == "__main__": + + #Default parameters settings + path = '.' + expression = '.*' + xml = "" + + #parse command lines and set parameters for Discover function + try: + opts, args = getopt.getopt(sys.argv[1:], "p:e:hx:m:", ["path=", "exp=", "help", "xml=","matchword="]) + except getopt.GetoptError: + usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-h", "--help"): + usage() + sys.exit() + elif opt in ( "-p", "--path"): + path = arg + elif opt in ("-e", "--exp"): + expression = arg + elif opt in ("-x", "--xml"): + xml = arg + elif opt in ("-m","--matchword"): + expression = ".*{0}.*".format(arg) + + #Collect tests from files and paths + test = Discover(path, expression) + + #decide on unit testrunner to use, run it and save the results + if xml: + import xmlrunner + result = xmlrunner.XMLTestRunner(output=xml).run(test.suite) + else: + result = unittest.TextTestRunner(verbosity=2).run(test.suite) + + #collect the numeric results using expressions + FailedTestMatcher = re.compile(".*run=(\d+).*errors=(\d+).*failures=(\d+)") + matches = FailedTestMatcher.match(str(result)) + runErrorFailures = matches.groups(0) + + #add to get the total number of not succesfull tests + failingTests = int(runErrorFailures[1]) + int(runErrorFailures[2]) + + #provide number of failing tests as exit value + sys.exit(failingTests)