3737import codecs
3838import collections
3939import contextlib
40+ import datetime
4041import difflib
4142import filecmp
4243import functools
@@ -219,7 +220,7 @@ def get_data_path(relative_path):
219220 return data_path
220221
221222
222- class IrisTest (unittest .TestCase ):
223+ class IrisTest_nometa (unittest .TestCase ):
223224 """A subclass of unittest.TestCase which provides Iris specific testing functionality."""
224225
225226 _assertion_counts = collections .defaultdict (int )
@@ -853,10 +854,84 @@ def assertArrayShapeStats(self, result, shape, mean, std_dev, rtol=1e-6):
853854 self .assertArrayAllClose (result .data .std (), std_dev , rtol = rtol )
854855
855856
857+ # An environment variable controls whether test timings are output.
858+ #
859+ # NOTE: to run tests with timing output, nosetests cannot be used.
860+ # At present, that includes not using "python setup.py test"
861+ # The typically best way is like this :
862+ # $ export IRIS_TEST_TIMINGS=1
863+ # $ python -m unittest discover -s iris.tests
864+ # and commonly adding ...
865+ # | grep "TIMING TEST" >iris_test_output.txt
866+ #
867+ _PRINT_TEST_TIMINGS = bool (int (os .environ .get ('IRIS_TEST_TIMINGS' , 0 )))
868+
869+
870+ def _method_path (meth ):
871+ cls = meth .im_class
872+ return '.' .join ([cls .__module__ , cls .__name__ , meth .__name__ ])
873+
874+
875+ def _testfunction_timing_decorator (fn ):
876+ # Function decorator for making a testcase print its execution time.
877+ @functools .wraps (fn )
878+ def inner (* args , ** kwargs ):
879+ start_time = datetime .datetime .now ()
880+ try :
881+ result = fn (* args , ** kwargs )
882+ finally :
883+ end_time = datetime .datetime .now ()
884+ elapsed_time = (end_time - start_time ).total_seconds ()
885+ msg = '\n TEST TIMING -- "{}" took : {:12.6f} sec.'
886+ name = _method_path (fn )
887+ print (msg .format (name , elapsed_time ))
888+ return result
889+ return inner
890+
891+
892+ def iristest_timing_decorator (cls ):
893+ # Class decorator to make all "test_.." functions print execution timings.
894+ if _PRINT_TEST_TIMINGS :
895+ # NOTE: 'dir' scans *all* class properties, including inherited ones.
896+ attr_names = dir (cls )
897+ for attr_name in attr_names :
898+ attr = getattr (cls , attr_name )
899+ if callable (attr ) and attr_name .startswith ('test' ):
900+ attr = _testfunction_timing_decorator (attr )
901+ setattr (cls , attr_name , attr )
902+ return cls
903+
904+
905+ class _TestTimingsMetaclass (type ):
906+ # An alternative metaclass for IrisTest subclasses, which makes
907+ # them print execution timings for all the testcases.
908+ # This is equivalent to applying the @iristest_timing_decorator to
909+ # every test class that inherits from IrisTest.
910+ # NOTE: however, it means you *cannot* specify a different metaclass for
911+ # your test class inheriting from IrisTest.
912+ # See below for how to solve that where needed.
913+ def __new__ (cls , clsname , base_classes , attrs ):
914+ result = type .__new__ (cls , clsname , base_classes , attrs )
915+ if _PRINT_TEST_TIMINGS :
916+ result = iristest_timing_decorator (result )
917+ return result
918+
919+
920+ class IrisTest (six .with_metaclass (_TestTimingsMetaclass , IrisTest_nometa )):
921+ # Derive the 'ordinary' IrisTest from IrisTest_nometa, but add the
922+ # metaclass that enables test timings output.
923+ # This means that all subclasses also get the timing behaviour.
924+ # However, if a different metaclass is *wanted* for an IrisTest subclass,
925+ # this would cause a metaclass conflict.
926+ # Instead, you can inherit from IrisTest_nometa and apply the
927+ # @iristest_timing_decorator explicitly to your new testclass.
928+ pass
929+
930+
856931get_result_path = IrisTest .get_result_path
857932
858933
859- class GraphicsTest ( IrisTest ):
934+ class GraphicsTestMixin ( object ):
860935
861936 # nose directive: dispatch tests concurrently.
862937 _multiprocess_can_split_ = True
@@ -879,6 +954,15 @@ def tearDown(self):
879954 _lock .release ()
880955
881956
957+ class GraphicsTest (GraphicsTestMixin , IrisTest ):
958+ pass
959+
960+
961+ class GraphicsTest_nometa (GraphicsTestMixin , IrisTest_nometa ):
962+ # Graphicstest without the metaclass providing test timings.
963+ pass
964+
965+
882966class TestGribMessage (IrisTest ):
883967 def assertGribMessageContents (self , filename , contents ):
884968 """
0 commit comments