robotframework-pabot 4.0.0__tar.gz → 4.0.4__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.
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/PKG-INFO +1 -1
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/README.md +3 -3
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/SharedLibrary.py +7 -4
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/__init__.py +2 -1
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/arguments.py +22 -14
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/execution_items.py +2 -13
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/pabot.py +72 -31
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/pabotlib.py +7 -7
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/py3/messages.py +0 -1
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/PKG-INFO +1 -1
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_depends.py +16 -4
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_pabot.py +61 -40
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_pabotlib.py +1 -1
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_testlevelsplit_include.py +6 -4
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/LICENSE.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/MANIFEST.in +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/pyproject.toml +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/setup.cfg +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/setup.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/clientwrapper.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/coordinatorwrapper.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/py3/__init__.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/py3/client.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/py3/coordinator.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/py3/worker.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/result_merger.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/robotremoteserver.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/pabot/workerwrapper.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/SOURCES.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/requires.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_arguments_output.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_functional.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_ordering.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_pabotsuitenames_io.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_resultmerger.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_stacktrace.py +0 -0
- {robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_testlevelsplit_output_task_order.py +0 -0
|
@@ -90,9 +90,9 @@ between parallel test executions.
|
|
|
90
90
|
Disable the PabotLib remote server if you don't need locking or resource distribution features.
|
|
91
91
|
|
|
92
92
|
--pabotlibhost [HOSTNAME]
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
Connect to an already running instance of the PabotLib remote server at the given host
|
|
94
|
+
(disables the local PabotLib server start).
|
|
95
|
+
The remote server can be also started and executed separately from pabot instances:
|
|
96
96
|
|
|
97
97
|
python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
|
|
98
98
|
python -m pabot.pabotlib resource.txt 192.168.1.123 8271
|
|
@@ -11,7 +11,6 @@ from .robotremoteserver import RemoteLibraryFactory
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class SharedLibrary(object):
|
|
14
|
-
|
|
15
14
|
ROBOT_LIBRARY_SCOPE = "GLOBAL"
|
|
16
15
|
|
|
17
16
|
def __init__(self, name, args=None):
|
|
@@ -26,8 +25,10 @@ class SharedLibrary(object):
|
|
|
26
25
|
"Not currently running pabot. Importing library for this process."
|
|
27
26
|
)
|
|
28
27
|
self._lib = RemoteLibraryFactory(
|
|
29
|
-
TestLibrary.from_name(
|
|
30
|
-
|
|
28
|
+
TestLibrary.from_name(
|
|
29
|
+
name, args=args, variables=None, create_keywords=True
|
|
30
|
+
).instance
|
|
31
|
+
if ROBOT_VERSION >= "7.0"
|
|
31
32
|
else TestLibrary(name, args=args).get_instance()
|
|
32
33
|
)
|
|
33
34
|
return
|
|
@@ -36,7 +37,9 @@ class SharedLibrary(object):
|
|
|
36
37
|
remotelib = Remote(uri) if uri else None
|
|
37
38
|
if remotelib:
|
|
38
39
|
try:
|
|
39
|
-
port = remotelib.run_keyword(
|
|
40
|
+
port = remotelib.run_keyword(
|
|
41
|
+
"import_shared_library", [name], {"args": args}
|
|
42
|
+
)
|
|
40
43
|
except RuntimeError:
|
|
41
44
|
logger.error("No connection - is pabot called with --pabotlib option?")
|
|
42
45
|
raise
|
|
@@ -93,12 +93,16 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
93
93
|
}
|
|
94
94
|
# Explicitly define argument types for validation
|
|
95
95
|
flag_args = {
|
|
96
|
-
"verbose",
|
|
97
|
-
"
|
|
96
|
+
"verbose",
|
|
97
|
+
"help",
|
|
98
|
+
"testlevelsplit",
|
|
99
|
+
"pabotlib",
|
|
100
|
+
"artifactsinsubfolders",
|
|
101
|
+
"chunk",
|
|
98
102
|
}
|
|
99
103
|
value_args = {
|
|
100
104
|
"hive": str,
|
|
101
|
-
"processes": lambda x: int(x) if x !=
|
|
105
|
+
"processes": lambda x: int(x) if x != "all" else None,
|
|
102
106
|
"resourcefile": str,
|
|
103
107
|
"pabotlibhost": str,
|
|
104
108
|
"pabotlibport": int,
|
|
@@ -106,7 +110,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
106
110
|
"ordering": _parse_ordering,
|
|
107
111
|
"suitesfrom": str,
|
|
108
112
|
"artifacts": lambda x: x.split(","),
|
|
109
|
-
"shard": _parse_shard
|
|
113
|
+
"shard": _parse_shard,
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
argumentfiles = []
|
|
@@ -119,11 +123,11 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
119
123
|
|
|
120
124
|
while i < len(args):
|
|
121
125
|
arg = args[i]
|
|
122
|
-
if not arg.startswith(
|
|
126
|
+
if not arg.startswith("--"):
|
|
123
127
|
remaining_args.append(arg)
|
|
124
128
|
i += 1
|
|
125
129
|
continue
|
|
126
|
-
|
|
130
|
+
|
|
127
131
|
arg_name = arg[2:] # Strip '--'
|
|
128
132
|
|
|
129
133
|
if arg_name == "no-pabotlib":
|
|
@@ -135,23 +139,23 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
135
139
|
saw_pabotlib_flag = True
|
|
136
140
|
args = args[1:]
|
|
137
141
|
continue
|
|
138
|
-
|
|
142
|
+
|
|
139
143
|
# Special case for command
|
|
140
144
|
if arg_name == "command":
|
|
141
145
|
try:
|
|
142
146
|
end_index = args.index("--end-command", i)
|
|
143
|
-
pabot_args["command"] = args[i+1:end_index]
|
|
147
|
+
pabot_args["command"] = args[i + 1 : end_index]
|
|
144
148
|
i = end_index + 1
|
|
145
149
|
continue
|
|
146
150
|
except ValueError:
|
|
147
151
|
raise DataError("--command requires matching --end-command")
|
|
148
|
-
|
|
152
|
+
|
|
149
153
|
# Handle flag arguments
|
|
150
154
|
if arg_name in flag_args:
|
|
151
155
|
pabot_args[arg_name] = True
|
|
152
156
|
i += 1
|
|
153
157
|
continue
|
|
154
|
-
|
|
158
|
+
|
|
155
159
|
# Handle value arguments
|
|
156
160
|
if arg_name in value_args:
|
|
157
161
|
if i + 1 >= len(args):
|
|
@@ -160,13 +164,16 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
160
164
|
value = value_args[arg_name](args[i + 1])
|
|
161
165
|
if arg_name == "shard":
|
|
162
166
|
pabot_args["shardindex"], pabot_args["shardcount"] = value
|
|
167
|
+
elif arg_name == "pabotlibhost":
|
|
168
|
+
pabot_args["pabotlib"] = False
|
|
169
|
+
pabot_args[arg_name] = value
|
|
163
170
|
else:
|
|
164
171
|
pabot_args[arg_name] = value
|
|
165
172
|
i += 2
|
|
166
173
|
continue
|
|
167
174
|
except (ValueError, TypeError) as e:
|
|
168
175
|
raise DataError(f"Invalid value for --{arg_name}: {args[i + 1]}")
|
|
169
|
-
|
|
176
|
+
|
|
170
177
|
# Handle argument files
|
|
171
178
|
match = ARGSMATCHER.match(arg)
|
|
172
179
|
if match:
|
|
@@ -175,15 +182,16 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
175
182
|
argumentfiles.append((match.group(1), args[i + 1]))
|
|
176
183
|
i += 2
|
|
177
184
|
continue
|
|
178
|
-
|
|
185
|
+
|
|
179
186
|
# If we get here, it's a non-pabot argument
|
|
180
187
|
remaining_args.append(arg)
|
|
181
188
|
i += 1
|
|
182
|
-
|
|
189
|
+
|
|
183
190
|
if saw_pabotlib_flag and saw_no_pabotlib:
|
|
184
191
|
raise DataError("Cannot use both --pabotlib and --no-pabotlib options together")
|
|
185
|
-
|
|
192
|
+
|
|
186
193
|
pabot_args["argumentfiles"] = argumentfiles
|
|
194
|
+
|
|
187
195
|
return remaining_args, pabot_args
|
|
188
196
|
|
|
189
197
|
|
|
@@ -8,7 +8,6 @@ from robot.utils import PY2, is_unicode
|
|
|
8
8
|
|
|
9
9
|
@total_ordering
|
|
10
10
|
class ExecutionItem(object):
|
|
11
|
-
|
|
12
11
|
isWait = False
|
|
13
12
|
type = None # type: str
|
|
14
13
|
name = None # type: str
|
|
@@ -51,7 +50,6 @@ class ExecutionItem(object):
|
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
class HivedItem(ExecutionItem):
|
|
54
|
-
|
|
55
53
|
type = "hived"
|
|
56
54
|
|
|
57
55
|
def __init__(self, item, hive):
|
|
@@ -67,7 +65,6 @@ class HivedItem(ExecutionItem):
|
|
|
67
65
|
|
|
68
66
|
|
|
69
67
|
class GroupItem(ExecutionItem):
|
|
70
|
-
|
|
71
68
|
type = "group"
|
|
72
69
|
|
|
73
70
|
def __init__(self):
|
|
@@ -128,7 +125,6 @@ class RunnableItem(ExecutionItem):
|
|
|
128
125
|
|
|
129
126
|
|
|
130
127
|
class SuiteItem(RunnableItem):
|
|
131
|
-
|
|
132
128
|
type = "suite"
|
|
133
129
|
|
|
134
130
|
def __init__(self, name, tests=None, suites=None, dynamictests=None):
|
|
@@ -163,9 +159,9 @@ class SuiteItem(RunnableItem):
|
|
|
163
159
|
return False
|
|
164
160
|
if self.name == other.name:
|
|
165
161
|
return True
|
|
166
|
-
if other.name.endswith(
|
|
162
|
+
if other.name.endswith("." + self.name):
|
|
167
163
|
return True
|
|
168
|
-
if self.name.endswith(
|
|
164
|
+
if self.name.endswith("." + other.name):
|
|
169
165
|
return True
|
|
170
166
|
return False
|
|
171
167
|
|
|
@@ -178,7 +174,6 @@ class SuiteItem(RunnableItem):
|
|
|
178
174
|
|
|
179
175
|
|
|
180
176
|
class TestItem(RunnableItem):
|
|
181
|
-
|
|
182
177
|
type = "test"
|
|
183
178
|
|
|
184
179
|
def __init__(self, name):
|
|
@@ -229,7 +224,6 @@ class DynamicSuiteItem(SuiteItem):
|
|
|
229
224
|
|
|
230
225
|
|
|
231
226
|
class DynamicTestItem(ExecutionItem):
|
|
232
|
-
|
|
233
227
|
type = "dynamictest"
|
|
234
228
|
|
|
235
229
|
def __init__(self, name, suite):
|
|
@@ -258,7 +252,6 @@ class DynamicTestItem(ExecutionItem):
|
|
|
258
252
|
|
|
259
253
|
|
|
260
254
|
class WaitItem(ExecutionItem):
|
|
261
|
-
|
|
262
255
|
type = "wait"
|
|
263
256
|
isWait = True
|
|
264
257
|
|
|
@@ -270,7 +263,6 @@ class WaitItem(ExecutionItem):
|
|
|
270
263
|
|
|
271
264
|
|
|
272
265
|
class GroupStartItem(ExecutionItem):
|
|
273
|
-
|
|
274
266
|
type = "group"
|
|
275
267
|
|
|
276
268
|
def __init__(self):
|
|
@@ -281,7 +273,6 @@ class GroupStartItem(ExecutionItem):
|
|
|
281
273
|
|
|
282
274
|
|
|
283
275
|
class GroupEndItem(ExecutionItem):
|
|
284
|
-
|
|
285
276
|
type = "group"
|
|
286
277
|
|
|
287
278
|
def __init__(self):
|
|
@@ -292,7 +283,6 @@ class GroupEndItem(ExecutionItem):
|
|
|
292
283
|
|
|
293
284
|
|
|
294
285
|
class IncludeItem(ExecutionItem):
|
|
295
|
-
|
|
296
286
|
type = "include"
|
|
297
287
|
|
|
298
288
|
def __init__(self, tag):
|
|
@@ -309,7 +299,6 @@ class IncludeItem(ExecutionItem):
|
|
|
309
299
|
|
|
310
300
|
|
|
311
301
|
class SuiteItems(ExecutionItem):
|
|
312
|
-
|
|
313
302
|
type = "suite"
|
|
314
303
|
|
|
315
304
|
def __init__(self, suites):
|
|
@@ -44,12 +44,20 @@ options (these must be before normal RF options):
|
|
|
44
44
|
Indicator for a file that can contain shared variables for
|
|
45
45
|
distributing resources.
|
|
46
46
|
|
|
47
|
-
--pabotlib
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
--no-pabotlib
|
|
48
|
+
Disable the PabotLib remote server if you don't need locking or resource distribution features.
|
|
49
|
+
PabotLib remote server is started by default.
|
|
50
50
|
|
|
51
|
-
--pabotlibhost [HOSTNAME]
|
|
52
|
-
|
|
51
|
+
--pabotlibhost [HOSTNAME]
|
|
52
|
+
Connect to an already running instance of the PabotLib remote server at the given host
|
|
53
|
+
(disables the local PabotLib server start). For example, to connect to a
|
|
54
|
+
remote PabotLib server running on another machine:
|
|
55
|
+
|
|
56
|
+
pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
|
|
57
|
+
|
|
58
|
+
The remote PabotLib server can be started separately using:
|
|
59
|
+
python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
|
|
60
|
+
python -m pabot.pabotlib resource.txt 192.168.1.123 8271
|
|
53
61
|
|
|
54
62
|
--pabotlibport [PORT]
|
|
55
63
|
Port number of the PabotLib remote server (default is 8270)
|
|
@@ -236,7 +244,7 @@ def execute_and_wait_with(item):
|
|
|
236
244
|
caller_id,
|
|
237
245
|
item.index,
|
|
238
246
|
item.execution_item.type != "test",
|
|
239
|
-
process_timeout=item.timeout
|
|
247
|
+
process_timeout=item.timeout,
|
|
240
248
|
)
|
|
241
249
|
outputxml_preprocessing(
|
|
242
250
|
item.options, outs_dir, name, item.verbose, _make_id(), caller_id
|
|
@@ -294,7 +302,7 @@ def _try_execute_and_wait(
|
|
|
294
302
|
caller_id,
|
|
295
303
|
my_index=-1,
|
|
296
304
|
show_stdout_on_failure=False,
|
|
297
|
-
process_timeout=None
|
|
305
|
+
process_timeout=None,
|
|
298
306
|
):
|
|
299
307
|
# type: (List[str], str, str, bool, int, str, int, bool, Optional[int]) -> None
|
|
300
308
|
plib = None
|
|
@@ -305,7 +313,15 @@ def _try_execute_and_wait(
|
|
|
305
313
|
with open(os.path.join(outs_dir, cmd[0] + "_stdout.out"), "w") as stdout:
|
|
306
314
|
with open(os.path.join(outs_dir, cmd[0] + "_stderr.out"), "w") as stderr:
|
|
307
315
|
process, (rc, elapsed) = _run(
|
|
308
|
-
cmd,
|
|
316
|
+
cmd,
|
|
317
|
+
stderr,
|
|
318
|
+
stdout,
|
|
319
|
+
item_name,
|
|
320
|
+
verbose,
|
|
321
|
+
pool_id,
|
|
322
|
+
my_index,
|
|
323
|
+
outs_dir,
|
|
324
|
+
process_timeout,
|
|
309
325
|
)
|
|
310
326
|
except:
|
|
311
327
|
_write(traceback.format_exc())
|
|
@@ -478,7 +494,17 @@ def _increase_completed(plib, my_index):
|
|
|
478
494
|
)
|
|
479
495
|
|
|
480
496
|
|
|
481
|
-
def _run(
|
|
497
|
+
def _run(
|
|
498
|
+
command,
|
|
499
|
+
stderr,
|
|
500
|
+
stdout,
|
|
501
|
+
item_name,
|
|
502
|
+
verbose,
|
|
503
|
+
pool_id,
|
|
504
|
+
item_index,
|
|
505
|
+
outs_dir,
|
|
506
|
+
process_timeout,
|
|
507
|
+
):
|
|
482
508
|
# type: (List[str], IO[Any], IO[Any], str, bool, int, int, str, Optional[int]) -> Tuple[Union[subprocess.Popen[bytes], subprocess.Popen], Tuple[int, float]]
|
|
483
509
|
timestamp = datetime.datetime.now()
|
|
484
510
|
cmd = " ".join(command)
|
|
@@ -487,10 +513,14 @@ def _run(command, stderr, stdout, item_name, verbose, pool_id, item_index, outs_
|
|
|
487
513
|
# avoid hitting https://bugs.python.org/issue10394
|
|
488
514
|
with POPEN_LOCK:
|
|
489
515
|
my_env = os.environ.copy()
|
|
490
|
-
syslog_file = my_env.get(
|
|
516
|
+
syslog_file = my_env.get("ROBOT_SYSLOG_FILE", None)
|
|
491
517
|
if syslog_file:
|
|
492
|
-
my_env[
|
|
493
|
-
|
|
518
|
+
my_env["ROBOT_SYSLOG_FILE"] = os.path.join(
|
|
519
|
+
outs_dir, os.path.basename(syslog_file)
|
|
520
|
+
)
|
|
521
|
+
process = subprocess.Popen(
|
|
522
|
+
cmd, shell=True, stderr=stderr, stdout=stdout, env=my_env
|
|
523
|
+
)
|
|
494
524
|
if verbose:
|
|
495
525
|
_write_with_id(
|
|
496
526
|
process,
|
|
@@ -507,7 +537,9 @@ def _run(command, stderr, stdout, item_name, verbose, pool_id, item_index, outs_
|
|
|
507
537
|
"EXECUTING %s" % item_name,
|
|
508
538
|
timestamp=timestamp,
|
|
509
539
|
)
|
|
510
|
-
return process, _wait_for_return_code(
|
|
540
|
+
return process, _wait_for_return_code(
|
|
541
|
+
process, item_name, pool_id, item_index, process_timeout
|
|
542
|
+
)
|
|
511
543
|
|
|
512
544
|
|
|
513
545
|
def _wait_for_return_code(process, item_name, pool_id, item_index, process_timeout):
|
|
@@ -522,12 +554,15 @@ def _wait_for_return_code(process, item_name, pool_id, item_index, process_timeo
|
|
|
522
554
|
if process_timeout and elapsed / 10.0 >= process_timeout:
|
|
523
555
|
process.terminate()
|
|
524
556
|
process.wait()
|
|
525
|
-
rc =
|
|
557
|
+
rc = (
|
|
558
|
+
-1
|
|
559
|
+
) # Set a return code indicating that the process was killed due to timeout
|
|
526
560
|
_write_with_id(
|
|
527
561
|
process,
|
|
528
562
|
pool_id,
|
|
529
563
|
item_index,
|
|
530
|
-
"Process %s killed due to exceeding the maximum timeout of %s seconds"
|
|
564
|
+
"Process %s killed due to exceeding the maximum timeout of %s seconds"
|
|
565
|
+
% (item_name, process_timeout),
|
|
531
566
|
)
|
|
532
567
|
break
|
|
533
568
|
|
|
@@ -544,7 +579,6 @@ def _wait_for_return_code(process, item_name, pool_id, item_index, process_timeo
|
|
|
544
579
|
return rc, elapsed / 10.0
|
|
545
580
|
|
|
546
581
|
|
|
547
|
-
|
|
548
582
|
def _read_file(file_handle):
|
|
549
583
|
try:
|
|
550
584
|
with open(file_handle.name, "r") as content_file:
|
|
@@ -1121,7 +1155,6 @@ def store_suite_names(hashes, suite_names):
|
|
|
1121
1155
|
for d in suite_lines
|
|
1122
1156
|
)
|
|
1123
1157
|
except IOError:
|
|
1124
|
-
|
|
1125
1158
|
_write(
|
|
1126
1159
|
"[ "
|
|
1127
1160
|
+ _wrap_with(Color.YELLOW, "WARNING")
|
|
@@ -1154,12 +1187,14 @@ def generate_suite_names_with_builder(outs_dir, datasources, options):
|
|
|
1154
1187
|
# Note: first argument (included_suites) is deprecated from RobotFramework 6.1
|
|
1155
1188
|
if ROBOT_VERSION >= "6.1":
|
|
1156
1189
|
builder = TestSuiteBuilder(
|
|
1157
|
-
included_extensions=settings.extension,
|
|
1190
|
+
included_extensions=settings.extension,
|
|
1191
|
+
rpa=settings.rpa,
|
|
1192
|
+
lang=opts.get("language"),
|
|
1158
1193
|
)
|
|
1159
1194
|
else:
|
|
1160
1195
|
builder = TestSuiteBuilder(
|
|
1161
1196
|
settings["SuiteNames"], settings.extension, rpa=settings.rpa
|
|
1162
|
-
)
|
|
1197
|
+
)
|
|
1163
1198
|
|
|
1164
1199
|
suite = builder.build(*datasources)
|
|
1165
1200
|
settings.rpa = builder.rpa
|
|
@@ -1657,7 +1692,7 @@ class QueueItem(object):
|
|
|
1657
1692
|
argfile,
|
|
1658
1693
|
hive=None,
|
|
1659
1694
|
processes=0,
|
|
1660
|
-
timeout=None
|
|
1695
|
+
timeout=None,
|
|
1661
1696
|
):
|
|
1662
1697
|
# type: (List[str], str, Dict[str, object], ExecutionItem, List[str], bool, Tuple[str, Optional[str]], Optional[str], int, Optional[int]) -> None
|
|
1663
1698
|
self.datasources = datasources
|
|
@@ -1755,7 +1790,7 @@ def _create_items(datasources, opts_for_run, outs_dir, pabot_args, suite_group):
|
|
|
1755
1790
|
argfile,
|
|
1756
1791
|
pabot_args.get("hive"),
|
|
1757
1792
|
pabot_args["processes"],
|
|
1758
|
-
pabot_args["processtimeout"]
|
|
1793
|
+
pabot_args["processtimeout"],
|
|
1759
1794
|
)
|
|
1760
1795
|
for suite in suite_group
|
|
1761
1796
|
for argfile in pabot_args["argumentfiles"] or [("", None)]
|
|
@@ -1774,9 +1809,7 @@ def _create_execution_items_for_dry_run(
|
|
|
1774
1809
|
datasources, opts_for_run, outs_dir, pabot_args, suite_group
|
|
1775
1810
|
)
|
|
1776
1811
|
chunk_size = (
|
|
1777
|
-
round(len(items) / processes_count)
|
|
1778
|
-
if len(items) > processes_count
|
|
1779
|
-
else 1
|
|
1812
|
+
round(len(items) / processes_count) if len(items) > processes_count else 1
|
|
1780
1813
|
)
|
|
1781
1814
|
chunked_items = list(_chunk_items(items, chunk_size))
|
|
1782
1815
|
_NUMBER_OF_ITEMS_TO_BE_EXECUTED += len(chunked_items)
|
|
@@ -1800,7 +1833,7 @@ def _chunk_items(items, chunk_size):
|
|
|
1800
1833
|
base_item.verbose,
|
|
1801
1834
|
(base_item.argfile_index, base_item.argfile),
|
|
1802
1835
|
processes=base_item.processes,
|
|
1803
|
-
timeout=base_item.timeout
|
|
1836
|
+
timeout=base_item.timeout,
|
|
1804
1837
|
)
|
|
1805
1838
|
yield chunked_item
|
|
1806
1839
|
|
|
@@ -1880,7 +1913,7 @@ def _get_dynamically_created_execution_items(
|
|
|
1880
1913
|
("", None),
|
|
1881
1914
|
pabot_args.get("hive"),
|
|
1882
1915
|
pabot_args["processes"],
|
|
1883
|
-
pabot_args["processtimeout"]
|
|
1916
|
+
pabot_args["processtimeout"],
|
|
1884
1917
|
)
|
|
1885
1918
|
for suite in suite_group
|
|
1886
1919
|
]
|
|
@@ -1982,7 +2015,7 @@ def _group_suites(outs_dir, datasources, options, pabot_args):
|
|
|
1982
2015
|
grouped_suites = (
|
|
1983
2016
|
_chunked_suite_names(shard_suites, pabot_args["processes"])
|
|
1984
2017
|
if pabot_args["chunk"]
|
|
1985
|
-
else _group_by_wait(_group_by_groups(
|
|
2018
|
+
else _group_by_wait(_group_by_groups(shard_suites))
|
|
1986
2019
|
)
|
|
1987
2020
|
grouped_by_depend = _all_grouped_suites_by_depend(grouped_suites)
|
|
1988
2021
|
return grouped_by_depend
|
|
@@ -2019,17 +2052,23 @@ def _verify_depends(suite_names):
|
|
|
2019
2052
|
)
|
|
2020
2053
|
)
|
|
2021
2054
|
if suites_with_depends != suites_with_found_dependencies:
|
|
2022
|
-
raise DataError(
|
|
2055
|
+
raise DataError(
|
|
2056
|
+
"Invalid test configuration: Some test suites have dependencies (#DEPENDS) that cannot be found."
|
|
2057
|
+
)
|
|
2023
2058
|
suites_with_circular_dependencies = list(
|
|
2024
2059
|
filter(lambda suite: suite.depends == suite.name, suites_with_depends)
|
|
2025
2060
|
)
|
|
2026
2061
|
if suites_with_circular_dependencies:
|
|
2027
|
-
raise DataError(
|
|
2062
|
+
raise DataError(
|
|
2063
|
+
"Invalid test configuration: Test suites cannot depend on themselves."
|
|
2064
|
+
)
|
|
2028
2065
|
grouped_suites = list(
|
|
2029
2066
|
filter(lambda suite: isinstance(suite, GroupItem), suite_names)
|
|
2030
2067
|
)
|
|
2031
2068
|
if grouped_suites and suites_with_depends:
|
|
2032
|
-
raise DataError(
|
|
2069
|
+
raise DataError(
|
|
2070
|
+
"Invalid test configuration: Cannot use both #DEPENDS and grouped suites."
|
|
2071
|
+
)
|
|
2033
2072
|
|
|
2034
2073
|
|
|
2035
2074
|
def _group_by_depend(suite_names):
|
|
@@ -2057,7 +2096,9 @@ def _group_by_depend(suite_names):
|
|
|
2057
2096
|
dependency_tree += [dependent_on_last_stage]
|
|
2058
2097
|
flattened_dependency_tree = sum(dependency_tree, [])
|
|
2059
2098
|
if len(flattened_dependency_tree) != len(runnable_suites):
|
|
2060
|
-
raise DataError(
|
|
2099
|
+
raise DataError(
|
|
2100
|
+
"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions."
|
|
2101
|
+
)
|
|
2061
2102
|
return dependency_tree
|
|
2062
2103
|
|
|
2063
2104
|
|
|
@@ -43,7 +43,6 @@ PABOT_MIN_QUEUE_INDEX_EXECUTING_PARALLEL_VALUE = "pabot_min_queue_index_executin
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class _PabotLib(object):
|
|
46
|
-
|
|
47
46
|
_TAGS_KEY = "tags"
|
|
48
47
|
|
|
49
48
|
def __init__(self, resourcefile=None): # type: (Optional[str]) -> None
|
|
@@ -155,17 +154,19 @@ class _PabotLib(object):
|
|
|
155
154
|
content[self._TAGS_KEY] = []
|
|
156
155
|
self._values[name] = content
|
|
157
156
|
|
|
158
|
-
def import_shared_library(
|
|
157
|
+
def import_shared_library(
|
|
158
|
+
self, name, args=None
|
|
159
|
+
): # type: (str, Iterable[Any]|None) -> int
|
|
159
160
|
if name in self._remote_libraries:
|
|
160
161
|
return self._remote_libraries[name][0]
|
|
161
162
|
if name in STDLIBS:
|
|
162
|
-
import_name =
|
|
163
|
+
import_name = "robot.libraries." + name
|
|
163
164
|
else:
|
|
164
165
|
import_name = name
|
|
165
|
-
imported = Importer(
|
|
166
|
-
|
|
167
|
-
imported, port=0, serve=False, allow_stop=True
|
|
166
|
+
imported = Importer("library").import_class_or_module(
|
|
167
|
+
name_or_path=import_name, instantiate_with_args=args
|
|
168
168
|
)
|
|
169
|
+
server = RobotRemoteServer(imported, port=0, serve=False, allow_stop=True)
|
|
169
170
|
server_thread = threading.Thread(target=server.serve)
|
|
170
171
|
server_thread.start()
|
|
171
172
|
time.sleep(1)
|
|
@@ -197,7 +198,6 @@ class _PabotLib(object):
|
|
|
197
198
|
|
|
198
199
|
|
|
199
200
|
class PabotLib(_PabotLib):
|
|
200
|
-
|
|
201
201
|
__version__ = 0.67
|
|
202
202
|
ROBOT_LIBRARY_SCOPE = "GLOBAL"
|
|
203
203
|
ROBOT_LISTENER_API_VERSION = 2
|
|
@@ -96,7 +96,10 @@ class DependsTest(unittest.TestCase):
|
|
|
96
96
|
--test Test.The Test S1Test 08
|
|
97
97
|
""",
|
|
98
98
|
)
|
|
99
|
-
self.assertIn(
|
|
99
|
+
self.assertIn(
|
|
100
|
+
b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
|
|
101
|
+
stdout,
|
|
102
|
+
)
|
|
100
103
|
|
|
101
104
|
def test_unmet_dependency(self):
|
|
102
105
|
stdout, stderr = self._run_tests_with(
|
|
@@ -107,7 +110,10 @@ class DependsTest(unittest.TestCase):
|
|
|
107
110
|
--test Test.The Test S1Test 08
|
|
108
111
|
""",
|
|
109
112
|
)
|
|
110
|
-
self.assertIn(
|
|
113
|
+
self.assertIn(
|
|
114
|
+
b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
|
|
115
|
+
stdout,
|
|
116
|
+
)
|
|
111
117
|
|
|
112
118
|
def test_same_reference(self):
|
|
113
119
|
stdout, stderr = self._run_tests_with(
|
|
@@ -118,7 +124,10 @@ class DependsTest(unittest.TestCase):
|
|
|
118
124
|
--test Test.The Test S1Test 08
|
|
119
125
|
""",
|
|
120
126
|
)
|
|
121
|
-
self.assertIn(
|
|
127
|
+
self.assertIn(
|
|
128
|
+
b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
|
|
129
|
+
stdout,
|
|
130
|
+
)
|
|
122
131
|
|
|
123
132
|
def test_wait(self):
|
|
124
133
|
stdout, stderr = self._run_tests_with(
|
|
@@ -130,4 +139,7 @@ class DependsTest(unittest.TestCase):
|
|
|
130
139
|
--test Test.The Test S1Test 08
|
|
131
140
|
""",
|
|
132
141
|
)
|
|
133
|
-
self.assertIn(
|
|
142
|
+
self.assertIn(
|
|
143
|
+
b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
|
|
144
|
+
stdout,
|
|
145
|
+
)
|
|
@@ -86,7 +86,6 @@ class PabotTests(unittest.TestCase):
|
|
|
86
86
|
"--resourcefile",
|
|
87
87
|
"resourcefile.ini",
|
|
88
88
|
"--testlevelsplit",
|
|
89
|
-
"--pabotlib",
|
|
90
89
|
"--pabotlibhost",
|
|
91
90
|
"123.123.233.123",
|
|
92
91
|
"--pabotlibport",
|
|
@@ -105,7 +104,7 @@ class PabotTests(unittest.TestCase):
|
|
|
105
104
|
self.assertEqual(pabot_args["command"], ["my_own_command.sh"])
|
|
106
105
|
self.assertEqual(pabot_args["processes"], 12)
|
|
107
106
|
self.assertEqual(pabot_args["resourcefile"], "resourcefile.ini")
|
|
108
|
-
self.assertEqual(pabot_args["pabotlib"],
|
|
107
|
+
self.assertEqual(pabot_args["pabotlib"], False)
|
|
109
108
|
self.assertEqual(pabot_args["pabotlibhost"], "123.123.233.123")
|
|
110
109
|
self.assertEqual(pabot_args["pabotlibport"], 4562)
|
|
111
110
|
self.assertEqual(pabot_args["suitesfrom"], "some.xml")
|
|
@@ -1226,16 +1225,17 @@ class PabotTests(unittest.TestCase):
|
|
|
1226
1225
|
file_path = os.path.join(_opts["outputdir"], f)
|
|
1227
1226
|
self.assertTrue(os.path.isfile(file_path), "file not copied: {}".format(f))
|
|
1228
1227
|
os.remove(file_path) # clean up
|
|
1229
|
-
|
|
1228
|
+
|
|
1230
1229
|
def test_merge_one_run_with_and_without_legacyoutput(self):
|
|
1231
1230
|
dtemp = tempfile.mkdtemp()
|
|
1232
1231
|
# Create the same directory structure as pabot
|
|
1233
|
-
test_outputs = os.path.join(dtemp,
|
|
1232
|
+
test_outputs = os.path.join(dtemp, "outputs")
|
|
1234
1233
|
os.makedirs(test_outputs)
|
|
1235
|
-
test_output = os.path.join(test_outputs,
|
|
1234
|
+
test_output = os.path.join(test_outputs, "output.xml")
|
|
1236
1235
|
# Create a minimal but valid output.xml
|
|
1237
|
-
with open(test_output,
|
|
1238
|
-
f.write(
|
|
1236
|
+
with open(test_output, "w") as f:
|
|
1237
|
+
f.write(
|
|
1238
|
+
"""<?xml version="1.0" encoding="UTF-8"?>
|
|
1239
1239
|
<robot generator="Rebot 7.1.1 (Python 3.11.7 on darwin)" generated="20241130 11:19:45.235" rpa="false" schemaversion="4">
|
|
1240
1240
|
<suite id="s1" name="Suites">
|
|
1241
1241
|
<suite id="s1-s1" name="Test" source="/Users/mkorpela/workspace/pabot/test.robot">
|
|
@@ -1281,16 +1281,17 @@ class PabotTests(unittest.TestCase):
|
|
|
1281
1281
|
<msg timestamp="20241130 11:19:44.910" level="ERROR">Error in file '/Users/mkorpela/workspace/pabot/test.robot' on line 2: Library 'Easter' expected 0 arguments, got 1.</msg>
|
|
1282
1282
|
<msg timestamp="20241130 11:19:44.913" level="ERROR">Error in file '/Users/mkorpela/workspace/pabot/test.robot' on line 2: Library 'Easter' expected 0 arguments, got 1.</msg>
|
|
1283
1283
|
</errors>
|
|
1284
|
-
</robot>"""
|
|
1285
|
-
|
|
1286
|
-
|
|
1284
|
+
</robot>"""
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
self._options["outputdir"] = dtemp
|
|
1287
1288
|
if ROBOT_VERSION >= "7.0":
|
|
1288
|
-
self._options[
|
|
1289
|
+
self._options["legacyoutput"] = True
|
|
1289
1290
|
try:
|
|
1290
1291
|
output = pabot._merge_one_run(
|
|
1291
1292
|
outs_dir=dtemp,
|
|
1292
1293
|
options=self._options,
|
|
1293
|
-
tests_root_name=
|
|
1294
|
+
tests_root_name="Test", # Should match suite name in XML
|
|
1294
1295
|
stats={
|
|
1295
1296
|
"total": 0,
|
|
1296
1297
|
"passed": 0,
|
|
@@ -1298,9 +1299,12 @@ class PabotTests(unittest.TestCase):
|
|
|
1298
1299
|
"skipped": 0,
|
|
1299
1300
|
},
|
|
1300
1301
|
copied_artifacts=[],
|
|
1301
|
-
outputfile=
|
|
1302
|
-
|
|
1303
|
-
|
|
1302
|
+
outputfile="merged_output.xml",
|
|
1303
|
+
) # Use different name to avoid confusion
|
|
1304
|
+
self.assertTrue(
|
|
1305
|
+
output, "merge_one_run returned empty string"
|
|
1306
|
+
) # Verify we got output path
|
|
1307
|
+
with open(output, "r") as f:
|
|
1304
1308
|
content = f.read()
|
|
1305
1309
|
if ROBOT_VERSION >= "6.1":
|
|
1306
1310
|
self.assertIn('schemaversion="4"', content)
|
|
@@ -1309,11 +1313,11 @@ class PabotTests(unittest.TestCase):
|
|
|
1309
1313
|
elif ROBOT_VERSION >= "4.0":
|
|
1310
1314
|
self.assertIn('schemaversion="2"', content)
|
|
1311
1315
|
if ROBOT_VERSION >= "7.0":
|
|
1312
|
-
del self._options[
|
|
1316
|
+
del self._options["legacyoutput"]
|
|
1313
1317
|
output = pabot._merge_one_run(
|
|
1314
1318
|
outs_dir=dtemp,
|
|
1315
1319
|
options=self._options,
|
|
1316
|
-
tests_root_name=
|
|
1320
|
+
tests_root_name="Test", # Should match suite name in XML
|
|
1317
1321
|
stats={
|
|
1318
1322
|
"total": 0,
|
|
1319
1323
|
"passed": 0,
|
|
@@ -1321,24 +1325,36 @@ class PabotTests(unittest.TestCase):
|
|
|
1321
1325
|
"skipped": 0,
|
|
1322
1326
|
},
|
|
1323
1327
|
copied_artifacts=[],
|
|
1324
|
-
outputfile=
|
|
1325
|
-
|
|
1326
|
-
|
|
1328
|
+
outputfile="merged_2_output.xml",
|
|
1329
|
+
) # Use different name to avoid confusion
|
|
1330
|
+
self.assertTrue(
|
|
1331
|
+
output, "merge_one_run returned empty string"
|
|
1332
|
+
) # Verify we got output path
|
|
1333
|
+
with open(output, "r") as f:
|
|
1327
1334
|
content = f.read()
|
|
1328
1335
|
self.assertIn('schemaversion="5"', content)
|
|
1329
1336
|
self.assertNotIn('schemaversion="4"', content)
|
|
1330
1337
|
finally:
|
|
1331
|
-
shutil.rmtree(dtemp)
|
|
1332
|
-
|
|
1338
|
+
shutil.rmtree(dtemp)
|
|
1339
|
+
|
|
1333
1340
|
def test_parse_args_mixed_order(self):
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1341
|
+
(
|
|
1342
|
+
options,
|
|
1343
|
+
datasources,
|
|
1344
|
+
pabot_args,
|
|
1345
|
+
options_for_subprocesses,
|
|
1346
|
+
) = arguments.parse_args(
|
|
1347
|
+
[
|
|
1348
|
+
"--exitonfailure",
|
|
1349
|
+
"--processes",
|
|
1350
|
+
"12",
|
|
1351
|
+
"--outputdir",
|
|
1352
|
+
"mydir",
|
|
1353
|
+
"--verbose",
|
|
1354
|
+
"--pabotlib",
|
|
1355
|
+
"suite",
|
|
1356
|
+
]
|
|
1357
|
+
)
|
|
1342
1358
|
self.assertEqual(pabot_args["processes"], 12)
|
|
1343
1359
|
self.assertEqual(pabot_args["verbose"], True)
|
|
1344
1360
|
self.assertEqual(pabot_args["pabotlib"], True)
|
|
@@ -1360,15 +1376,17 @@ class PabotTests(unittest.TestCase):
|
|
|
1360
1376
|
self.assertIn("requires matching --end-command", str(cm.exception))
|
|
1361
1377
|
|
|
1362
1378
|
def test_parse_args_command_with_pabot_args(self):
|
|
1363
|
-
options, datasources, pabot_args, _ = arguments.parse_args(
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1379
|
+
options, datasources, pabot_args, _ = arguments.parse_args(
|
|
1380
|
+
[
|
|
1381
|
+
"--command",
|
|
1382
|
+
"script.sh",
|
|
1383
|
+
"--processes",
|
|
1384
|
+
"5",
|
|
1385
|
+
"--end-command",
|
|
1386
|
+
"--verbose",
|
|
1387
|
+
"suite",
|
|
1388
|
+
]
|
|
1389
|
+
)
|
|
1372
1390
|
self.assertEqual(pabot_args["command"], ["script.sh", "--processes", "5"])
|
|
1373
1391
|
self.assertEqual(pabot_args["verbose"], True)
|
|
1374
1392
|
|
|
@@ -1390,7 +1408,10 @@ class PabotTests(unittest.TestCase):
|
|
|
1390
1408
|
def test_conflicting_pabotlib_options_raise_error(self):
|
|
1391
1409
|
with self.assertRaises(DataError) as context:
|
|
1392
1410
|
arguments.parse_args(["--pabotlib", "--no-pabotlib", "suite"])
|
|
1393
|
-
self.assertIn(
|
|
1411
|
+
self.assertIn(
|
|
1412
|
+
"Cannot use both --pabotlib and --no-pabotlib", str(context.exception)
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1394
1415
|
|
|
1395
1416
|
if __name__ == "__main__":
|
|
1396
1417
|
unittest.main()
|
|
@@ -28,7 +28,7 @@ class PabotLibTests(unittest.TestCase):
|
|
|
28
28
|
def test_shared_library_with_args(self):
|
|
29
29
|
try:
|
|
30
30
|
self._create_ctx() # Set up Robot Framework context
|
|
31
|
-
lib = SharedLibrary("mylib", ["2"])
|
|
31
|
+
lib = SharedLibrary("mylib", ["2"])
|
|
32
32
|
self.assertIsNotNone(lib)
|
|
33
33
|
lib._remote = None
|
|
34
34
|
lib._lib.run_keyword("mykeyword", ["arg"], {})
|
{robotframework_pabot-4.0.0 → robotframework_pabot-4.0.4}/tests/test_testlevelsplit_include.py
RENAMED
|
@@ -7,14 +7,14 @@ import shutil
|
|
|
7
7
|
import subprocess
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
class PabotPassJsonUsingVariableOptionTests(unittest.TestCase):
|
|
12
11
|
def setUp(self):
|
|
13
12
|
self.tmpdir = tempfile.mkdtemp()
|
|
14
13
|
file_path = f"{self.tmpdir}/test.robot"
|
|
15
14
|
with open(file_path, "w") as robot_file:
|
|
16
15
|
robot_file.write(
|
|
17
|
-
textwrap.dedent(
|
|
16
|
+
textwrap.dedent(
|
|
17
|
+
"""
|
|
18
18
|
*** Test Cases ***
|
|
19
19
|
Testing 1
|
|
20
20
|
[Tags] tag
|
|
@@ -23,8 +23,10 @@ Testing 1
|
|
|
23
23
|
Testing 2
|
|
24
24
|
[Tags] tag
|
|
25
25
|
Log world
|
|
26
|
-
"""
|
|
27
|
-
|
|
26
|
+
"""
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
28
30
|
process = subprocess.Popen(
|
|
29
31
|
[
|
|
30
32
|
sys.executable,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|