summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephan Bergmann <sbergman@redhat.com>2018-09-06 17:13:54 +0200
committerStephan Bergmann <sbergman@redhat.com>2018-09-07 07:31:48 +0200
commite7a3329fd0a68c95f00e6cdfdc3e40e6afa5411c (patch)
treea79054d20a3904d73347dcd6af696dcbb9897a78
parentdbb444e4ed7c19a11733ce8438bbb6546d42f852 (diff)
DeInitVCL in PythonTest
After b9757f5cfdb62b24e79eeb4c0ef0c8b98056cecf "loplugin:useuniqueptr in vcl/svdata" ASan/UBSan builds started to fail (like <https://ci.libreoffice.org//job/lo_ubsan/1025/>) at the end of PythonTest_dbaccess_python (and probably other PythonTests), when during exit the static utl::ConfigManager instance already happens to be destroyed by the time the static ImplSVData's mpSettingsConfigItem is destroyed (which would normally be cleared during DeInitVCL, if PythonTests would call that, and which in the past had thus simply been leaked in PythonTests when that mpSettingsConfigItem was a plain pointer instead of std::unique_ptr). So ensure that PythonTests that initialize VCL also call DeInitVCL, via a new private_deinitTestEnvironment, complementing the existing private_initTestEnvironment. However, while private_initTestEnvironment is called once (typically via UnoInProcess.setUp, which internally makes sure to only call it once) as soon as the first executed test needs it, private_deinitTestEnvironment must be called once after the lasts test needing it has executed. The only way that I found to do that is to override unittest.TextTestResult's stopTestRun method, which is called once after all tests have been executed. Hence a new test runner setup in unotest/source/python/org/libreoffice/unittest.py that is now called from solenv/gbuild/PythonTest.mk. That revealed a few places in PythonTests that didn't yet close/delete documents that they had opened, which has now been added. One remaining problem then is that classes like SwXTextDocument and friends call Application::GetSolarMutex from their dtors, via sw::UnoImplPtrDeleter (a "Smart pointer class ensuring that the pointed object is deleted with a locked SolarMutex", sw/inc/unobaseclass.hxx). That means that any PyUNO proxies to such C++ objects that remain alive after private_deinitTestEnvironment will cause issues at exit, when Python does a final garbage collection of those objects. The ultimate fix will be to remove that unhelpful UnoImplPtrDeleter and its locking of SolarMutex from the dtors of UNO objects; until then, the Python code is now sprinkled with some HACKs to make sure all those PyUNO proxies are released in a timely fashion (see the comment in unotest/source/python/org/libreoffice/unittest.py for details). (Also, it would probably help if UnoInProcess didn't keep a local self.xDoc around referencing (just) the last result of calling one of its open* methods, confusingly making it the responsibility of UnoInProcess to close that one document while making it the responsibility of the test code making the other UnoInProcess.open* calls to close any other documents.) Change-Id: Ief27c81e2b763e9be20cbf3234b68924315f13be Reviewed-on: https://gerrit.libreoffice.org/60100 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
-rw-r--r--pyuno/qa/pytests/testcollections_XCellRange.py34
-rw-r--r--pyuno/qa/pytests/testcollections_XEnumeration.py11
-rw-r--r--pyuno/qa/pytests/testcollections_XEnumerationAccess.py15
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexAccess.py24
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexContainer.py2
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexReplace.py18
-rw-r--r--pyuno/qa/pytests/testcollections_XNameAccess.py22
-rw-r--r--pyuno/qa/pytests/testcollections_XNameContainer.py12
-rw-r--r--pyuno/qa/pytests/testcollections_XNameReplace.py6
-rw-r--r--pyuno/qa/pytests/testcollections_misc.py8
-rw-r--r--pyuno/qa/pytests/testcollections_mixednameindex.py2
-rw-r--r--pyuno/source/module/pyuno_module.cxx33
-rw-r--r--sfx2/qa/python/check_sidebar.py3
-rw-r--r--sfx2/qa/python/check_sidebar_registry.py1
-rw-r--r--solenv/gbuild/PythonTest.mk7
-rw-r--r--sw/qa/python/check_bookmarks.py4
-rw-r--r--sw/qa/python/check_change_color.py1
-rw-r--r--sw/qa/python/check_cross_references.py4
-rw-r--r--sw/qa/python/check_fields.py8
-rw-r--r--sw/qa/python/check_flies.py8
-rw-r--r--sw/qa/python/check_indexed_property_values.py1
-rw-r--r--sw/qa/python/check_named_property_values.py1
-rw-r--r--sw/qa/python/check_table.py4
-rw-r--r--sw/qa/python/get_expression.py4
-rw-r--r--sw/qa/python/set_expression.py4
-rw-r--r--sw/qa/python/text_portion_enumeration_test.py5
-rw-r--r--sw/qa/python/var_fields.py3
-rw-r--r--test/source/bootstrapfixture.cxx6
-rw-r--r--unotest/source/python/org/libreoffice/unittest.py29
-rw-r--r--unotest/source/python/org/libreoffice/unotest.py7
30 files changed, 266 insertions, 21 deletions
diff --git a/pyuno/qa/pytests/testcollections_XCellRange.py b/pyuno/qa/pytests/testcollections_XCellRange.py
index 6754ef52814c..2e0ef8a7d3d7 100644
--- a/pyuno/qa/pytests/testcollections_XCellRange.py
+++ b/pyuno/qa/pytests/testcollections_XCellRange.py
@@ -43,6 +43,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(0, cell.CellAddress.Row)
self.assertEqual(0, cell.CellAddress.Column)
+ spr.close(True)
+
# Tests syntax:
# cell = cellrange[0,0] # Access cell by indices
# For:
@@ -63,6 +65,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual('A1', cell.CellName)
+ doc.close(True)
+
# Tests syntax:
# cell = cellrange[0,0] # Access cell by indices
# For:
@@ -81,6 +85,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(3, rng.CellAddress.Row)
self.assertEqual(7, rng.CellAddress.Column)
+ spr.close(True)
+
# Tests syntax:
# cell = cellrange[0,0] # Access cell by indices
# For:
@@ -101,6 +107,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual('H4', cell.CellName)
+ doc.close(True)
+
# Tests syntax:
# rng = cellrange[0,1:2] # Access cell range by index,slice
# For:
@@ -120,6 +128,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(0, rng.RangeAddress.EndRow)
self.assertEqual(2, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[0,1:2] # Access cell range by index,slice
# For:
@@ -142,6 +152,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual((('101', '102'),), rng.DataArray)
+ doc.close(True)
+
# Tests syntax:
# rng = cellrange[1:2,0] # Access cell range by slice,index
# For:
@@ -161,6 +173,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(2, rng.RangeAddress.EndRow)
self.assertEqual(0, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[1:2,0] # Access cell range by index,slice
# For:
@@ -183,6 +197,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual((('110',), ('120',)), rng.DataArray)
+ doc.close(True)
+
# Tests syntax:
# rng = cellrange[0:1,2:3] # Access cell range by slices
# For:
@@ -202,6 +218,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(2, rng.RangeAddress.EndRow)
self.assertEqual(4, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[0:1,2:3] # Access cell range by slices
# For:
@@ -218,6 +236,8 @@ class TestXCellRange(CollectionsTestBase):
with self.assertRaises(KeyError):
rng = sht[1:3, 3:3]
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[0:1,2:3] # Access cell range by slices
# For:
@@ -240,6 +260,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual((('113', '114'), ('123', '124')), rng.DataArray)
+ doc.close(True)
+
# Tests syntax:
# rng = cellrange['A1:B2'] # Access cell range by descriptor
# For:
@@ -259,6 +281,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(3, rng.RangeAddress.EndRow)
self.assertEqual(1, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange['A1:B2'] # Access cell range by descriptor
# For:
@@ -281,6 +305,8 @@ class TestXCellRange(CollectionsTestBase):
# Then
self.assertEqual((('120', '121'), ('130', '131')), rng.DataArray)
+ doc.close(True)
+
# Tests syntax:
# rng = cellrange['Name'] # Access cell range by name
# For:
@@ -303,6 +329,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(9, rng.RangeAddress.EndRow)
self.assertEqual(5, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[0] # Access cell range by row index
# For:
@@ -322,6 +350,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(0, rng.RangeAddress.EndRow)
self.assertEqual(1023, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[0,:] # Access cell range by row index
# For:
@@ -341,6 +371,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(0, rng.RangeAddress.EndRow)
self.assertEqual(1023, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
# Tests syntax:
# rng = cellrange[:,0] # Access cell range by column index
# For:
@@ -360,6 +392,8 @@ class TestXCellRange(CollectionsTestBase):
self.assertEqual(1048575, rng.RangeAddress.EndRow)
self.assertEqual(0, rng.RangeAddress.EndColumn)
+ spr.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XEnumeration.py b/pyuno/qa/pytests/testcollections_XEnumeration.py
index 6edc77f44952..8d1f8eece046 100644
--- a/pyuno/qa/pytests/testcollections_XEnumeration.py
+++ b/pyuno/qa/pytests/testcollections_XEnumeration.py
@@ -38,6 +38,8 @@ class TestXEnumeration(CollectionsTestBase):
# Then
self.assertEqual(1, len(paragraphs))
+ doc.close(True)
+
# Tests syntax:
# if val in itr: ... # Test value presence
# For:
@@ -54,6 +56,8 @@ class TestXEnumeration(CollectionsTestBase):
# Then
self.assertTrue(result)
+ doc.close(True)
+
# Tests syntax:
# if val in itr: ... # Test value presence
# For:
@@ -71,6 +75,9 @@ class TestXEnumeration(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc1.close(True)
+ doc2.close(True)
+
# Tests syntax:
# if val in itr: ... # Test value presence
# For:
@@ -86,6 +93,8 @@ class TestXEnumeration(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc.close(True)
+
# Tests syntax:
# if val in itr: ... # Test value presence
# For:
@@ -104,6 +113,8 @@ class TestXEnumeration(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XEnumerationAccess.py b/pyuno/qa/pytests/testcollections_XEnumerationAccess.py
index dc5a52f54537..a62b05ce9c5f 100644
--- a/pyuno/qa/pytests/testcollections_XEnumerationAccess.py
+++ b/pyuno/qa/pytests/testcollections_XEnumerationAccess.py
@@ -37,6 +37,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
# Then
self.assertEqual(1, len(paragraphs))
+ doc.close(True)
+
# Tests syntax:
# itr = iter(obj) # Named iterator
# For:
@@ -53,6 +55,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
with self.assertRaises(StopIteration):
next(itr)
+ doc.close(True)
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -68,6 +72,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
# Then
self.assertTrue(result)
+ doc.close(True)
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -84,6 +90,9 @@ class TestXEnumerationAccess(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc1.close(True)
+ doc2.close(True)
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -98,6 +107,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc.close(True)
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -112,6 +123,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
# Then
self.assertFalse(result)
+ doc.close(True)
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -124,6 +137,8 @@ class TestXEnumerationAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
result = {} in doc.Text
+ doc.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XIndexAccess.py b/pyuno/qa/pytests/testcollections_XIndexAccess.py
index 4631ca3706ed..7228ed87336b 100644
--- a/pyuno/qa/pytests/testcollections_XIndexAccess.py
+++ b/pyuno/qa/pytests/testcollections_XIndexAccess.py
@@ -77,6 +77,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertEqual(0, count)
+ doc.close(True);
+
# Tests syntax:
# num = len(obj) # Number of elements
# For:
@@ -94,6 +96,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertEqual(1, count)
+ doc.close(True);
+
# Tests syntax:
# val = obj[0] # Access by index
# For:
@@ -117,6 +121,7 @@ class TestXIndexAccess(CollectionsTestBase):
self.readValuesTestFixture(doc, 2, 1, 1)
self.readValuesTestFixture(doc, 2, 2, IndexError)
self.readValuesTestFixture(doc, 2, 3, IndexError)
+ doc.close(True);
def test_XIndexAccess_ReadIndex_Single_Invalid(self):
doc = self.createBlankTextDocument()
@@ -126,6 +131,7 @@ class TestXIndexAccess(CollectionsTestBase):
self.readValuesTestFixture(doc, 0, (0, 1), TypeError)
self.readValuesTestFixture(doc, 0, [0, 1], TypeError)
self.readValuesTestFixture(doc, 0, {'a': 'b'}, TypeError)
+ doc.close(True);
# Tests syntax:
# val1,val2 = obj[2:4] # Access by slice
@@ -139,6 +145,7 @@ class TestXIndexAccess(CollectionsTestBase):
key = slice(j, k)
expected = t[key]
self.readValuesTestFixture(doc, i, key, expected)
+ doc.close(True);
# Tests syntax:
# val1,val2 = obj[0:3:2] # Access by extended slice
@@ -153,6 +160,7 @@ class TestXIndexAccess(CollectionsTestBase):
key = slice(j, k, l)
expected = t[key]
self.readValuesTestFixture(doc, i, key, expected)
+ doc.close(True);
# Tests syntax:
# if val in obj: ... # Test value presence
@@ -173,6 +181,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertTrue(present)
+ doc.close(True);
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -187,6 +197,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertFalse(present)
+ doc.close(True);
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -201,6 +213,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertFalse(present)
+ doc.close(True);
+
# Tests syntax:
# if val in obj: ... # Test value presence
# For:
@@ -213,6 +227,8 @@ class TestXIndexAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
present = {} in doc.Footnotes
+ doc.close(True);
+
# Tests syntax:
# for val in obj: ... # Implicit iterator (values)
# For:
@@ -229,6 +245,8 @@ class TestXIndexAccess(CollectionsTestBase):
# Then
self.assertEqual(0, len(read_footnotes))
+ doc.close(True);
+
# Tests syntax:
# for val in obj: ... # Implicit iterator (values)
# For:
@@ -250,6 +268,8 @@ class TestXIndexAccess(CollectionsTestBase):
self.assertEqual(1, len(read_footnotes))
self.assertEqual('foo', read_footnotes[0].Label)
+ doc.close(True);
+
# Tests syntax:
# for val in obj: ... # Implicit iterator (values)
# For:
@@ -275,6 +295,8 @@ class TestXIndexAccess(CollectionsTestBase):
self.assertEqual('foo', read_footnotes[0].Label)
self.assertEqual('bar', read_footnotes[1].Label)
+ doc.close(True);
+
# Tests syntax:
# itr = iter(obj) # Named iterator (values)
# For:
@@ -295,6 +317,8 @@ class TestXIndexAccess(CollectionsTestBase):
with self.assertRaises(StopIteration):
next(itr)
+ doc.close(True);
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XIndexContainer.py b/pyuno/qa/pytests/testcollections_XIndexContainer.py
index 7bf9e5223039..73be6b57c25d 100644
--- a/pyuno/qa/pytests/testcollections_XIndexContainer.py
+++ b/pyuno/qa/pytests/testcollections_XIndexContainer.py
@@ -145,6 +145,8 @@ class TestXIndexContainer(CollectionsTestBase):
# Then
self.assertEqual('foo', doc.DrawPage.Forms[0].Name)
+ doc.close(True)
+
# Tests syntax:
# obj[0:3:2] = val1,val2 # Replace by extended slice
# For:
diff --git a/pyuno/qa/pytests/testcollections_XIndexReplace.py b/pyuno/qa/pytests/testcollections_XIndexReplace.py
index 45d1cc075f33..bbf424f0bdfb 100644
--- a/pyuno/qa/pytests/testcollections_XIndexReplace.py
+++ b/pyuno/qa/pytests/testcollections_XIndexReplace.py
@@ -78,6 +78,8 @@ class TestXIndexReplace(CollectionsTestBase):
# Then
self.assertEqual(('Caption',), index.LevelParagraphStyles[0])
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -91,6 +93,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = None
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -104,6 +108,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = 'foo'
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -117,6 +123,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = 12.34
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -130,6 +138,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = [0, 1]
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -143,6 +153,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = {'a': 'b'}
+ doc.close(True)
+
# Tests syntax:
# obj[0] = val # Replace by index
# For:
@@ -156,6 +168,8 @@ class TestXIndexReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
index.LevelParagraphStyles[0] = ('Caption', ())
+ doc.close(True)
+
# Tests syntax:
# obj[2:4] = val1,val2 # Replace by slice
# For:
@@ -177,6 +191,7 @@ class TestXIndexReplace(CollectionsTestBase):
if (len(expected) != 10):
expected = ValueError()
self.assignValuesTestFixture(doc, key, assign, expected)
+ doc.close(True)
# Tests syntax:
# obj[2:4] = val1,val2 # Replace by slice
@@ -194,6 +209,8 @@ class TestXIndexReplace(CollectionsTestBase):
12.34
)
+ doc.close(True)
+
# Tests syntax:
# obj[0:3:2] = val1,val2 # Replace by extended slice
# For:
@@ -214,6 +231,7 @@ class TestXIndexReplace(CollectionsTestBase):
except Exception as e:
expected = e
self.assignValuesTestFixture(doc, key, assign, expected)
+ doc.close(True)
if __name__ == '__main__':
diff --git a/pyuno/qa/pytests/testcollections_XNameAccess.py b/pyuno/qa/pytests/testcollections_XNameAccess.py
index a93064e78bb1..7f987a370077 100644
--- a/pyuno/qa/pytests/testcollections_XNameAccess.py
+++ b/pyuno/qa/pytests/testcollections_XNameAccess.py
@@ -35,6 +35,8 @@ class TestXNameAccess(CollectionsTestBase):
# Then
self.assertEqual(2, length)
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -50,6 +52,8 @@ class TestXNameAccess(CollectionsTestBase):
# Then
self.assertEqual('foo', link.getName())
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -62,6 +66,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(KeyError):
link = drw.Links['foo']
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -74,6 +80,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
link = drw.Links[None]
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -86,6 +94,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
link = drw.Links[12.34]
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -98,6 +108,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
link = drw.Links[(1, 2)]
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -110,6 +122,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
link = drw.Links[[1, 2]]
+ drw.close(True)
+
# Tests syntax:
# val = obj[key] # Access by key
# For:
@@ -122,6 +136,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(TypeError):
link = drw.Links[{'a': 'b'}]
+ drw.close(True)
+
# Tests syntax:
# if key in obj: ... # Test key presence
# For:
@@ -137,6 +153,8 @@ class TestXNameAccess(CollectionsTestBase):
# Then
self.assertTrue(present)
+ drw.close(True)
+
# Tests syntax:
# for key in obj: ... # Implicit iterator (keys)
# For:
@@ -157,6 +175,8 @@ class TestXNameAccess(CollectionsTestBase):
# Then
self.assertEqual(['foo0', 'foo1'], read_links)
+ drw.close(True)
+
# Tests syntax:
# itr = iter(obj) # Named iterator (keys)
# For:
@@ -174,6 +194,8 @@ class TestXNameAccess(CollectionsTestBase):
with self.assertRaises(StopIteration):
next(itr)
+ drw.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XNameContainer.py b/pyuno/qa/pytests/testcollections_XNameContainer.py
index 48b63a786f7e..5c8b676c0f6e 100644
--- a/pyuno/qa/pytests/testcollections_XNameContainer.py
+++ b/pyuno/qa/pytests/testcollections_XNameContainer.py
@@ -41,6 +41,8 @@ class TestXNameContainer(CollectionsTestBase):
# Then
self.assertEqual(1, len(ranges.ElementNames))
+ spr.close(True)
+
# Tests syntax:
# obj[key] = val # Insert by key
# For:
@@ -55,6 +57,8 @@ class TestXNameContainer(CollectionsTestBase):
with self.assertRaises(TypeError):
ranges[12.34] = new_range
+ spr.close(True)
+
# Tests syntax:
# obj[key] = val # Replace by key
def test_XNameContainer_ReplaceName(self):
@@ -73,6 +77,8 @@ class TestXNameContainer(CollectionsTestBase):
read_range = ranges['foo']
self.assertEqual(6, read_range.CellAddress.Column)
+ spr.close(True)
+
# Tests syntax:
# del obj[key] # Delete by key
# For:
@@ -89,6 +95,8 @@ class TestXNameContainer(CollectionsTestBase):
self.assertEqual(1, len(spr.Sheets))
self.assertFalse('foo' in spr.Sheets)
+ spr.close(True)
+
# Tests syntax:
# del obj[key] # Delete by key
# For:
@@ -101,6 +109,8 @@ class TestXNameContainer(CollectionsTestBase):
with self.assertRaises(KeyError):
del spr.Sheets['foo']
+ spr.close(True)
+
# Tests syntax:
# del obj[key] # Delete by key
# For:
@@ -113,6 +123,8 @@ class TestXNameContainer(CollectionsTestBase):
with self.assertRaises(TypeError):
del spr.Sheets[12.34]
+ spr.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_XNameReplace.py b/pyuno/qa/pytests/testcollections_XNameReplace.py
index 2176f935d9dc..18476fd2b447 100644
--- a/pyuno/qa/pytests/testcollections_XNameReplace.py
+++ b/pyuno/qa/pytests/testcollections_XNameReplace.py
@@ -40,6 +40,8 @@ class TestXNameReplace(CollectionsTestBase):
on_save = [p.Value for p in doc.Events['OnSave'] if p.Name == 'Script'][0]
self.assertEqual(getScriptName(), on_save)
+ doc.close(True)
+
# Tests syntax:
# obj[key] = val # Replace by key
# For:
@@ -53,6 +55,8 @@ class TestXNameReplace(CollectionsTestBase):
with self.assertRaises(KeyError):
doc.Events['qqqqq'] = event_properties
+ doc.close(True)
+
# Tests syntax:
# obj[key] = val # Replace by key
# For:
@@ -66,6 +70,8 @@ class TestXNameReplace(CollectionsTestBase):
with self.assertRaises(TypeError):
doc.Events[12.34] = event_properties
+ doc.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_misc.py b/pyuno/qa/pytests/testcollections_misc.py
index 04dcf595931a..1dba098eeccd 100644
--- a/pyuno/qa/pytests/testcollections_misc.py
+++ b/pyuno/qa/pytests/testcollections_misc.py
@@ -32,6 +32,8 @@ class TestMisc(CollectionsTestBase):
for val in doc.UIConfigurationManager:
pass
+ doc.close(True)
+
# Tests syntax:
# if val in itr: ... # Test value presence
# For:
@@ -44,6 +46,8 @@ class TestMisc(CollectionsTestBase):
with self.assertRaises(TypeError):
foo = "bar" in doc.UIConfigurationManager
+ doc.close(True)
+
# Tests syntax:
# num = len(obj) # Number of elements
# For:
@@ -56,6 +60,8 @@ class TestMisc(CollectionsTestBase):
with self.assertRaises(TypeError):
len(doc.UIConfigurationManager)
+ doc.close(True)
+
# Tests syntax:
# val = obj[0] # Access by index
# For:
@@ -68,6 +74,8 @@ class TestMisc(CollectionsTestBase):
with self.assertRaises(TypeError):
doc.UIConfigurationManager[0]
+ doc.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/qa/pytests/testcollections_mixednameindex.py b/pyuno/qa/pytests/testcollections_mixednameindex.py
index 67e97a88dd90..b4c7958c6996 100644
--- a/pyuno/qa/pytests/testcollections_mixednameindex.py
+++ b/pyuno/qa/pytests/testcollections_mixednameindex.py
@@ -41,6 +41,8 @@ class TestMixedNameIndex(CollectionsTestBase):
self.assertEqual('foo', table_by_index.Name)
self.assertEqual(table_by_name, table_by_index)
+ doc.close(True)
+
if __name__ == '__main__':
unittest.main()
diff --git a/pyuno/source/module/pyuno_module.cxx b/pyuno/source/module/pyuno_module.cxx
index e212e8de9f8d..0f08ebc53367 100644
--- a/pyuno/source/module/pyuno_module.cxx
+++ b/pyuno/source/module/pyuno_module.cxx
@@ -22,6 +22,7 @@
#include "pyuno_impl.hxx"
+#include <cassert>
#include <unordered_map>
#include <utility>
@@ -318,12 +319,22 @@ static PyObject* getComponentContext(
return ret.getAcquired();
}
+// While pyuno.private_initTestEnvironment is called from individual Python tests (e.g., from
+// UnoInProcess in unotest/source/python/org/libreoffice/unotest.py, which makes sure to call it
+// only once), pyuno.private_deinitTestEnvironment is called centrally from
+// unotest/source/python/org/libreoffice/unittest.py at the end of every PythonTest (to DeInitVCL
+// exactly once near the end of the process, if InitVCL has ever been called via
+// pyuno.private_initTestEnvironment):
+
+static osl::Module * testModule = nullptr;
+
static PyObject* initTestEnvironment(
SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*)
{
// this tries to bootstrap enough of the soffice from python to run
// unit tests, which is only possible indirectly because pyuno is URE
// so load "test" library and invoke a function there to do the work
+ assert(testModule == nullptr);
try
{
PyObject *const ctx(getComponentContext(nullptr, nullptr));
@@ -353,6 +364,7 @@ static PyObject* initTestEnvironment(
mod.getFunctionSymbol("test_init"));
if (!pFunc) { abort(); }
reinterpret_cast<void (SAL_CALL *)(XMultiServiceFactory*)>(pFunc)(xMSF.get());
+ testModule = &mod;
}
catch (const css::uno::Exception &)
{
@@ -361,6 +373,26 @@ static PyObject* initTestEnvironment(
return Py_None;
}
+static PyObject* deinitTestEnvironment(
+ SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*)
+{
+ if (testModule != nullptr)
+ {
+ try
+ {
+ oslGenericFunction const pFunc(
+ testModule->getFunctionSymbol("test_deinit"));
+ if (!pFunc) { abort(); }
+ reinterpret_cast<void (SAL_CALL *)()>(pFunc)();
+ }
+ catch (const css::uno::Exception &)
+ {
+ abort();
+ }
+ }
+ return Py_None;
+}
+
PyObject * extractOneStringArg( PyObject *args, char const *funcName )
{
if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 )
@@ -843,6 +875,7 @@ static PyObject *sal_debug(
struct PyMethodDef PyUNOModule_methods [] =
{
{"private_initTestEnvironment", initTestEnvironment, METH_VARARGS, nullptr},
+ {"private_deinitTestEnvironment", deinitTestEnvironment, METH_VARARGS, nullptr},
{"getComponentContext", getComponentContext, METH_VARARGS, nullptr},
{"_createUnoStructHelper", reinterpret_cast<PyCFunction>(createUnoStructHelper), METH_VARARGS | METH_KEYWORDS, nullptr},
{"getTypeByName", getTypeByName, METH_VARARGS, nullptr},
diff --git a/sfx2/qa/python/check_sidebar.py b/sfx2/qa/python/check_sidebar.py
index e8fd50338b27..59cc955b8016 100644
--- a/sfx2/qa/python/check_sidebar.py
+++ b/sfx2/qa/python/check_sidebar.py
@@ -24,7 +24,6 @@ class CheckSidebar(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xDoc = cls._uno.openEmptyDoc( url = "private:factory/scalc", bHidden = False, bReadOnly = False)
@classmethod
def tearDownClass(cls):
@@ -32,7 +31,7 @@ class CheckSidebar(unittest.TestCase):
def test_check_sidebar(self):
- xDoc = self.__class__._xDoc
+ xDoc = self.__class__._uno.openEmptyDoc( url = "private:factory/scalc", bHidden = False, bReadOnly = False)
xController = xDoc.getCurrentController()
xSidebar = xController.getSidebar()
diff --git a/sfx2/qa/python/check_sidebar_registry.py b/sfx2/qa/python/check_sidebar_registry.py
index 86f22f4fd44a..47b8eecdefb9 100644
--- a/sfx2/qa/python/check_sidebar_registry.py
+++ b/sfx2/qa/python/check_sidebar_registry.py
@@ -19,7 +19,6 @@ class CheckSidebarRegistry(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xDoc = cls._uno.openEmptyDoc( url = "private:factory/scalc", bHidden = False, bReadOnly = False)
@classmethod
def tearDownClass(cls):
diff --git a/solenv/gbuild/PythonTest.mk b/solenv/gbuild/PythonTest.mk
index a2bac3819e02..329009c4b1d2 100644
--- a/solenv/gbuild/PythonTest.mk
+++ b/solenv/gbuild/PythonTest.mk
@@ -21,7 +21,7 @@ gb_PythonTest_EXECUTABLE_GDB := $(PYTHON_FOR_BUILD)
gb_PythonTest_DEPS :=
endif
-gb_PythonTest_COMMAND := $(gb_PythonTest_EXECUTABLE) -m unittest
+gb_PythonTest_COMMAND := $(gb_PythonTest_EXECUTABLE) -m org.libreoffice.unittest
.PHONY : $(call gb_PythonTest_get_clean_target,%)
$(call gb_PythonTest_get_clean_target,%) :
@@ -31,7 +31,10 @@ $(call gb_PythonTest_get_clean_target,%) :
ifneq ($(DISABLE_PYTHON),TRUE)
.PHONY : $(call gb_PythonTest_get_target,%)
-$(call gb_PythonTest_get_target,%) :| $(gb_PythonTest_DEPS)
+$(call gb_PythonTest_get_target,%) :\
+ $(call gb_Library_get_target,pyuno) \
+ $(if $(filter-out WNT,$(OS)),$(call gb_Library_get_target,pyuno_wrapper)) \
+ | $(gb_PythonTest_DEPS)
ifneq ($(gb_SUPPRESS_TESTS),)
@true
else
diff --git a/sw/qa/python/check_bookmarks.py b/sw/qa/python/check_bookmarks.py
index 73fd9bb6b98e..8210b4eb005a 100644
--- a/sw/qa/python/check_bookmarks.py
+++ b/sw/qa/python/check_bookmarks.py
@@ -47,6 +47,10 @@ class CheckBookmarks(unittest.TestCase):
@classmethod
def tearDownClass(cls):
cls._uno.tearDown()
+ # HACK in case cls._xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
+ # garbage-collected after VCL has already been deinitialized:
+ cls._xDoc = None
def test_bookmarks(self):
self.xDoc = self.__class__._xDoc
diff --git a/sw/qa/python/check_change_color.py b/sw/qa/python/check_change_color.py
index d8562bc77b52..07b622031a61 100644
--- a/sw/qa/python/check_change_color.py
+++ b/sw/qa/python/check_change_color.py
@@ -27,7 +27,6 @@ class CheckChangeColor(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xEmptyDoc = cls._uno.openEmptyWriterDoc()
cls.RED = 0xFF0000
cls.BLUE = 0x0000FF
cls.GREEN = 0x008000
diff --git a/sw/qa/python/check_cross_references.py b/sw/qa/python/check_cross_references.py
index 742cc0d94ad5..6aad1c47ca3a 100644
--- a/sw/qa/python/check_cross_references.py
+++ b/sw/qa/python/check_cross_references.py
@@ -46,6 +46,10 @@ class CheckCrossReferences(unittest.TestCase):
@classmethod
def tearDownClass(cls):
cls._uno.tearDown()
+ # HACK in case cls.document holds a UNO proxy to an SwXTextDocument (whose dtor calls
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
+ # garbage-collected after VCL has already been deinitialized:
+ cls.document = None
def getNextField(self):
while True:
diff --git a/sw/qa/python/check_fields.py b/sw/qa/python/check_fields.py
index 60418a93001d..eb6dd2dc1c8a 100644
--- a/sw/qa/python/check_fields.py
+++ b/sw/qa/python/check_fields.py
@@ -17,8 +17,6 @@ class CheckFields(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xDoc = cls._uno.openTemplateFromTDOC("fdo39694.ott")
- cls._xEmptyDoc = cls._uno.openEmptyWriterDoc()
@classmethod
def tearDownClass(cls):
@@ -26,7 +24,7 @@ class CheckFields(unittest.TestCase):
def test_fdo39694_load(self):
placeholders = ["<Kadr1>", "<Kadr2>", "<Kadr3>", "<Kadr4>", "<Pnname>", "<Pvname>", "<Pgeboren>"]
- xDoc = self.__class__._xDoc
+ xDoc = self.__class__._uno.openTemplateFromTDOC("fdo39694.ott")
xEnumerationAccess = xDoc.getTextFields()
xFieldEnum = xEnumerationAccess.createEnumeration()
for xField in xFieldEnum:
@@ -35,9 +33,10 @@ class CheckFields(unittest.TestCase):
read_content = xAnchor.getString()
self.assertTrue(read_content in placeholders,
"field %s is not contained: " % read_content)
+ xDoc.close(True)
def test_fdo42073(self):
- xDoc = self.__class__._xEmptyDoc
+ xDoc = self.__class__._uno.openEmptyWriterDoc()
xBodyText = xDoc.getText()
xCursor = xBodyText.createTextCursor()
xTextField = xDoc.createInstance("com.sun.star.text.TextField.Input")
@@ -48,6 +47,7 @@ class CheckFields(unittest.TestCase):
xTextField.setPropertyValue("Content", content)
read_content = xTextField.getPropertyValue("Content")
self.assertEqual(content, read_content)
+ xDoc.close(True)
if __name__ == '__main__':
unittest.main()
diff --git a/sw/qa/python/check_flies.py b/sw/qa/python/check_flies.py
index 6353ccda150d..0e60b2195e89 100644
--- a/sw/qa/python/check_flies.py
+++ b/sw/qa/python/check_flies.py
@@ -26,18 +26,18 @@ class CheckFlies(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls.document = cls._uno.openDocFromTDOC("CheckFlies.odt")
@classmethod
def tearDownClass(cls):
cls._uno.tearDown()
def test_checkFlies(self):
- xTFS = self.__class__.document
+ document = self.__class__._uno.openDocFromTDOC("CheckFlies.odt")
+ xTFS = document
self.checkTextFrames(xTFS)
- xTGOS = self.__class__.document
+ xTGOS = document
self.checkGraphicFrames(xTGOS)
- xTEOS = self.__class__.document
+ xTEOS = document
self.checkEmbeddedFrames(xTEOS)
def checkEmbeddedFrames(self, xTGOS):
diff --git a/sw/qa/python/check_indexed_property_values.py b/sw/qa/python/check_indexed_property_values.py
index 5609aa4225cb..ceaf82a6cac6 100644
--- a/sw/qa/python/check_indexed_property_values.py
+++ b/sw/qa/python/check_indexed_property_values.py
@@ -34,7 +34,6 @@ class CheckIndexedPropertyValues(unittest.TestCase):
cls._uno = UnoInProcess()
cls._uno.setUp()
cls.xContext = cls._uno.getContext()
- cls.xDoc = cls._uno.openEmptyWriterDoc()
@classmethod
def tearDownClass(cls):
diff --git a/sw/qa/python/check_named_property_values.py b/sw/qa/python/check_named_property_values.py
index dd06adc60313..1a81d13a6323 100644
--- a/sw/qa/python/check_named_property_values.py
+++ b/sw/qa/python/check_named_property_values.py
@@ -36,7 +36,6 @@ class CheckNamedPropertyValues(unittest.TestCase):
cls._uno = UnoInProcess()
cls._uno.setUp()
cls.xContext = cls._uno.getContext()
- cls.xDoc = cls._uno.openEmptyWriterDoc()
@classmethod
def tearDownClass(cls):
diff --git a/sw/qa/python/check_table.py b/sw/qa/python/check_table.py
index 35da08fe8195..8fd888f187bd 100644
--- a/sw/qa/python/check_table.py
+++ b/sw/qa/python/check_table.py
@@ -583,6 +583,8 @@ class CheckTable(unittest.TestCase):
xCellRangeString = xChartDataProvider.convertRangeFromXML("Table1.$A$1:.$C$3")
self.assertEqual("Table1.A1:C3", xCellRangeString)
+ xDoc.dispose()
+
def test_splitRangeHorizontal(self):
xDoc = CheckTable._uno.openEmptyWriterDoc()
xTable = xDoc.createInstance("com.sun.star.text.TextTable")
@@ -600,6 +602,7 @@ class CheckTable(unittest.TestCase):
self.assertTrue(math.isnan(xTable.Data[1][1]))
self.assertTrue(math.isnan(xTable.Data[2][0]))
self.assertTrue(math.isnan(xTable.Data[2][1]))
+ xDoc.dispose()
def test_mergeRangeHorizontal(self):
xDoc = CheckTable._uno.openEmptyWriterDoc()
@@ -618,6 +621,7 @@ class CheckTable(unittest.TestCase):
self.assertEqual(xTable.Data[1][1], float(5))
self.assertEqual(xTable.Data[1][2], float(6))
self.assertEqual(xTable.Data[2], (float(7), float(8), float(9)))
+ xDoc.dispose()
if __name__ == '__main__':
unittest.main()
diff --git a/sw/qa/python/get_expression.py b/sw/qa/python/get_expression.py
index 7462db68a730..98e9402bb602 100644
--- a/sw/qa/python/get_expression.py
+++ b/sw/qa/python/get_expression.py
@@ -22,6 +22,10 @@ class TestGetExpression(unittest.TestCase):
@classmethod
def tearDownClass(cls):
cls._uno.tearDown()
+ # HACK in case cls._xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
+ # garbage-collected after VCL has already been deinitialized:
+ cls._xDoc = None
def test_get_expression(self):
self.__class__._uno.checkProperties(
diff --git a/sw/qa/python/set_expression.py b/sw/qa/python/set_expression.py
index 220952536fea..c5dc5e6ae2e9 100644
--- a/sw/qa/python/set_expression.py
+++ b/sw/qa/python/set_expression.py
@@ -18,15 +18,15 @@ class TestSetExpression(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xDoc = cls._uno.openEmptyWriterDoc()
@classmethod
def tearDownClass(cls):
cls._uno.tearDown()
def test_set_expression(self):
+ xDoc = self.__class__._uno.openEmptyWriterDoc()
self.__class__._uno.checkProperties(
- self.__class__._xDoc.createInstance("com.sun.star.text.textfield.SetExpression"),
+ xDoc.createInstance("com.sun.star.text.textfield.SetExpression"),
{"NumberingType": 0,
"Content": "foo",
"CurrentPresentation": "bar",
diff --git a/sw/qa/python/text_portion_enumeration_test.py b/sw/qa/python/text_portion_enumeration_test.py
index 3a7e9d8586be..c379138db303 100644
--- a/sw/qa/python/text_portion_enumeration_test.py
+++ b/sw/qa/python/text_portion_enumeration_test.py
@@ -934,6 +934,11 @@ class TextPortionEnumerationTest(unittest.TestCase):
@classmethod
def tearDownClass(cls):
cls.xDoc.close(True)
+ cls._uno.tearDown()
+ # HACK in case cls.xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
+ # garbage-collected after VCL has already been deinitialized:
+ cls.xDoc = None
def test_text(self):
root = TreeNode()
diff --git a/sw/qa/python/var_fields.py b/sw/qa/python/var_fields.py
index c2af7a40f408..52fe3ddd6938 100644
--- a/sw/qa/python/var_fields.py
+++ b/sw/qa/python/var_fields.py
@@ -19,7 +19,6 @@ class TestVarFields(unittest.TestCase):
def setUpClass(cls):
cls._uno = UnoInProcess()
cls._uno.setUp()
- cls._xDoc = cls._uno.openEmptyWriterDoc()
@classmethod
def tearDownClass(cls):
@@ -32,7 +31,7 @@ class TestVarFields(unittest.TestCase):
sw/qa/complex/writer/VarFields.java
"""
- xDoc = self.__class__._xDoc
+ xDoc = self.__class__._uno.openEmptyWriterDoc()
xBodyText = xDoc.getText()
xCursor = xBodyText.createTextCursor()
# 0. create text field
diff --git a/test/source/bootstrapfixture.cxx b/test/source/bootstrapfixture.cxx
index a26b79a00c10..a8da11caed92 100644
--- a/test/source/bootstrapfixture.cxx
+++ b/test/source/bootstrapfixture.cxx
@@ -99,6 +99,12 @@ SAL_DLLPUBLIC_EXPORT void test_init(lang::XMultiServiceFactory *pFactory)
catch (...) { abort(); }
}
+// this is called from pyuno
+SAL_DLLPUBLIC_EXPORT void test_deinit()
+{
+ DeInitVCL();
+}
+
} // extern "C"
void test::BootstrapFixture::setUp()
diff --git a/unotest/source/python/org/libreoffice/unittest.py b/unotest/source/python/org/libreoffice/unittest.py
new file mode 100644
index 000000000000..364462ed3827
--- /dev/null
+++ b/unotest/source/python/org/libreoffice/unittest.py
@@ -0,0 +1,29 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+import gc
+import pyuno
+import unittest
+
+class LoTestResult(unittest.TextTestResult):
+ def stopTestRun(self):
+ # HACK calling gc.collect() to get rid of as many still existing UNO proxies to
+ # SwXTextDocument and friends as possible; those C++ classes' dtors call
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter, so the dtors must be called before
+ # DeInitVCL in the call to pyuno.private_deinitTestEnvironment(); any remainging proxies
+ # that are still referenced (UnoInProcess' self.xDoc in
+ # unotest/source/python/org/libreoffice/unotest.py, or per-class variables in the various
+ # PythonTests) need to be individually released (each marked as "HACK" in the code):
+ gc.collect()
+ pyuno.private_deinitTestEnvironment()
+
+if __name__ == '__main__':
+ unittest.main(module=None, testRunner=unittest.TextTestRunner(resultclass=LoTestResult))
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/unotest/source/python/org/libreoffice/unotest.py b/unotest/source/python/org/libreoffice/unotest.py
index 000a148b353f..804ddafc5518 100644
--- a/unotest/source/python/org/libreoffice/unotest.py
+++ b/unotest/source/python/org/libreoffice/unotest.py
@@ -246,7 +246,12 @@ class UnoInProcess:
def postTest(self):
assert(self.xContext)
def tearDown(self):
- self.xDoc.close(True)
+ if hasattr(self, 'xDoc'):
+ self.xDoc.close(True)
+ # HACK in case self.xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls
+ # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
+ # garbage-collected after VCL has already been deinitialized:
+ self.xDoc = None
def simpleInvoke(connection, test):
try: