Commit b0972239 authored by Jérome Perrin's avatar Jérome Perrin

Connector to annotate commits with test results status on gitlab

This adds a new field on Test Suite Repository to configure a "gitlab connector".

If this is set, then the commit from this repository will be annotated with the test status (failed or success). Gitlab uses these annotations to show that status on the merge request.

At the same time, fix a few minor problems on `erp5_test_result`.

/reviewed-on !924
parents 9a1cb966 38e2d655
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/GitlabRESTConnector_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2019 Nexedi SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from urllib import quote_plus
from urlparse import urlparse
from urlparse import urljoin
import logging
from AccessControl import ClassSecurityInfo
import requests
from Products.ERP5Type import Permissions
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.XMLObject import XMLObject
class GitlabRESTConnector(XMLObject):
"""Connects to gitlab v4 REST API.
"""
logger = logging.getLogger(__name__)
security = ClassSecurityInfo()
def _getRepositoryIdFromRepositoryUrl(self, repository_url):
# repository_url will be https://lab.nexedi.com/user/project.git
# convert this to user%2fproject
username_repo = urlparse(repository_url).path[1:] # remove leading /
if username_repo.endswith('/'):
username_repo = username_repo[:-1]
if username_repo.endswith('.git'):
username_repo = username_repo[:-4]
return quote_plus(username_repo)
security.declareProtected(
Permissions.AccessContentsInformation,
'postCommitStatus')
def postCommitStatus(self, repository_url, commit_sha, state, target_url, name):
"""Post the build status of a commit.
https://docs.gitlab.com/ce/api/commits.html#post-the-build-status-to-a-commit
"""
url = urljoin(
self.getUrlString(),
"projects/{id}/statuses/{sha}".format(
id=self._getRepositoryIdFromRepositoryUrl(repository_url),
sha=commit_sha
)
)
self.logger.info("posting commit status to %s", url)
response = requests.post(
url,
headers={"PRIVATE-TOKEN": self.getToken()},
json={
"state": state,
"target_url": target_url,
"name": name
},
timeout=5)
response.raise_for_status()
InitializeClass(GitlabRESTConnector)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>GitlabRESTConnector</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.GitlabRESTConnector</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
...@@ -33,4 +33,7 @@ ...@@ -33,4 +33,7 @@
<item>SlapOS Agent Test Suite</item> <item>SlapOS Agent Test Suite</item>
<item>Test Suite</item> <item>Test Suite</item>
</portal_type> </portal_type>
<portal_type id="Web Service Tool">
<item>Gitlab REST Connector</item>
</portal_type>
</allowed_content_type_list> </allowed_content_type_list>
\ No newline at end of file
...@@ -13,6 +13,11 @@ ...@@ -13,6 +13,11 @@
<portal_type id="ERP5 Scalability Distributor"> <portal_type id="ERP5 Scalability Distributor">
<item>ScalabilityDistributor</item> <item>ScalabilityDistributor</item>
</portal_type> </portal_type>
<portal_type id="Gitlab REST Connector">
<item>GitlabRESTConnector</item>
<item>Reference</item>
<item>Url</item>
</portal_type>
<portal_type id="Scalability Test Suite"> <portal_type id="Scalability Test Suite">
<item>Arrow</item> <item>Arrow</item>
<item>ScalabilityTestSuite</item> <item>ScalabilityTestSuite</item>
...@@ -58,6 +63,7 @@ ...@@ -58,6 +63,7 @@
<item>TestSuiteConstraint</item> <item>TestSuiteConstraint</item>
</portal_type> </portal_type>
<portal_type id="Test Suite Repository"> <portal_type id="Test Suite Repository">
<item>Arrow</item>
<item>TestSuiteRepository</item> <item>TestSuiteRepository</item>
<item>TestSuiteRepositoryConstraint</item> <item>TestSuiteRepositoryConstraint</item>
</portal_type> </portal_type>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>content_icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Gitlab REST Connector</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Base Type</string> </value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>GitlabRESTConnector</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
<type>ERP5 Scalability Distributor</type> <type>ERP5 Scalability Distributor</type>
<workflow>edit_workflow, validation_workflow</workflow> <workflow>edit_workflow, validation_workflow</workflow>
</chain> </chain>
<chain>
<type>Gitlab REST Connector</type>
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain> <chain>
<type>Scalability Test Suite</type> <type>Scalability Test Suite</type>
<workflow>edit_workflow, test_suite_workflow</workflow> <workflow>edit_workflow, test_suite_workflow</workflow>
...@@ -37,7 +41,7 @@ ...@@ -37,7 +41,7 @@
</chain> </chain>
<chain> <chain>
<type>Test Result</type> <type>Test Result</type>
<workflow>edit_workflow, test_result_workflow</workflow> <workflow>edit_workflow, test_result_gitlab_interaction_workflow, test_result_workflow</workflow>
</chain> </chain>
<chain> <chain>
<type>Test Result Line</type> <type>Test Result Line</type>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>GitlabRESTConnector</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Personal access token\n
\n
https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>token_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>read_permission</string> </key>
<value> <string>Manage properties</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?> <?xml version="1.0"?>
<ZopeData> <ZopeData>
<record id="1" aka="AAAAAAAAAAE="> <record id="1" aka="AAAAAAAAAAE=">
<pickle> <pickle>
<global name="Standard Property" module="erp5.portal_type"/> <global name="Standard Property" module="erp5.portal_type"/>
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item> <item>
<key> <string>categories</string> </key> <key> <string>categories</string> </key>
<value> <value>
<tuple> <tuple>
<string>elementary_type/string</string> <string>elementary_type/string</string>
</tuple> </tuple>
</value> </value>
</item> </item>
<item> <item>
<key> <string>description</string> </key> <key> <string>description</string> </key>
<value> <value>
<none/> <none/>
</value> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>computer_guid_property</string> </value> <value> <string>computer_guid_property</string> </value>
</item> </item>
<item> <item>
<key> <string>portal_type</string> </key> <key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value> <value> <string>Standard Property</string> </value>
</item> </item>
<item> <item>
<key> <string>title</string> </key> <key> <string>title</string> </key>
<value> <string>computer_guid</string> </value> <value> <string>computer_guid</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
</ZopeData> </ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string>multipart/form-data</string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list>
<string>my_description</string>
</list>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_reference</string>
<string>my_url_string</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list>
<string>my_token</string>
<string>my_translated_validation_state_title</string>
</list>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>GitlabRESTConnector_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>GitlabRESTConnector_view</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gitlab REST Connector</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_description</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_description</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_token</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<list>
<tuple>
<string>FTP</string>
<string>ftp</string>
</tuple>
<tuple>
<string>SFTP</string>
<string>sftp</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Token</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_translated_workflow_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_url_string</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>URL</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"""Annotate a commit with state of the test (running, canceled, success, failed)
"""
portal = context.getPortalObject()
portal_absolute_url = portal.ERP5Site_getAbsoluteUrl()
test_suite_data = context.TestResult_getTestSuiteData()
if test_suite_data:
for repository_info in test_suite_data['repository_dict'].values():
connector_url = repository_info['connector_relative_url']
if connector_url:
portal.restrictedTraverse(connector_url).postCommitStatus(
repository_info['repository_url'],
repository_info['revision'],
state,
'%s/%s' % ( portal_absolute_url, context.getRelativeUrl() ),
context.getTitle()
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestResult_annotateCommit</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
"Jump to gitlab or gitweb web interface to view commit" "Jump to gitlab or gitweb web interface to view commit"
portal = context.getPortalObject() from Products.PythonScripts.standard import Object
test_suite_list = portal.portal_catalog(
portal_type='Test Suite',
validation_state=('validated', 'invalidated'),
title={'query': context.getTitle(), 'key': 'ExactMatch'})
if not test_suite_list:
return []
test_suite = sorted(
[test_suite.getObject() for test_suite in test_suite_list],
key=lambda test_suite: test_suite.getValidationState() == 'validated')[0]
# TODO: make this jump test suite
# decode the reference ( ${buildout_section_id}=${number of commits}-${hash},${buildout_section_id}=${number of commits}-${hash}, ... )
repository_dict = {}
for repository_string in context.getReference().split(','):
repository_code, revision = repository_string.split('-')
repository_dict[repository_code.split('=')[0]] = revision
test_suite_data = context.TestResult_getTestSuiteData()
result_list = [] result_list = []
from Products.PythonScripts.standard import Object
def makeVCSLink(repository_url, revision): def makeVCSLink(repository_url, revision):
# https://user:pass@lab.nexedi.cn/user/repo.git -> https://user:pass@lab.nexedi.cn/user/repo/commit/hash # https://user:pass@lab.nexedi.cn/user/repo.git -> https://user:pass@lab.nexedi.cn/user/repo/commit/hash
if 'lab.nexedi.cn' in repository_url and repository_url.endswith('.git'): if 'lab.nexedi' in repository_url and repository_url.endswith('.git'):
repository_url = repository_url[:-len('.git')] repository_url = repository_url[:-len('.git')]
if '@' in repository_url: # remove credentials if '@' in repository_url: # remove credentials
scheme = repository_url.split(':')[0] scheme = repository_url.split(':')[0]
...@@ -49,11 +29,11 @@ def makeVCSLink(repository_url, revision): ...@@ -49,11 +29,11 @@ def makeVCSLink(repository_url, revision):
repository=repository_url, repository=repository_url,
revision=revision) revision=revision)
for repository in test_suite.contentValues(portal_type='Test Suite Repository'): for repository in test_suite_data['repository_dict'].values():
result_list.append( result_list.append(
makeVCSLink( makeVCSLink(
repository.getProperty('git_url'), repository['repository_url'],
repository_dict[repository.getProperty('buildout_section_id')])) repository['revision']))
if len(result_list) == 1: if len(result_list) == 1:
from zExceptions import Redirect from zExceptions import Redirect
......
"""Returns info about a test result, a mapping containing:
test_suite_relative_url: relative url of test suite
repository_dict: for each test suite repository, keyed by buildout section id:
revision: commit sha
repository_url: git url of the repository
commits_count: number of commits
returns None if test suite cannot be found.
"""
portal = context.getPortalObject()
test_suite_list = portal.portal_catalog(
portal_type='Test Suite',
validation_state=('validated', 'invalidated'),
title={'query': context.getTitle(), 'key': 'ExactMatch'})
if not test_suite_list:
return None
test_suite = sorted(
[test_suite.getObject() for test_suite in test_suite_list],
key=lambda test_suite: test_suite.getValidationState() == 'validated')[-1]
# decode the reference ( ${buildout_section_id}=${number of commits}-${hash},${buildout_section_id}=${number of commits}-${hash} )
repository_dict = {}
if context.getReference() and '-' in context.getReference(): # tolerate invalid references, especially for tests
for repository_string in context.getReference().split(','):
buildout_section_id_and_commits_count, revision = repository_string.split('-')
buildout_section_id, commits_count = buildout_section_id_and_commits_count.split('=')
repository_dict[buildout_section_id] = {
'revision': revision,
'commits_count': int(commits_count),
}
# add information about test suite repositories
for test_result_repository in test_suite.contentValues(portal_type='Test Suite Repository'):
repository_data = repository_dict.setdefault(test_result_repository.getBuildoutSectionId(), {})
repository_data['repository_url'] = test_result_repository.getGitUrl()
repository_data['connector_relative_url'] = test_result_repository.getDestination()
if REQUEST:
REQUEST.RESPONSE.setHeader('content-type', 'application/json; charset=utf-8')
return {
'test_suite_relative_url': test_suite.getRelativeUrl(),
'repository_dict': repository_dict,
}
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>REQUEST=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestResult_getTestSuiteData</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -103,7 +103,9 @@ ...@@ -103,7 +103,9 @@
<item> <item>
<key> <string>right</string> </key> <key> <string>right</string> </key>
<value> <value>
<list/> <list>
<string>my_destination_title</string>
</list>
</value> </value>
</item> </item>
</dictionary> </dictionary>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>columns</string>
<string>description</string>
<string>portal_type</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_destination_title</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>columns</string> </key>
<value>
<list>
<tuple>
<string>title</string>
<string>Gitlab REST Connector</string>
</tuple>
<tuple>
<string>url_string</string>
<string>URL</string>
</tuple>
<tuple>
<string>translated_validation_state_title</string>
<string>State</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Connector to gitlab API to annotate commits from this repository with test result status.</string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_relation_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value>
<list>
<tuple>
<string>Gitlab REST Connector</string>
<string>Gitlab REST Connector</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Gitlab Connector</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -98,6 +98,10 @@ ...@@ -98,6 +98,10 @@
<string>branch</string> <string>branch</string>
<string>Branch</string> <string>Branch</string>
</tuple> </tuple>
<tuple>
<string>destination_title</string>
<string>Gitlab Connector</string>
</tuple>
</list> </list>
</value> </value>
</item> </item>
......
...@@ -3,6 +3,9 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase ...@@ -3,6 +3,9 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
import json import json
from time import sleep from time import sleep
from DateTime import DateTime from DateTime import DateTime
import responses
import httplib
class TestTaskDistribution(ERP5TypeTestCase): class TestTaskDistribution(ERP5TypeTestCase):
def afterSetUp(self): def afterSetUp(self):
...@@ -152,7 +155,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -152,7 +155,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
self.scalability_distributor.getRelativeUrl()) self.scalability_distributor.getRelativeUrl())
def test_02b_checkConsistencyOnTestSuite(self): def test_02b_checkConsistencyOnTestSuite(self):
test_suite, = self._createTestSuite() test_suite, = self._createTestSuite() # pylint: disable=unbalanced-tuple-unpacking
self.tic() self.tic()
test_suite_repository, = test_suite.objectValues(portal_type="Test Suite Repository") test_suite_repository, = test_suite.objectValues(portal_type="Test Suite Repository")
self.checkPropertyConstraint(test_suite, 'title', 'ERP5-MASTER') self.checkPropertyConstraint(test_suite, 'title', 'ERP5-MASTER')
...@@ -169,7 +172,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -169,7 +172,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
Make sure validation of test suite generate a reference, and revalidating Make sure validation of test suite generate a reference, and revalidating
a test suite should not change reference a test suite should not change reference
""" """
test_suite, = self._createTestSuite() test_suite, = self._createTestSuite() # pylint: disable=unbalanced-tuple-unpacking
self.assertTrue(test_suite.getReference() != None) self.assertTrue(test_suite.getReference() != None)
self.tic() self.tic()
test_suite.invalidate() test_suite.invalidate()
...@@ -187,7 +190,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -187,7 +190,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
""" """
Check the constraint avoiding duplicates of test suites Check the constraint avoiding duplicates of test suites
""" """
test_suite, = self._createTestSuite() test_suite, = self._createTestSuite() # pylint: disable=unbalanced-tuple-unpacking
self.tic() self.tic()
test_suite_clone = test_suite.Base_createCloneDocument(batch_mode=1) test_suite_clone = test_suite.Base_createCloneDocument(batch_mode=1)
self.assertRaises(ValidationFailed, self.portal.portal_workflow.doActionFor, test_suite_clone, 'validate_action') self.assertRaises(ValidationFailed, self.portal.portal_workflow.doActionFor, test_suite_clone, 'validate_action')
...@@ -360,10 +363,9 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -360,10 +363,9 @@ class TestTaskDistribution(ERP5TypeTestCase):
while a test suite with high priority is waiting for more nodes to speed up. while a test suite with high priority is waiting for more nodes to speed up.
""" """
for x in range(10): for x in range(10):
config_list = json.loads(self.distributor.startTestSuite( json.loads(self.distributor.startTestSuite(title="COMP%s-Node1" % x))
title="COMP%s-Node1" % x))
test_suite_list = self._createTestSuite(quantity=3) test_suite_list = self._createTestSuite(quantity=3)
test_suite_1, test_suite_2, test_suite_3 = test_suite_list test_suite_1, test_suite_2, test_suite_3 = test_suite_list # pylint: disable=unbalanced-tuple-unpacking
test_suite_list[0].setIntIndex(1) # test suite 1, up to 1 testnode test_suite_list[0].setIntIndex(1) # test suite 1, up to 1 testnode
test_suite_list[1].setIntIndex(5) # test suite 2, up to 5 testnodes test_suite_list[1].setIntIndex(5) # test suite 2, up to 5 testnodes
test_suite_list[2].setIntIndex(8) # test suite 3, up to 10 testnodes test_suite_list[2].setIntIndex(8) # test suite 3, up to 10 testnodes
...@@ -869,7 +871,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -869,7 +871,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
It shall be possible on a test suite to define configuration we would like It shall be possible on a test suite to define configuration we would like
to use to create slapos instance. to use to create slapos instance.
""" """
test_suite, = self._createTestSuite(cluster_configuration=None) test_suite, = self._createTestSuite(cluster_configuration=None) # pylint: disable=unbalanced-tuple-unpacking
self.tic() self.tic()
self.assertEquals('{"configuration_list": [{}]}', self.distributor.generateConfiguration(test_suite.getTitle())) self.assertEquals('{"configuration_list": [{}]}', self.distributor.generateConfiguration(test_suite.getTitle()))
test_suite.setClusterConfiguration("{'foo': 3}") test_suite.setClusterConfiguration("{'foo': 3}")
...@@ -896,7 +898,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -896,7 +898,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
When we have two test suites and we have two test nodes, we should have When we have two test suites and we have two test nodes, we should have
one test suite distributed per test node one test suite distributed per test node
""" """
test_node_one, test_node_two = self._createTestNode(quantity=2) test_node_one, test_node_two = self._createTestNode(quantity=2) # pylint: disable=unbalanced-tuple-unpacking
test_suite_one = self._createTestSuite(reference_correction=+0, test_suite_one = self._createTestSuite(reference_correction=+0,
title='one')[0] title='one')[0]
self._createTestSuite(reference_correction=+1, self._createTestSuite(reference_correction=+1,
...@@ -989,23 +991,23 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -989,23 +991,23 @@ class TestTaskDistribution(ERP5TypeTestCase):
[test_node_two, ["one", "seven", "height" , "six"]]) [test_node_two, ["one", "seven", "height" , "six"]])
# Check that additional test node would get work for missing assignments # Check that additional test node would get work for missing assignments
# No move a test suite is done since in average we miss slots # No move a test suite is done since in average we miss slots
test_node_three, = self._createTestNode(reference_correction=2) test_node_three, = self._createTestNode(reference_correction=2) # pylint: disable=unbalanced-tuple-unpacking
check([test_node_zero, ["three", "four", "height", "six"]], check([test_node_zero, ["three", "four", "height", "six"]],
[test_node_one, ["two", "four", "seven" , "six"]], [test_node_one, ["two", "four", "seven" , "six"]],
[test_node_two, ["one", "seven", "height" , "six"]], [test_node_two, ["one", "seven", "height" , "six"]],
[test_node_three, ["seven", "height"]]) [test_node_three, ["seven", "height"]])
# With even more test node, check that we move some work to less # With even more test node, check that we move some work to less
# busy test nodes # busy test nodes
test_node_four, = self._createTestNode(reference_correction=3) test_node_four, = self._createTestNode(reference_correction=3) # pylint: disable=unbalanced-tuple-unpacking
test_node_five, = self._createTestNode(reference_correction=4) test_node_five, = self._createTestNode(reference_correction=4) # pylint: disable=unbalanced-tuple-unpacking
check([test_node_zero, ["three", "six", "height"]], check([test_node_zero, ["three", "six", "height"]],
[test_node_one, ["two", "six", "seven"]], [test_node_one, ["two", "six", "seven"]],
[test_node_two, ["one", "seven", "height"]], [test_node_two, ["one", "seven", "height"]],
[test_node_three, ["four", "seven", "height"]], [test_node_three, ["four", "seven", "height"]],
[test_node_four, ["four", "seven", "height"]], [test_node_four, ["four", "seven", "height"]],
[test_node_five, ["six", "seven", "height"]]) [test_node_five, ["six", "seven", "height"]])
test_node_six, = self._createTestNode(reference_correction=5) test_node_six, = self._createTestNode(reference_correction=5) # pylint: disable=unbalanced-tuple-unpacking
test_node_seven, = self._createTestNode(reference_correction=6) test_node_seven, = self._createTestNode(reference_correction=6) # pylint: disable=unbalanced-tuple-unpacking
check([test_node_zero, ["three", "height"]], check([test_node_zero, ["three", "height"]],
[test_node_one, ["two", "seven"]], [test_node_one, ["two", "seven"]],
[test_node_two, ["one", "height"]], [test_node_two, ["one", "height"]],
...@@ -1020,7 +1022,7 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -1020,7 +1022,7 @@ class TestTaskDistribution(ERP5TypeTestCase):
Check that the property max_test_suite on the distributor could Check that the property max_test_suite on the distributor could
be used to customize the quantity of test suite affected per test node be used to customize the quantity of test suite affected per test node
""" """
test_node, = self._createTestNode(quantity=1) test_node, = self._createTestNode(quantity=1) # pylint: disable=unbalanced-tuple-unpacking
test_suite_list = self._createTestSuite(quantity=5) test_suite_list = self._createTestSuite(quantity=5)
self.tool.TestTaskDistribution.setMaxTestSuite(None) self.tool.TestTaskDistribution.setMaxTestSuite(None)
self.tic() self.tic()
...@@ -1040,8 +1042,9 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -1040,8 +1042,9 @@ class TestTaskDistribution(ERP5TypeTestCase):
When we have two test suites and we have two test nodes, we should have When we have two test suites and we have two test nodes, we should have
one test suite distributed per test node one test suite distributed per test node
""" """
test_node_one, test_node_two = self._createTestNode(quantity=2, test_node_one, test_node_two = self._createTestNode( # pylint: disable=unbalanced-tuple-unpacking
specialise_value=self.performance_distributor) quantity=2,
specialise_value=self.performance_distributor)
test_suite_one = self._createTestSuite( test_suite_one = self._createTestSuite(
title='one', specialise_value=self.performance_distributor)[0] title='one', specialise_value=self.performance_distributor)[0]
self._createTestSuite(title='two', reference_correction=+1, self._createTestSuite(title='two', reference_correction=+1,
...@@ -1103,7 +1106,6 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -1103,7 +1106,6 @@ class TestTaskDistribution(ERP5TypeTestCase):
self.assertEqual(set(['test suite 1|COMP32-Node1', self.assertEqual(set(['test suite 1|COMP32-Node1',
'test suite 2|COMP32-Node1']), 'test suite 2|COMP32-Node1']),
set([x['test_suite_title'] for x in config_list])) set([x['test_suite_title'] for x in config_list]))
revision = 'a=a,b=b,c=c'
result = self._createTestResult(test_title='test suite 1|COMP32-Node1', result = self._createTestResult(test_title='test suite 1|COMP32-Node1',
distributor=self.performance_distributor) distributor=self.performance_distributor)
self.tic() self.tic()
...@@ -1410,4 +1412,133 @@ class TestTaskDistribution(ERP5TypeTestCase): ...@@ -1410,4 +1412,133 @@ class TestTaskDistribution(ERP5TypeTestCase):
self.tic() self.tic()
self._createTestResult(test_title='Periodicity Disabled Test Suite') self._createTestResult(test_title='Periodicity Disabled Test Suite')
self.assertEqual(None, test_suite.getAlarmDate()) self.assertEqual(None, test_suite.getAlarmDate())
\ No newline at end of file
class TestGitlabRESTConnectorInterface(ERP5TypeTestCase):
"""Tests for Gitlab commits annotations.
"""
def afterSetUp(self):
connector = self.portal.portal_web_services.newContent(
portal_type='Gitlab REST Connector',
reference='lab.example.com',
url_string='https://lab.example.com/api/v4/',
token='123456'
)
connector.validate()
self.project = self.portal.project_module.newContent(
portal_type='Project',
title=self.id()
)
self.test_suite = self.portal.test_suite_module.newContent(
portal_type='Test Suite',
title=self.id(),
source_project_value=self.project,
)
self.test_suite.newContent(
portal_type='Test Suite Repository',
branch='master',
git_url='https://lab.example.com/nexedi/test.git',
buildout_section_id='test',
destination_value=connector,
)
# another unrelated repository, without connector, this
# should not cause any API call.
self.test_suite.newContent(
portal_type='Test Suite Repository',
branch='master',
git_url='https://lab.nexedi.com/nexedi/erp5.git',
buildout_section_id='erp5',
profile_path='software-release/software.cfg'
)
self.test_suite.validate()
self.test_result = self.portal.test_result_module.newContent(
portal_type='Test Result',
source_project_value=self.project,
title=self.id(),
reference='erp5=1-dc7b6e2e85e9434a97694a698884b057b7d30286,test=10-cc4c79c003f7cfe0bfcbc7b302eac988110c96ae'
)
self.post_commit_status_url = \
'https://lab.example.com/api/v4/projects/nexedi%2Ftest/statuses/cc4c79c003f7cfe0bfcbc7b302eac988110c96ae'
self.tic()
def beforeTearDown(self):
self.test_suite.invalidate()
self.tic()
def _response_callback(self, state):
"""Callback for responses, checking that request was correct to mark commit as `state`
"""
def _callback(request):
self.assertEqual(
'123456',
request.headers['PRIVATE-TOKEN'])
body = json.loads(request.body)
self.assertEqual(
state,
body['state'])
self.assertIn(
self.test_result.getRelativeUrl(),
body['target_url'])
self.assertEqual(
self.id(),
body['name'])
return (httplib.CREATED, {'content-type': 'application/json'}, '{}')
return _callback
def test_start_test(self):
with responses.RequestsMock() as rsps:
rsps.add_callback(
responses.POST,
self.post_commit_status_url,
self._response_callback('running'))
self.test_result.start()
self.tic()
def _start_test_result(self):
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST,
self.post_commit_status_url,
{})
self.test_result.start()
self.tic()
def test_cancel_test(self):
self._start_test_result()
with responses.RequestsMock() as rsps:
rsps.add_callback(
responses.POST,
self.post_commit_status_url,
self._response_callback('canceled'))
self.test_result.cancel()
self.tic()
def test_stop_test_success(self):
self._start_test_result()
self.test_result.newContent(
portal_type='Test Result Line',
all_tests=1
)
self.test_result.setStringIndex('PASS')
with responses.RequestsMock() as rsps:
rsps.add_callback(
responses.POST,
self.post_commit_status_url,
self._response_callback('success'))
self.test_result.stop()
self.tic()
def test_stop_test_failure(self):
self._start_test_result()
self.test_result.setStringIndex('FAILED')
with responses.RequestsMock() as rsps:
rsps.add_callback(
responses.POST,
self.post_commit_status_url,
self._response_callback('failed'))
self.test_result.stop()
self.tic()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionWorkflowDefinition" module="Products.ERP5.InteractionWorkflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>creation_guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Update gitlab status of commits after test results</string> </value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test_result_gitlab_interaction_workflow</string> </value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Interaction Workflow Definition</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interactions</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>TestResult_afterCancel</string>
</list>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>cancel</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>cancel</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>TestResult_afterStart</string>
</list>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>start</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>start</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>TestResult_afterStop</string>
</list>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>stop</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>stop</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Scripts" module="Products.DCWorkflow.Scripts"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>scripts</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>sci</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestResult_afterCancel</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>sci</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestResult_afterStart</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
test_result = sci['object']
state = 'failed'
if test_result.getStringIndex() == 'PASS' and test_result.getProperty('all_tests', 0) > 0:
state = 'success'
test_result.activate(activity='SQLQueue').TestResult_annotateCommit(state)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>sci</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TestResult_afterStop</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Variables" module="Products.DCWorkflow.Variables"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variables</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Worklists" module="Products.DCWorkflow.Worklists"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_mapping</string> </key>
<value>
<dictionary/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>worklists</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_project erp5_project
\ No newline at end of file erp5_web_service
\ No newline at end of file
...@@ -3,6 +3,7 @@ Benchmark Result | view ...@@ -3,6 +3,7 @@ Benchmark Result | view
Cloud Performance Unit Test Distributor | view Cloud Performance Unit Test Distributor | view
ERP5 Project Unit Test Distributor | view ERP5 Project Unit Test Distributor | view
ERP5 Scalability Distributor | view ERP5 Scalability Distributor | view
Gitlab REST Connector | view
Scalability Test Suite | vcs_repository Scalability Test Suite | vcs_repository
Scalability Test Suite | view Scalability Test Suite | view
SlapOS Agent Distributor | view SlapOS Agent Distributor | view
......
...@@ -2,4 +2,5 @@ document.erp5.TestNode ...@@ -2,4 +2,5 @@ document.erp5.TestNode
document.erp5.TestSuite document.erp5.TestSuite
document.erp5.TestSuiteRepository document.erp5.TestSuiteRepository
document.erp5.ERP5ScalabilityDistributor document.erp5.ERP5ScalabilityDistributor
document.erp5.SlapOSAgentDistributor document.erp5.SlapOSAgentDistributor
\ No newline at end of file document.erp5.GitlabRESTConnector
\ No newline at end of file
...@@ -13,4 +13,5 @@ Test Result | Test Result Node ...@@ -13,4 +13,5 @@ Test Result | Test Result Node
Test Suite Module | Scalability Test Suite Test Suite Module | Scalability Test Suite
Test Suite Module | SlapOS Agent Test Suite Test Suite Module | SlapOS Agent Test Suite
Test Suite Module | Test Suite Test Suite Module | Test Suite
Test Suite | Test Suite Repository Test Suite | Test Suite Repository
\ No newline at end of file Web Service Tool | Gitlab REST Connector
\ No newline at end of file
...@@ -3,6 +3,7 @@ Benchmark Result Line ...@@ -3,6 +3,7 @@ Benchmark Result Line
Cloud Performance Unit Test Distributor Cloud Performance Unit Test Distributor
ERP5 Project Unit Test Distributor ERP5 Project Unit Test Distributor
ERP5 Scalability Distributor ERP5 Scalability Distributor
Gitlab REST Connector
Scalability Test Suite Scalability Test Suite
SlapOS Agent Distributor SlapOS Agent Distributor
SlapOS Agent Test Suite SlapOS Agent Test Suite
......
...@@ -4,6 +4,9 @@ Benchmark Result | SortIndex ...@@ -4,6 +4,9 @@ Benchmark Result | SortIndex
Benchmark Result | Task Benchmark Result | Task
ERP5 Project Unit Test Distributor | UnitTestDistributor ERP5 Project Unit Test Distributor | UnitTestDistributor
ERP5 Scalability Distributor | ScalabilityDistributor ERP5 Scalability Distributor | ScalabilityDistributor
Gitlab REST Connector | GitlabRESTConnector
Gitlab REST Connector | Reference
Gitlab REST Connector | Url
Scalability Test Suite | Arrow Scalability Test Suite | Arrow
Scalability Test Suite | ScalabilityTestSuite Scalability Test Suite | ScalabilityTestSuite
Scalability Test Suite | TestSuite Scalability Test Suite | TestSuite
...@@ -23,6 +26,7 @@ Test Result Node | Task ...@@ -23,6 +26,7 @@ Test Result Node | Task
Test Result | Comment Test Result | Comment
Test Result | SortIndex Test Result | SortIndex
Test Result | Task Test Result | Task
Test Suite Repository | Arrow
Test Suite Repository | TestSuiteRepository Test Suite Repository | TestSuiteRepository
Test Suite Repository | TestSuiteRepositoryConstraint Test Suite Repository | TestSuiteRepositoryConstraint
Test Suite | Arrow Test Suite | Arrow
......
...@@ -8,6 +8,8 @@ ERP5 Project Unit Test Distributor | edit_workflow ...@@ -8,6 +8,8 @@ ERP5 Project Unit Test Distributor | edit_workflow
ERP5 Project Unit Test Distributor | validation_workflow ERP5 Project Unit Test Distributor | validation_workflow
ERP5 Scalability Distributor | edit_workflow ERP5 Scalability Distributor | edit_workflow
ERP5 Scalability Distributor | validation_workflow ERP5 Scalability Distributor | validation_workflow
Gitlab REST Connector | edit_workflow
Gitlab REST Connector | validation_workflow
Scalability Test Suite | edit_workflow Scalability Test Suite | edit_workflow
Scalability Test Suite | test_suite_workflow Scalability Test Suite | test_suite_workflow
SlapOS Agent Distributor | edit_workflow SlapOS Agent Distributor | edit_workflow
...@@ -21,6 +23,7 @@ Test Result Line | test_result_workflow ...@@ -21,6 +23,7 @@ Test Result Line | test_result_workflow
Test Result Node | edit_workflow Test Result Node | edit_workflow
Test Result Node | test_result_workflow Test Result Node | test_result_workflow
Test Result | edit_workflow Test Result | edit_workflow
Test Result | test_result_gitlab_interaction_workflow
Test Result | test_result_workflow Test Result | test_result_workflow
Test Suite | edit_workflow Test Suite | edit_workflow
Test Suite | test_suite_workflow Test Suite | test_suite_workflow
\ No newline at end of file
TestSuite TestSuite
GitlabRESTConnector
CommandLineResult CommandLineResult
TestSuiteRepository TestSuiteRepository
TestSuiteRepositoryConstraint TestSuiteRepositoryConstraint
......
test_node_workflow test_node_workflow
test_result_gitlab_interaction_workflow
test_result_workflow test_result_workflow
test_suite_workflow test_suite_workflow
\ No newline at end of file
...@@ -443,6 +443,7 @@ class TestXHTML(TestXHTMLMixin): ...@@ -443,6 +443,7 @@ class TestXHTML(TestXHTMLMixin):
'erp5_email_reader', 'erp5_email_reader',
'erp5_commerce', 'erp5_commerce',
'erp5_credential', 'erp5_credential',
'erp5_web_service',
'erp5_test_result', 'erp5_test_result',
'erp5_forge', 'erp5_forge',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment