Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests on test_services_types.py fail randomly on Jenkins #1018

Closed
wesleybl opened this issue Nov 11, 2020 · 20 comments · Fixed by plone/plone.dexterity#159
Closed

Tests on test_services_types.py fail randomly on Jenkins #1018

wesleybl opened this issue Nov 11, 2020 · 20 comments · Fixed by plone/plone.dexterity#159

Comments

@wesleybl
Copy link
Member

wesleybl commented Nov 11, 2020

I noticed that the tests at test_services_types.py are failing with considerable frequency in Jenkins. Errors like:

Error message:

500 != 204

Traceback

500 != 204

  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/jenkins/workspace/pull-request-5.2/src/plone.restapi/src/plone/restapi/tests/test_services_types.py", line 216, in test_types_document_patch_fieldsets
    self.assertEqual(response.status_code, 204)
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 513, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 506, in _baseAssertEqual
    raise self.failureException(msg)

For example:

https://jenkins.plone.org/job/pull-request-5.2/1712/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_get_field/

https://jenkins.plone.org/job/pull-request-5.2/1714/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_get_fieldset/

https://jenkins.plone.org/job/pull-request-5.2/1729/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_patch_fieldsets/

https://jenkins.plone.org/job/pull-request-5.2/1730/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_patch_create_missing/

https://jenkins.plone.org/job/pull-request-5.2/1736/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_get_field/

https://jenkins.plone.org/job/pull-request-5.2/1737/testReport/junit/plone.restapi.tests.test_services_types/TestServicesTypes/test_types_document_patch_create_missing/

These errors are likely due to high load on the Jenkins server. But why do only tests on test_services_types.py fail? Are type services heavier than others? Messages like 500 != 204 do not help to understand these errors. Perhaps it is good to print the body of the response when the return code is 500, to see the backend traceback.

@tisto
Copy link
Member

tisto commented Nov 11, 2020

@wesleybl thanks for bringing this up! I wasn't aware of those 500 errors. As far as I know Jenkins nodes all run on the same machine which could cause load problems. We run performance tests on REST API (on our company Jenkins) and I never saw those 500 errors there. Maybe we should include this in our jMeter performance tests:

https://github.com/plone/plone.restapi/blob/master/performance.jmx

(Anyone can run those tests. The only reason I do not run them on the Plone Jenkins is that I do not have permissions on Jenkins any longer, since I stepped down as testing/ci team lead)

@wesleybl
Copy link
Member Author

@tisto @mauritsvanrees is there any parallelism in the execution of tests in Jenkins? I am suspecting that these errors occur because of parallels. For example, one test creates a field when another test expected that field to not exist.

@mauritsvanrees
Copy link
Member

There is no parallelism as far as I know. In the future it might be possible to add this in the test runner.

Note: for a Python 3 PR job, this is the main line in the job description.

The only "sort-of" parallelism is that each node has several workers (node 1 has 6, the others have 4), so multiple jobs can run at the same time. I guess the effect is mostly race conditions like this: a robot test tries a random port to run a server, the port is free, but before it can start the server, another job has taken the port. I am not sure if this is what happens, but I can imagine it does.

@wesleybl
Copy link
Member Author

@mauritsvanrees according to the situation you described, my theory makes sense. This situation can occur not only in robot tests but also in tests such as:

response = self.api_session.patch(
"/@types/Document/author_email",
json={
"title": "Author e-mail",
"description": "The e-mail address of the author",
"minLength": 10,
"maxLength": 200,
"required": False,
},
)
self.assertEqual(response.status_code, 204)

self.portal_url in:

self.api_session = RelativeSession(self.portal_url)

Is always:

http://localhost:55001/plone

So, when we have jobs in parallel in Jenkins, the request module doesn't know for which test it is making the request.

This explains why these errors don't occur in Travis, where jobs are run on separate machines.

Is it possible that these tests run on random ports, like robot tests?

@wesleybl
Copy link
Member Author

In reality I saw that in the plone.restapi the port is always the same because of that:

os.environ['ZSERVER_PORT'] = '55001'

Anyway, even being random in Jenkins, collisions can occur, as @mauritsvanrees suspected.

@mauritsvanrees
Copy link
Member

Aha, so Travis uses the bin/test script created the buildout in the plone.restapi package, which always has port 55001, which is fine there. And Jenkins uses the bin/test script created by the coredev buildout, which has a random port.
That explains why I get changes in the repository when I run that last script, for example:

diff --git a/src/plone/restapi/tests/http-examples/404_not_found.resp b/src/plone/restapi/tests/http-examples/404_not_found.resp
index bbf39099..5bb8601a 100644
--- a/src/plone/restapi/tests/http-examples/404_not_found.resp
+++ b/src/plone/restapi/tests/http-examples/404_not_found.resp
@@ -2,6 +2,6 @@ HTTP/1.1 404 Not Found
 Content-Type: application/json
 
 {
-  "message": "Resource not found: http://localhost:55001/plone/non-existing-resource",
+  "message": "Resource not found: http://localhost:50054/plone/non-existing-resource", 
   "type": "NotFound"
 }

I am not sure though when the port randomisation occurs, especially: does this happen multiple times during a test run, which could explain a random failure in a single test, or does this happen once for the entire run. Ah: all files in the http-example directory contain the same random port (50054 this time), except for a few that have the standard port 55001. So the random port is set once, though it could still be once per Jenkins job or once per test layer. But running the test_services_types tests does not change any of these files.

@tisto
Copy link
Member

tisto commented Nov 17, 2020

If I recall correctly the zope testrunner on jenkins.plone.org does run in parallel.

The REST API tests on Jenkins do not use the buildout in the REST API repo but instead the buildout.coredev one. We have to make sure we do not have any hardcoded ports in plone.restapi (which we don't have as far as I know).

I looked into running stuff in parallel myself long time ago and dropped the idea exactly because of very hard to track edge cases like this.

Just my 2c. Maybe that helps a little bit...

@wesleybl
Copy link
Member Author

@mauritsvanrees in buildout.coredev, for each bin/test run, all tests use the same port. Next time, use another port.

@wesleybl
Copy link
Member Author

@tisto @mauritsvanrees I noticed that these errors only occur in Python 2. Does this give you any hint of what it might be? Is the way Python 3 handles threads different from Python 2?

@wesleybl
Copy link
Member Author

I was able to simulate the error locally in buildout.coredev and Python 2 with the command::

for run in {1..5}; do bin/test -m plone.restapi -t test_services_types & done

It is not all the time that the error occurs but if you run the command a few times, you will see the error.

That is, parallel executions actually cause the error.

@mauritsvanrees
Copy link
Member

I don't think the port randomisation or parallellisation is a problem. When I open two terminals and run WSGI_SERVER_PORT=55555 bin/test -s plone.restapi -t test_types_document_put in parallel, one of the runs fails at test layer setup, with an error saying that the port is not available so the wsgi server cannot start. This does not happen on Jenkins, so this does not seem the problem. Also, I sometimes get errors running just a single test.

Looking at test_types_document_put there are two errors I sometimes get:

Error in test test_types_document_put (plone.restapi.tests.test_services_types.TestServicesTypes)
Traceback (most recent call last):
  File "/Users/maurits/.pyenv/versions/2.7.17/lib/python2.7/unittest/case.py", line 398, in debug
    getattr(self, self._testMethodName)()
  File "/Users/maurits/community/plone-coredev/5.2/src/plone.restapi/src/plone/restapi/tests/test_services_types.py", line 383, in test_types_document_put
    doc_json["properties"].pop("author_url")
KeyError: 'author_url'

That is this line. doc_json is what we get from self.api_session.get("/@types/Document").json(). The test expects that this contains an author_url field, which we added in the setUp. The test tries to pop this, but this fails because the field is not there.

Second thing that sometimes goes wrong, is a few lines further. The PUT gives a 400 BadRequest "Missing/Invalid parameter factory" for the author_email field. And this seems the correct answer: in these test lines we pass a new author_email property without a factory.
But: why does this test usually pass?

My guess: this is caused by our good friend the dexterity schema cache.

So we might need to call:

    plone.dexterity.schema.SCHEMA_CACHE.clear()

or:

    plone.dexterity.schema.SCHEMA_CACHE.invalidate("Document")

That could be done at the start and/or end of the test setUp and/or in tearDown. When I try this in all three places at the same time, I can run the tests 10 times without failure.

Or maybe the test failures are a real bug and this needs to be fixed in the actual code. plone.dexterity has an event handler for when a Dexterity FTI is modified. I think plone.restapi should call something like notify(ObjectModifiedEvent(fti)) whenever we change an FTI.
I suspect this would be the real fix.

@wesleybl Are you up for creating a PR for this?

@wesleybl
Copy link
Member Author

@mauritsvanrees when I have time I’ll take a look. Thanks!

@wesleybl
Copy link
Member Author

@mauritsvanrees the strange thing is that this error only occurs with Python 2. So I think it is not a plone.restapi error.

@wesleybl
Copy link
Member Author

@mauritsvanrees I can confirm that calling:

plone.dexterity.schema.SCHEMA_CACHE.clear()

or

plone.dexterity.schema.SCHEMA_CACHE.invalidate("Document")

in setUp does not fix the problem.

The plone.dexterity event handler is already called without notify(ObjectModifiedEvent(fti)). Anyway, I called it on:

def serializeSchema(schema):
"""Taken from plone.app.dexterity.serialize
Finds the FTI and model associated with a schema, and synchronizes
the schema to the FTI model_source attribute.
"""
# determine portal_type
try:
prefix, portal_type, schemaName = splitSchemaName(schema.__name__)
except ValueError:
# not a dexterity schema
return
# find the FTI and model
fti = queryUtility(IDexterityFTI, name=portal_type)
model = fti.lookupModel()
# synchronize changes to the model
syncSchema(schema, model.schemata[schemaName], overwrite=True)
fti.model_source = serializeModel(model)

and it didn't fix the problem.

@wesleybl
Copy link
Member Author

wesleybl commented Sep 6, 2021

I found this occurs because of plone.dexterity 2.9.10+. More specifically after plone/plone.dexterity#137

Strangely, it only occurs in Python 2.

The error occurred in Jenkins, but it did not occur in plone.restapi GA, because the plone.restapi buildout was pinned to plone.dexterity 2.9.8.

After the merge of PR #1218 , the error can happen in GA as well.

@mauritsvanrees
Copy link
Member

I looked at this in the coredev buildout on Python 2.7, adapting it to have two zeoclients. I checked out plone.dexterity and edited it to print whenever the schema cache is invalidated. Nothing I did ever resulted in this printing anything. Well, one time when adding a completely new portal_type, but not when changing anything about a field. I tried editing in the Plone UI and with the restapi (via Postman). I did not see anything go wrong because of this: a change in the field title was always visible in the all responses, via RestAPI, in the Plone UI control panel, and on the view and edit of an instance of the new portal type. I start doubting whether the schema cache is effectively used much at all...

Still, it occasionally goes wrong on Jenkins...

One suspect change is that in testing.py in the RelativeSession we retry the request when there is a ConnectionError. That is a change from myself at the end of 2018: #654. So that is almost three years, and it fixed test failures at the time, so should be okay.

Another thing is that maybe some changes go too fast: when the FTI is changed, fti._p_mtime is changed automatically by the ZODB. But this is a timestamp of for example 1631634607.65. So this is measured in one hundredths of a second. If a request is handled very fast, the modification time of the FTI will stay the same, leading to problems like the above. I wonder if we should do time.sleep(0.0.1) between session calls.
No, that does not help.

Huh? A transaction.commit() helps! Well, I currently don't get the above errors locally anyway, so hard to say if it helps. But look at the end of the setUp method. The setUp calls self.api_session.post four times to change the schema of the Document type. When I check the model source at the end, it is empty. But then I do a commit:

(Pdb) print(self.portal.portal_types["Document"].model_source)

(Pdb) import transaction
(Pdb) transaction.commit()
(Pdb) print(self.portal.portal_types["Document"].model_source)
<model xmlns:form="http://namespaces.plone.org/supermodel/form" xmlns:i18n="http://xml.zope.org/namespaces/i18n" xmlns:lingua="http://namespaces.plone.org/supermodel/lingua" xmlns:marshal="http://namespaces.plone.org/supermodel/marshal" xmlns:security="http://namespaces.plone.org/supermodel/security" xmlns:users="http://namespaces.plone.org/supermodel/users" xmlns="http://namespaces.plone.org/supermodel/schema">
  <schema>
    <field name="author_email" type="plone.schema.email.Email">
      <description>Email of the author</description>
      <required>False</required>
      <title>Author email</title>
    </field>
    <field name="author_url" type="zope.schema.URI">
      <description>Website of the author</description>
      <required>False</required>
      <title>Author url</title>
    </field>
    <fieldset name="contact_info" label="Contact Info" description="Contact information"/>
    <fieldset name="location" label="Location" description="Location"/>
  </schema>
</model>

So it looks like doing a request with self.api_session does not actually make a change in the test database! That is rather unexpected, and can lead to all kinds of problems.

@mauritsvanrees
Copy link
Member

Using my branch maurits/use-plone-restapi-checkout-52-7xx I ran these three tests on Python 2:

bin/test -s plone.restapi -t test_types_document_patch_properties -t test_addable_types_for_non_manager_user -t test_types_document_put

The middle one does an explicit transaction commit.
Most of the time, all three tests pass, but not always:

Failure in test test_types_document_put (plone.restapi.tests.test_services_types.TestServicesTypes)
Traceback (most recent call last):
  File "/Users/maurits/.pyenv/versions/2.7.18/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/Users/maurits/community/plone-coredev/5.2/src/plone.restapi/src/plone/restapi/tests/test_services_types.py", line 407, in test_types_document_put
    self.assertIn("author_name", response.json().get("properties"))
  File "/Users/maurits/.pyenv/versions/2.7.18/lib/python2.7/unittest/case.py", line 804, in assertIn
    self.fail(self._formatMessage(msg, standardMsg))
  File "/Users/maurits/.pyenv/versions/2.7.18/lib/python2.7/unittest/case.py", line 410, in fail
    raise self.failureException(msg)
AssertionError: 'author_name' not found in {u'exclude_from_nav': {u'description': u'If selected, this item will not appear in the navigation tree', u'title': u'Exclude from navigation', u'default': False, u'factory': u'Yes/No', u'behavior': u'plone.excludefromnavigation', u'type': u'boolean'}, u'table_of_contents': {u'title': u'Table of contents', u'type': u'boolean', u'description': u'If selected, this will show a table of contents at the top of the page.', u'behavior': u'plone.tableofcontents', u'factory': u'Yes/No'}, u'contributors': {u'additionalItems': True, u'description': u'The names of people that have contributed to this item. Each contributor should be on a separate line.', u'title': u'Contributors', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.Users'}}, u'type': u'array'}, u'effective': {u'widget': u'datetime', u'description': u'If this date is in the future, the content will not show up in listings and searches until this date.', u'title': u'Publishing Date', u'factory': u'Date/Time', u'behavior': u'plone.dublincore', u'type': u'string'}, u'rights': {u'widget': u'textarea', u'description': u'Copyright statement or other rights information on this item.', u'title': u'Rights', u'factory': u'Text', u'behavior': u'plone.dublincore', u'type': u'string'}, u'text': {u'widget': u'richtext', u'description': u'', u'title': u'Text', u'factory': u'Rich Text', u'behavior': u'plone.richtext', u'type': u'string'}, u'expires': {u'widget': u'datetime', u'description': u'When this date is reached, the content will no longer be visible in listings and searches.', u'title': u'Expiration Date', u'factory': u'Date/Time', u'behavior': u'plone.dublincore', u'type': u'string'}, u'allow_discussion': {u'description': u'Allow discussion for this content object.', u'vocabulary': {u'@id': u'http://localhost:54637/plone/@sources/allow_discussion'}, u'title': u'Allow discussion', u'enum': [u'True', u'False'], u'factory': u'Choice', u'choices': [[u'True', u'Yes'], [u'False', u'No']], u'enumNames': [u'Yes', u'No'], u'behavior': u'plone.allowdiscussion', u'type': u'string'}, u'changeNote': {u'title': u'Change Note', u'type': u'string', u'description': u'Enter a comment that describes the changes you made.', u'behavior': u'plone.versioning', u'factory': u'Text line (String)'}, u'language': {u'description': u'', u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.SupportedContentLanguages'}, u'title': u'Language', u'default': u'en', u'factory': u'Choice', u'behavior': u'plone.dublincore', u'type': u'string'}, u'relatedItems': {u'additionalItems': True, u'description': u'', u'title': u'Related Items', u'default': [], u'items': {u'title': u'Related', u'type': u'string', u'description': u'', u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.Catalog'}, u'factory': u'Relation Choice'}, u'factory': u'Relation List', u'behavior': u'plone.relateditems', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.Catalog'}, u'pattern_options': {u'recentlyUsed': True}}, u'type': u'array'}, u'versioning_enabled': {u'description': u'Enable/disable versioning for this document.', u'title': u'Versioning enabled', u'default': True, u'factory': u'Yes/No', u'behavior': u'plone.versioning', u'type': u'boolean'}, u'title': {u'title': u'Title', u'type': u'string', u'description': u'', u'behavior': u'plone.dublincore', u'factory': u'Text line (String)'}, u'subjects': {u'additionalItems': True, u'description': u'Tags are commonly used for ad-hoc organization of content.', u'title': u'Tags', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.Keywords'}}, u'type': u'array'}, u'creators': {u'additionalItems': True, u'description': u'Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first.', u'title': u'Creators', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:54637/plone/@vocabularies/plone.app.vocabularies.Users'}}, u'type': u'array'}, u'id': {u'title': u'Short name', u'type': u'string', u'description': u'This name will be displayed in the URL.', u'behavior': u'plone.shortname', u'factory': u'Text line (String)'}, u'description': {u'widget': u'textarea', u'description': u'Used in item listings and search results.', u'title': u'Summary', u'factory': u'Text', u'behavior': u'plone.dublincore', u'type': u'string'}}

@mauritsvanrees
Copy link
Member

When using zope.testbrowser in a functional test case, a browser request does commit to the database. See for example this plone.app.users test.
This is because the integration in plone.testing syncs the transaction before returning the result.

A similar change in RelativeSession does the trick:

$ g di
diff --git a/src/plone/restapi/testing.py b/src/plone/restapi/testing.py
index 9e35799b..c1942f54 100644
--- a/src/plone/restapi/testing.py
+++ b/src/plone/restapi/testing.py
@@ -352,24 +352,31 @@ class RelativeSession(requests.Session):
     base if their URL is relative (doesn't begin with a HTTP[S] scheme).
     """
 
-    def __init__(self, base_url):
+    def __init__(self, base_url, app=None):
         super(RelativeSession, self).__init__()
         if not base_url.endswith("/"):
             base_url += "/"
         self.__base_url = base_url
+        self.__app = app
 
     def request(self, method, url, **kwargs):
         if urlparse(url).scheme not in ("http", "https"):
             url = url.lstrip("/")
             url = urljoin(self.__base_url, url)
         try:
-            return super(RelativeSession, self).request(method, url, **kwargs)
+            result = super(RelativeSession, self).request(method, url, **kwargs)
         except ConnectionError:
             # On Jenkins we often get one ConnectionError in a seemingly
             # random test, mostly in test_documentation.py.
             # The server is still listening: the port is open.  We retry once.
             time.sleep(1)
-            return super(RelativeSession, self).request(method, url, **kwargs)
+            result = super(RelativeSession, self).request(method, url, **kwargs)
+
+        # Sync transaction, just like the zope testbrowser integration does:
+        # https://github.com/plone/plone.testing/blob/8.0.3/src/plone/testing/_z2_testbrowser.py#L69
+        if self.__app is not None:
+            self.__app._p_jar.sync()
+        return result
 
 
 @implementer(IUUIDGenerator)
diff --git a/src/plone/restapi/tests/test_services_types.py b/src/plone/restapi/tests/test_services_types.py
index 040204f9..a868152e 100644
--- a/src/plone/restapi/tests/test_services_types.py
+++ b/src/plone/restapi/tests/test_services_types.py
@@ -21,7 +21,7 @@ class TestServicesTypes(unittest.TestCase):
         self.portal_url = self.portal.absolute_url()
         setRoles(self.portal, TEST_USER_ID, ["Manager"])
 
-        self.api_session = RelativeSession(self.portal_url)
+        self.api_session = RelativeSession(self.portal_url, app=self.app)
         self.api_session.headers.update({"Accept": "application/json"})
         self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)

Then after each request, I see the change reflected in self.portal.portal_types["Document"].model_source.
This should be done in all places where we use RelativeSession, and we may need to do a few transaction commits before calling it, but this seems to do the trick. At least then RelativeSession behaves more like zope.testbrowser does.

I can make a PR later, but first there is another PR of me open, which should be merged first.

mauritsvanrees added a commit that referenced this issue Sep 14, 2021
This should fix some random test failures.
Fixes #1018

Summary of my latest comments there:

- In a functional test layer using zope.testbrowser, a request that changes something in Plone leads to a transaction commit.
- plone.restapi uses RelativeSession instead, and this missed such an integration: changes did not really end up in the database.

It somehow mostly worked so far, but to me that seems luck.
@wesleybl
Copy link
Member Author

Fix in #1232

@wesleybl
Copy link
Member Author

Reopening because I still see failures. For example:

2 != None

  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/jenkins/workspace/pull-request-5.2-2.7/src/plone.restapi/src/plone/restapi/tests/test_services_types.py", line 379, in test_types_document_update_min_max
    self.assertEqual(2, response.json().get("minLength"))
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 513, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 506, in _baseAssertEqual
    raise self.failureException(msg)
'author_name' not found in {u'exclude_from_nav': {u'description': u'If selected, this item will not appear in the navigation tree', u'title': u'Exclude from navigation', u'default': False, u'factory': u'Yes/No', u'behavior': u'plone.excludefromnavigation', u'type': u'boolean'}, u'table_of_contents': {u'title': u'Table of contents', u'type': u'boolean', u'description': u'If selected, this will show a table of contents at the top of the page.', u'behavior': u'plone.tableofcontents', u'factory': u'Yes/No'}, u'contributors': {u'additionalItems': True, u'description': u'The names of people that have contributed to this item. Each contributor should be on a separate line.', u'title': u'Contributors', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.Users'}}, u'type': u'array'}, u'effective': {u'widget': u'datetime', u'description': u'If this date is in the future, the content will not show up in listings and searches until this date.', u'title': u'Publishing Date', u'factory': u'Date/Time', u'behavior': u'plone.dublincore', u'type': u'string'}, u'rights': {u'widget': u'textarea', u'description': u'Copyright statement or other rights information on this item.', u'title': u'Rights', u'factory': u'Text', u'behavior': u'plone.dublincore', u'type': u'string'}, u'text': {u'widget': u'richtext', u'description': u'', u'title': u'Text', u'factory': u'Rich Text', u'behavior': u'plone.richtext', u'type': u'string'}, u'expires': {u'widget': u'datetime', u'description': u'When this date is reached, the content will no longer be visible in listings and searches.', u'title': u'Expiration Date', u'factory': u'Date/Time', u'behavior': u'plone.dublincore', u'type': u'string'}, u'allow_discussion': {u'description': u'Allow discussion for this content object.', u'vocabulary': {u'@id': u'http://localhost:48059/plone/@sources/allow_discussion'}, u'title': u'Allow discussion', u'enum': [u'True', u'False'], u'factory': u'Choice', u'choices': [[u'True', u'Yes'], [u'False', u'No']], u'enumNames': [u'Yes', u'No'], u'behavior': u'plone.allowdiscussion', u'type': u'string'}, u'changeNote': {u'title': u'Change Note', u'type': u'string', u'description': u'Enter a comment that describes the changes you made.', u'behavior': u'plone.versioning', u'factory': u'Text line (String)'}, u'language': {u'description': u'', u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.SupportedContentLanguages'}, u'title': u'Language', u'default': u'en', u'factory': u'Choice', u'behavior': u'plone.dublincore', u'type': u'string'}, u'relatedItems': {u'additionalItems': True, u'description': u'', u'title': u'Related Items', u'default': [], u'items': {u'title': u'Related', u'type': u'string', u'description': u'', u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.Catalog'}, u'factory': u'Relation Choice'}, u'factory': u'Relation List', u'behavior': u'plone.relateditems', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.Catalog'}, u'pattern_options': {u'recentlyUsed': True}}, u'type': u'array'}, u'versioning_enabled': {u'description': u'Enable/disable versioning for this document.', u'title': u'Versioning enabled', u'default': True, u'factory': u'Yes/No', u'behavior': u'plone.versioning', u'type': u'boolean'}, u'title': {u'title': u'Title', u'type': u'string', u'description': u'', u'behavior': u'plone.dublincore', u'factory': u'Text line (String)'}, u'subjects': {u'additionalItems': True, u'description': u'Tags are commonly used for ad-hoc organization of content.', u'title': u'Tags', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.Keywords'}}, u'type': u'array'}, u'creators': {u'additionalItems': True, u'description': u'Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first.', u'title': u'Creators', u'items': {u'title': u'', u'type': u'string', u'description': u'', u'factory': u'Text line (String)'}, u'factory': u'Tuple', u'behavior': u'plone.dublincore', u'uniqueItems': True, u'widgetOptions': {u'vocabulary': {u'@id': u'http://localhost:48059/plone/@vocabularies/plone.app.vocabularies.Users'}}, u'type': u'array'}, u'id': {u'title': u'Short name', u'type': u'string', u'description': u'This name will be displayed in the URL.', u'behavior': u'plone.shortname', u'factory': u'Text line (String)'}, u'description': {u'widget': u'textarea', u'description': u'Used in item listings and search results.', u'title': u'Summary', u'factory': u'Text', u'behavior': u'plone.dublincore', u'type': u'string'}}

  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/jenkins/workspace/pull-request-5.2-2.7/src/plone.restapi/src/plone/restapi/tests/test_services_types.py", line 426, in test_types_document_put
    self.assertIn("author_name", response.json().get("properties"))
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 804, in assertIn
    self.fail(self._formatMessage(msg, standardMsg))
  File "/srv/python2.7/lib/python2.7/unittest/case.py", line 410, in fail
    raise self.failureException(msg)

See: https://jenkins.plone.org/job/pull-request-5.2-2.7/2236

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants