2 Documentation Resource for supporting http Requests based on data stored in files.
4 ... Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
6 ... This program and the accompanying materials are made available under the
7 ... terms of the Eclipse Public License v1.0 which accompanies this distribution,
8 ... and is available at http://www.eclipse.org/legal/epl-v10.html
11 ... The main strength of this library are *_As_*_Templated keywords
12 ... User gives a path to directory where files with templates for URI
13 ... and XML (or JSON) data are present, and a mapping with substitution to make;
14 ... the keywords will take it from there.
15 ... Mapping can be given as a dict object, or as its json text representation.
16 ... Simple example (tidy insists on single space where 4 spaces should be):
17 ... TemplatedRequests.Put_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"}
18 ... TemplatedRequests.Get_As_Json_Templated folder=${VAR_BASE}/person mapping={"NAME":"joe"} verify=True
20 ... In that example, we are PUTting "person" data with specified value for "NAME" placeholder.
21 ... We are not verifying PUT response (probably empty string which is not a valid JSON),
22 ... but we are issuing GET (same URI) and verifying the repsonse matches the same data.
23 ... Both lines are returning text response, but in the example we are not saving it into variable.
25 ... Optionally, *_As_*_Templated keywords call verification of response.
26 ... There are separate Verify_* keywords, for users who use intermediate processing.
27 ... For JSON responses, there is a support for normalizing.
28 ... *_Templated keywords without As allow more customization at cost of more arguments.
29 ... *_Uri keywords do not use templates, but may be useful in general,
30 ... perhaps for users who call Resolve_Text_* keywords.
31 ... *_As_*_Uri are the less flexible but less argument-heavy versions of *_Uri keywords.
33 ... This resource supports generating data with simple lists.
34 ... ${iterations} argument control number of items, "$i" will be substituted
35 ... automatically (not by the provided mapping) with integers starting with ${iter_start} (default 1).
36 ... For example "iterations=2 iter_start=3" will create items with i=3 and i=4.
38 ... This implementation relies on file names to distinguish data.
39 ... Each file is expected to end in newline, compiled data has final newline removed.
40 ... Here is a table so that users can create their own templates:
41 ... location.uri: Template with URI.
42 ... data.xml: Template with XML data to send, or GET data to expect.
43 ... data.json: Template with JSON data to send, or GET data to expect.
44 ... post_data.xml: Template with XML data to POST, (different from GET response).
45 ... post_data.json: Template with JSON data to POST, (different from GET response).
46 ... response.xml: Template with PUT or POST XML response to expect.
47 ... response.json: Template with PUT or POST JSON response to expect.
48 ... *.prolog.*: Temlate with data before iterated items.
49 ... *.item.*: Template with data piece corresponding to one item.
50 ... *.epilog.*: Temlate with data after iterated items.
52 ... One typical use of this Resource is to make runtime changes to ODL configuration.
53 ... Different ODL parts have varying ways of configuration,
54 ... this library affects only the Config Subsystem way.
55 ... Config Subsystem has (except for Java APIs mostly available only from inside of ODL)
56 ... a NETCONF server as its publicly available entry point.
57 ... Netconf-connector feature makes this netconf server available for RESTCONF calls.
58 ... Be careful to use appropriate feature, odl-netconf-connector* does not work in cluster.
60 ... This Resource currently calls RequestsLibrary directly,
61 ... so it does not work with AuthStandalone or similar.
62 ... This Resource does not maintain any internal Sessions.
63 ... If caller does not provide any, session with alias "default" is used.
64 ... There is a helper Keyword to create the "default" session.
65 ... The session used is assumed to have most things pre-configured appropriately,
66 ... which includes auth, host, port and (lack of) base URI.
67 ... It is recommended to have everything past port (for example /restconf) be defined
68 ... not in the session, but in URI data of individual templates.
69 ... Headers are set in Keywords appropriately.
71 ... These Keywords contain frequent BuiltIn.Log invocations,
72 ... so they are not suited for scale or performance suites.
73 ... And as usual, performance tests should use specialized utilities,
74 ... as Robot in general and this Resource specifically will be too slow.
76 ... As this Resource makes assumptions about intended headers,
77 ... it is not flexible enough for suites specifically testing Restconf corner cases.
78 ... Also, list of allowed http status codes is quite rigid and broad.
80 ... Rules for ordering Keywords within this Resource:
81 ... 1. User friendlier Keywords first.
82 ... 2. Get, Put, Post, Delete, Verify.
83 ... 3. Within class of equally usable, use order in which a suite would call them.
84 ... 4. Higher-level Keywords first.
85 ... 5. Json before Xml.
86 ... Motivation: Users read from the start, so it is important
87 ... to offer them the better-to-use Keywords first.
88 ... https://wiki.opendaylight.org/view/Integration/Test/Test_Code_Guidelines#Keyword_ordering
89 ... In this case, templates are nicer that raw data,
90 ... *_As_* keywords are better than messing wth explicit header dicts,
91 ... Json is less prone to element ordering issues.
92 ... PUT does not fail on existing element, also it does not allow
93 ... shortened URIs (container instead keyed list element) as Post does.
95 ... TODO: Add ability to override allowed status codes,
96 ... so that negative tests do not need to parse the failure message.
98 ... TODO: Migrate suites to this Resource and remove *ViaRestconf Resources.
100 ... TODO: Currently the verification step is only in *_As_*_Templated keywords.
101 ... It could be moved to "non-as" *_Templated ones,
102 ... but that would take even more horizontal space. Is it worth doing?
104 ... TODO: Should iterations=0 be supported for JSON (remove [])?
106 ... TODO: Currently, ${ACCEPT_EMPTY} is used for JSON-expecting requests.
107 ... perhaps explicit ${ACCEPT_JSON} will be better, even if it sends few bytes more?
109 Library OperatingSystem
110 Library RequestsLibrary
111 Library ${CURDIR}/norm_json.py
112 Variables ${CURDIR}/../variables/Variables.py
115 # TODO: Make the following list more narrow when streams without Bug 2594 fix (up to beryllium) are no longer used.
116 @{ALLOWED_STATUS_CODES} ${200} ${201} ${204} # List of integers, not strings. Used by both PUT and DELETE (if the resource should have been present).
117 @{DATA_VALIDATION_ERROR} ${500}
118 @{ALLOWED_DELETE_STATUS_CODES} ${200} ${201} ${204} ${404} # List of integers, not strings. Used by DELETE if the resource may be not present.
119 @{KEYS_WITH_BITS} op # the default list with keys to be sorted when norm_json libray is used
120 # TODO: Add option for delete to require 404.
123 Create_Default_Session
124 [Arguments] ${url}=http://${ODL_SYSTEM_IP}:${RESTCONFPORT} ${auth}=${AUTH}
125 [Documentation] Create "default" session to ${url} with default authentication.
126 ... This Keyword is in this Resource only so that user do not need to call RequestsLibrary directly.
127 RequestsLibrary.Create_Session alias=default url=${url} auth=${auth}
129 Get_As_Json_Templated
130 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
131 [Documentation] Add arguments sensible for JSON data, return Get_Templated response text.
132 ... Optionally, verification against JSON data (may be iterated) is called.
133 ${response_text} = Get_Templated folder=${folder} mapping=${mapping} accept=${ACCEPT_EMPTY} session=${session} normalize_json=True
134 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Json_Templated response=${response_text} folder=${folder} base_name=data mapping=${mapping}
135 ... iterations=${iterations} iter_start=${iter_start}
136 [Return] ${response_text}
139 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
140 [Documentation] Add arguments sensible for XML data, return Get_Templated response text.
141 ... Optionally, verification against XML data (may be iterated) is called.
142 ${response_text} = Get_Templated folder=${folder} mapping=${mapping} accept=${ACCEPT_XML} session=${session} normalize_json=False
143 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Xml_Templated response=${response_text} folder=${folder} base_name=data mapping=${mapping}
144 ... iterations=${iterations} iter_start=${iter_start}
145 [Return] ${response_text}
147 Put_As_Json_Templated
148 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
149 [Documentation] Add arguments sensible for JSON data, return Put_Templated response text.
150 ... Optionally, verification against response.json (no iteration) is called.
151 ${response_text} = Put_Templated folder=${folder} base_name=data extension=json accept=${ACCEPT_EMPTY} content_type=${HEADERS_YANG_JSON}
152 ... mapping=${mapping} session=${session} normalize_json=True endline=${\n} iterations=${iterations} iter_start=${iter_start}
153 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Json_Templated response=${response_text} folder=${folder} base_name=response mapping=${mapping}
154 [Return] ${response_text}
157 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
158 [Documentation] Add arguments sensible for XML data, return Put_Templated response text.
159 ... Optionally, verification against response.xml (no iteration) is called.
160 # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
161 ${response_text} = Put_Templated folder=${folder} base_name=data extension=xml accept=${ACCEPT_XML} content_type=${HEADERS_XML}
162 ... mapping=${mapping} session=${session} normalize_json=False endline=${\n} iterations=${iterations} iter_start=${iter_start}
163 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Xml_Templated response=${response_text} folder=${folder} base_name=response mapping=${mapping}
164 [Return] ${response_text}
166 Post_As_Json_Templated
167 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
168 [Documentation] Add arguments sensible for JSON data, return Post_Templated response text.
169 ... Optionally, verification against response.json (no iteration) is called.
170 ${response_text} = Post_Templated folder=${folder} base_name=data extension=json accept=${ACCEPT_EMPTY} content_type=${HEADERS_YANG_JSON}
171 ... mapping=${mapping} session=${session} normalize_json=True endline=${\n} iterations=${iterations} iter_start=${iter_start}
172 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Json_Templated response=${response_text} folder=${folder} base_name=response mapping=${mapping}
173 [Return] ${response_text}
175 Post_As_Xml_Templated
176 [Arguments] ${folder} ${mapping}={} ${session}=default ${verify}=False ${iterations}=${EMPTY} ${iter_start}=1
177 [Documentation] Add arguments sensible for XML data, return Post_Templated response text.
178 ... Optionally, verification against response.xml (no iteration) is called.
179 # In case of iterations, we use endlines in data to send, as it should not matter and it is more readable.
180 ${response_text} = Post_Templated folder=${folder} base_name=data extension=xml accept=${ACCEPT_XML} content_type=${HEADERS_XML}
181 ... mapping=${mapping} session=${session} normalize_json=False endline=${\n} iterations=${iterations} iter_start=${iter_start}
182 BuiltIn.Run_Keyword_If ${verify} Verify_Response_As_Xml_Templated response=${response_text} folder=${folder} base_name=response mapping=${mapping}
183 [Return] ${response_text}
186 [Arguments] ${folder} ${mapping}={} ${session}=default ${allow_404}=False
187 [Documentation] Resolve URI from folder, issue DELETE request.
188 ${uri} = Resolve_Text_From_Template_Folder folder=${folder} base_name=location extension=uri mapping=${mapping}
189 ${response_text} = Delete_From_Uri uri=${uri} session=${session} allow_404=${allow_404}
190 [Return] ${response_text}
192 Verify_Response_As_Json_Templated
193 [Arguments] ${response} ${folder} ${base_name}=response ${mapping}={} ${iterations}=${EMPTY} ${iter_start}=1
194 [Documentation] Resolve expected JSON data, should be equal to provided \${response}.
195 ... JSON normalization is used, endlines enabled for readability.
196 Verify_Response_Templated response=${response} folder=${folder} base_name=${base_name} extension=json mapping=${mapping} normalize_json=True
197 ... endline=${\n} iterations=${iterations} iter_start=${iter_start}
199 Verify_Response_As_Xml_Templated
200 [Arguments] ${response} ${folder} ${base_name}=response ${mapping}={} ${iterations}=${EMPTY} ${iter_start}=1
201 [Documentation] Resolve expected XML data, should be equal to provided \${response}.
202 ... Endline set to empty, as this Resource does not support indented XML comparison.
203 Verify_Response_Templated response=${response} folder=${folder} base_name=${base_name} extension=xml mapping=${mapping} normalize_json=False
204 ... endline=${EMPTY} iterations=${iterations} iter_start=${iter_start}
207 [Arguments] ${uri} ${session}=default
208 [Documentation] Specify JSON headers and return Get_From_Uri normalized response text.
209 ${response_text} = Get_From_Uri uri=${uri} accept=${ACCEPT_EMPTY} session=${session} normalize_json=True
210 [Return] ${response_text}
213 [Arguments] ${uri} ${session}=default
214 [Documentation] Specify XML headers and return Get_From_Uri response text.
215 ${response_text} = Get_From_Uri uri=${uri} accept=${ACCEPT_XML} session=${session} normalize_json=False
216 [Return] ${response_text}
219 [Arguments] ${uri} ${data} ${session}=default
220 [Documentation] Specify JSON headers and return Put_To_Uri normalized response text.
221 ... Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
222 ${response_text} = Put_To_Uri uri=${uri} data=${data} accept=${ACCEPT_EMPTY} content_type=${HEADERS_YANG_JSON} session=${session}
223 ... normalize_json=True
224 [Return] ${response_text}
227 [Arguments] ${uri} ${data} ${session}=default
228 [Documentation] Specify XML headers and return Put_To_Uri response text.
229 ${response_text} = Put_To_Uri uri=${uri} data=${data} accept=${ACCEPT_XML} content_type=${HEADERS_XML} session=${session}
230 ... normalize_json=False
231 [Return] ${response_text}
234 [Arguments] ${uri} ${data} ${session}=default
235 [Documentation] Specify JSON headers and return Post_To_Uri normalized response text.
236 ... Yang json content type is used as a workaround to RequestsLibrary json conversion eagerness.
237 ${response_text} = Post_To_Uri uri=${uri} data=${data} accept=${ACCEPT_EMPTY} content_type=${HEADERS_YANG_JSON} session=${session}
238 ... normalize_json=True
239 [Return] ${response_text}
242 [Arguments] ${uri} ${data} ${session}=default
243 [Documentation] Specify XML headers and return Post_To_Uri response text.
244 ${response_text} = Post_To_Uri uri=${uri} data=${data} accept=${ACCEPT_XML} content_type=${HEADERS_XML} session=${session}
245 ... normalize_json=False
246 [Return] ${response_text}
249 [Arguments] ${uri} ${session}=default ${allow_404}=False
250 [Documentation] DELETE resource at URI, check status_code and return response text..
252 ${response} = RequestsLibrary.Delete_Request alias=${session} uri=${uri}
253 Check_Status_Code ${response} allow_404=${allow_404}
254 [Return] ${response.text}
257 [Arguments] ${folder} ${accept} ${mapping}={} ${session}=default ${normalize_json}=False
258 [Documentation] Resolve URI from folder, call Get_From_Uri, return response text.
259 ${uri} = Resolve_Text_From_Template_Folder folder=${folder} base_name=location extension=uri mapping=${mapping}
260 ${response_text} = Get_From_Uri uri=${uri} accept=${accept} session=${session} normalize_json=${normalize_json}
261 [Return] ${response_text}
264 [Arguments] ${folder} ${base_name} ${extension} ${content_type} ${accept} ${mapping}={}
265 ... ${session}=default ${normalize_json}=False ${endline}=${\n} ${iterations}=${EMPTY} ${iter_start}=1
266 [Documentation] Resolve URI and data from folder, call Put_To_Uri, return response text.
267 ${uri} = Resolve_Text_From_Template_Folder folder=${folder} base_name=location extension=uri mapping=${mapping}
268 ${data} = Resolve_Text_From_Template_Folder folder=${folder} base_name=${base_name} extension=${extension} mapping=${mapping} endline=${endline}
269 ... iterations=${iterations} iter_start=${iter_start}
270 ${response_text} = Put_To_Uri uri=${uri} data=${data} content_type=${content_type} accept=${accept} session=${session}
271 ... normalize_json=${normalize_json}
272 [Return] ${response_text}
275 [Arguments] ${folder} ${base_name} ${extension} ${content_type} ${accept} ${mapping}={}
276 ... ${session}=default ${normalize_json}=False ${endline}=${\n} ${iterations}=${EMPTY} ${iter_start}=1
277 [Documentation] Resolve URI and data from folder, call Post_To_Uri, return response text.
278 ${uri} = Resolve_Text_From_Template_Folder folder=${folder} base_name=location extension=uri mapping=${mapping}
279 ${data} = Resolve_Text_From_Template_Folder folder=${folder} name_prefix=post_ base_name=${base_name} extension=${extension} mapping=${mapping}
280 ... endline=${endline} iterations=${iterations} iter_start=${iter_start}
281 ${response_text} = Post_To_Uri uri=${uri} data=${data} content_type=${content_type} accept=${accept} session=${session}
282 ... normalize_json=${normalize_json}
283 [Return] ${response_text}
285 Verify_Response_Templated
286 [Arguments] ${response} ${folder} ${base_name} ${extension} ${mapping}={} ${normalize_json}=False
287 ... ${endline}=${\n} ${iterations}=${EMPTY} ${iter_start}=1
288 [Documentation] Resolve expected text from template, provided response shuld be equal.
289 ... If \${normalize_json}, perform normalization before comparison.
290 # TODO: Support for XML-aware comparison could be added, but there are issues with namespaces and similar.
291 ${expected_text} = Resolve_Text_From_Template_Folder folder=${folder} base_name=${base_name} extension=${extension} mapping=${mapping} endline=${endline}
292 ... iterations=${iterations} iter_start=${iter_start}
293 BuiltIn.Run_Keyword_If ${normalize_json} Normalize_Jsons_And_Compare expected_raw=${expected_text} actual_raw=${response}
294 ... ELSE BuiltIn.Should_Be_Equal ${expected_text} ${response}
297 [Arguments] ${uri} ${accept}=${ACCEPT_EMPTY} ${session}=default ${normalize_json}=False
298 [Documentation] GET data from given URI, check status code and return response text.
299 ... \${accept} is a Python object with headers to use.
300 ... If \${normalize_json}, normalize as JSON text before returning.
302 BuiltIn.Log ${accept}
303 ${response} = RequestsLibrary.Get_Request alias=${session} uri=${uri} headers=${accept}
304 Check_Status_Code ${response}
305 BuiltIn.Run_Keyword_Unless ${normalize_json} BuiltIn.Return_From_Keyword ${response.text}
306 ${text_normalized} = norm_json.normalize_json_text ${response.text}
307 [Return] ${text_normalized}
310 [Arguments] ${uri} ${data} ${content_type} ${accept} ${session}=default ${normalize_json}=False
311 [Documentation] PUT data to given URI, check status code and return response text.
312 ... \${content_type} and \${accept} are mandatory Python objects with headers to use.
313 ... If \${normalize_json}, normalize text before returning.
316 BuiltIn.Log ${content_type}
317 BuiltIn.Log ${accept}
318 ${headers} = Join_Two_Headers first=${content_type} second=${accept}
319 ${response} = RequestsLibrary.Put_Request alias=${session} uri=${uri} data=${data} headers=${headers}
320 Check_Status_Code ${response}
321 BuiltIn.Run_Keyword_Unless ${normalize_json} BuiltIn.Return_From_Keyword ${response.text}
322 ${text_normalized} = norm_json.normalize_json_text ${response.text}
323 [Return] ${text_normalized}
326 [Arguments] ${uri} ${data} ${content_type} ${accept} ${session}=default ${normalize_json}=False
327 [Documentation] POST data to given URI, check status code and return response text.
328 ... \${content_type} and \${accept} are mandatory Python objects with headers to use.
329 ... If \${normalize_json}, normalize text before returning.
332 BuiltIn.Log ${content_type}
333 BuiltIn.Log ${accept}
334 ${headers} = Join_Two_Headers first=${content_type} second=${accept}
335 ${response} = RequestsLibrary.Post_Request alias=${session} uri=${uri} data=${data} headers=${headers}
336 Check_Status_Code ${response}
337 BuiltIn.Run_Keyword_Unless ${normalize_json} BuiltIn.Return_From_Keyword ${response.text}
338 ${text_normalized} = norm_json.normalize_json_text ${response.text}
339 [Return] ${text_normalized}
342 [Arguments] ${response} ${allow_404}=False
343 [Documentation] Log response text, check status_code is one of allowed ones.
344 # TODO: Remove overlap with keywords from Utils.robot
345 BuiltIn.Log ${response.text}
346 BuiltIn.Log ${response.status_code}
347 Run_Keyword_If ${allow_404} BuiltIn.Should_Contain ${ALLOWED_DELETE_STATUS_CODES} ${response.status_code}
348 ... ELSE BuiltIn.Should_Contain ${ALLOWED_STATUS_CODES} ${response.status_code}
351 [Arguments] ${first} ${second}
352 [Documentation] Take two dicts, join them, return result. Second argument values take precedence.
353 ${accumulator} = Collections.Copy_Dictionary ${first}
354 ${items_to_add} = Collections.Get_Dictionary_Items ${second}
355 Collections.Set_To_Dictionary ${accumulator} @{items_to_add}
356 BuiltIn.Log ${accumulator}
357 [Return] ${accumulator}
359 Resolve_Text_From_Template_Folder
360 [Arguments] ${folder} ${name_prefix}=${EMPTY} ${base_name}=data ${extension}=json ${mapping}={} ${iterations}=${EMPTY}
361 ... ${iter_start}=1 ${endline}=${\n}
362 [Documentation] Read a template from folder, strip endline, make changes according to mapping, return the result.
363 ... If \${iterations} value is present, put text together from "prolog", "item" and "epilog" parts,
364 ... where additional template variable ${i} goes from ${iter_start}, by one ${iterations} times.
365 ... POST (as opposed to PUT) needs slightly different data, \${name_prefix} may be used to distinguish.
366 ... (Actually, it is GET who formats data differently when URI is a top-level container.)
367 BuiltIn.Run_Keyword_And_Return_If not "${iterations}" Resolve_Text_From_Template_File folder=${folder} file_name=${name_prefix}${base_name}.${extension} mapping=${mapping}
368 ${prolog} = Resolve_Text_From_Template_File folder=${folder} file_name=${name_prefix}${base_name}.prolog.${extension} mapping=${mapping}
369 ${epilog} = Resolve_Text_From_Template_File folder=${folder} file_name=${name_prefix}${base_name}.epilog.${extension} mapping=${mapping}
370 # Even POST uses the same item template (except indentation), so name prefix is ignored.
371 ${item_template} = Resolve_Text_From_Template_File folder=${folder} file_name=${base_name}.item.${extension} mapping=${mapping}
372 ${items} = BuiltIn.Create_List
373 ${separator} = BuiltIn.Set_Variable_If '${extension}' != 'json' ${endline} ,${endline}
374 : FOR ${iteration} IN RANGE ${iter_start} ${iterations}+${iter_start}
375 \ BuiltIn.Run_Keyword_If ${iteration} > ${iter_start} Collections.Append_To_List ${items} ${separator}
376 \ ${item} = BuiltIn.Evaluate string.Template('''${item_template}''').substitute({"i":"${iteration}"}) modules=string
377 \ Collections.Append_To_List ${items} ${item}
378 # TODO: The following makes ugly result for iterations=0. Should we fix that?
379 ${final_text} = BuiltIn.Catenate SEPARATOR= ${prolog} ${endline} @{items} ${endline}
381 [Return] ${final_text}
383 Resolve_Text_From_Template_File
384 [Arguments] ${folder} ${file_name} ${mapping}={}
385 [Documentation] Check if ${folder}.${ODL_STREAM}/${file_name} exists. If yes read and Log contents of file ${folder}.${ODL_STREAM}/${file_name},
386 ... remove endline, perform safe substitution, return result.
387 ... If no do it with the default ${folder}/${file_name}.
388 ${file_path_stream}= BuiltIn.Set Variable ${folder}.${ODL_STREAM}${/}${file_name}
389 ${file_stream_exists}= BuiltIn.Run Keyword And Return Status OperatingSystem.File Should Exist ${file_path_stream}
390 ${file_path}= BuiltIn.Set Variable If ${file_stream_exists} ${file_path_stream} ${folder}${/}${file_name}
391 ${template} = OperatingSystem.Get_File ${file_path}
392 BuiltIn.Log ${template}
393 ${final_text} = BuiltIn.Evaluate string.Template('''${template}'''.rstrip()).safe_substitute(${mapping}) modules=string
394 # Final text is logged where used.
395 [Return] ${final_text}
397 Normalize_Jsons_And_Compare
398 [Arguments] ${expected_raw} ${actual_raw}
399 [Documentation] Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
400 ${expected_normalized} = norm_json.normalize_json_text ${expected_raw}
401 ${actual_normalized} = norm_json.normalize_json_text ${actual_raw}
402 # Should_Be_Equal shall print nice diff-style line comparison.
403 BuiltIn.Should_Be_Equal ${expected_normalized} ${actual_normalized}
404 # TODO: Add garbage collection? Check whether the temporary data accumulates.
406 Normalize_Jsons_With_Bits_And_Compare
407 [Arguments] ${expected_raw} ${actual_raw} ${keys_with_bits}=${KEYS_WITH_BITS}
408 [Documentation] Use norm_json to normalize both JSON arguments, call Should_Be_Equal.
409 ${expected_normalized} = norm_json.normalize_json_text ${expected_raw} keys_with_bits=${keys_with_bits}
410 ${actual_normalized} = norm_json.normalize_json_text ${actual_raw} keys_with_bits=${keys_with_bits}
411 BuiltIn.Should_Be_Equal ${expected_normalized} ${actual_normalized}