robotframework-nl 3.0.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: robotframework-nl
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: robotnl is a proving ground to boost Robot framework closer to Natural Language.
5
5
  Author-email: Johan Foederer <github@famfoe.nl>
6
6
  License: BSD 3-Clause License
@@ -42,14 +42,15 @@ Requires-Python: >=3.8
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
44
  Requires-Dist: robotframework>=7.0
45
+ Dynamic: license-file
45
46
 
46
- # robotframeworkNL - the oneliner
47
- robotframeworkNL is a proving ground to boost Robot framework closer to Natural Language.
47
+ # RobotNL - the oneliner
48
+ RobotNL is a proving ground to boost Robot framework closer to Natural Language.
48
49
 
49
50
  ## Introduction
50
51
  This project is an extension to [Robot framework](https://robotframework.org/) and although [Robot framework](https://robotframework.org/) made a very good step towards the goals of [keyword-driven testing ](https://en.wikipedia.org/wiki/Keyword-driven_testing) to make it readable for all stakeholders, there is still quite a lot of syntax involved that keeps test cases from really staying concise and to-the-point. In this project we will be introducing concepts to lift [Robot framework](https://robotframework.org/) to an even higher level.
51
52
 
52
- This second release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
53
+ This release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
53
54
 
54
55
  ## Installation
55
56
  The recommended installation method is using [pip](http://pip-installer.org)
@@ -100,17 +101,20 @@ Using *Check that* keywords offers a large reduction in the need for variables i
100
101
 
101
102
  ### Time constraints
102
103
 
103
- *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps robotnl optimise its polling algorithm.
104
+ *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps RobotNL optimise its polling algorithm.
104
105
 
105
106
  |**Example using time constaints**||||||
106
107
  |---|---|---|---|---|---|
107
108
  | Request elevator at floor | 3 |||||
108
109
  | Check that | elevator doors are closed | within | 20 seconds ||
109
110
  | Check that | current elevator floor | equals | 3 | within | 1 minute |
111
+ | Check that | elevator doors are opened | within | same timespan ||
112
+
113
+ The last line in the example uses the special argument `same timespan` to indicate that this check is linked to the same time constraint as the previous check. So, once the doors are closed, the elevator should not only reach the requested floor within 1 minute, but also the doors should be opened within that same 1-minute timespan. To use `within same timespan` you must have executed a check with a time constraint before. This is not necessarily the directly preceding line. The most recent time constraint set within scope is used as starting point. For test suites, that is the current test suite, for keywords that is the current keyword.
110
114
 
111
115
  ### Hybrid manual testing
112
116
 
113
- To manually interact with your automated test run during testing or test case development, robotnl offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
117
+ To manually interact with your automated test run during testing or test case development, RobotNL offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
114
118
 
115
119
  ***Check Manual*** allows asking the tester a question. The question typically requests manual verification of an expected outcome. The answer will PASS or FAIL the test case, which is also reflected in the test report.
116
120
 
@@ -1,10 +1,10 @@
1
- # robotframeworkNL - the oneliner
2
- robotframeworkNL is a proving ground to boost Robot framework closer to Natural Language.
1
+ # RobotNL - the oneliner
2
+ RobotNL is a proving ground to boost Robot framework closer to Natural Language.
3
3
 
4
4
  ## Introduction
5
5
  This project is an extension to [Robot framework](https://robotframework.org/) and although [Robot framework](https://robotframework.org/) made a very good step towards the goals of [keyword-driven testing ](https://en.wikipedia.org/wiki/Keyword-driven_testing) to make it readable for all stakeholders, there is still quite a lot of syntax involved that keeps test cases from really staying concise and to-the-point. In this project we will be introducing concepts to lift [Robot framework](https://robotframework.org/) to an even higher level.
6
6
 
7
- This second release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
7
+ This release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
8
8
 
9
9
  ## Installation
10
10
  The recommended installation method is using [pip](http://pip-installer.org)
@@ -55,17 +55,20 @@ Using *Check that* keywords offers a large reduction in the need for variables i
55
55
 
56
56
  ### Time constraints
57
57
 
58
- *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps robotnl optimise its polling algorithm.
58
+ *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps RobotNL optimise its polling algorithm.
59
59
 
60
60
  |**Example using time constaints**||||||
61
61
  |---|---|---|---|---|---|
62
62
  | Request elevator at floor | 3 |||||
63
63
  | Check that | elevator doors are closed | within | 20 seconds ||
64
64
  | Check that | current elevator floor | equals | 3 | within | 1 minute |
65
+ | Check that | elevator doors are opened | within | same timespan ||
66
+
67
+ The last line in the example uses the special argument `same timespan` to indicate that this check is linked to the same time constraint as the previous check. So, once the doors are closed, the elevator should not only reach the requested floor within 1 minute, but also the doors should be opened within that same 1-minute timespan. To use `within same timespan` you must have executed a check with a time constraint before. This is not necessarily the directly preceding line. The most recent time constraint set within scope is used as starting point. For test suites, that is the current test suite, for keywords that is the current keyword.
65
68
 
66
69
  ### Hybrid manual testing
67
70
 
68
- To manually interact with your automated test run during testing or test case development, robotnl offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
71
+ To manually interact with your automated test run during testing or test case development, RobotNL offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
69
72
 
70
73
  ***Check Manual*** allows asking the tester a question. The question typically requests manual verification of an expected outcome. The answer will PASS or FAIL the test case, which is also reflected in the test report.
71
74
 
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "robotframework-nl"
10
- version = "3.0.0"
10
+ version = "3.2.0"
11
11
  description = "robotnl is a proving ground to boost Robot framework closer to Natural Language."
12
12
  readme = "README.md"
13
13
  authors = [{ name = "Johan Foederer", email = "github@famfoe.nl" }]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: robotframework-nl
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: robotnl is a proving ground to boost Robot framework closer to Natural Language.
5
5
  Author-email: Johan Foederer <github@famfoe.nl>
6
6
  License: BSD 3-Clause License
@@ -42,14 +42,15 @@ Requires-Python: >=3.8
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
44
  Requires-Dist: robotframework>=7.0
45
+ Dynamic: license-file
45
46
 
46
- # robotframeworkNL - the oneliner
47
- robotframeworkNL is a proving ground to boost Robot framework closer to Natural Language.
47
+ # RobotNL - the oneliner
48
+ RobotNL is a proving ground to boost Robot framework closer to Natural Language.
48
49
 
49
50
  ## Introduction
50
51
  This project is an extension to [Robot framework](https://robotframework.org/) and although [Robot framework](https://robotframework.org/) made a very good step towards the goals of [keyword-driven testing ](https://en.wikipedia.org/wiki/Keyword-driven_testing) to make it readable for all stakeholders, there is still quite a lot of syntax involved that keeps test cases from really staying concise and to-the-point. In this project we will be introducing concepts to lift [Robot framework](https://robotframework.org/) to an even higher level.
51
52
 
52
- This second release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
53
+ This release introduces ``inline keywords`` and a set of ``Check that`` keywords that help to vastly reduce the amount of ``${...}`` cluttering in your test cases. Combining these concepts helps to write a cleaner [Domain Specific Language](https://en.wikipedia.org/wiki/Domain-specific_language) around your [domain vocabulary](https://en.wikipedia.org/wiki/Jargon).
53
54
 
54
55
  ## Installation
55
56
  The recommended installation method is using [pip](http://pip-installer.org)
@@ -100,17 +101,20 @@ Using *Check that* keywords offers a large reduction in the need for variables i
100
101
 
101
102
  ### Time constraints
102
103
 
103
- *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps robotnl optimise its polling algorithm.
104
+ *Check that* offers support for executing checks that may take some time to complete. When using the optional `within` argument, followed by a time duration, *Check that* will apply *smart polling* to re-evaluate the expression and the keywords during the given period. Specifying the time limit is done using the standard [Robot Framework time format](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#toc-entry-176). It is advised to use a realistic time duration. This sets the correct expectation for the reader and helps RobotNL optimise its polling algorithm.
104
105
 
105
106
  |**Example using time constaints**||||||
106
107
  |---|---|---|---|---|---|
107
108
  | Request elevator at floor | 3 |||||
108
109
  | Check that | elevator doors are closed | within | 20 seconds ||
109
110
  | Check that | current elevator floor | equals | 3 | within | 1 minute |
111
+ | Check that | elevator doors are opened | within | same timespan ||
112
+
113
+ The last line in the example uses the special argument `same timespan` to indicate that this check is linked to the same time constraint as the previous check. So, once the doors are closed, the elevator should not only reach the requested floor within 1 minute, but also the doors should be opened within that same 1-minute timespan. To use `within same timespan` you must have executed a check with a time constraint before. This is not necessarily the directly preceding line. The most recent time constraint set within scope is used as starting point. For test suites, that is the current test suite, for keywords that is the current keyword.
110
114
 
111
115
  ### Hybrid manual testing
112
116
 
113
- To manually interact with your automated test run during testing or test case development, robotnl offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
117
+ To manually interact with your automated test run during testing or test case development, RobotNL offers the *Check manual* and *Check interactive* keywords. These keywords can be included at any point in the test case to suspend the test run at the current position for user input.
114
118
 
115
119
  ***Check Manual*** allows asking the tester a question. The question typically requests manual verification of an expected outcome. The answer will PASS or FAIL the test case, which is also reflected in the test report.
116
120
 
@@ -0,0 +1,394 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # BSD 3-Clause License
4
+ #
5
+ # Copyright (c) 2021, J. Foederer
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # 1. Redistributions of source code must retain the above copyright notice, this
12
+ # list of conditions and the following disclaimer.
13
+ #
14
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
15
+ # this list of conditions and the following disclaimer in the documentation
16
+ # and/or other materials provided with the distribution.
17
+ #
18
+ # 3. Neither the name of the copyright holder nor the names of its
19
+ # contributors may be used to endorse or promote products derived from
20
+ # this software without specific prior written permission.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
33
+ from robot.api import TypeInfo
34
+ from robot.libraries.BuiltIn import BuiltIn
35
+ from robot.running.arguments import TypeConverter
36
+ from robot.utils import is_list_like
37
+
38
+ from .inline_keywords import keyword
39
+
40
+ class CheckOperator:
41
+ """
42
+ This class defines a set of commonly used operators for use by 'Check that'
43
+ and other check keywords
44
+ """
45
+
46
+ ################################################################################################
47
+ # Generic operators that can work on basically any object type
48
+ def equals(self, lValue, rValue):
49
+ """Checks whether the left and right side are equal to each other [`=`]
50
+
51
+ Applies Robot type conversions when executing the check.
52
+ Examples:
53
+ | `Check that` | 7 | `=` | 7 |
54
+ | `Check that` | ${7} | `=` | 7.0 |
55
+ | `Check that` | _Two times_ | 6 | `equals` | 12 |
56
+ | `Check that` | text | `equals` | TeXT |
57
+ """
58
+ return OperatorProxy("==").basicOperator(lValue, rValue)
59
+
60
+ def is_less_than(self, lValue, rValue):
61
+ """Checks whether the left side `is less than` or smaller than the right side [`<`]
62
+
63
+ Applies Robot type conversions when executing the check.
64
+ Examples:
65
+ | `Check that` | 2 | `<` | 4 |
66
+ | `Check that` | 2 | `is less than` | 4 |
67
+ """
68
+ return OperatorProxy("<").basicOperator(lValue, rValue)
69
+
70
+ def is_greater_than(self, lValue, rValue):
71
+ """Checks whether the left side `is greater than` or larger than the right side [`>`]
72
+
73
+ Applies Robot type conversions when executing the check.
74
+ Examples:
75
+ | `Check that` | 4 | `>` | 2 |
76
+ | `Check that` | 4 | `is greater than` | 2 |
77
+ """
78
+ return OperatorProxy(">").basicOperator(lValue, rValue)
79
+
80
+ def is_less_than_or_equal_to(self, lValue, rValue):
81
+ """Checks whether the left side `is less than or equal to` the right side [`≤`]
82
+
83
+ Applies Robot type conversions when executing the check.
84
+ Examples:
85
+ | `Check that` | 2 | `≤` | 2 |
86
+ | `Check that` | 2 | `is less than or equal to` | 4 |
87
+ """
88
+ return OperatorProxy("<=").basicOperator(lValue, rValue)
89
+
90
+ def is_greater_than_or_equal_to(self, lValue, rValue):
91
+ """Checks whether the left side `is greater than or equal to` the right side [`≥`]
92
+
93
+ Applies Robot type conversions when executing the check.
94
+ Examples:
95
+ | `Check that` | 4 | `≥` | 4 |
96
+ | `Check that` | 4 | `is greater than or equal to` | 2 |
97
+ """
98
+ return OperatorProxy(">=").basicOperator(lValue, rValue)
99
+
100
+ def does_not_equal(self, lValue, rValue):
101
+ """Checks whether the left side `does not equal`, i.e. is different from the right side [`≠`]
102
+
103
+ Applies Robot type conversions when executing the check.
104
+ Examples:
105
+ | `Check that` | 7 | `≠` | 13 |
106
+ | `Check that` | 7 | `≠` | 7.01 |
107
+ | `Check that` | _Two times_ | 6 | `does not equal` | 13 |
108
+ | `Check that` | random text | `does not equal` | my text |
109
+ """
110
+ return OperatorProxy("!=").basicOperator(lValue, rValue)
111
+
112
+ ################################################################################################
113
+ # Operators that work on text items str() or Unicode()
114
+ def contains_text(self, baseString, subString):
115
+ """Performs a case insensitive check whether the right side is a substring of the left side
116
+
117
+ Examples:
118
+ | `Check that` | the time is now | `contains text` | me |
119
+ | `Check that` | the time is now | `contains text` | ME |
120
+ | `Check That` | Robotstraße | `contains text` | Strasse |
121
+ """
122
+ try:
123
+ return self.contains_exact_text(baseString.casefold(), subString.casefold())
124
+ except:
125
+ return False
126
+
127
+ def contains_exact_text(self, baseString, subString):
128
+ """Performs a case sensitive check whether the right side is a substring of the left side
129
+
130
+ Examples:
131
+ | `Check that` | the time is now | `contains text` | me |
132
+ | `Check That` | Robotstraße | `contains text` | straße |
133
+ """
134
+ try:
135
+ return subString in baseString
136
+ except:
137
+ return False
138
+
139
+ def matches_without_case_to(self, leftText, rightText):
140
+ """Performs a case insensitive check whether the left and right side texts are equal
141
+
142
+ Examples:
143
+ | `Check that` | the time is now | `matches without case to` | the time is now |
144
+ | `Check that` | the time is now | `matches without case to` | THE TIME IS NOW |
145
+ | `Check That` | Robotstraße | `matches without case to` | ROBOTstrasse |
146
+ """
147
+ try:
148
+ return self.matches_with_case_to(leftText.casefold(), rightText.casefold())
149
+ except:
150
+ return False
151
+
152
+ def matches_with_case_to(self, leftText, rightText):
153
+ """Performs a case sensitive check whether the left and right side texts are equal
154
+
155
+ Examples:
156
+ | `Check that` | the time is now | `matches with case to` | the time is now |
157
+ | `Check That` | Robotstraße | `matches with case to` | Robotstraße |
158
+ """
159
+ return leftText == rightText
160
+
161
+ def does_not_contain_text(self, baseString, subString):
162
+ """Performs a case insensitive check whether the right side is not a substring of the left side
163
+
164
+ Examples:
165
+ | `Check that` | random text | `does not contain text` | my text |
166
+ """
167
+ return not self.contains_text(baseString, subString)
168
+
169
+ def does_not_contain_exact_text(self, baseString, subString):
170
+ """Performs a case sensitive check whether the right side is not a substring of the left side
171
+
172
+ Examples:
173
+ | `Check that` | random text | `does not contain exact text` | my text |
174
+ | `Check that` | random text | `does not contain exact text` | RANDOM text |
175
+ """
176
+ return not self.contains_exact_text(baseString, subString)
177
+
178
+ def does_not_match_without_case_to(self, leftText, rightText):
179
+ """Performs a case insensitive check whether the left and right side texts are different
180
+
181
+ Examples:
182
+ | `Check that` | random text | `does not match without case to` | my text |
183
+ """
184
+ return not self.matches_without_case_to(leftText, rightText)
185
+
186
+ def does_not_match_with_case_to(self, leftText, rightText):
187
+ """Performs a case sensitive check whether the left and right side texts are different
188
+
189
+ Examples:
190
+ | `Check that` | random text | `does not match with case to` | my text |
191
+ | `Check that` | random text | `does not match with case to` | RANDOM text |
192
+ """
193
+ return not self.matches_with_case_to(leftText, rightText)
194
+
195
+ ################################################################################################
196
+ # Operators that work on lists or other sequences
197
+ def is_empty(self, sequence):
198
+ """Checks whether the sequence on the left does not contain any items.
199
+
200
+ Example:
201
+ | Take a new suitcase |
202
+ | `Check that` | suitcase | `is empty` |
203
+ _Assumes a 'box' type to be defined with associated action and observation keywords._
204
+ """
205
+ return len(sequence) == 0
206
+
207
+ @keyword("contains ${n} items")
208
+ def contains_n_items(self, n:int, sequence):
209
+ """Checks whether the sequence on the left contains ${n} items. Uses python's len-operator
210
+ to count the number of items.
211
+
212
+ Example:
213
+ | `Check precondition` | suitcase | `is empty` |
214
+ | Put toothbrush into suitcase |
215
+ | `Check that` | suitcase | `contains 1 item` |
216
+ | Put t-shirt into suitcase |
217
+ | `Check that` | suitcase | `contains 2 items` |
218
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
219
+ """
220
+ count = len(sequence)
221
+ BuiltIn().log(f"Counted {count} items")
222
+ return count == n
223
+
224
+ def contains_1_item(self, sequence):
225
+ return self.contains_n_items(1, sequence)
226
+ contains_1_item.__doc__ = contains_n_items.__doc__
227
+
228
+ def contains(self, sequence, part):
229
+ """Checks whether part is present in sequence. Uses Python's primitive in-operator.
230
+
231
+ Example:
232
+ | Put toothbrush into suitcase |
233
+ | Put t-shirt into suitcase |
234
+ | `Check that` | suitcase | `contains` | toothbrush |
235
+ | `Check that` | suitcase | `contains` | t-shirt |
236
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
237
+ """
238
+ return part in sequence
239
+
240
+ def does_not_contain(self, sequence, part):
241
+ """Checks whether part is present in sequence. Uses Python's primitive 'not in' operator.
242
+
243
+ Example:
244
+ | `Check precondition` | suitcase | `is empty` |
245
+ | Put toothbrush into suitcase |
246
+ | `Check that` | suitcase | `does not contain` | t-shirt |
247
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
248
+ """
249
+ return part not in sequence
250
+
251
+ def contains_item(self, sequence, part):
252
+ """Checks whether the right side item(s) is/are part of the sequence on the left side.
253
+
254
+ `Contains item` and the plural `Contains items` are aliases. The difference with `Contains`
255
+ is that these iterate over the sequence and applies *automatic Robot type conversion*
256
+ between elements when applicable. Duplicate items from the right side can match a single
257
+ item from the left side.
258
+
259
+ Example:
260
+ | Put toothbrush into suitcase |
261
+ | Put t-shirt into suitcase |
262
+ | `Check that` | suitcase | `contains item` | toothbrush |
263
+ | `Check that` | suitcase | `contains items` | t-shirt | toothbrush |
264
+ | `Check that` | suitcase's lock code | `contains items` | 7 | ${2} |
265
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
266
+ """
267
+ if not is_list_like(part):
268
+ part = [part]
269
+ for elem in part:
270
+ BuiltIn().log(f"Processing '{elem}' from right side")
271
+ for item in sequence:
272
+ if self.equals(elem, item):
273
+ BuiltIn().log("Matched")
274
+ break
275
+ BuiltIn().log("No match")
276
+ else:
277
+ BuiltIn().log(f"{elem} not present in left side list")
278
+ return False
279
+ return True
280
+
281
+ # Alias for plural form
282
+ contains_items = contains_item
283
+
284
+ def contains_exactly_the_items_from(self, sequence, sequence_right):
285
+ """
286
+ Checks whether the sequence on the right side contains all items of the left side and vice versa.
287
+ Iterates over sequence on the left and matches each element with a single element on the right.
288
+ The items can be in any order. Automatic Robot type conversion is applied when applicable.
289
+
290
+ Example:
291
+ | `Check precondition` | suitcase | `is empty` |
292
+ | Put toothbrush into suitcase |
293
+ | Put t-shirt into suitcase |
294
+ | `Check that` | suitcase | `contains exactly the items from` | toothbrush | t-shirt |
295
+ | `Check that` | suitcase | `contains exactly the items from` | t-shirt | toothbrush |
296
+ | `Check that` | my packing list | `contains exactly the items from` | toothbrush | t-shirt |
297
+ | `Check that` | suitcase | `contains exactly the items from` | my packing list |
298
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
299
+ """
300
+ if isinstance(sequence_right, str):
301
+ sequence_right = [sequence_right]
302
+ sequence_right = [*sequence_right]
303
+ for item in sequence:
304
+ BuiltIn().log(f"Processing '{item}' from left side list")
305
+ for i in range(len(sequence_right)):
306
+ if self.equals(item, sequence_right[i]):
307
+ sequence_right.pop(i)
308
+ break
309
+ else:
310
+ BuiltIn().log(f"Item '{item}' from left side is not found in the list on the right side")
311
+ return False
312
+ if len(sequence_right) > 0:
313
+ BuiltIn().log(f"Not all items from right side list are present: {sequence_right}")
314
+ return False
315
+
316
+ return True
317
+
318
+ def does_not_contain_item(self, sequence, part):
319
+ """
320
+ Checks whether the right side item is not part of the sequence on the left side.
321
+ Iterates over the sequence and applies automatic Robot type conversion where applicable.
322
+
323
+ No plural variant is available for this keyword due to ambiguity with List-like values. When
324
+ writing "[2, 4, 6] does not contain items [4, 5, 6]", one could expect a pass, because the
325
+ set [4, 5, 6] is not contained, but also a fail because item 4 *is* part of the left side set.
326
+
327
+ Example:
328
+ | `Check precondition` | suitcase | `is empty` |
329
+ | Put toothbrush into suitcase |
330
+ | `Check that` | suitcase | `does not contain item` | t-shirt |
331
+ _Assumes a 'suitcase' type to be defined with associated action and observation keywords._
332
+ """
333
+ if is_list_like(part):
334
+ raise TypeError("List-like items not accepted as right side value")
335
+ return not self.contains_item(sequence, part)
336
+
337
+ # Add operator keywords that do not comply to Python's identifier syntax
338
+ setattr(CheckOperator, "=", CheckOperator.equals)
339
+ setattr(CheckOperator, "<", CheckOperator.is_less_than)
340
+ setattr(CheckOperator, ">", CheckOperator.is_greater_than)
341
+ setattr(CheckOperator, "≤", CheckOperator.is_less_than_or_equal_to)
342
+ setattr(CheckOperator, "≥", CheckOperator.is_greater_than_or_equal_to)
343
+ setattr(CheckOperator, "≠", CheckOperator.does_not_equal)
344
+
345
+ class OperatorProxy:
346
+ """
347
+ Proxy class for mapping generic Robot comparison keywords to Python operators
348
+ """
349
+ def __init__(self, s_operator):
350
+ self.__s_Operator = s_operator
351
+
352
+ @staticmethod
353
+ def __typeCastRobotStringValue(leadingValue, otherValue, name):
354
+ """
355
+ When Robot files are parsed, arguments are always passed as string even when comparing
356
+ numbers or other objects. When a string argument is detected the other argument is
357
+ deemed leading. This function converts the other argument to the leading value's type
358
+ when possible. Otherwise the value is kept unchanged, inevitably leading to a mismatch
359
+ in comparison.
360
+
361
+ Precondition: otherValue is of types str and supports .casefold()
362
+ returns type casted otherValue
363
+ """
364
+ CastedOther = otherValue # By default leave untouched
365
+ converter = TypeConverter.converter_for(TypeInfo.from_type(type(leadingValue)))
366
+ if converter:
367
+ try:
368
+ CastedOther = converter.convert(otherValue, name)
369
+ except ValueError as err:
370
+ BuiltIn().log(err, level='DEBUG')
371
+ BuiltIn().log(f"Comparing as {converter.type_name} values")
372
+
373
+ if isinstance(CastedOther, str):
374
+ # By default compare as case insensitive Unicode. Note that it already was a string.
375
+ BuiltIn().log(f"Interpreting {name} '{otherValue}' as string (case insensitive)")
376
+ CastedOther = str(otherValue).casefold()
377
+
378
+ return CastedOther
379
+
380
+ def basicOperator(self, lvalue, rvalue):
381
+ # Local argument copies for assignment
382
+ lValue = lvalue
383
+ rValue = rvalue
384
+
385
+ if type(lvalue) is str:
386
+ lValue = OperatorProxy.__typeCastRobotStringValue(rvalue, lvalue, "left operand")
387
+
388
+ if type(rvalue) is str:
389
+ rValue = OperatorProxy.__typeCastRobotStringValue(lvalue, rvalue, "right operand")
390
+
391
+ if lValue is lvalue and rValue is rvalue:
392
+ BuiltIn().log("Comparing values as is")
393
+
394
+ return eval(f"lValue {self.__s_Operator} rValue")
@@ -38,6 +38,7 @@ except ImportError:
38
38
  tkinter = False
39
39
 
40
40
  from robot.libraries.BuiltIn import BuiltIn
41
+ from robot.running import RUN_KW_REGISTER
41
42
  from robot.utils import timestr_to_secs, secs_to_timestr
42
43
  from .inline_keywords import is_keyword
43
44
 
@@ -47,7 +48,25 @@ class CheckFailed(RuntimeError):
47
48
  class RobotChecks:
48
49
  ROBOT_LIBRARY_SCOPE = "GLOBAL"
49
50
  def __init__(self):
51
+ self.ROBOT_LIBRARY_LISTENER = self
50
52
  self.__gui = None
53
+ self.scope_depth = 0
54
+ self.nested_timespans = {}
55
+
56
+ def _start_suite(self, data, result):
57
+ self.scope_depth = 0
58
+ self.nested_timespans = {}
59
+ _end_suite = _start_suite
60
+
61
+ def _start_keyword(self, data, result):
62
+ self.scope_depth += 1
63
+
64
+ def _end_keyword(self, data, result):
65
+ if self.scope_depth + 1 in self.nested_timespans:
66
+ # use depth+1 because 'check that' itself is also a keyword. The +1
67
+ # keeps all check thats on the same level grouped.
68
+ self.nested_timespans.pop(self.scope_depth + 1)
69
+ self.scope_depth -= 1
51
70
 
52
71
  @property
53
72
  def _gui(self):
@@ -73,10 +92,11 @@ class RobotChecks:
73
92
  point where it was able to check the requirement it was testing for.
74
93
  """
75
94
  try:
76
- return RobotChecks.__execute_check("Precondition", *args)
95
+ return self.__execute_check("Precondition", *args)
77
96
  except CheckFailed as failure:
78
97
  failure.ROBOT_CONTINUE_ON_FAILURE = False
79
98
  raise failure
99
+ RUN_KW_REGISTER.register_run_keyword('robotnl', check_precondition.__name__, args_to_process=0, deprecation_warning=False)
80
100
 
81
101
  def check_postcondition(self, *args):
82
102
  """
@@ -89,10 +109,11 @@ class RobotChecks:
89
109
  the cause of failure.
90
110
  """
91
111
  try:
92
- return RobotChecks.__execute_check("Postcondition", *args)
112
+ return self.__execute_check("Postcondition", *args)
93
113
  except CheckFailed as failure:
94
114
  failure.ROBOT_CONTINUE_ON_FAILURE = False
95
115
  raise failure
116
+ RUN_KW_REGISTER.register_run_keyword('robotnl', check_postcondition.__name__, args_to_process=0, deprecation_warning=False)
96
117
 
97
118
  def check_that(self, *args):
98
119
  """
@@ -105,37 +126,51 @@ class RobotChecks:
105
126
  Check that has two basic forms.
106
127
  - A single keyword (with its arguments) can be evaluated to a truth value
107
128
  - Two values or keywords (with their arguments) can be evaluated using an operator. It will
108
- then have the form Check that ``<keyword or value>`` ``<operator>`` ``<keyword or value>``.
129
+ then have the form: Check that ``<keyword or value>`` ``<operator>`` ``<keyword or value>``.
109
130
 
110
- Operator can be any Robot keyword taking exactly two values (left and right operands) as
111
- input. A number of predefined operators on numeric, string and list types are included in
112
- this library.
131
+ Operator can be any Robot keyword taking at least one argument as input. The first argument is
132
+ placed on the left side of the operator, the left operand. Any following arguments are placed
133
+ to the right of the operator and form the right operands. A keyword with its argument values is
134
+ treated as a single operand. A number of predefined operators on numeric, string and list types
135
+ are included in this library.
113
136
 
114
137
  Examples:
115
138
  | `Check that` | 3 | `=` | 3 |
116
- | `Check that` | Two times | 6 | `equals` | 12 |
117
- | `Check that` | Two times | 5 | `≠` | Two times | 7 |
118
- | `Check that` | Earth exists |
139
+ | `Check that` | _Two times_ | 6 | `equals` | 12 |
140
+ | `Check that` | _Two times_ | 5 | `≠` | _Two times_ | 7 |
141
+ | `Check that` | _the shoe box_ | _contains 2 items_ |
142
+ | `Check that` | _Earth exists_ |
119
143
 
120
144
  'Two times' in these examples is assumed to be defined as a Robot keyword that takes one
121
- argument and multiplies it by 2. Check that will pass if the evaluated result of Two times 6
122
- equals the fixed expected value 12.
145
+ argument and multiplies it by 2. `Check that` will pass if the evaluated result of _Two
146
+ times_ 6 equals the fixed expected value 12.
123
147
 
124
148
  *Adding time constraints*:\n
125
149
  Any check can be extended with an additional timing constraint by adding ``within``
126
150
  This will cause the condition to be reevaluated until it becomes true, or until
127
151
  the specified time has passed. In the latter case the test case will fail.
128
152
 
153
+ Multiple checks can be linked to the same time constraint by using ``same timespan``
154
+ as the time constraint value. Suppose the previous time constraint was 5 seconds
155
+ and that check took 2 seconds to complete. If the next check uses
156
+ ``within same timespan``, then this check has the remaining 3 seconds to
157
+ complete.
158
+
129
159
  Example with time constraint:
130
- | `Check that` | condition is true | within | 1 minute 30 seconds |
160
+ | `Check that` | _condition is true_ | within | 1 minute 30 seconds |
131
161
 
132
162
  Elevator example:
133
- | `Check that` | elevator doors are closed |
134
- | Request elevator at floor | 3 |
135
- | `Check that` | elevator floor | `equals` | 3 | within | 20 seconds |
136
- | `Check that` | offset to floor level in mm | `≤` | 5 | within | 3 seconds |
163
+ | _Request elevator at floor_ | 3 |
164
+ | `Check that` | _elevator doors are closed_ | within | 20 seconds |
165
+ | `Check that` | _elevator floor_ | `equals` | 3 | within | 1 minute |
166
+ | `Check that` | _elevator doors are opened_ | within | same timespan |
167
+
168
+ In the elevator example, the doors should be closed within 20 seconds after requesting a
169
+ floor. Then the elevator can take a maximum of 1 minute to reach the floor and open its
170
+ doors. `Check that` will continue as soon as the condition is detected.
137
171
  """
138
- return RobotChecks.__execute_check("Requirement", *args)
172
+ return self.__execute_check("Requirement", *args)
173
+ RUN_KW_REGISTER.register_run_keyword('robotnl', check_that.__name__, args_to_process=0, deprecation_warning=False)
139
174
 
140
175
  def check_manual(self, checkRequestText=""):
141
176
  """
@@ -206,8 +241,7 @@ class RobotChecks:
206
241
  except Exception as e:
207
242
  BuiltIn().log_to_console("Error in interactive keyword '%s'\n\n%s" % (newInput, e))
208
243
 
209
- @staticmethod
210
- def __execute_check(checkType, *args):
244
+ def __execute_check(self, checkType, *args):
211
245
  """
212
246
  Parse arguments for check keyword to determine its operands, evaluate them and execute the
213
247
  check.
@@ -219,14 +253,35 @@ class RobotChecks:
219
253
  TimeOutInSeconds = 0
220
254
  TimeRemaining = True
221
255
  s_TimeConstraint = ""
256
+ StartTime = time.perf_counter()
222
257
  if len(Arguments) >= 2 and str(Arguments[-2]).lower() == 'within':
223
- EvaluatedTimeArg = RobotChecks.__evaluateOperand([Arguments[-1]])[0]
224
- TimeOutInSeconds = timestr_to_secs(EvaluatedTimeArg)
225
- s_TimeConstraint = Arguments[-1]
258
+ timearg = Arguments[-1]
226
259
  Arguments = Arguments[:-2]
260
+ running_timespan_start, running_timespan, s_TimeConstraint =self.nested_timespans.get(
261
+ self.scope_depth, (None, None, ''))
262
+ if str(timearg).lower() == 'same timespan':
263
+ if running_timespan is None:
264
+ BuiltIn().fail("Joint timespan expected, but was not set before this check.")
265
+ TimeOutInSeconds = round(running_timespan_start + running_timespan - StartTime, ndigits=3)
266
+
267
+ remaining = self._human_time(0 if TimeOutInSeconds < 0 else TimeOutInSeconds)
268
+ BuiltIn().log(f"Reusing joint timespan: {s_TimeConstraint}\n"
269
+ f"Must complete within the remaining {remaining}")
270
+ else:
271
+ try:
272
+ EvaluatedTimeArg, s_TimeConstraint = RobotChecks.__evaluateOperand([timearg])
273
+ TimeOutInSeconds = timestr_to_secs(EvaluatedTimeArg)
274
+ except:
275
+ # End any running timespan on error
276
+ if self.scope_depth in self.nested_timespans:
277
+ self.nested_timespans.pop(self.scope_depth)
278
+ raise
279
+ if is_keyword(timearg):
280
+ s_TimeConstraint = f"{timearg} [{secs_to_timestr(TimeOutInSeconds)}]"
281
+ self.nested_timespans[self.scope_depth] = (StartTime, TimeOutInSeconds, s_TimeConstraint)
227
282
 
228
283
  if not len(Arguments):
229
- BuiltIn().fail("%s check failed. There was nothing to check." % checkType)
284
+ BuiltIn().fail(f"{checkType} check failed. There was nothing to check.")
230
285
 
231
286
  ############################################################################################
232
287
  # Build expression
@@ -259,8 +314,6 @@ class RobotChecks:
259
314
  # Evaluate expression
260
315
  EvaluatedResult = None
261
316
 
262
- StartTime = time.perf_counter()
263
- TimeLeft = TimeOutInSeconds
264
317
  PollMax = 20 # After 20s people start wondering: "Is it still going?" Time for an update.
265
318
  PollMin = min(PollMax/8, TimeOutInSeconds*3/100) # Shortest delay is 3% of the target time.
266
319
  PollDelay = PollMin # Initial poll delay will be 2x PollMin
@@ -308,8 +361,14 @@ class RobotChecks:
308
361
  ReportString = f"{checkType} check on '{s_LeftOperand} {OperatorKeyword} {s_RightOperand}'"
309
362
 
310
363
  if s_TimeConstraint:
311
- ReportString += " within %s" % secs_to_timestr(TimeOutInSeconds)
364
+ running_start, timespan, timestr = self.nested_timespans.get(self.scope_depth, (None, None, ''))
365
+ if TimeRemaining and running_start != StartTime:
366
+ elapsed = time.perf_counter() - running_start
367
+ BuiltIn().log(f"Check completed {self._human_time(elapsed)} into the alotted timespan ({timestr})")
368
+ ReportString += f" within {timestr}"
312
369
  if not TimeRemaining and EvaluatedResult == "passed":
370
+ overshoot = time.perf_counter() - (running_start + timespan)
371
+ BuiltIn().log(f"Check completed {self._human_time(overshoot)} too late")
313
372
  ReportString += " (too late)"
314
373
  raise CheckFailed(ReportString)
315
374
 
@@ -352,3 +411,10 @@ class RobotChecks:
352
411
  s_Operand += f" [{s_Value}]"
353
412
 
354
413
  return Value, s_Operand
414
+
415
+ @staticmethod
416
+ def _human_time(time_in_seconds: float):
417
+ """return a human readable string for a time in seconds"""
418
+ SKIP_MILLI = 10 # Do not report milliseconds for timespans above SKIP_MILLI seconds
419
+ rounded_time = int(time_in_seconds) if time_in_seconds > SKIP_MILLI else round(time_in_seconds, ndigits=3)
420
+ return secs_to_timestr(rounded_time)
@@ -0,0 +1 @@
1
+ VERSION = '3.2.0'
@@ -1,228 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # BSD 3-Clause License
4
- #
5
- # Copyright (c) 2021, J. Foederer
6
- # All rights reserved.
7
- #
8
- # Redistribution and use in source and binary forms, with or without
9
- # modification, are permitted provided that the following conditions are met:
10
- #
11
- # 1. Redistributions of source code must retain the above copyright notice, this
12
- # list of conditions and the following disclaimer.
13
- #
14
- # 2. Redistributions in binary form must reproduce the above copyright notice,
15
- # this list of conditions and the following disclaimer in the documentation
16
- # and/or other materials provided with the distribution.
17
- #
18
- # 3. Neither the name of the copyright holder nor the names of its
19
- # contributors may be used to endorse or promote products derived from
20
- # this software without specific prior written permission.
21
- #
22
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26
- # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
- # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
- # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
- # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
- # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
-
33
- from robot.api import TypeInfo
34
- from robot.libraries.BuiltIn import BuiltIn
35
- from robot.running.arguments import TypeConverter
36
-
37
-
38
- class CheckOperator:
39
- """
40
- This class defines a set of commonly used operators for use by 'Check that'
41
- and other check keywords
42
- """
43
-
44
- ################################################################################################
45
- # Generic operators that can work on basically any object type
46
- def equals(self, lValue, rValue):
47
- """Checks whether the left and right side are equal to each other [=]"""
48
- return OperatorProxy("==").basicOperator(lValue, rValue)
49
-
50
- def is_less_than(self, lValue, rValue):
51
- """Checks whether the left side is less than or smaller than the right side [<]"""
52
- return OperatorProxy("<").basicOperator(lValue, rValue)
53
-
54
- def is_greater_than(self, lValue, rValue):
55
- """Checks whether the left side is greater than or larger than the right side [>]"""
56
- return OperatorProxy(">").basicOperator(lValue, rValue)
57
-
58
- def is_less_than_or_equal_to(self, lValue, rValue):
59
- """Checks whether the left side is less than or equal to the right side [≤]"""
60
- return OperatorProxy("<=").basicOperator(lValue, rValue)
61
-
62
- def is_greater_than_or_equal_to(self, lValue, rValue):
63
- """Checks whether the left side is greater than or equal to the right side [≥]"""
64
- return OperatorProxy(">=").basicOperator(lValue, rValue)
65
-
66
- def does_not_equal(self, lValue, rValue):
67
- """Checks whether the left side is different from the right side [≠]"""
68
- return OperatorProxy("!=").basicOperator(lValue, rValue)
69
-
70
- ################################################################################################
71
- # Operators that work on text items str() or Unicode()
72
- def contains_text(self, baseString, subString):
73
- """Performs a case insensitive check whether the right side is a substring of the left side"""
74
- try:
75
- return self.contains_exact_text(baseString.lower(), subString.lower())
76
- except:
77
- return False
78
-
79
- def contains_exact_text(self, baseString, subString):
80
- """Performs a case sensitive check whether the right side is a substring of the left side"""
81
- try:
82
- return subString in baseString
83
- except:
84
- return False
85
-
86
- def matches_without_case_to(self, leftText, rightText):
87
- """Performs a case insensitive check whether the left and right side texts are equal"""
88
- try:
89
- return self.matches_with_case_to(leftText.lower(), rightText.lower())
90
- except:
91
- return False
92
-
93
- def matches_with_case_to(self, leftText, rightText):
94
- """Performs a case sensitive check whether the left and right side texts are equal"""
95
- return leftText == rightText
96
-
97
- def does_not_contain_text(self, baseString, subString):
98
- """Performs a case insensitive check whether the right side is not a substring of the left side"""
99
- return not self.contains_text(baseString, subString)
100
-
101
- def does_not_contain_exact_text(self, baseString, subString):
102
- """Performs a case sensitive check whether the right side is not a substring of the left side"""
103
- return not self.contains_exact_text(baseString, subString)
104
-
105
- def does_not_match_without_case_to(self, leftText, rightText):
106
- """Performs a case insensitive check whether the left and right side texts are different"""
107
- return not self.matches_without_case_to(leftText, rightText)
108
-
109
- def does_not_match_with_case_to(self, leftText, rightText):
110
- """Performs a case sensitive check whether the left and right side texts are different"""
111
- return not self.matches_with_case_to(leftText, rightText)
112
-
113
- ################################################################################################
114
- # Operators that work on lists or other sequences
115
- def is_empty(self, iterable):
116
- return len(iterable) == 0
117
-
118
- def contains(self, iterable, part):
119
- """Checks whether part is present in iterable. Uses primitive 'in' operator."""
120
- return part in iterable
121
-
122
- def does_not_contain(self, iterable, part):
123
- """Checks whether part is present in iterable. Uses primitive 'not in' operator."""
124
- return part not in iterable
125
-
126
- def contains_item(self, sequence, part):
127
- """
128
- Checks whether the right side item is part of the sequence on the left side.
129
- Iterates over the sequence to apply automatic Robot type conversion where applicable.
130
- """
131
- for item in sequence:
132
- BuiltIn().log(f"Processing '{item}' from list")
133
- if self.equals(part, item):
134
- BuiltIn().log("Matched")
135
- return True
136
- BuiltIn().log("No match")
137
- return False
138
-
139
- def contains_exactly_the_items_from(self, sequence, sequence_right):
140
- """
141
- Checks whether the sequence on the right side contains all items of the left side and vice versa.
142
- Iterates over sequence on the left and matches each element with a single element on the right.
143
- """
144
- if isinstance(sequence_right, str):
145
- sequence_right = [sequence_right]
146
- sequence_right = [*sequence_right]
147
- for item in sequence:
148
- item_found_in_parts = False
149
- BuiltIn().log(f"Processing '{item}' from left side list")
150
- for i in range(len(sequence_right)):
151
- if self.equals(item, sequence_right[i]):
152
- sequence_right.pop(i)
153
- item_found_in_parts = True
154
- break
155
- if not item_found_in_parts:
156
- BuiltIn().log(f"Item '{item}' from left side is not found in the list on the right side")
157
- return False
158
- if len(sequence_right) > 0:
159
- BuiltIn().log(f"Not all items from right side list are present: {sequence_right}")
160
- return False
161
-
162
- return True
163
-
164
- def does_not_contain_item(self, sequence, part):
165
- """
166
- Checks whether the right side item is not part of the sequence on the left side.
167
- Iterates over the sequence to apply automatic Robot type conversion where applicable.
168
- """
169
- return not self.contains_item(sequence, part)
170
-
171
- # Add operator keywords that do not comply to Python's identifier syntax
172
- setattr(CheckOperator, "=", CheckOperator.equals)
173
- setattr(CheckOperator, "<", CheckOperator.is_less_than)
174
- setattr(CheckOperator, ">", CheckOperator.is_greater_than)
175
- setattr(CheckOperator, "≤", CheckOperator.is_less_than_or_equal_to)
176
- setattr(CheckOperator, "≥", CheckOperator.is_greater_than_or_equal_to)
177
- setattr(CheckOperator, "≠", CheckOperator.does_not_equal)
178
-
179
- class OperatorProxy:
180
- """
181
- Proxy class for mapping generic Robot comparison keywords to Python operators
182
- """
183
- def __init__(self, s_operator):
184
- self.__s_Operator = s_operator
185
-
186
- @staticmethod
187
- def __typeCastRobotStringValue(leadingValue, otherValue, name):
188
- """
189
- When Robot files are parsed, arguments are always passed as string even when comparing
190
- numbers or other objects. When a string argument is detected the other argument is
191
- deemed leading. This function converts the other argument to the leading value's type
192
- when possible. Otherwise the value is kept unchanged, inevitably leading to a mismatch
193
- in comparison.
194
-
195
- Precondition: otherValue is of types str and supports .casefold()
196
- returns type casted otherValue
197
- """
198
- CastedOther = otherValue # By default leave untouched
199
- converter = TypeConverter.converter_for(TypeInfo.from_type(type(leadingValue)))
200
- if converter:
201
- try:
202
- CastedOther = converter.convert(otherValue, name)
203
- except ValueError as err:
204
- BuiltIn().log(err, level='DEBUG')
205
- BuiltIn().log(f"Comparing as {converter.type_name} values")
206
-
207
- if isinstance(CastedOther, str):
208
- # By default compare as case insensitive Unicode. Note that it already was a string.
209
- BuiltIn().log(f"Interpreting {name} '{otherValue}' as string (case insensitive)")
210
- CastedOther = str(otherValue).casefold()
211
-
212
- return CastedOther
213
-
214
- def basicOperator(self, lvalue, rvalue):
215
- # Local argument copies for assignment
216
- lValue = lvalue
217
- rValue = rvalue
218
-
219
- if type(lvalue) is str:
220
- lValue = OperatorProxy.__typeCastRobotStringValue(rvalue, lvalue, "left operand")
221
-
222
- if type(rvalue) is str:
223
- rValue = OperatorProxy.__typeCastRobotStringValue(lvalue, rvalue, "right operand")
224
-
225
- if lValue is lvalue and rValue is rvalue:
226
- BuiltIn().log("Comparing values as is")
227
-
228
- return eval(f"lValue {self.__s_Operator} rValue")
@@ -1 +0,0 @@
1
- VERSION = '3.0.0'