refactor validator selection using a content type based dictionnary (which may be overriden by view id) + functions to instrumentize the registry to check what's tested and what's not
authorsylvain.thenault@logilab.fr
Fri, 30 Jan 2009 15:32:02 +0100
changeset 534 1368c80276bc
parent 533 215ecdb5f047
child 535 ad9ac2169089
child 537 f16da6c874da
refactor validator selection using a content type based dictionnary (which may be overriden by view id) + functions to instrumentize the registry to check what's tested and what's not
devtools/testlib.py
--- a/devtools/testlib.py	Fri Jan 30 15:30:55 2009 +0100
+++ b/devtools/testlib.py	Fri Jan 30 15:32:02 2009 +0100
@@ -93,28 +93,24 @@
     #  SaxOnlyValidator : guarantees XML is well formed
     #  None : do not try to validate anything
     # validators used must be imported from from.devtools.htmlparser
-    validators = {
-        # maps vid : validator name
-        'hcal' : SaxOnlyValidator,
-        'rss' : SaxOnlyValidator,
-        'rssitem' : None,
-        'xml' : SaxOnlyValidator,
-        'xmlitem' : None,
-        'xbel' : SaxOnlyValidator,
-        'xbelitem' : None,
-        'vcard' : None,
-        'fulltext': None,
-        'fullthreadtext': None,
-        'fullthreadtext_descending': None,
-        'text' : None,
-        'treeitemview': None,
-        'textincontext' : None,
-        'textoutofcontext' : None,
-        'combobox' : None,
-        'csvexport' : None,
-        'ecsvexport' : None,
-        'owl' : SaxOnlyValidator, # XXX
-        'owlabox' : SaxOnlyValidator, # XXX
+    content_type_validators = {
+        # maps MIME type : validator name
+        #
+        # do not set html validators here, we need HTMLValidator for html
+        # snippets
+        #'text/html': DTDValidator,
+        #'application/xhtml+xml': DTDValidator,
+        'application/xml': SaxOnlyValidator,
+        'text/xml': SaxOnlyValidator,
+        'text/plain': None,
+        'text/comma-separated-values': None,
+        'text/x-vcard': None,
+        'text/calendar': None,
+        'application/json': None,
+        'image/png': None,
+        }
+    vid_validators = {
+        # maps vid : validator name (override content_type_validators)
         }
     valmap = {None: None, 'dtd': DTDValidator, 'xml': SaxOnlyValidator}
     no_auto_populate = ()
@@ -165,21 +161,24 @@
         self.commit()
 
     @nocoverage
-    def _check_html(self, output, vid, template='main'):
+    def _check_html(self, output, view, template='main'):
         """raises an exception if the HTML is invalid"""
-        if template is None:
-            default_validator = HTMLValidator
-        else:
-            default_validator = DTDValidator
-        validatorclass = self.validators.get(vid, default_validator)
+        try:
+            validatorclass = self.vid_validators[view.id]
+        except KeyError:
+            if template is None:
+                default_validator = HTMLValidator
+            else:
+                default_validator = DTDValidator
+            validatorclass = self.content_type_validators.get(view.content_type,
+                                                              default_validator)
         if validatorclass is None:
             return None
         validator = validatorclass()
-        output = output.strip()
-        return validator.parse_string(output)
+        return validator.parse_string(output.strip())
 
 
-    def view(self, vid, rset, req=None, template='main', htmlcheck=True, **kwargs):
+    def view(self, vid, rset, req=None, template='main', **kwargs):
         """This method tests the view `vid` on `rset` using `template`
 
         If no error occured while rendering the view, the HTML is analyzed
@@ -196,8 +195,6 @@
         #     print 
         req.form['vid'] = vid
         view = self.vreg.select_view(vid, req, rset, **kwargs)
-        if view.content_type not in ('application/xml', 'application/xhtml+xml', 'text/html'):
-            htmlcheck = False
         # set explicit test description
         if rset is not None:
             self.set_description("testing %s, mod=%s (%s)" % (vid, view.__module__, rset.printable_rql()))
@@ -211,13 +208,13 @@
             # patch TheMainTemplate.process_rql to avoid recomputing resultset
             TheMainTemplate._select_view_and_rset = lambda *a, **k: (view, rset)
         try:
-            return self._test_view(viewfunc, vid, htmlcheck, template, **kwargs)
+            return self._test_view(viewfunc, view, template, **kwargs)
         finally:
             if template == 'main':
                 TheMainTemplate._select_view_and_rset = _select_view_and_rset
 
 
-    def _test_view(self, viewfunc, vid, htmlcheck=True, template='main', **kwargs):
+    def _test_view(self, viewfunc, view, template='main', **kwargs):
         """this method does the actual call to the view
 
         If no error occured while rendering the view, the HTML is analyzed
@@ -229,10 +226,7 @@
         output = None
         try:
             output = viewfunc(**kwargs)
-            if htmlcheck:
-                return self._check_html(output, vid, template)
-            else:
-                return output
+            return self._check_html(output, view, template)
         except (SystemExit, KeyboardInterrupt):
             raise
         except:
@@ -240,19 +234,16 @@
             # is not an AssertionError
             klass, exc, tcbk = sys.exc_info()
             try:
-                msg = '[%s in %s] %s' % (klass, vid, exc)
+                msg = '[%s in %s] %s' % (klass, view.id, exc)
             except:
-                msg = '[%s in %s] undisplayable exception' % (klass, vid)
+                msg = '[%s in %s] undisplayable exception' % (klass, view.id)
             if output is not None:
                 position = getattr(exc, "position", (0,))[0]
                 if position:
                     # define filter
-                    
-                    
                     output = output.splitlines()
                     width = int(log(len(output), 10)) + 1
                     line_template = " %" + ("%i" % width) + "i: %s"
-
                     # XXX no need to iterate the whole file except to get
                     # the line number
                     output = '\n'.join(line_template % (idx + 1, line)
@@ -286,23 +277,26 @@
         """returns the list of views that can be applied on `rset`"""
         req = rset.req
         only_once_vids = ('primary', 'secondary', 'text')
-        skipped = ('restriction', 'cell')
         req.data['ex'] = ValueError("whatever")
         for vid, views in self.vreg.registry('views').items():
             if vid[0] == '_':
                 continue
-            try:
-                view = self.vreg.select(views, req, rset)
-                if view.id in skipped:
-                    continue
-                if view.category == 'startupview':
+            if rset.rowcount > 1 and vid in only_once_vids:
+                continue
+            views = [view for view in views
+                     if view.category != 'startupview'
+                     and not issubclass(view, NotificationView)]
+            if views:
+                try:
+                    view = self.vreg.select(views, req, rset)
+                    if view.linkable():
+                        yield view
+                    else:
+                        not_selected(self.vreg, view)
+                    # else the view is expected to be used as subview and should
+                    # not be tested directly
+                except NoSelectableObject:
                     continue
-                if rset.rowcount > 1 and view.id in only_once_vids:
-                    continue
-                if not isinstance(view, NotificationView):
-                    yield view
-            except NoSelectableObject:
-                continue
 
     def list_actions_for(self, rset):
         """returns the list of actions that can be applied on `rset`"""
@@ -310,22 +304,21 @@
         for action in self.vreg.possible_objects('actions', req, rset):
             yield action
 
-        
     def list_boxes_for(self, rset):
         """returns the list of boxes that can be applied on `rset`"""
         req = rset.req
         for box in self.vreg.possible_objects('boxes', req, rset):
             yield box
             
-        
     def list_startup_views(self):
         """returns the list of startup views"""
         req = self.request()
         for view in self.vreg.possible_views(req, None):
-            if view.category != 'startupview':
-                continue
-            yield view.id
-
+            if view.category == 'startupview':
+                yield view.id
+            else:
+                not_selected(self.vreg, view)
+                
     def _test_everything_for(self, rset):
         """this method tries to find everything that can be tested
         for `rset` and yields a callable test (as needed in generative tests)
@@ -339,7 +332,7 @@
             backup_rset = rset._prepare_copy(rset.rows, rset.description)
             yield InnerTest(self._testname(rset, view.id, 'view'),
                             self.view, view.id, rset,
-                            rset.req.reset_headers(), 'main', not view.binary)
+                            rset.req.reset_headers(), 'main')
             # We have to do this because some views modify the
             # resultset's syntax tree
             rset = backup_rset
@@ -352,8 +345,6 @@
         for box in self.list_boxes_for(rset):
             yield InnerTest(self._testname(rset, box.id, 'box'), box.dispatch)
 
-
-
     @staticmethod
     def _testname(rset, objid, objtype):
         return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype)
@@ -394,4 +385,36 @@
                 rset2 = rset.limit(limit=1, offset=row)
                 yield rset2
 
+def not_selected(vreg, vobject):
+    try:
+        vreg._selected[vobject.__class__] -= 1
+    except (KeyError, AttributeError):
+        pass
         
+def vreg_instrumentize(testclass):
+    from cubicweb.devtools.apptest import TestEnvironment
+    env = testclass._env = TestEnvironment('data', configcls=testclass.configcls,
+                                           requestcls=testclass.requestcls)
+    vreg = env.vreg
+    vreg._selected = {}
+    orig_select = vreg.__class__.select
+    def instr_select(self, *args, **kwargs):
+        selected = orig_select(self, *args, **kwargs)
+        try:
+            self._selected[selected.__class__] += 1
+        except KeyError:
+            self._selected[selected.__class__] = 1
+        except AttributeError:
+            pass # occurs on vreg used to restore database
+        return selected
+    vreg.__class__.select = instr_select
+
+def print_untested_objects(testclass, skipregs=('hooks', 'etypes')):
+    vreg = testclass._env.vreg
+    for registry, vobjectsdict in vreg.items():
+        if registry in skipregs:
+            continue
+        for vobjects in vobjectsdict.values():
+            for vobject in vobjects:
+                if not vreg._selected.get(vobject):
+                    print 'not tested', registry, vobject