Mercurial > hg > orthanc-tests
annotate Tests/Toolbox.py @ 735:be8f174d3c9d find-refactoring tip
tools/find: Limit and Since are now forbidden when filtering on DICOM tags that are not stored in DB
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Thu, 24 Oct 2024 15:08:59 +0200 |
parents | 0322fda1834e |
children | d2ac1c6588db |
rev | line source |
---|---|
0 | 1 #!/usr/bin/python |
2 | |
1 | 3 # Orthanc - A Lightweight, RESTful DICOM Store |
73 | 4 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics |
1 | 5 # Department, University Hospital of Liege, Belgium |
649
5d7b6e43ab7d
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
640
diff
changeset
|
6 # Copyright (C) 2017-2023 Osimis S.A., Belgium |
5d7b6e43ab7d
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
640
diff
changeset
|
7 # Copyright (C) 2024-2024 Orthanc Team SRL, Belgium |
640
9f8276ac1cdd
update year to 2024
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
543
diff
changeset
|
8 # Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium |
1 | 9 # |
10 # This program is free software: you can redistribute it and/or | |
11 # modify it under the terms of the GNU General Public License as | |
12 # published by the Free Software Foundation, either version 3 of the | |
13 # License, or (at your option) any later version. | |
14 # | |
15 # This program is distributed in the hope that it will be useful, but | |
16 # WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 # General Public License for more details. | |
19 # | |
20 # You should have received a copy of the GNU General Public License | |
21 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
0 | 22 |
23 | |
24 import hashlib | |
25 import httplib2 | |
26 import json | |
4 | 27 import os |
1 | 28 import re |
4 | 29 import signal |
1 | 30 import subprocess |
291
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
31 import tempfile |
4 | 32 import threading |
83 | 33 import sys |
0 | 34 import time |
1 | 35 import zipfile |
0 | 36 |
337
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
37 from xml.dom import minidom |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
38 |
304 | 39 from PIL import Image, ImageChops |
40 import math | |
41 import operator | |
42 | |
83 | 43 |
44 if (sys.version_info >= (3, 0)): | |
45 from urllib.parse import urlencode | |
46 from io import StringIO | |
47 from io import BytesIO | |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
48 from urllib.parse import unquote |
83 | 49 |
50 else: | |
51 from urllib import urlencode | |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
52 from urlparse import unquote |
83 | 53 |
54 # http://stackoverflow.com/a/1313868/881731 | |
55 try: | |
56 from cStringIO import StringIO | |
57 except: | |
58 from StringIO import StringIO | |
3 | 59 |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
60 |
398
9528e2a03d3c
adapt DICOMweb tests following fix of issue 196 (STOW-RS: Should return 200 only when successfully stored all instances)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
391
diff
changeset
|
61 def DecodeJson(s): |
83 | 62 t = s |
63 | |
64 if (sys.version_info >= (3, 0)): | |
65 try: | |
66 t = s.decode() | |
67 except: | |
68 pass | |
69 | |
70 try: | |
71 return json.loads(t) | |
72 except: | |
73 return t | |
0 | 74 |
75 | |
13 | 76 def DefineOrthanc(server = 'localhost', |
77 restPort = 8042, | |
1 | 78 username = None, |
79 password = None, | |
80 aet = 'ORTHANC', | |
81 dicomPort = 4242): | |
13 | 82 #m = re.match(r'(http|https)://([^:]+):([^@]+)@([^@]+)', url) |
83 #if m != None: | |
84 # url = m.groups()[0] + '://' + m.groups()[3] | |
85 # username = m.groups()[1] | |
86 # password = m.groups()[2] | |
0 | 87 |
13 | 88 #if not url.endswith('/'): |
89 # url += '/' | |
0 | 90 |
1 | 91 return { |
13 | 92 'Server' : server, |
93 'Url' : 'http://%s:%d/' % (server, restPort), | |
1 | 94 'Username' : username, |
95 'Password' : password, | |
96 'DicomAet' : aet, | |
97 'DicomPort' : dicomPort | |
98 } | |
0 | 99 |
100 | |
101 def _SetupCredentials(orthanc, http): | |
1 | 102 if (orthanc['Username'] != None and |
103 orthanc['Password'] != None): | |
104 http.add_credentials(orthanc['Username'], orthanc['Password']) | |
0 | 105 |
28 | 106 def DoGetRaw(orthanc, uri, data = {}, body = None, headers = {}): |
0 | 107 d = '' |
108 if len(data.keys()) > 0: | |
109 d = '?' + urlencode(data) | |
110 | |
111 http = httplib2.Http() | |
21
2a29bcff60a7
tests of image decoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
112 http.follow_redirects = False |
0 | 113 _SetupCredentials(orthanc, http) |
114 | |
1 | 115 resp, content = http.request(orthanc['Url'] + uri + d, 'GET', body = body, |
0 | 116 headers = headers) |
28 | 117 return (resp, content) |
118 | |
119 | |
120 def DoGet(orthanc, uri, data = {}, body = None, headers = {}): | |
121 (resp, content) = DoGetRaw(orthanc, uri, data = data, body = body, headers = headers) | |
122 | |
0 | 123 if not (resp.status in [ 200 ]): |
239
8980bd19e31d
dicomweb: test_allowed_methods
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
222
diff
changeset
|
124 raise Exception(resp.status, resp) |
0 | 125 else: |
398
9528e2a03d3c
adapt DICOMweb tests following fix of issue 196 (STOW-RS: Should return 200 only when successfully stored all instances)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
391
diff
changeset
|
126 return DecodeJson(content) |
0 | 127 |
128 def _DoPutOrPost(orthanc, uri, method, data, contentType, headers): | |
129 http = httplib2.Http() | |
21
2a29bcff60a7
tests of image decoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
13
diff
changeset
|
130 http.follow_redirects = False |
0 | 131 _SetupCredentials(orthanc, http) |
132 | |
83 | 133 if isinstance(data, (str, bytearray, bytes)): |
0 | 134 body = data |
135 if len(contentType) != 0: | |
136 headers['content-type'] = contentType | |
137 else: | |
138 body = json.dumps(data) | |
139 headers['content-type'] = 'application/json' | |
140 | |
141 headers['expect'] = '' | |
142 | |
1 | 143 resp, content = http.request(orthanc['Url'] + uri, method, |
0 | 144 body = body, |
145 headers = headers) | |
391
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
146 return (resp, content) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
147 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
148 def DoDeleteRaw(orthanc, uri, headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
149 http = httplib2.Http() |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
150 http.follow_redirects = False |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
151 _SetupCredentials(orthanc, http) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
152 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
153 resp, content = http.request(orthanc['Url'] + uri, 'DELETE', headers = headers) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
154 return (resp, content) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
155 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
156 def DoDelete(orthanc, uri, headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
157 (resp, content) = DoDeleteRaw(orthanc, uri, headers) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
158 if not (resp.status in [ 200 ]): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
159 raise Exception(resp.status, resp) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
160 else: |
398
9528e2a03d3c
adapt DICOMweb tests following fix of issue 196 (STOW-RS: Should return 200 only when successfully stored all instances)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
391
diff
changeset
|
161 return DecodeJson(content) |
391
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
162 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
163 def DoPutRaw(orthanc, uri, data = {}, contentType = '', headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
164 return _DoPutOrPost(orthanc, uri, 'PUT', data, contentType, headers) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
165 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
166 def DoPut(orthanc, uri, data = {}, contentType = '', headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
167 (resp, content) = DoPutRaw(orthanc, uri, data, contentType, headers) |
342
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
168 if not (resp.status in [ 200, 201, 302 ]): |
239
8980bd19e31d
dicomweb: test_allowed_methods
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
222
diff
changeset
|
169 raise Exception(resp.status, resp) |
0 | 170 else: |
398
9528e2a03d3c
adapt DICOMweb tests following fix of issue 196 (STOW-RS: Should return 200 only when successfully stored all instances)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
391
diff
changeset
|
171 return DecodeJson(content) |
0 | 172 |
391
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
173 def DoPostRaw(orthanc, uri, data = {}, contentType = '', headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
174 return _DoPutOrPost(orthanc, uri, 'POST', data, contentType, headers) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
175 |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
176 def DoPost(orthanc, uri, data = {}, contentType = '', headers = {}): |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
177 (resp, content) = DoPostRaw(orthanc, uri, data, contentType, headers) |
227d9a932467
testing revisions in metadata
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
363
diff
changeset
|
178 if not (resp.status in [ 200, 201, 302 ]): |
239
8980bd19e31d
dicomweb: test_allowed_methods
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
222
diff
changeset
|
179 raise Exception(resp.status, resp) |
0 | 180 else: |
398
9528e2a03d3c
adapt DICOMweb tests following fix of issue 196 (STOW-RS: Should return 200 only when successfully stored all instances)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
391
diff
changeset
|
181 return DecodeJson(content) |
0 | 182 |
13 | 183 def GetDatabasePath(filename): |
184 return os.path.join(os.path.dirname(__file__), '..', 'Database', filename) | |
185 | |
0 | 186 def UploadInstance(orthanc, filename): |
83 | 187 with open(GetDatabasePath(filename), 'rb') as f: |
188 d = f.read() | |
189 | |
0 | 190 return DoPost(orthanc, '/instances', d, 'application/dicom') |
191 | |
192 def UploadFolder(orthanc, path): | |
13 | 193 for i in os.listdir(GetDatabasePath(path)): |
1 | 194 try: |
195 UploadInstance(orthanc, os.path.join(path, i)) | |
196 except: | |
197 pass | |
0 | 198 |
199 def DropOrthanc(orthanc): | |
200 # Reset the Lua callbacks | |
201 DoPost(orthanc, '/tools/execute-script', 'function OnStoredInstance(instanceId, tags, metadata) end', 'application/lua') | |
202 | |
203 DoDelete(orthanc, '/exports') | |
204 | |
205 for s in DoGet(orthanc, '/patients'): | |
206 DoDelete(orthanc, '/patients/%s' % s) | |
207 | |
174 | 208 def InstallLuaScriptFromPath(orthanc, path): |
209 with open(GetDatabasePath(path), 'r') as f: | |
210 InstallLuaScript(orthanc, f.read()) | |
211 | |
212 def InstallLuaScript(orthanc, script): | |
213 DoPost(orthanc, '/tools/execute-script', script, 'application/lua') | |
214 | |
215 def UninstallLuaCallbacks(orthanc): | |
216 DoPost(orthanc, '/tools/execute-script', 'function OnStoredInstance() end', 'application/lua') | |
217 InstallLuaScriptFromPath(orthanc, 'Lua/TransferSyntaxEnable.lua') | |
218 | |
219 | |
0 | 220 def ComputeMD5(data): |
221 m = hashlib.md5() | |
222 m.update(data) | |
223 return m.hexdigest() | |
224 | |
249
24e5c8ca9440
DICOMweb: test_rendered
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
239
diff
changeset
|
225 def UncompressImage(data): |
83 | 226 if (sys.version_info >= (3, 0)): |
227 return Image.open(BytesIO(data)) | |
228 else: | |
229 return Image.open(StringIO(data)) | |
0 | 230 |
249
24e5c8ca9440
DICOMweb: test_rendered
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
239
diff
changeset
|
231 def GetImage(orthanc, uri, headers = {}): |
24e5c8ca9440
DICOMweb: test_rendered
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
239
diff
changeset
|
232 # http://www.pythonware.com/library/pil/handbook/introduction.htm |
24e5c8ca9440
DICOMweb: test_rendered
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
239
diff
changeset
|
233 return UncompressImage(DoGet(orthanc, uri, headers = headers)) |
24e5c8ca9440
DICOMweb: test_rendered
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
239
diff
changeset
|
234 |
404
931be0125954
Tests/CheckZipStreams.py
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
398
diff
changeset
|
235 def ParseArchive(s): |
0 | 236 # http://stackoverflow.com/a/1313868/881731 |
83 | 237 if (sys.version_info >= (3, 0)): |
238 return zipfile.ZipFile(BytesIO(s), "r") | |
239 else: | |
240 return zipfile.ZipFile(StringIO(s), "r") | |
0 | 241 |
404
931be0125954
Tests/CheckZipStreams.py
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
398
diff
changeset
|
242 def GetArchive(orthanc, uri): |
931be0125954
Tests/CheckZipStreams.py
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
398
diff
changeset
|
243 return ParseArchive(DoGet(orthanc, uri)) |
931be0125954
Tests/CheckZipStreams.py
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
398
diff
changeset
|
244 |
192 | 245 def PostArchive(orthanc, uri, body): |
246 # http://stackoverflow.com/a/1313868/881731 | |
404
931be0125954
Tests/CheckZipStreams.py
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
398
diff
changeset
|
247 return ParseArchive(DoPost(orthanc, uri, body)) |
192 | 248 |
1 | 249 def IsDefinedInLua(orthanc, name): |
0 | 250 s = DoPost(orthanc, '/tools/execute-script', 'print(type(%s))' % name, 'application/lua') |
251 return (s.strip() != 'nil') | |
252 | |
1 | 253 def WaitEmpty(orthanc): |
0 | 254 while True: |
1 | 255 if len(DoGet(orthanc, '/instances')) == 0: |
0 | 256 return |
342
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
257 time.sleep(0.01) |
0 | 258 |
137
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
259 def WaitJobDone(orthanc, job): |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
260 while True: |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
261 s = DoGet(orthanc, '/jobs/%s' % job) ['State'] |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
262 |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
263 if s == 'Success': |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
264 return True |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
265 elif s == 'Failure': |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
266 return False |
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
267 |
342
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
268 time.sleep(0.01) |
137
412d5f70447e
testing asynchronous c-move
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
130
diff
changeset
|
269 |
138 | 270 def MonitorJob(orthanc, func): # "func" is a lambda |
271 a = set(DoGet(orthanc, '/jobs')) | |
272 func() | |
273 b = set(DoGet(orthanc, '/jobs')) | |
274 | |
275 diff = list(b - a) | |
276 if len(diff) != 1: | |
277 print('No job was created!') | |
278 return False | |
279 else: | |
280 return WaitJobDone(orthanc, diff[0]) | |
281 | |
179
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
282 def MonitorJob2(orthanc, func): # "func" is a lambda |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
283 a = set(DoGet(orthanc, '/jobs')) |
181 | 284 job = func() |
179
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
285 b = set(DoGet(orthanc, '/jobs')) |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
286 |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
287 diff = list(b - a) |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
288 if len(diff) != 1: |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
289 print('No job was created!') |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
290 return None |
181 | 291 elif (not 'ID' in job or |
292 diff[0] != job['ID']): | |
293 print('Mismatch in the job ID') | |
294 return None | |
179
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
295 elif WaitJobDone(orthanc, diff[0]): |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
296 return diff[0] |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
297 else: |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
298 print('Error while executing the job') |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
299 return None |
8a2dd77d4035
testing split/merge
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
174
diff
changeset
|
300 |
220 | 301 def WaitAllNewJobsDone(orthanc, func): # "func" is a lambda |
302 a = set(DoGet(orthanc, '/jobs')) | |
303 func() | |
304 | |
305 first = True | |
306 | |
307 while True: | |
308 b = set(DoGet(orthanc, '/jobs')) | |
309 | |
310 diff = list(b - a) | |
311 if len(diff) == 0: | |
312 if first: | |
313 raise Exception('No job was created') | |
314 else: | |
315 return # We're done | |
316 else: | |
317 first = False | |
318 | |
319 if WaitJobDone(orthanc, diff[0]): | |
320 a.add(diff[0]) | |
321 else: | |
322 raise Exception('Error while executing the job') | |
323 | |
324 | |
1 | 325 def GetDockerHostAddress(): |
326 route = subprocess.check_output([ '/sbin/ip', 'route' ]) | |
327 m = re.search(r'default via ([0-9.]+)', route) | |
328 if m == None: | |
329 return 'localhost' | |
330 else: | |
331 return m.groups()[0] | |
4 | 332 |
44
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
333 def FindExecutable(name): |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
334 p = os.path.join('/usr/local/bin', name) |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
335 if os.path.isfile(p): |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
336 return p |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
337 |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
338 p = os.path.join('/usr/local/sbin', name) |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
339 if os.path.isfile(p): |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
340 return p |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
341 |
ffa542cce638
Toolbox.FindExecutable()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
28
diff
changeset
|
342 return name |
4 | 343 |
173 | 344 def IsOrthancVersionAbove(orthanc, major, minor, revision): |
345 v = DoGet(orthanc, '/system')['Version'] | |
346 | |
703 | 347 if v.startswith('mainline'): |
173 | 348 return True |
349 else: | |
350 tmp = v.split('.') | |
351 a = int(tmp[0]) | |
352 b = int(tmp[1]) | |
353 c = int(tmp[2]) | |
354 return (a > major or | |
355 (a == major and b > minor) or | |
356 (a == major and b == minor and c >= revision)) | |
4 | 357 |
693 | 358 |
359 def HasExtendedFind(orthanc): | |
360 v = DoGet(orthanc, '/system') | |
361 | |
362 if 'Capabilities' in v and 'HasExtendedFind' in v['Capabilities']: | |
363 return v['Capabilities']['HasExtendedFind'] | |
364 return False | |
365 | |
366 | |
367 def HasExtendedChanges(orthanc): | |
368 v = DoGet(orthanc, '/system') | |
369 | |
370 if 'Capabilities' in v and 'HasExtendedChanges' in v['Capabilities']: | |
371 return v['Capabilities']['HasExtendedChanges'] | |
372 return False | |
373 | |
374 | |
375 def GetStorageAccessesCount(orthanc): | |
376 mm = DoGetRaw(orthanc, "/tools/metrics-prometheus")[1] | |
377 | |
378 if (sys.version_info >= (3, 0)): | |
379 try: | |
380 mm = mm.decode() | |
381 except: | |
382 pass | |
383 | |
384 mm = [x.split(" ") for x in mm.split("\n")] | |
385 | |
386 count = 0 | |
387 for m in mm: | |
388 if m[0] == 'orthanc_storage_cache_hit_count': | |
389 count += int(m[1]) | |
390 if m[0] == 'orthanc_storage_cache_miss_count': | |
391 count += int(m[1]) | |
392 | |
393 # print("storage access count = %s" % count) | |
394 return count | |
395 | |
396 | |
532 | 397 def IsPluginVersionAbove(orthanc, plugin, major, minor, revision): |
398 v = DoGet(orthanc, '/plugins/%s' % plugin)['Version'] | |
399 | |
711 | 400 if v.startswith('mainline'): |
532 | 401 return True |
402 else: | |
403 tmp = v.split('.') | |
404 if len(tmp) >= 3: | |
405 a = int(tmp[0]) | |
406 b = int(tmp[1]) | |
407 c = int(tmp[2]) | |
408 return (a > major or | |
409 (a == major and b > minor) or | |
410 (a == major and b == minor and c >= revision)) | |
411 elif len(tmp) >= 2: | |
412 a = int(tmp[0]) | |
413 b = int(tmp[1]) | |
414 return (a > major or | |
415 (a == major and b > minor)) | |
416 else: | |
417 return False | |
4 | 418 |
419 class ExternalCommandThread: | |
420 @staticmethod | |
421 def ExternalCommandFunction(arg, stop_event, command, env): | |
318
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
422 with open(os.devnull, 'w') as devnull: |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
423 external = subprocess.Popen(command, env = env, stderr = devnull) |
4 | 424 |
318
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
425 while (not stop_event.is_set()): |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
426 error = external.poll() |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
427 if error != None: |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
428 # http://stackoverflow.com/a/1489838/881731 |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
429 os._exit(-1) |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
430 stop_event.wait(0.1) |
4 | 431 |
83 | 432 print('Stopping the external command') |
4 | 433 external.terminate() |
9 | 434 external.communicate() # Wait for the command to stop |
4 | 435 |
436 def __init__(self, command, env = None): | |
437 self.thread_stop = threading.Event() | |
438 self.thread = threading.Thread(target = self.ExternalCommandFunction, | |
439 args = (10, self.thread_stop, command, env)) | |
9 | 440 #self.daemon = True |
4 | 441 self.thread.start() |
442 | |
443 def stop(self): | |
444 self.thread_stop.set() | |
445 self.thread.join() | |
220 | 446 |
447 | |
222
0f03ee6ffa80
DICOMweb: test_wado_hierarchy, test_wado_bulk, test_bitbucket_issue_112
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
220
diff
changeset
|
448 def AssertAlmostEqualRecursive(self, a, b, places = 7, ignoreKeys = []): |
220 | 449 if type(a) is dict: |
450 self.assertTrue(type(b) is dict) | |
451 self.assertEqual(a.keys(), b.keys()) | |
452 for key, value in a.items(): | |
222
0f03ee6ffa80
DICOMweb: test_wado_hierarchy, test_wado_bulk, test_bitbucket_issue_112
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
220
diff
changeset
|
453 if not key in ignoreKeys: |
0f03ee6ffa80
DICOMweb: test_wado_hierarchy, test_wado_bulk, test_bitbucket_issue_112
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
220
diff
changeset
|
454 AssertAlmostEqualRecursive(self, a[key], b[key], places) |
220 | 455 |
456 elif type(a) is list: | |
457 self.assertTrue(type(b) is list) | |
458 self.assertEqual(len(a), len(b)) | |
459 for i in range(len(a)): | |
460 AssertAlmostEqualRecursive(self, a[i], b[i], places) | |
461 | |
462 else: | |
463 self.assertAlmostEqual(a, b, places = places) | |
291
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
464 |
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
465 |
543 | 466 def GetTransferSyntax(dicom, encoding='utf-8'): |
291
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
467 with tempfile.NamedTemporaryFile(delete = True) as f: |
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
468 f.write(dicom) |
cfa785074c64
test_modify_transcode
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
260
diff
changeset
|
469 f.flush() |
318
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
470 |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
471 with open(os.devnull, 'w') as devnull: |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
472 data = subprocess.check_output([ FindExecutable('dcm2xml'), f.name ], |
bac7cc80f240
dicomweb: test_wado_transcoding
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
304
diff
changeset
|
473 stderr = devnull) |
543 | 474 return re.search('<data-set xfer="(.*?)"', data.decode(encoding)).group(1) |
304 | 475 |
476 | |
477 def HasGdcmPlugin(orthanc): | |
478 plugins = DoGet(orthanc, '/plugins') | |
479 return ('gdcm' in plugins) | |
480 | |
481 | |
482 def _GetMaxImageDifference(im1, im2): | |
483 h = ImageChops.difference(im1, im2).histogram() | |
484 | |
485 if len(h) < 256: | |
486 raise Exception() | |
487 | |
488 i = len(h) - 1 | |
489 while h[i] == 0: | |
490 i -= 1 | |
491 | |
492 return i | |
493 | |
494 | |
495 def GetMaxImageDifference(im1, im2): | |
496 if im1.mode != im2.mode: | |
497 raise Exception('Incompatible image modes') | |
498 | |
499 if im1.mode == 'RGB': | |
500 red1, green1, blue1 = im1.split() | |
501 red2, green2, blue2 = im2.split() | |
502 return max([ _GetMaxImageDifference(red1, red2), | |
503 _GetMaxImageDifference(green1, green2), | |
504 _GetMaxImageDifference(blue1, blue2) ]) | |
505 else: | |
506 return _GetMaxImageDifference(im1, im2) | |
337
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
507 |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
508 |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
509 def DoPropFind(orthanc, uri, depth): |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
510 http = httplib2.Http() |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
511 http.follow_redirects = False |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
512 _SetupCredentials(orthanc, http) |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
513 |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
514 resp, content = http.request(orthanc['Url'] + uri, 'PROPFIND', headers = { 'Depth' : str(depth) }) |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
515 |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
516 if not (resp.status in [ 207 ]): |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
517 raise Exception(resp.status, resp) |
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
518 else: |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
519 xml = minidom.parseString(content) |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
520 |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
521 if (xml.documentElement.nodeName != 'D:multistatus' or |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
522 xml.documentElement.attributes['xmlns:D'].value != 'DAV:'): |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
523 raise Exception() |
337
ec13ace43bde
trying webdav tests
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
318
diff
changeset
|
524 |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
525 result = {} |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
526 |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
527 for i in xml.documentElement.childNodes: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
528 if i.nodeType == minidom.Node.ELEMENT_NODE: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
529 if i.nodeName != 'D:response': |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
530 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
531 href = None |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
532 prop = None |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
533 for j in i.childNodes: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
534 if j.nodeType == minidom.Node.ELEMENT_NODE: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
535 if j.nodeName == 'D:href': |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
536 if href == None: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
537 href = unquote(j.firstChild.nodeValue) |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
538 else: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
539 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
540 elif j.nodeName == 'D:propstat': |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
541 for k in j.childNodes: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
542 if k.nodeName == 'D:status': |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
543 if k.firstChild.nodeValue != 'HTTP/1.1 200 OK': |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
544 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
545 elif k.nodeType == minidom.Node.ELEMENT_NODE: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
546 if (k.nodeName != 'D:prop' or |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
547 prop != None): |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
548 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
549 prop = k |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
550 else: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
551 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
552 if href == None or prop == None: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
553 raise Exception() |
342
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
554 |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
555 info = {} |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
556 |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
557 for j in prop.childNodes: |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
558 if j.nodeType == minidom.Node.ELEMENT_NODE: |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
559 if j.nodeName == 'D:displayname': |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
560 info['displayname'] = j.firstChild.nodeValue if j.firstChild != None else '' |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
561 elif j.nodeName == 'D:creationdate': |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
562 info['creationdate'] = j.firstChild.nodeValue |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
563 elif j.nodeName == 'D:getlastmodified': |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
564 info['lastmodified'] = j.firstChild.nodeValue |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
565 elif j.nodeName == 'D:resourcetype': |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
566 k = j.getElementsByTagName('D:collection') |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
567 info['folder'] = (len(k) == 1) |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
568 |
bf8369ea3ff1
more tests of webdav
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
341
diff
changeset
|
569 result[href] = info |
341
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
570 elif i.nodeType != minidom.Node.TEXT_NODE: |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
571 raise Exception() |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
572 |
66a36befb208
extending Toolbox.DoPropFind()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
337
diff
changeset
|
573 return result |