-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
BuiltIn.py
3510 lines (2901 loc) · 157 KB
/
BuiltIn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import difflib
import re
import time
import token
from tokenize import generate_tokens, untokenize
from robot.api import logger
from robot.errors import (ContinueForLoop, DataError, ExecutionFailed,
ExecutionFailures, ExecutionPassed, ExitForLoop,
PassExecution, ReturnFromKeyword)
from robot.running import Keyword, RUN_KW_REGISTER
from robot.running.context import EXECUTION_CONTEXTS
from robot.running.usererrorhandler import UserErrorHandler
from robot.utils import (DotDict, escape, format_assign_message,
get_error_message, get_time, html_escape, is_falsy, is_integer,
is_string, is_truthy, is_unicode, IRONPYTHON, JYTHON,
Matcher, normalize, NormalizedDict, parse_time, prepr,
RERAISED_EXCEPTIONS, plural_or_not as s, roundup,
secs_to_timestr, seq2str, split_from_equals, StringIO,
timestr_to_secs, type_name, unic, is_list_like)
from robot.utils.asserts import assert_equal, assert_not_equal
from robot.variables import (is_list_var, is_var, DictVariableTableValue,
VariableTableValue, VariableSplitter,
variable_not_found)
from robot.version import get_version
if JYTHON:
from java.lang import String, Number
# TODO: Clean-up registering run keyword variants in RF 3.1.
# https://github.com/robotframework/robotframework/issues/2190
def run_keyword_variant(resolve):
def decorator(method):
RUN_KW_REGISTER.register_run_keyword('BuiltIn', method.__name__,
resolve, deprecation_warning=False)
return method
return decorator
class _BuiltInBase(object):
@property
def _context(self):
return self._get_context()
def _get_context(self, top=False):
ctx = EXECUTION_CONTEXTS.current if not top else EXECUTION_CONTEXTS.top
if ctx is None:
raise RobotNotRunningError('Cannot access execution context')
return ctx
@property
def _namespace(self):
return self._get_context().namespace
@property
def _variables(self):
return self._namespace.variables
def _matches(self, string, pattern, caseless=False):
# Must use this instead of fnmatch when string may contain newlines.
matcher = Matcher(pattern, caseless=caseless, spaceless=False)
return matcher.match(string)
def _is_true(self, condition):
if is_string(condition):
condition = self.evaluate(condition, modules='os,sys')
return bool(condition)
def _log_types(self, *args):
self._log_types_at_level('DEBUG', *args)
def _log_types_at_level(self, level, *args):
msg = ["Argument types are:"] + [self._get_type(a) for a in args]
self.log('\n'.join(msg), level)
def _get_type(self, arg):
# In IronPython type(u'x') is str. We want to report unicode anyway.
if is_unicode(arg):
return "<type 'unicode'>"
return str(type(arg))
class _Converter(_BuiltInBase):
def convert_to_integer(self, item, base=None):
"""Converts the given item to an integer number.
If the given item is a string, it is by default expected to be an
integer in base 10. There are two ways to convert from other bases:
- Give base explicitly to the keyword as ``base`` argument.
- Prefix the given string with the base so that ``0b`` means binary
(base 2), ``0o`` means octal (base 8), and ``0x`` means hex (base 16).
The prefix is considered only when ``base`` argument is not given and
may itself be prefixed with a plus or minus sign.
The syntax is case-insensitive and possible spaces are ignored.
Examples:
| ${result} = | Convert To Integer | 100 | | # Result is 100 |
| ${result} = | Convert To Integer | FF AA | 16 | # Result is 65450 |
| ${result} = | Convert To Integer | 100 | 8 | # Result is 64 |
| ${result} = | Convert To Integer | -100 | 2 | # Result is -4 |
| ${result} = | Convert To Integer | 0b100 | | # Result is 4 |
| ${result} = | Convert To Integer | -0x100 | | # Result is -256 |
See also `Convert To Number`, `Convert To Binary`, `Convert To Octal`,
`Convert To Hex`, and `Convert To Bytes`.
"""
self._log_types(item)
return self._convert_to_integer(item, base)
def _convert_to_integer(self, orig, base=None):
try:
item = self._handle_java_numbers(orig)
item, base = self._get_base(item, base)
if base:
return int(item, self._convert_to_integer(base))
return int(item)
except:
raise RuntimeError("'%s' cannot be converted to an integer: %s"
% (orig, get_error_message()))
def _handle_java_numbers(self, item):
if not JYTHON:
return item
if isinstance(item, String):
return unic(item)
if isinstance(item, Number):
return item.doubleValue()
return item
def _get_base(self, item, base):
if not is_string(item):
return item, base
item = normalize(item)
if item.startswith(('-', '+')):
sign = item[0]
item = item[1:]
else:
sign = ''
bases = {'0b': 2, '0o': 8, '0x': 16}
if base or not item.startswith(tuple(bases)):
return sign+item, base
return sign+item[2:], bases[item[:2]]
def convert_to_binary(self, item, base=None, prefix=None, length=None):
"""Converts the given item to a binary string.
The ``item``, with an optional ``base``, is first converted to an
integer using `Convert To Integer` internally. After that it
is converted to a binary number (base 2) represented as a
string such as ``1011``.
The returned value can contain an optional ``prefix`` and can be
required to be of minimum ``length`` (excluding the prefix and a
possible minus sign). If the value is initially shorter than
the required length, it is padded with zeros.
Examples:
| ${result} = | Convert To Binary | 10 | | | # Result is 1010 |
| ${result} = | Convert To Binary | F | base=16 | prefix=0b | # Result is 0b1111 |
| ${result} = | Convert To Binary | -2 | prefix=B | length=4 | # Result is -B0010 |
See also `Convert To Integer`, `Convert To Octal` and `Convert To Hex`.
"""
return self._convert_to_bin_oct_hex(item, base, prefix, length, 'b')
def convert_to_octal(self, item, base=None, prefix=None, length=None):
"""Converts the given item to an octal string.
The ``item``, with an optional ``base``, is first converted to an
integer using `Convert To Integer` internally. After that it
is converted to an octal number (base 8) represented as a
string such as ``775``.
The returned value can contain an optional ``prefix`` and can be
required to be of minimum ``length`` (excluding the prefix and a
possible minus sign). If the value is initially shorter than
the required length, it is padded with zeros.
Examples:
| ${result} = | Convert To Octal | 10 | | | # Result is 12 |
| ${result} = | Convert To Octal | -F | base=16 | prefix=0 | # Result is -017 |
| ${result} = | Convert To Octal | 16 | prefix=oct | length=4 | # Result is oct0020 |
See also `Convert To Integer`, `Convert To Binary` and `Convert To Hex`.
"""
return self._convert_to_bin_oct_hex(item, base, prefix, length, 'o')
def convert_to_hex(self, item, base=None, prefix=None, length=None,
lowercase=False):
"""Converts the given item to a hexadecimal string.
The ``item``, with an optional ``base``, is first converted to an
integer using `Convert To Integer` internally. After that it
is converted to a hexadecimal number (base 16) represented as
a string such as ``FF0A``.
The returned value can contain an optional ``prefix`` and can be
required to be of minimum ``length`` (excluding the prefix and a
possible minus sign). If the value is initially shorter than
the required length, it is padded with zeros.
By default the value is returned as an upper case string, but the
``lowercase`` argument a true value (see `Boolean arguments`) turns
the value (but not the given prefix) to lower case.
Examples:
| ${result} = | Convert To Hex | 255 | | | # Result is FF |
| ${result} = | Convert To Hex | -10 | prefix=0x | length=2 | # Result is -0x0A |
| ${result} = | Convert To Hex | 255 | prefix=X | lowercase=yes | # Result is Xff |
See also `Convert To Integer`, `Convert To Binary` and `Convert To Octal`.
"""
spec = 'x' if is_truthy(lowercase) else 'X'
return self._convert_to_bin_oct_hex(item, base, prefix, length, spec)
def _convert_to_bin_oct_hex(self, item, base, prefix, length, format_spec):
self._log_types(item)
ret = format(self._convert_to_integer(item, base), format_spec)
prefix = prefix or ''
if ret[0] == '-':
prefix = '-' + prefix
ret = ret[1:]
if length:
ret = ret.rjust(self._convert_to_integer(length), '0')
return prefix + ret
def convert_to_number(self, item, precision=None):
"""Converts the given item to a floating point number.
If the optional ``precision`` is positive or zero, the returned number
is rounded to that number of decimal digits. Negative precision means
that the number is rounded to the closest multiple of 10 to the power
of the absolute precision. If a number is equally close to a certain
precision, it is always rounded away from zero.
Examples:
| ${result} = | Convert To Number | 42.512 | | # Result is 42.512 |
| ${result} = | Convert To Number | 42.512 | 1 | # Result is 42.5 |
| ${result} = | Convert To Number | 42.512 | 0 | # Result is 43.0 |
| ${result} = | Convert To Number | 42.512 | -1 | # Result is 40.0 |
Notice that machines generally cannot store floating point numbers
accurately. This may cause surprises with these numbers in general
and also when they are rounded. For more information see, for example,
these resources:
- http://docs.python.org/tutorial/floatingpoint.html
- http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition
If you want to avoid possible problems with floating point numbers,
you can implement custom keywords using Python's
[http://docs.python.org/library/decimal.html|decimal] or
[http://docs.python.org/library/fractions.html|fractions] modules.
If you need an integer number, use `Convert To Integer` instead.
"""
self._log_types(item)
return self._convert_to_number(item, precision)
def _convert_to_number(self, item, precision=None):
number = self._convert_to_number_without_precision(item)
if precision is not None:
number = roundup(number, self._convert_to_integer(precision),
return_type=float)
return number
def _convert_to_number_without_precision(self, item):
try:
if JYTHON:
item = self._handle_java_numbers(item)
return float(item)
except:
error = get_error_message()
try:
return float(self._convert_to_integer(item))
except RuntimeError:
raise RuntimeError("'%s' cannot be converted to a floating "
"point number: %s" % (item, error))
def convert_to_string(self, item):
"""Converts the given item to a Unicode string.
Uses ``__unicode__`` or ``__str__`` method with Python objects and
``toString`` with Java objects.
Use `Encode String To Bytes` and `Decode Bytes To String` keywords
in ``String`` library if you need to convert between Unicode and byte
strings using different encodings. Use `Convert To Bytes` if you just
want to create byte strings.
"""
self._log_types(item)
return self._convert_to_string(item)
def _convert_to_string(self, item):
return unic(item)
def convert_to_boolean(self, item):
"""Converts the given item to Boolean true or false.
Handles strings ``True`` and ``False`` (case-insensitive) as expected,
otherwise returns item's
[http://docs.python.org/library/stdtypes.html#truth|truth value]
using Python's ``bool()`` method.
"""
self._log_types(item)
if is_string(item):
if item.upper() == 'TRUE':
return True
if item.upper() == 'FALSE':
return False
return bool(item)
def convert_to_bytes(self, input, input_type='text'):
u"""Converts the given ``input`` to bytes according to the ``input_type``.
Valid input types are listed below:
- ``text:`` Converts text to bytes character by character. All
characters with ordinal below 256 can be used and are converted to
bytes with same values. Many characters are easiest to represent
using escapes like ``\\x00`` or ``\\xff``. Supports both Unicode
strings and bytes.
- ``int:`` Converts integers separated by spaces to bytes. Similarly as
with `Convert To Integer`, it is possible to use binary, octal, or
hex values by prefixing the values with ``0b``, ``0o``, or ``0x``,
respectively.
- ``hex:`` Converts hexadecimal values to bytes. Single byte is always
two characters long (e.g. ``01`` or ``FF``). Spaces are ignored and
can be used freely as a visual separator.
- ``bin:`` Converts binary values to bytes. Single byte is always eight
characters long (e.g. ``00001010``). Spaces are ignored and can be
used freely as a visual separator.
In addition to giving the input as a string, it is possible to use
lists or other iterables containing individual characters or numbers.
In that case numbers do not need to be padded to certain length and
they cannot contain extra spaces.
Examples (last column shows returned bytes):
| ${bytes} = | Convert To Bytes | hyv\xe4 | | # hyv\\xe4 |
| ${bytes} = | Convert To Bytes | \\xff\\x07 | | # \\xff\\x07 |
| ${bytes} = | Convert To Bytes | 82 70 | int | # RF |
| ${bytes} = | Convert To Bytes | 0b10 0x10 | int | # \\x02\\x10 |
| ${bytes} = | Convert To Bytes | ff 00 07 | hex | # \\xff\\x00\\x07 |
| ${bytes} = | Convert To Bytes | 5246212121 | hex | # RF!!! |
| ${bytes} = | Convert To Bytes | 0000 1000 | bin | # \\x08 |
| ${input} = | Create List | 1 | 2 | 12 |
| ${bytes} = | Convert To Bytes | ${input} | int | # \\x01\\x02\\x0c |
| ${bytes} = | Convert To Bytes | ${input} | hex | # \\x01\\x02\\x12 |
Use `Encode String To Bytes` in ``String`` library if you need to
convert text to bytes using a certain encoding.
"""
try:
try:
ordinals = getattr(self, '_get_ordinals_from_%s' % input_type)
except AttributeError:
raise RuntimeError("Invalid input type '%s'." % input_type)
return bytes(bytearray(o for o in ordinals(input)))
except:
raise RuntimeError("Creating bytes failed: %s" % get_error_message())
def _get_ordinals_from_text(self, input):
# https://github.com/IronLanguages/main/issues/1237
if IRONPYTHON and isinstance(input, bytearray):
input = bytes(input)
for char in input:
ordinal = char if is_integer(char) else ord(char)
yield self._test_ordinal(ordinal, char, 'Character')
def _test_ordinal(self, ordinal, original, type):
if 0 <= ordinal <= 255:
return ordinal
raise RuntimeError("%s '%s' cannot be represented as a byte."
% (type, original))
def _get_ordinals_from_int(self, input):
if is_string(input):
input = input.split()
elif is_integer(input):
input = [input]
for integer in input:
ordinal = self._convert_to_integer(integer)
yield self._test_ordinal(ordinal, integer, 'Integer')
def _get_ordinals_from_hex(self, input):
for token in self._input_to_tokens(input, length=2):
ordinal = self._convert_to_integer(token, base=16)
yield self._test_ordinal(ordinal, token, 'Hex value')
def _get_ordinals_from_bin(self, input):
for token in self._input_to_tokens(input, length=8):
ordinal = self._convert_to_integer(token, base=2)
yield self._test_ordinal(ordinal, token, 'Binary value')
def _input_to_tokens(self, input, length):
if not is_string(input):
return input
input = ''.join(input.split())
if len(input) % length != 0:
raise RuntimeError('Expected input to be multiple of %d.' % length)
return (input[i:i+length] for i in range(0, len(input), length))
def create_list(self, *items):
"""Returns a list containing given items.
The returned list can be assigned both to ``${scalar}`` and ``@{list}``
variables.
Examples:
| @{list} = | Create List | a | b | c |
| ${scalar} = | Create List | a | b | c |
| ${ints} = | Create List | ${1} | ${2} | ${3} |
"""
return list(items)
@run_keyword_variant(resolve=0)
def create_dictionary(self, *items):
"""Creates and returns a dictionary based on the given ``items``.
Items are typically given using the ``key=value`` syntax same way as
``&{dictionary}`` variables are created in the Variable table. Both
keys and values can contain variables, and possible equal sign in key
can be escaped with a backslash like ``escaped\\=key=value``. It is
also possible to get items from existing dictionaries by simply using
them like ``&{dict}``.
Alternatively items can be specified so that keys and values are given
separately. This and the ``key=value`` syntax can even be combined,
but separately given items must be first.
If same key is used multiple times, the last value has precedence.
The returned dictionary is ordered, and values with strings as keys
can also be accessed using a convenient dot-access syntax like
``${dict.key}``.
Examples:
| &{dict} = | Create Dictionary | key=value | foo=bar | | | # key=value syntax |
| Should Be True | ${dict} == {'key': 'value', 'foo': 'bar'} |
| &{dict2} = | Create Dictionary | key | value | foo | bar | # separate key and value |
| Should Be Equal | ${dict} | ${dict2} |
| &{dict} = | Create Dictionary | ${1}=${2} | &{dict} | foo=new | | # using variables |
| Should Be True | ${dict} == {1: 2, 'key': 'value', 'foo': 'new'} |
| Should Be Equal | ${dict.key} | value | | | | # dot-access |
This keyword was changed in Robot Framework 2.9 in many ways:
- Moved from ``Collections`` library to ``BuiltIn``.
- Support also non-string keys in ``key=value`` syntax.
- Returned dictionary is ordered and dot-accessible.
- Old syntax to give keys and values separately was deprecated, but
deprecation was later removed in RF 3.0.1.
"""
separate, combined = self._split_dict_items(items)
result = DotDict(self._format_separate_dict_items(separate))
combined = DictVariableTableValue(combined).resolve(self._variables)
result.update(combined)
return result
def _split_dict_items(self, items):
separate = []
for item in items:
name, value = split_from_equals(item)
if value is not None or VariableSplitter(item).is_dict_variable():
break
separate.append(item)
return separate, items[len(separate):]
def _format_separate_dict_items(self, separate):
separate = self._variables.replace_list(separate)
if len(separate) % 2 != 0:
raise DataError('Expected even number of keys and values, got %d.'
% len(separate))
return [separate[i:i+2] for i in range(0, len(separate), 2)]
class _Verify(_BuiltInBase):
def _set_and_remove_tags(self, tags):
set_tags = [tag for tag in tags if not tag.startswith('-')]
remove_tags = [tag[1:] for tag in tags if tag.startswith('-')]
if remove_tags:
self.remove_tags(*remove_tags)
if set_tags:
self.set_tags(*set_tags)
def fail(self, msg=None, *tags):
"""Fails the test with the given message and optionally alters its tags.
The error message is specified using the ``msg`` argument.
It is possible to use HTML in the given error message, similarly
as with any other keyword accepting an error message, by prefixing
the error with ``*HTML*``.
It is possible to modify tags of the current test case by passing tags
after the message. Tags starting with a hyphen (e.g. ``-regression``)
are removed and others added. Tags are modified using `Set Tags` and
`Remove Tags` internally, and the semantics setting and removing them
are the same as with these keywords.
Examples:
| Fail | Test not ready | | | # Fails with the given message. |
| Fail | *HTML*<b>Test not ready</b> | | | # Fails using HTML in the message. |
| Fail | Test not ready | not-ready | | # Fails and adds 'not-ready' tag. |
| Fail | OS not supported | -regression | | # Removes tag 'regression'. |
| Fail | My message | tag | -t* | # Removes all tags starting with 't' except the newly added 'tag'. |
See `Fatal Error` if you need to stop the whole test execution.
"""
self._set_and_remove_tags(tags)
raise AssertionError(msg) if msg else AssertionError()
def fatal_error(self, msg=None):
"""Stops the whole test execution.
The test or suite where this keyword is used fails with the provided
message, and subsequent tests fail with a canned message.
Possible teardowns will nevertheless be executed.
See `Fail` if you only want to stop one test case unconditionally.
"""
error = AssertionError(msg) if msg else AssertionError()
error.ROBOT_EXIT_ON_FAILURE = True
raise error
def should_not_be_true(self, condition, msg=None):
"""Fails if the given condition is true.
See `Should Be True` for details about how ``condition`` is evaluated
and how ``msg`` can be used to override the default error message.
"""
if self._is_true(condition):
raise AssertionError(msg or "'%s' should not be true." % condition)
def should_be_true(self, condition, msg=None):
"""Fails if the given condition is not true.
If ``condition`` is a string (e.g. ``${rc} < 10``), it is evaluated as
a Python expression as explained in `Evaluating expressions` and the
keyword status is decided based on the result. If a non-string item is
given, the status is got directly from its
[http://docs.python.org/library/stdtypes.html#truth|truth value].
The default error message (``<condition> should be true``) is not very
informative, but it can be overridden with the ``msg`` argument.
Examples:
| Should Be True | ${rc} < 10 |
| Should Be True | '${status}' == 'PASS' | # Strings must be quoted |
| Should Be True | ${number} | # Passes if ${number} is not zero |
| Should Be True | ${list} | # Passes if ${list} is not empty |
Variables used like ``${variable}``, as in the examples above, are
replaced in the expression before evaluation. Variables are also
available in the evaluation namespace and can be accessed using special
syntax ``$variable``. This is a new feature in Robot Framework 2.9
and it is explained more thoroughly in `Evaluating expressions`.
Examples:
| Should Be True | $rc < 10 |
| Should Be True | $status == 'PASS' | # Expected string must be quoted |
`Should Be True` automatically imports Python's
[http://docs.python.org/library/os.html|os] and
[http://docs.python.org/library/sys.html|sys] modules that contain
several useful attributes:
| Should Be True | os.linesep == '\\n' | # Unixy |
| Should Be True | os.linesep == '\\r\\n' | # Windows |
| Should Be True | sys.platform == 'darwin' | # OS X |
| Should Be True | sys.platform.startswith('java') | # Jython |
"""
if not self._is_true(condition):
raise AssertionError(msg or "'%s' should be true." % condition)
def should_be_equal(self, first, second, msg=None, values=True,
ignore_case=False):
"""Fails if the given objects are unequal.
Optional ``msg`` and ``values`` arguments specify how to construct
the error message if this keyword fails:
- If ``msg`` is not given, the error message is ``<first> != <second>``.
- If ``msg`` is given and ``values`` gets a true value (default),
the error message is ``<msg>: <first> != <second>``.
- If ``msg`` is given and ``values`` gets a false value, the error
message is simply ``<msg>``. See `Boolean arguments` for more details
about using false values.
If ``ignore_case`` is given a true value (see `Boolean arguments`) and
arguments are strings, it indicates that comparison should be
case-insensitive. New option in Robot Framework 3.0.1.
If both arguments are multiline strings, the comparison is done using
`multiline string comparisons`.
Examples:
| Should Be Equal | ${x} | expected |
| Should Be Equal | ${x} | expected | Custom error message |
| Should Be Equal | ${x} | expected | Custom message | values=False |
| Should Be Equal | ${x} | expected | ignore_case=True |
"""
self._log_types_at_info_if_different(first, second)
if is_truthy(ignore_case) and is_string(first) and is_string(second):
first = first.lower()
second = second.lower()
self._should_be_equal(first, second, msg, values)
def _should_be_equal(self, first, second, msg, values):
if first == second:
return
include_values = self._include_values(values)
if include_values and is_string(first) and is_string(second):
self._raise_multi_diff(first, second)
assert_equal(first, second, msg, include_values)
def _log_types_at_info_if_different(self, first, second):
level = 'DEBUG' if type(first) == type(second) else 'INFO'
self._log_types_at_level(level, first, second)
def _raise_multi_diff(self, first, second):
first_lines, second_lines = first.splitlines(), second.splitlines()
if len(first_lines) < 3 or len(second_lines) < 3:
return
self.log("%s\n!=\n%s" % (first, second))
err = 'Multiline strings are different:\n'
for line in difflib.unified_diff(first_lines, second_lines,
fromfile='first', tofile='second',
lineterm=''):
err += line + '\n'
raise AssertionError(err)
def _include_values(self, values):
return is_truthy(values) and str(values).upper() != 'NO VALUES'
def should_not_be_equal(self, first, second, msg=None, values=True,
ignore_case=False):
"""Fails if the given objects are equal.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
If ``ignore_case`` is given a true value (see `Boolean arguments`) and
both arguments are strings, it indicates that comparison should be
case-insensitive. New option in Robot Framework 3.0.1.
"""
self._log_types_at_info_if_different(first, second)
if is_truthy(ignore_case) and is_string(first) and is_string(second):
first = first.lower()
second = second.lower()
self._should_not_be_equal(first, second, msg, values)
def _should_not_be_equal(self, first, second, msg, values):
assert_not_equal(first, second, msg, self._include_values(values))
def should_not_be_equal_as_integers(self, first, second, msg=None,
values=True, base=None):
"""Fails if objects are equal after converting them to integers.
See `Convert To Integer` for information how to convert integers from
other bases than 10 using ``base`` argument or ``0b/0o/0x`` prefixes.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
See `Should Be Equal As Integers` for some usage examples.
"""
self._log_types_at_info_if_different(first, second)
self._should_not_be_equal(self._convert_to_integer(first, base),
self._convert_to_integer(second, base),
msg, values)
def should_be_equal_as_integers(self, first, second, msg=None, values=True,
base=None):
"""Fails if objects are unequal after converting them to integers.
See `Convert To Integer` for information how to convert integers from
other bases than 10 using ``base`` argument or ``0b/0o/0x`` prefixes.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
Examples:
| Should Be Equal As Integers | 42 | ${42} | Error message |
| Should Be Equal As Integers | ABCD | abcd | base=16 |
| Should Be Equal As Integers | 0b1011 | 11 |
"""
self._log_types_at_info_if_different(first, second)
self._should_be_equal(self._convert_to_integer(first, base),
self._convert_to_integer(second, base),
msg, values)
def should_not_be_equal_as_numbers(self, first, second, msg=None,
values=True, precision=6):
"""Fails if objects are equal after converting them to real numbers.
The conversion is done with `Convert To Number` keyword using the
given ``precision``.
See `Should Be Equal As Numbers` for examples on how to use
``precision`` and why it does not always work as expected. See also
`Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
"""
self._log_types_at_info_if_different(first, second)
first = self._convert_to_number(first, precision)
second = self._convert_to_number(second, precision)
self._should_not_be_equal(first, second, msg, values)
def should_be_equal_as_numbers(self, first, second, msg=None, values=True,
precision=6):
"""Fails if objects are unequal after converting them to real numbers.
The conversion is done with `Convert To Number` keyword using the
given ``precision``.
Examples:
| Should Be Equal As Numbers | ${x} | 1.1 | | # Passes if ${x} is 1.1 |
| Should Be Equal As Numbers | 1.123 | 1.1 | precision=1 | # Passes |
| Should Be Equal As Numbers | 1.123 | 1.4 | precision=0 | # Passes |
| Should Be Equal As Numbers | 112.3 | 75 | precision=-2 | # Passes |
As discussed in the documentation of `Convert To Number`, machines
generally cannot store floating point numbers accurately. Because of
this limitation, comparing floats for equality is problematic and
a correct approach to use depends on the context. This keyword uses
a very naive approach of rounding the numbers before comparing them,
which is both prone to rounding errors and does not work very well if
numbers are really big or small. For more information about comparing
floats, and ideas on how to implement your own context specific
comparison algorithm, see
http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/.
If you want to avoid possible problems with floating point numbers,
you can implement custom keywords using Python's
[http://docs.python.org/library/decimal.html|decimal] or
[http://docs.python.org/library/fractions.html|fractions] modules.
See `Should Not Be Equal As Numbers` for a negative version of this
keyword and `Should Be Equal` for an explanation on how to override
the default error message with ``msg`` and ``values``.
"""
self._log_types_at_info_if_different(first, second)
first = self._convert_to_number(first, precision)
second = self._convert_to_number(second, precision)
self._should_be_equal(first, second, msg, values)
def should_not_be_equal_as_strings(self, first, second, msg=None,
values=True, ignore_case=False):
"""Fails if objects are equal after converting them to strings.
If ``ignore_case`` is given a true value (see `Boolean arguments`), it
indicates that comparison should be case-insensitive. New option in
Robot Framework 3.0.1.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
"""
self._log_types_at_info_if_different(first, second)
first = self._convert_to_string(first)
second = self._convert_to_string(second)
if is_truthy(ignore_case):
first = first.lower()
second = second.lower()
self._should_not_be_equal(first, second, msg, values)
def should_be_equal_as_strings(self, first, second, msg=None, values=True,
ignore_case=False):
"""Fails if objects are unequal after converting them to strings.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``.
If ``ignore_case`` is given a true value (see `Boolean arguments`), it
indicates that comparison should be case-insensitive. New option in
Robot Framework 3.0.1.
If both arguments are multiline strings, the comparison is done using
`multiline string comparisons`.
"""
self._log_types_at_info_if_different(first, second)
first = self._convert_to_string(first)
second = self._convert_to_string(second)
if is_truthy(ignore_case):
first = first.lower()
second = second.lower()
self._should_be_equal(first, second, msg, values)
def should_not_start_with(self, str1, str2, msg=None, values=True,
ignore_case=False):
"""Fails if the string ``str1`` starts with the string ``str2``.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``, as well as for semantics
of the ``ignore_case`` option.
"""
if is_truthy(ignore_case):
str1 = str1.lower()
str2 = str2.lower()
if str1.startswith(str2):
raise AssertionError(self._get_string_msg(str1, str2, msg, values,
'starts with'))
def should_start_with(self, str1, str2, msg=None, values=True,
ignore_case=False):
"""Fails if the string ``str1`` does not start with the string ``str2``.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``, as well as for semantics
of the ``ignore_case`` option.
"""
if is_truthy(ignore_case):
str1 = str1.lower()
str2 = str2.lower()
if not str1.startswith(str2):
raise AssertionError(self._get_string_msg(str1, str2, msg, values,
'does not start with'))
def should_not_end_with(self, str1, str2, msg=None, values=True,
ignore_case=False):
"""Fails if the string ``str1`` ends with the string ``str2``.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``, as well as for semantics
of the ``ignore_case`` option.
"""
if is_truthy(ignore_case):
str1 = str1.lower()
str2 = str2.lower()
if str1.endswith(str2):
raise AssertionError(self._get_string_msg(str1, str2, msg, values,
'ends with'))
def should_end_with(self, str1, str2, msg=None, values=True,
ignore_case=False):
"""Fails if the string ``str1`` does not end with the string ``str2``.
See `Should Be Equal` for an explanation on how to override the default
error message with ``msg`` and ``values``, as well as for semantics
of the ``ignore_case`` option.
"""
if is_truthy(ignore_case):
str1 = str1.lower()
str2 = str2.lower()
if not str1.endswith(str2):
raise AssertionError(self._get_string_msg(str1, str2, msg, values,
'does not end with'))
def should_not_contain(self, container, item, msg=None, values=True,
ignore_case=False):
"""Fails if ``container`` contains ``item`` one or more times.
Works with strings, lists, and anything that supports Python's ``in``
operator.
See `Should Be Equal` for an explanation on how to override the default
error message with arguments ``msg`` and ``values``. ``ignore_case``
has exactly the same semantics as with `Should Contain`.
Examples:
| Should Not Contain | ${some list} | value |
| Should Not Contain | ${output} | FAILED | ignore_case=True |
"""
# TODO: It is inconsistent that errors show original case in 'container'
# 'item' is in lower case. Should rather show original case everywhere
# and add separate '(case-insensitive)' not to the error message.
# This same logic should be used with all keywords supporting
# case-insensitive comparisons.
orig_container = container
if is_truthy(ignore_case) and is_string(item):
item = item.lower()
if is_string(container):
container = container.lower()
elif is_list_like(container):
container = set(x.lower() if is_string(x) else x for x in container)
if item in container:
raise AssertionError(self._get_string_msg(orig_container, item, msg,
values, 'contains'))
def should_contain(self, container, item, msg=None, values=True,
ignore_case=False):
"""Fails if ``container`` does not contain ``item`` one or more times.
Works with strings, lists, and anything that supports Python's ``in``
operator.
See `Should Be Equal` for an explanation on how to override the default
error message with arguments ``msg`` and ``values``.
If ``ignore_case`` is given a true value (see `Boolean arguments`) and
compared items are strings, it indicates that comparison should be
case-insensitive. If the ``container`` is a list-like object, string
items in it are compared case-insensitively. New option in Robot
Framework 3.0.1.
Examples:
| Should Contain | ${output} | PASS |
| Should Contain | ${some list} | value | msg=Failure! | values=False |
| Should Contain | ${some list} | value | ignore_case=True |
"""
orig_container = container
if is_truthy(ignore_case) and is_string(item):
item = item.lower()
if is_string(container):
container = container.lower()
elif is_list_like(container):
container = set(x.lower() if is_string(x) else x for x in container)
if item not in container:
raise AssertionError(self._get_string_msg(orig_container, item, msg,
values, 'does not contain'))
def should_contain_any(self, container, *items, **configuration):
"""Fails if ``container`` does not contain any of the ``*items``.
Works with strings, lists, and anything that supports Python's ``in``
operator.
Supports additional configuration parameters ``msg``, ``values``
and ``ignore_case``, which have exactly the same semantics as arguments
with same names have with `Should Contain`. These arguments must
always be given using ``name=value`` syntax after all ``items``.
Note that possible equal signs in ``items`` must be escaped with
a backslash (e.g. ``foo\\=bar``) to avoid them to be passed in
as ``**configuration``.
Examples:
| Should Contain Any | ${string} | substring 1 | substring 2 |
| Should Contain Any | ${list} | item 1 | item 2 | item 3 |
| Should Contain Any | ${list} | item 1 | item 2 | item 3 | ignore_case=True |
| Should Contain Any | ${list} | @{items} | msg=Custom message | values=False |
New in Robot Framework 3.0.1.
"""
msg = configuration.pop('msg', None)
values = configuration.pop('values', True)
ignore_case = configuration.pop('ignore_case', False)
if configuration:
raise RuntimeError("Unsupported configuration parameter%s: %s."
% (s(configuration),
seq2str(sorted(configuration))))
if not items:
raise RuntimeError('One or more items required.')
orig_container = container
if is_truthy(ignore_case):
items = [x.lower() if is_string(x) else x for x in items]
if is_string(container):
container = container.lower()
elif is_list_like(container):
container = set(x.lower() if is_string(x) else x for x in container)
if not any(item in container for item in items):
msg = self._get_string_msg(orig_container,
seq2str(items, lastsep=' or '),
msg, values,
'does not contain any of',
quote_item2=False)
raise AssertionError(msg)
def should_not_contain_any(self, container, *items, **configuration):
"""Fails if ``container`` contains one or more of the ``*items``.
Works with strings, lists, and anything that supports Python's ``in``
operator.
Supports additional configuration parameters ``msg``, ``values``
and ``ignore_case``, which have exactly the same semantics as arguments
with same names have with `Should Contain`. These arguments must
always be given using ``name=value`` syntax after all ``items``.
Note that possible equal signs in ``items`` must be escaped with
a backslash (e.g. ``foo\\=bar``) to avoid them to be passed in
as ``**configuration``.
Examples:
| Should Not Contain Any | ${string} | substring 1 | substring 2 |
| Should Not Contain Any | ${list} | item 1 | item 2 | item 3 |