robotframework-pabot 3.1.0__py3-none-any.whl

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.
pabot/SharedLibrary.py ADDED
@@ -0,0 +1,62 @@
1
+ from __future__ import absolute_import
2
+
3
+ from robot import __version__ as ROBOT_VERSION
4
+ from robot.api import logger
5
+ from robot.libraries.BuiltIn import BuiltIn
6
+ from robot.libraries.Remote import Remote
7
+ from robot.running.testlibraries import TestLibrary
8
+
9
+ from .pabotlib import PABOT_QUEUE_INDEX
10
+ from .robotremoteserver import RemoteLibraryFactory
11
+
12
+
13
+ class SharedLibrary(object):
14
+
15
+ ROBOT_LIBRARY_SCOPE = "GLOBAL"
16
+
17
+ def __init__(self, name, args=None):
18
+ """
19
+ Import a library so that the library instance is shared between executions.
20
+ [https://pabot.org/PabotLib.html?ref=log#import-shared-library|Open online docs.]
21
+ """
22
+ # FIXME: RELATIVE IMPORTS WITH FILE NAME
23
+ self._remote = None
24
+ if BuiltIn().get_variable_value("${%s}" % PABOT_QUEUE_INDEX) is None:
25
+ logger.debug(
26
+ "Not currently running pabot. Importing library for this process."
27
+ )
28
+ self._lib = RemoteLibraryFactory(
29
+ TestLibrary.from_name(name, args=args, variables=None, create_keywords=True).instance
30
+ if ROBOT_VERSION >= "7.0"
31
+ else TestLibrary(name, args=args).get_instance()
32
+ )
33
+ return
34
+ uri = BuiltIn().get_variable_value("${PABOTLIBURI}")
35
+ logger.debug("PabotLib URI %r" % uri)
36
+ remotelib = Remote(uri) if uri else None
37
+ if remotelib:
38
+ try:
39
+ port = remotelib.run_keyword("import_shared_library", [name], {"args": args})
40
+ except RuntimeError:
41
+ logger.error("No connection - is pabot called with --pabotlib option?")
42
+ raise
43
+ self._remote = Remote("http://127.0.0.1:%s" % port)
44
+ logger.debug(
45
+ "Lib imported with name %s from http://127.0.0.1:%s" % (name, port)
46
+ )
47
+ else:
48
+ logger.error("No connection - is pabot called with --pabotlib option?")
49
+ raise AssertionError("No connection to pabotlib")
50
+
51
+ def get_keyword_names(self):
52
+ if self._remote:
53
+ return self._remote.get_keyword_names()
54
+ return self._lib.get_keyword_names()
55
+
56
+ def run_keyword(self, name, args, kwargs):
57
+ if self._remote:
58
+ return self._remote.run_keyword(name, args, kwargs)
59
+ result = self._lib.run_keyword(name, args, kwargs)
60
+ if result["status"] == "FAIL":
61
+ raise AssertionError(result["error"])
62
+ return result.get("return")
pabot/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from __future__ import absolute_import
2
+
3
+ from .pabotlib import PabotLib
4
+ __version__ = "3.1.0"
pabot/arguments.py ADDED
@@ -0,0 +1,236 @@
1
+ import multiprocessing
2
+ import re
3
+ from typing import Dict, List, Optional, Tuple
4
+
5
+ from robot import __version__ as ROBOT_VERSION
6
+ from robot.errors import DataError
7
+ from robot.run import USAGE
8
+ from robot.utils import ArgumentParser
9
+
10
+ from .execution_items import (
11
+ DynamicTestItem,
12
+ ExecutionItem,
13
+ GroupEndItem,
14
+ GroupStartItem,
15
+ IncludeItem,
16
+ SuiteItem,
17
+ TestItem,
18
+ WaitItem,
19
+ )
20
+
21
+ ARGSMATCHER = re.compile(r"--argumentfile(\d+)")
22
+
23
+
24
+ def _processes_count(): # type: () -> int
25
+ try:
26
+ return max(multiprocessing.cpu_count(), 2)
27
+ except NotImplementedError:
28
+ return 2
29
+
30
+
31
+ def _filter_argument_parser_options(**options):
32
+ # Note: auto_pythonpath is deprecated since RobotFramework 5.0, but only
33
+ # communicated to users from 6.1
34
+ if ROBOT_VERSION >= "5.0" and "auto_pythonpath" in options:
35
+ del options["auto_pythonpath"]
36
+ return options
37
+
38
+
39
+ def parse_args(
40
+ args,
41
+ ): # type: (List[str]) -> Tuple[Dict[str, object], List[str], Dict[str, object], Dict[str, object]]
42
+ args, pabot_args = _parse_pabot_args(args)
43
+ options, datasources = ArgumentParser(
44
+ USAGE,
45
+ **_filter_argument_parser_options(
46
+ auto_pythonpath=False,
47
+ auto_argumentfile=True,
48
+ env_options="ROBOT_OPTIONS",
49
+ ),
50
+ ).parse_args(args)
51
+ options_for_subprocesses, sources_without_argfile = ArgumentParser(
52
+ USAGE,
53
+ **_filter_argument_parser_options(
54
+ auto_pythonpath=False,
55
+ auto_argumentfile=False,
56
+ env_options="ROBOT_OPTIONS",
57
+ ),
58
+ ).parse_args(args)
59
+ if len(datasources) != len(sources_without_argfile):
60
+ raise DataError(
61
+ "Pabot does not support datasources in argumentfiles.\nPlease move datasources to commandline."
62
+ )
63
+ if len(datasources) > 1 and options["name"] is None:
64
+ options["name"] = "Suites"
65
+ options_for_subprocesses["name"] = "Suites"
66
+ opts = _delete_none_keys(options)
67
+ opts_sub = _delete_none_keys(options_for_subprocesses)
68
+ return opts, datasources, pabot_args, opts_sub
69
+
70
+
71
+ def _parse_shard(arg):
72
+ # type: (str) -> Tuple[int, int]
73
+ parts = arg.split("/")
74
+ return int(parts[0]), int(parts[1])
75
+
76
+
77
+ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str, object]]
78
+ pabot_args = {
79
+ "command": ["pybot" if ROBOT_VERSION < "3.1" else "robot"],
80
+ "verbose": False,
81
+ "help": False,
82
+ "testlevelsplit": False,
83
+ "pabotlib": False,
84
+ "pabotlibhost": "127.0.0.1",
85
+ "pabotlibport": 8270,
86
+ "processes": _processes_count(),
87
+ "processtimeout": None,
88
+ "artifacts": ["png"],
89
+ "artifactsinsubfolders": False,
90
+ "shardindex": 0,
91
+ "shardcount": 1,
92
+ "chunk": False,
93
+ }
94
+ argumentfiles = []
95
+ while args and (
96
+ args[0]
97
+ in [
98
+ "--" + param
99
+ for param in [
100
+ "hive",
101
+ "command",
102
+ "processes",
103
+ "verbose",
104
+ "resourcefile",
105
+ "testlevelsplit",
106
+ "pabotlib",
107
+ "pabotlibhost",
108
+ "pabotlibport",
109
+ "processtimeout",
110
+ "ordering",
111
+ "suitesfrom",
112
+ "artifacts",
113
+ "artifactsinsubfolders",
114
+ "help",
115
+ "shard",
116
+ "chunk",
117
+ ]
118
+ ]
119
+ or ARGSMATCHER.match(args[0])
120
+ ):
121
+ if args[0] == "--hive":
122
+ pabot_args["hive"] = args[1]
123
+ args = args[2:]
124
+ continue
125
+ if args[0] == "--command":
126
+ end_index = args.index("--end-command")
127
+ pabot_args["command"] = args[1:end_index]
128
+ args = args[end_index + 1 :]
129
+ continue
130
+ if args[0] == "--processes":
131
+ pabot_args["processes"] = int(args[1]) if args[1] != 'all' else None
132
+ args = args[2:]
133
+ continue
134
+ if args[0] == "--verbose":
135
+ pabot_args["verbose"] = True
136
+ args = args[1:]
137
+ continue
138
+ if args[0] == "--chunk":
139
+ pabot_args["chunk"] = True
140
+ args = args[1:]
141
+ continue
142
+ if args[0] == "--resourcefile":
143
+ pabot_args["resourcefile"] = args[1]
144
+ args = args[2:]
145
+ continue
146
+ if args[0] == "--pabotlib":
147
+ pabot_args["pabotlib"] = True
148
+ args = args[1:]
149
+ continue
150
+ if args[0] == "--ordering":
151
+ pabot_args["ordering"] = _parse_ordering(args[1])
152
+ args = args[2:]
153
+ continue
154
+ if args[0] == "--testlevelsplit":
155
+ pabot_args["testlevelsplit"] = True
156
+ args = args[1:]
157
+ continue
158
+ if args[0] == "--pabotlibhost":
159
+ pabot_args["pabotlibhost"] = args[1]
160
+ args = args[2:]
161
+ continue
162
+ if args[0] == "--pabotlibport":
163
+ pabot_args["pabotlibport"] = int(args[1])
164
+ args = args[2:]
165
+ continue
166
+ if args[0] == "--processtimeout":
167
+ pabot_args["processtimeout"] = int(args[1])
168
+ args = args[2:]
169
+ continue
170
+ if args[0] == "--suitesfrom":
171
+ pabot_args["suitesfrom"] = args[1]
172
+ args = args[2:]
173
+ continue
174
+ if args[0] == "--artifacts":
175
+ pabot_args["artifacts"] = args[1].split(",")
176
+ args = args[2:]
177
+ continue
178
+ if args[0] == "--artifactsinsubfolders":
179
+ pabot_args["artifactsinsubfolders"] = True
180
+ args = args[1:]
181
+ continue
182
+ if args[0] == "--shard":
183
+ pabot_args["shardindex"], pabot_args["shardcount"] = _parse_shard(args[1])
184
+ args = args[2:]
185
+ continue
186
+ match = ARGSMATCHER.match(args[0])
187
+ if match:
188
+ argumentfiles += [(match.group(1), args[1])]
189
+ args = args[2:]
190
+ continue
191
+ if args and args[0] == "--help":
192
+ pabot_args["help"] = True
193
+ args = args[1:]
194
+ pabot_args["argumentfiles"] = argumentfiles
195
+ return args, pabot_args
196
+
197
+
198
+ def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
199
+ try:
200
+ with open(filename, "r") as orderingfile:
201
+ return [
202
+ parse_execution_item_line(line.strip())
203
+ for line in orderingfile.readlines()
204
+ ]
205
+ except:
206
+ raise DataError("Error parsing ordering file '%s'" % filename)
207
+
208
+
209
+ def _delete_none_keys(d): # type: (Dict[str, Optional[object]]) -> Dict[str, object]
210
+ keys = set()
211
+ for k in d:
212
+ if d[k] is None:
213
+ keys.add(k)
214
+ for k in keys:
215
+ del d[k]
216
+ return d
217
+
218
+
219
+ def parse_execution_item_line(text): # type: (str) -> ExecutionItem
220
+ if text.startswith("--suite "):
221
+ return SuiteItem(text[8:])
222
+ if text.startswith("--test "):
223
+ return TestItem(text[7:])
224
+ if text.startswith("--include "):
225
+ return IncludeItem(text[10:])
226
+ if text.startswith("DYNAMICTEST"):
227
+ suite, test = text[12:].split(" :: ")
228
+ return DynamicTestItem(test, suite)
229
+ if text == "#WAIT":
230
+ return WaitItem()
231
+ if text == "{":
232
+ return GroupStartItem()
233
+ if text == "}":
234
+ return GroupEndItem()
235
+ # Assume old suite name
236
+ return SuiteItem(text)
pabot/clientwrapper.py ADDED
@@ -0,0 +1,10 @@
1
+ import sys
2
+
3
+ try:
4
+ from .py3.client import make_order
5
+ except SyntaxError:
6
+
7
+ def make_order(*args):
8
+ print("Client needs to be run in Python >= 3.6")
9
+ print("You are running %s" % sys.version)
10
+ sys.exit(1)
@@ -0,0 +1,8 @@
1
+ import sys
2
+
3
+ try:
4
+ from .py3.coordinator import main
5
+ except SyntaxError:
6
+ print("Coordinator needs to be run in Python >= 3.6")
7
+ print("You are running %s" % sys.version)
8
+ sys.exit(1)
@@ -0,0 +1,320 @@
1
+ from functools import total_ordering
2
+ from typing import Dict, List, Optional, Tuple, Union
3
+
4
+ from robot import __version__ as ROBOT_VERSION
5
+ from robot.errors import DataError
6
+ from robot.utils import PY2, is_unicode
7
+
8
+
9
+ @total_ordering
10
+ class ExecutionItem(object):
11
+
12
+ isWait = False
13
+ type = None # type: str
14
+ name = None # type: str
15
+
16
+ def top_name(self):
17
+ # type: () -> str
18
+ return self.name.split(".")[0]
19
+
20
+ def contains(self, other):
21
+ # type: (ExecutionItem) -> bool
22
+ return False
23
+
24
+ def difference(self, from_items):
25
+ # type: (List[ExecutionItem]) -> List[ExecutionItem]
26
+ return []
27
+
28
+ def line(self):
29
+ # type: () -> str
30
+ return ""
31
+
32
+ def modify_options_for_executor(self, options):
33
+ options[self.type] = self.name
34
+
35
+ def __eq__(self, other):
36
+ if isinstance(other, ExecutionItem):
37
+ return (self.name, self.type) == (other.name, other.type)
38
+ return NotImplemented
39
+
40
+ def __ne__(self, other):
41
+ return not (self == other)
42
+
43
+ def __lt__(self, other):
44
+ return (self.name, self.type) < (other.name, other.type)
45
+
46
+ def __hash__(self):
47
+ return hash(self.name) | hash(self.type)
48
+
49
+ def __repr__(self):
50
+ return "<" + self.type + ":" + self.name + ">"
51
+
52
+
53
+ class HivedItem(ExecutionItem):
54
+
55
+ type = "hived"
56
+
57
+ def __init__(self, item, hive):
58
+ self._item = item
59
+ self._hive = hive
60
+
61
+ def modify_options_for_executor(self, options):
62
+ self._item.modify_options_for_executor(options)
63
+
64
+ @property
65
+ def name(self):
66
+ return self._item.name
67
+
68
+
69
+ class GroupItem(ExecutionItem):
70
+
71
+ type = "group"
72
+
73
+ def __init__(self):
74
+ self.name = "Group_"
75
+ self._items = []
76
+ self._element_type = None
77
+
78
+ def add(self, item):
79
+ if item.isWait:
80
+ raise DataError("[EXCEPTION] Ordering : Group can not contain #WAIT")
81
+ if self._element_type and self._element_type != item.type:
82
+ raise DataError(
83
+ "[EXCEPTION] Ordering : Group can contain only test or suite elements. Not bouth"
84
+ )
85
+ if len(self._items) > 0:
86
+ self.name += "_"
87
+ self.name += item.name
88
+ self._element_type = item.type
89
+ self._items.append(item)
90
+
91
+ def modify_options_for_executor(self, options):
92
+ for item in self._items:
93
+ if item.type not in options:
94
+ options[item.type] = []
95
+ opts = {}
96
+ item.modify_options_for_executor(opts)
97
+ options[item.type].append(opts[item.type])
98
+
99
+
100
+ class RunnableItem(ExecutionItem):
101
+ pass
102
+
103
+ depends = None # type: str
104
+ depends_keyword = "#DEPENDS"
105
+
106
+ def set_name_and_depends(self, name):
107
+ line_name = name.encode("utf-8") if PY2 and is_unicode(name) else name
108
+ depends_begin_index = line_name.find(self.depends_keyword)
109
+ self.name = (
110
+ line_name
111
+ if depends_begin_index == -1
112
+ else line_name[0:depends_begin_index].strip()
113
+ )
114
+ self.depends = (
115
+ line_name[depends_begin_index + len(self.depends_keyword) :].strip()
116
+ if depends_begin_index != -1
117
+ else None
118
+ )
119
+
120
+ def line(self):
121
+ # type: () -> str
122
+ line_without_depends = "--" + self.type + " " + self.name
123
+ return (
124
+ line_without_depends + " " + self.depends_keyword + " " + self.depends
125
+ if self.depends
126
+ else line_without_depends
127
+ )
128
+
129
+
130
+ class SuiteItem(RunnableItem):
131
+
132
+ type = "suite"
133
+
134
+ def __init__(self, name, tests=None, suites=None, dynamictests=None):
135
+ # type: (str, Optional[List[str]], Optional[List[str]], Optional[List[str]]) -> None
136
+ assert (PY2 and isinstance(name, basestring)) or isinstance(name, str)
137
+ self.set_name_and_depends(name)
138
+ testslist = [
139
+ TestItem(t) for t in tests or []
140
+ ] # type: List[Union[TestItem, DynamicTestItem]]
141
+ dynamictestslist = [
142
+ DynamicTestItem(t, self.name) for t in dynamictests or []
143
+ ] # type: List[Union[TestItem, DynamicTestItem]]
144
+ self.tests = testslist + dynamictestslist
145
+ self.suites = [SuiteItem(s) for s in suites or []]
146
+
147
+ def difference(self, from_items):
148
+ # type: (List[ExecutionItem]) -> List[ExecutionItem]
149
+ if self.tests:
150
+ return [t for t in self.tests if t not in from_items]
151
+ if self.suites:
152
+ return [s for s in self.suites if s not in from_items]
153
+ return []
154
+
155
+ def contains(self, other):
156
+ # type: (ExecutionItem) -> bool
157
+ if self == other:
158
+ return True
159
+ return other.name.startswith(self.name + ".")
160
+
161
+ def __eq__(self, other):
162
+ if not isinstance(other, SuiteItem):
163
+ return False
164
+ if self.name == other.name:
165
+ return True
166
+ if other.name.endswith('.'+self.name):
167
+ return True
168
+ if self.name.endswith('.'+other.name):
169
+ return True
170
+ return False
171
+
172
+ def __hash__(self):
173
+ return RunnableItem.__hash__(self)
174
+
175
+ def tags(self):
176
+ # TODO Make this happen
177
+ return []
178
+
179
+
180
+ class TestItem(RunnableItem):
181
+
182
+ type = "test"
183
+
184
+ def __init__(self, name):
185
+ # type: (str) -> None
186
+ self.set_name_and_depends(name)
187
+
188
+ if ROBOT_VERSION >= "3.1":
189
+
190
+ def modify_options_for_executor(self, options):
191
+ if "rerunfailed" in options:
192
+ del options["rerunfailed"]
193
+ name = self.name
194
+ for char in ["[", "?", "*"]:
195
+ name = name.replace(char, "[" + char + "]")
196
+ options[self.type] = name
197
+
198
+ else:
199
+
200
+ def modify_options_for_executor(self, options):
201
+ if "rerunfailed" in options:
202
+ del options["rerunfailed"]
203
+
204
+ def difference(self, from_items):
205
+ # type: (List[ExecutionItem]) -> List[ExecutionItem]
206
+ return []
207
+
208
+ def contains(self, other):
209
+ # type: (ExecutionItem) -> bool
210
+ return self == other
211
+
212
+ def tags(self):
213
+ # TODO Make this happen
214
+ return []
215
+
216
+
217
+ class DynamicSuiteItem(SuiteItem):
218
+ type = "dynamicsuite"
219
+
220
+ def __init__(self, name, variables):
221
+ SuiteItem.__init__(self, name)
222
+ self._variables = variables
223
+
224
+ def modify_options_for_executor(self, options):
225
+ variables = options.get("variable", [])[:]
226
+ variables.extend(self._variables)
227
+ options["variable"] = variables
228
+ options["suite"] = self.name
229
+
230
+
231
+ class DynamicTestItem(ExecutionItem):
232
+
233
+ type = "dynamictest"
234
+
235
+ def __init__(self, name, suite):
236
+ # type: (str, str) -> None
237
+ self.name = name.encode("utf-8") if PY2 and is_unicode(name) else name
238
+ self.suite = suite # type:str
239
+
240
+ def line(self):
241
+ return "DYNAMICTEST %s :: %s" % (self.suite, self.name)
242
+
243
+ def modify_options_for_executor(self, options):
244
+ options["suite"] = self.suite
245
+ variables = options.get("variable", [])[:]
246
+ variables.append("DYNAMICTEST:" + self.name)
247
+ options["variable"] = variables
248
+
249
+ def difference(self, from_items):
250
+ return []
251
+
252
+ def contains(self, other):
253
+ return self == other
254
+
255
+ def tags(self):
256
+ # TODO Make this happen
257
+ return []
258
+
259
+
260
+ class WaitItem(ExecutionItem):
261
+
262
+ type = "wait"
263
+ isWait = True
264
+
265
+ def __init__(self):
266
+ self.name = "#WAIT"
267
+
268
+ def line(self):
269
+ return self.name
270
+
271
+
272
+ class GroupStartItem(ExecutionItem):
273
+
274
+ type = "group"
275
+
276
+ def __init__(self):
277
+ self.name = "#START"
278
+
279
+ def line(self):
280
+ return "{"
281
+
282
+
283
+ class GroupEndItem(ExecutionItem):
284
+
285
+ type = "group"
286
+
287
+ def __init__(self):
288
+ self.name = "#END"
289
+
290
+ def line(self):
291
+ return "}"
292
+
293
+
294
+ class IncludeItem(ExecutionItem):
295
+
296
+ type = "include"
297
+
298
+ def __init__(self, tag):
299
+ self.name = tag
300
+
301
+ def line(self):
302
+ return "--include " + self.name
303
+
304
+ def contains(self, other):
305
+ return self.name in other.tags()
306
+
307
+ def tags(self):
308
+ return [self.name]
309
+
310
+
311
+ class SuiteItems(ExecutionItem):
312
+
313
+ type = "suite"
314
+
315
+ def __init__(self, suites):
316
+ self.suites = suites
317
+ self.name = " ".join([suite.name for suite in suites])
318
+
319
+ def modify_options_for_executor(self, options):
320
+ options["suite"] = [suite.name for suite in self.suites]