Skip to content

Add tests for external document adoption#1

Merged
scoder merged 2 commits intoscoder:LP1595781_adopt_external_docfrom
kovidgoyal:LP1595781_adopt_external_doc
Feb 19, 2017
Merged

Add tests for external document adoption#1
scoder merged 2 commits intoscoder:LP1595781_adopt_external_docfrom
kovidgoyal:LP1595781_adopt_external_doc

Conversation

@kovidgoyal
Copy link

As requested here: https://bugs.launchpad.net/lxml/+bug/1595781

Uses ctypes to avoid the complications of trying to run cython in the test suite. Since this code is pretty trivial, there aren't a lot of tests to run. At least that I can think of.

adopt_external_document() should type check its argument
xmlDoc *c_doc;
void *context;
*is_owned = 0;
if (unlikely_condition(!PyCapsule_IsValid(capsule, (const char*)"libxml2:xmlDoc"))) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already done internally by PyCapsule_GetPointer(), so I left it out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to be explicit since the documentation of GetPointer does not state that it checks the type of its argument, I dont want to rely on it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, why else could it fail? :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two possibilities, the object is not a capsule, i.e. PyCapsule_CheckExact fails or the capsule name is not correct. The documentation of GetPointer only claims that it checks the latter, not the former.

from common_imports import etree, HelperTestCase


@unittest.skipIf(sys.version_info[:2] < (2, 7),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unittest.skipIf() requires Python 2.7, though ;)
But there's a fallback for it in common_imports.

self.assertEqual(DOC_NAME, self.get_capsule_name(capsule))
# Create an lxml tree from the capsule (this is a move not a copy)
root = etree.adopt_external_document(capsule).getroot()
self.assertIsNone(self.get_capsule_name(capsule))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Py2.7, IIRC.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this means? The entire test case is conditioned to run only on py >= 2.7

from ctypes import pythonapi
from ctypes.util import find_library

def wrap(func, restype, *argtypes):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it very confusing to see the return type as second argument here. Looks like the first argument to the function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems natural to me indeed ctypes itself uses this convention for instance in CFUNCTYPE()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To expand on why it is natural -- when you read a function declaration in C the result type is before the argument types.

self.assertIsNone(self.get_capsule_name(capsule))
self.assertEqual(root.text, 't')
root.text = 'new text'
# Now reset the capsule so we can copy it
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the memory ownership clean, I'd test the copy first, then the move. It's sad that capsules do not allow setting their pointer to NULL in order to properly invalidate them when transferring ownership...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with doing copy first is you cannot then make modifications using the lxml api and check that they happen on the underlying tree as well, as is done on line 85. So you'd basically be reducing the amount of testing.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is, these modifications should not be done in the first place as they are illegal from an ownership perspective. The fact that the tree is not copied but transferred by pointer is an optimisation, not a feature meant to allow later modifications from two sides. I guess I should document that...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly, but this is a test suite, not a usage example. The intention of the test suite is to test as many code paths as possible. This is the only way to test that moving a tree is actually working as intended (well technically not the only way, one could check the original xmlDoc* pointer using libxml2 APIs but that just makes the test code a lot more complex.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Ok, the modifications are ok because they are done through the lxml API - it's the reuse of the capsule that is illegal.)

@scoder
Copy link
Owner

scoder commented Feb 19, 2017

Ok, works for me. Thanks!

@scoder scoder merged commit 60a244b into scoder:LP1595781_adopt_external_doc Feb 19, 2017
@kovidgoyal
Copy link
Author

You're welcome and thank you for implementing this feature :)

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 this pull request may close these issues.

2 participants