Commits

Olemis Lang committed 579791f

TracDutest : Package is easy_install-able, yet untested

Comments (0)

Files changed (7)

trac-dev/testing/setup.py

       "Topic :: Software Development :: Documentation", 
       "Topic :: Software Development :: Libraries :: Application Frameworks", 
       "Topic :: Software Development :: Libraries :: Python Modules", 
-      "Topic :: Software Development :: Libraries :: Python Modules", 
       "Topic :: Software Development :: Quality Assurance", 
       "Topic :: Software Development :: Testing", 
     ]
     maintainer='Olemis Lang',
     maintainer_email='olemis+trac@gmail.com',
     url='https://qa.blood-hound.net/wiki/Packages/%s' % (DIST_NM,),
-    download_url='http://pypi.python.org/packages/2.6/%s/%s/%s-%s-py2.5.egg' % \
+    download_url='http://pypi.python.org/packages/2.6/%s/%s/%s-%s-py2.6.egg' % \
                                   (DIST_NM[0], DIST_NM, DIST_NM, latest,),
     requires = ['trac',  'dutest', ],
     install_requires = ['setuptools>=0.6b1', 'Trac>=1.0',
-                        
-                        'PackageName>=0.2.4',
+                        'dutest>=0.2.4',
                        ],
     package_dir = dict([p, i[0]] for p, i in PKG_INFO.iteritems()),
     packages = PKG_INFO.keys(),

trac-dev/testing/tracdutest/__init__.py

 #!/usr/bin/env python
 # -*- coding: UTF-8 -*-
 
-# Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+# Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-
 r"""Advanced testing framework for Trac and Bloodhound
 
 This library offers a set of tools supporting best practices
 gathered throughout the development of test suites for
 Trac and Apache(TM) Bloodhound issue trackers. This includes
 (but is not limited to):
-- writing doctests, specialized unittest
-- test case classes,
-- advanced functional testing environments and testers.
 
-Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+  - writing doctests, 
+  - specialized unittest test case classes,
+  - advanced functional testing environments and testers.
+
+Notice: Most of the classes offered in this module have been imported from
+        TracGViz plugin; created in 2009
+
+Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
 Licensed under the Apache License
 """
 __author__ = 'Olemis Lang'
 
+__metaclass__ = type
+
+from trac.core import ComponentMeta
+from trac.db.api import _parse_db_str, DatabaseManager
+from trac.test import EnvironmentStub
+
+import os.path
+import sys
+import tempfile
+
+#------------------------------------------------------
+#    Minimalistic testing framework for Trac
+#------------------------------------------------------
+
+from dutest import DocTestLoader, DocTestSuiteFixture
+from os.path import dirname
+from types import MethodType
+
+from tracdutest.util.web import dummy_request
+
+# Hide this module from tracebacks written into test results.
+__unittest = True
+
+class DocTestTracLoader(DocTestLoader):
+    r"""A generic XUnit loader that allows to load doctests written 
+    to check that Trac plugins behave as expected.
+    """
+    def set_env(self, env):
+        if self.extraglobs is None :
+            self.extraglobs = dict(env=env)
+        else :
+            self.extraglobs['env'] = env
+
+    env = property(lambda self : self.extraglobs.get('env'), set_env, \
+                   doc="""The Trac environment used in doctests.""")
+    del set_env
+
+    def __init__(self, dt_finder=None, globs=None, extraglobs=None, \
+                                load=None, default_data=False, enable=None, \
+                                **opts):
+        r"""Initialization. It basically works like `DocTestLoader`'s 
+        initializer but creates also the Trac environment used for 
+        testing purposes. The default behavior is to create an instance 
+        of `EnvironmentStub` class. Subclasses can add more specific 
+        keyword parameters in order to use them to create the 
+        environment. Next it loads (and | or) enables the components 
+        needed by the test suite.
+
+        The following variables are magically available at testing time. 
+        They can be used directly in doctests :
+
+        - req         A dummy request object setup for anonymous access.
+        - auth_req    A dummy request object setup like if user `murphy` was  
+                        accessing the site.
+        - env         the Trac environment used as a stub for testing 
+                        purposes (i.e. `self.env`).
+
+        @param dt_finder        see docs for `DocTestLoader.__init__` 
+                                  method.
+        @param globs            see docs for `DocTestLoader.__init__` 
+                                  method.
+        @param extraglobs       see docs for `DocTestLoader.__init__` 
+                                  method.
+        @param load             a list of packages containing components 
+                                  that will be loaded to ensure they are 
+                                  available at testing time. It should be 
+                                  the top level module in that package 
+                                  (e.g. 'trac').
+        @param default_data     If true, populate the database with some 
+                                  defaults. This parameter has to be 
+                                  handled by `createTracEnv` method.
+        @param enable           a list of UNIX patterns specifying which 
+                                  components need to be enabled by default 
+                                  at testing time. This parameter should be 
+                                  handled by `createTracEnv` method.
+        """
+        super(DocTestTracLoader, self).__init__(dt_finder, globs, \
+                                                    extraglobs, **opts)
+        self.env = self.createTracEnv(default_data, enable, **opts)
+        self.load_components(load is None and self.default_packages or load)
+
+    # Load trac built-in components by default
+    default_packages = ['trac']
+
+    def createTracEnv(self, default_data=False, enable=None, **params):
+        r"""Create the Trac environment used for testing purposes. The 
+        default behavior is to create an instance of `EnvironmentStub` 
+        class. Subclasses can override this decision and add more specific 
+        keyword parameters in order to control environment creation in 
+        more detail. 
+
+        All parameters supplied at initialization time. By default they 
+        are ignored.
+        @param default_data     If True, populate the database with some 
+                                  defaults.
+        @param enable           a list of UNIX patterns specifying which 
+                                  components need to be enabled by default 
+                                  at testing time.
+        @return                 the environment used for testing purpose.
+        """
+        return EnvironmentStub(default_data, enable)
+
+    def load_components(self, pkgs):
+        r"""Load some packages to ensure that the components they 
+        implement are available at testing time.
+        """
+        from trac.loader import load_components
+        for pkg in pkgs :
+            try :
+                __import__(pkg)
+            except ImportError :
+                pass                        # Skip pkg. What a shame !
+            else :
+                mdl = sys.modules[pkg]
+                load_components(self.env, dirname(dirname(mdl.__file__)))
+
+    class doctestSuiteClass(DocTestSuiteFixture):
+        r"""Prepare the global namespace before running all doctests 
+        in the suite. Reset the Trac environment.
+        """
+        username = 'murphy'
+
+        @property
+        def env(self):
+            r"""The Trac environment involved in this test. It is 
+            retrieved using the global namespace ;o).
+            """
+            return self.globalns['env']
+
+        def new_request(self, uname=None, args=None):
+            r"""Create and initialize a new request object.
+            """
+            req = dummy_request(self.env, uname)
+            if args is not None :
+                req.args = args
+            return req
+
+        def setUp(self):
+            r"""Include two (i.e. `req` anonymous and `auth_req` 
+            authenticated) request objects in the global namespace, before 
+            running the doctests. Besides, clean up environment data and 
+            include only default data.
+            """
+            globs = self.globalns
+            req = self.new_request(args=dict())
+            auth_req = self.new_request(uname=self.username, args=dict())
+            globs['req'] = req
+            globs['auth_req'] = auth_req
+            # TODO: If the source docstrings belong to a Trac component, 
+            #       then instantiate it and include in the global 
+            #       namespace.
+            self.env.reset_db(default_data=True)
+            self.setup_logging()
+
+        def setup_logging(self):
+            r"""Log events to temp file
+            """
+            logdir = tempfile.gettempdir()
+            logpath = os.path.join(logdir, 'trac-testing.log')
+            config = self.env.config
+            config.set('logging', 'log_file', logpath)
+            config.set('logging', 'log_type', 'file')
+            config.set('logging', 'log_level', 'DEBUG')
+            config.save()
+            self.env.setup_log()
+            self.env.log.info('%s test case: %s %s', '-' * 9, self._dt.name,
+                              '-' * 9)
+

trac-dev/testing/tracdutest/gviz.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+r"""Classes used to test Trac data sources compatible with Google Charts API
+
+Notice: Most of the classes offered in this module have been imported from
+        TracGViz plugin; created in 2009
+
+Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License
+"""
+__author__ = 'Olemis Lang'
+
+__metaclass__ = type
+
+from trac.core import ComponentMeta
+from trac.db.api import _parse_db_str, DatabaseManager
+from trac.test import EnvironmentStub
+
+import os.path
+import sys
+import tempfile
+
+from dutest import DocTestLoader, DocTestSuiteFixture
+from os.path import dirname
+from types import MethodType
+
+from tracdutest import DocTestTracLoader
+from tracdutest.rpc import DocTestRpcLoader
+from tracdutest.util.web import dummy_request
+
+# Hide this module from tracebacks written into test results.
+__unittest = True
+
+#------------------------------------------------------
+#    Test artifacts used to test GViz providers
+#------------------------------------------------------
+
+class DocTestGVizLoader(DocTestRpcLoader):
+    r"""Load doctests used to test Trac GViz providers.
+    """
+    if not __name__.startswith('tracgviz.'):
+        # Load trac built-in components and standard RPC handlers 
+        # and GViz core system if used outside TracGViz package ;o).
+        default_packages = ['trac', 'tracrpc', 'tracgviz']
+
+    class doctestSuiteClass(DocTestTracLoader.doctestSuiteClass):
+        r"""Include the appropriate RPC handler in global namespace 
+        before running all test cases in the suite.
+        """
+        def setUp(self):
+            r"""Include the appropriate RPC handler and GViz provider in 
+            global namespace before running all test cases in the suite. 
+            In this case four objects are added to the global namespace :
+
+              - `rpcobj`          an instance of the RPC handler used to 
+                                  perform anonymous requests.
+              - `auth_rpcobj`     an instance of the RPC handler used to 
+                                  perform authenticated requests. The user 
+                                  name is determined by `username` 
+                                  attribute and defaults to `murphy`.
+              - `gvizobj`         an instance of the GViz provider used to 
+                                  retrieve data just like if `anonymous` 
+                                  user was accessing the site.
+              - `auth_gvizobj`    an instance of the GViz provider used to 
+                                  retrieve data just like if an 
+                                  authenticated user was accessing the site.
+                                  The user name is determined by `username` 
+                                  attribute and defaults to `murphy`.
+            """
+            # Fail here if either XmlRpcPlugin or TracGViz are not 
+            # available. Thus this fact will be reported as a failure and 
+            # subsequent test cases will be run anyway.
+            from tracrpc.api import XMLRPCSystem
+            self.rpcsys = XMLRPCSystem(self.env)
+            from tracgviz.api import TracGVizSystem
+            self.gvizsys = TracGVizSystem(self.env)
+
+            # Add request objects
+            DocTestTracLoader.doctestSuiteClass.setUp(self)
+
+            gvizns = self.ns_from_name()
+            if rpcns is None :
+                # TODO: If doctests belong to a GViz provider class then 
+                #       instantiate it. In the mean time ...
+                self.partial_setup()
+            else :
+                try :
+                    rpcns = self.setup_gviz(gvizns)
+                except RuntimeError :
+                    self.partial_setup()
+                else :
+                    try :
+                      self.setup_rpc(rpcns)
+                    except RuntimeError :
+                        DocTestRpcLoader.doctestSuiteClass.partial_setup(self)
+
+        def setup_gviz(self, gvizns):
+            r"""(Insert | update) the correct GViz provider in the global 
+            namespace.
+
+            @param gvizns               the namespace used to load the 
+                                        GViz provider.
+            @throws RuntimeError        if the provider bound to the 
+                                        requested namespace cannot be found.
+            """
+            globs = self.globalns
+            for p in self.gvizsys.providers :
+              if p.gviz_namespace() == gvizns :
+                globs['gvizobj'] = GVizDirectProxy(rpch, globs['req'])
+                globs['auth_gvizobj'] = GVizDirectProxy(rpch, globs['auth_req'])
+                break
+            else :
+              raise RuntimeError('Cannot load GViz provider for %s' % gvizns)
+
+        def partial_setup(self):
+            r"""Perform partial setup due to some minor failure (e.g. 
+            namespace missing in test name).
+            """
+            globs = self.globalns
+            globs.update([nm, None] for nm in ['rpcobj', 'auth_rpcobj', \
+                                                'gvizobj', 'auth_gvizobj'])
+
+class GVizDirectProxy:
+    r"""A proxy used in tests to encapsulate the interaction with GViz 
+    providers. In this case the provider's methods are invoked directly.
+
+    It also asserts that the permissions necessary to execute a given 
+    method are satisfied by the corresponding request object.
+    """
+    # TODO: Implement
+

trac-dev/testing/tracdutest/rpc.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+r"""Classes used to test Trac RPC providers
+
+Notice: Most of the classes offered in this module have been imported from
+        TracGViz plugin; created in 2009
+
+Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License
+"""
+__author__ = 'Olemis Lang'
+
+__metaclass__ = type
+
+from trac.core import ComponentMeta
+from trac.db.api import _parse_db_str, DatabaseManager
+from trac.test import EnvironmentStub
+
+import os.path
+import sys
+import tempfile
+
+from dutest import DocTestLoader, DocTestSuiteFixture
+from os.path import dirname
+from types import MethodType
+
+from tracdutest import DocTestTracLoader
+from tracdutest.util.web import dummy_request
+
+# Hide this module from tracebacks written into test results.
+__unittest = True
+
+
+class RpcDirectProxy :
+    r"""A proxy used in tests to interact with RPC handlers just like 
+    if it were an ordinary object (i.e. without the need for supplying 
+    a request object explicitly in each method call).
+
+    It also asserts that the permissions necessary to execute a given 
+    method are satisfied by the request object
+    """
+    def __init__(self, rpcobj, req):
+        r"""Initialization.
+
+        @param rpcobj         the target RPC handler object.
+        @param req            a request object that will be supplied 
+                                in each call to the methods defined by 
+                                the target RPC handler (i.e. `rpcobj`).
+        """
+        self._rpcobj = rpcobj
+        self._req = req
+        self._perms = dict([m.im_func.__name__, p] \
+                                    for p, _, m in rpcobj.xmlrpc_methods())
+    def __getattr__(self, attrnm):
+        r"""Control access to target RPC handler object, and assert that 
+        the right permissions have been granted to the user specified in 
+        the request.
+        """
+        try :
+            val = getattr(self._rpcobj, attrnm)
+        except AttributeError :
+            raise
+        else :
+            if isinstance(val, MethodType):
+                def rpcmethod(*args, **kwds):
+                    try :
+                        p = self._perms[attrnm]
+                    except KeyError :
+                        pass
+                    else :
+                        self._req.perm.require(p)
+                    return val(self._req, *args, **kwds)
+                return rpcmethod
+            else :
+                return val
+
+class DocTestRpcLoader(DocTestTracLoader):
+    r"""Load doctests used to test Trac RPC handlers.
+    """
+    # Load trac built-in components and standard RPC handlers by default
+    default_packages = ['trac', 'tracrpc']
+
+    class doctestSuiteClass(DocTestTracLoader.doctestSuiteClass):
+        r"""Include the appropriate RPC handler in global namespace 
+        before running all test cases in the suite.
+        """
+
+        def ns_from_name(self):
+            r"""Extract the target namespace under test using the name
+            of the DocTest instance manipulated by the suite.
+            """
+            try :
+                return self._dt.name.split(':', 1)[0].split('|', 1)[-1]
+            except :
+                return None
+
+        def partial_setup(self):
+            r"""Perform partial setup due to some minor failure (e.g. 
+            namespace missing in test name).
+            """
+            globs = self.globalns
+            globs['rpcobj'] = globs['auth_rpcobj'] = None
+
+        def setup_rpc(self, rpcns):
+            r"""(Insert | update) the correct RPC handler in the global 
+            namespace.
+
+            @param rpc                  the RPC namespace used to load the 
+                                        RPC handler.
+            @throws RuntimeError        if the handler bound to the 
+                                        requested namespace cannot be found.
+            """
+            globs = self.globalns
+            for rpch in self.rpcsys.method_handlers :
+                self.env.log.debug('Matching RPC namespace %s %s', rpcns, \
+                                    rpch.xmlrpc_namespace() )
+              if rpch.xmlrpc_namespace() == rpcns :
+                  globs['rpcobj'] = RpcDirectProxy(rpch, globs['req'])
+                  globs['auth_rpcobj'] = RpcDirectProxy(rpch, globs['auth_req'])
+                  break
+            else :
+                  raise RuntimeError('Cannot load RPC handler for %s' % rpcns)
+
+        def setUp(self):
+            r"""Include the appropriate RPC handler in global namespace 
+            before running all test cases in the suite. In this case two 
+            objects are added to the global namespace :
+
+              - `rpcobj`          an instnnce of the RPC handler used to 
+                                  perform anonymous requests.
+              - `auth_rpcobj`     an instance of the RPC handler used to 
+                                  perform authenticated requests. The user 
+                                  name is determined by `username` 
+                                  attribute and defaults to `murphy`.
+            """
+            # Fail here if XmlRpcPlugin is not available. Thus 
+            # this fact will be reported as a failure and subsequent test 
+            # cases will be run anyway.
+            from tracrpc.api import XMLRPCSystem
+            self.rpcsys = XMLRPCSystem(self.env)
+
+            # Add request objects
+            DocTestTracLoader.doctestSuiteClass.setUp(self)
+
+            rpcns = self.ns_from_name()
+            if rpcns is None :
+                # TODO: If doctests belong to an RPC handler class then 
+                #       instantiate it. In the mean time ...
+                self.partial_setup()
+            else :
+                try :
+                    self.setup_rpc(rpcns)
+                except RuntimeError :
+                    self.partial_setup()
+

trac-dev/testing/tracdutest/util/__init__.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+r"""Helper functions and classes.
+
+Notice: Some of the classes offered in this module have been imported from
+        TracGViz plugin; created in 2009
+
+Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License
+"""
+__author__ = 'Olemis Lang'
+
+__metaclass__ = type
+

trac-dev/testing/tracdutest/util/pkg.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+r"""Helper functions and classes to deal with packages and dependencies
+
+Copyright 2013-2018 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License
+"""
+__author__ = 'Olemis Lang'
+
+__metaclass__ = type
+
+from itertools import imap
+from pkg_resources import get_distribution, DistributionNotFound, parse_version
+
+def dist_version(distnm):
+    """Determine package version.
+    """
+    try:
+        version = parse_version(get_distribution(distnm).version)
+
+        def safe_int(v)
+            try:
+                return int(v)
+            except:
+                return v
+
+        return tuple(imap(safe_int, v))
+    except DisributionNotFound:
+        return None
+
+DEPS_VERSION = dict([alias, dist_version(distnm)] for alias, distnm in
+                    (('bhmp', 'BloodhoundMultiProduct'),
+                     ('tracrpc', 'TracXmlRpc'),
+                     ('bhrpc', 'BloodhoundRpc'),
+                     )])
+

trac-dev/testing/tracdutest/util/web.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+r"""Helper functions and classes related to Trac web API
+
+Notice: Some of the classes offered in this module have been imported from
+        TracGViz plugin; created in 2009
+
+Copyright 2009-2018 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License
+"""
+__author__ = 'Olemis Lang'
+
+__metaclass__ = type
+
+__all__ = 'dummy_request',
+
+from trac.web.api import Request
+from trac.web.chrome import Chrome
+from trac.web.main import RequestDispatcher
+
+from urlparse import urlparse
+from wsgiref.util import setup_testing_defaults
+
+# FIXME: Reuse Bloodhound code if available
+def dummy_request(env, uname=None):
+    environ = {}
+    setup_testing_defaults(environ)
+    environ.update({
+                'REQUEST_METHOD' : 'GET',
+                'SCRIPT_NAME' : urlparse(str(env._abs_href())).path,
+                'trac.base_url' : str(env._abs_href()),
+                })
+    req = Request(environ, lambda *args, **kwds: None)
+    # Intercept redirection
+    req.redirect = lambda *args, **kwds: None
+    # Setup user information
+    if uname is not None :
+        environ['REMOTE_USER'] = req.authname = uname
+
+    rd = RequestDispatcher(env)
+    chrome = Chrome(env)
+    req.callbacks.update({
+        'authname': rd.authenticate,
+        'chrome': chrome.prepare_request,
+        'hdf': getattr(rd, '_get_hdf', None),
+        'lc_time': rd._get_lc_time,
+        'locale' : getattr(rd, '_get_locale', None),
+        'perm': rd._get_perm,
+        'session': rd._get_session,
+        'tz': rd._get_timezone,
+        'form_token': rd._get_form_token
+    })
+    return req
+