robotframework-pabot 4.0.6__tar.gz → 4.1.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.
Files changed (43) hide show
  1. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/PKG-INFO +3 -2
  2. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/README.md +58 -47
  3. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/__init__.py +1 -1
  4. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/arguments.py +2 -12
  5. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/execution_items.py +19 -7
  6. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/pabot.py +70 -82
  7. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/PKG-INFO +3 -2
  8. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/SOURCES.txt +1 -0
  9. robotframework_pabot-4.1.0/tests/test_depends.py +219 -0
  10. robotframework_pabot-4.1.0/tests/test_pabotprerunmodifier.py +212 -0
  11. robotframework_pabot-4.0.6/tests/test_depends.py +0 -145
  12. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/LICENSE.txt +0 -0
  13. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/MANIFEST.in +0 -0
  14. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/pyproject.toml +0 -0
  15. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/setup.cfg +0 -0
  16. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/setup.py +0 -0
  17. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/SharedLibrary.py +0 -0
  18. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/clientwrapper.py +0 -0
  19. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/coordinatorwrapper.py +0 -0
  20. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/pabotlib.py +0 -0
  21. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/__init__.py +0 -0
  22. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/client.py +0 -0
  23. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/coordinator.py +0 -0
  24. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/messages.py +0 -0
  25. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/worker.py +0 -0
  26. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/result_merger.py +0 -0
  27. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/robotremoteserver.py +0 -0
  28. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/workerwrapper.py +0 -0
  29. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
  30. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
  31. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/requires.txt +0 -0
  32. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
  33. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_arguments_output.py +0 -0
  34. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_functional.py +0 -0
  35. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_ordering.py +0 -0
  36. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabot.py +0 -0
  37. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabotlib.py +0 -0
  38. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabotsuitenames_io.py +0 -0
  39. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_prerunmodifier.py +0 -0
  40. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_resultmerger.py +0 -0
  41. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_stacktrace.py +0 -0
  42. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_testlevelsplit_include.py +0 -0
  43. {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_testlevelsplit_output_task_order.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: robotframework-pabot
3
- Version: 4.0.6
3
+ Version: 4.1.0
4
4
  Summary: Parallel test runner for Robot Framework
5
5
  Home-page: https://pabot.org
6
6
  Download-URL: https://pypi.python.org/pypi/robotframework-pabot
@@ -20,5 +20,6 @@ License-File: LICENSE.txt
20
20
  Requires-Dist: robotframework>=3.2
21
21
  Requires-Dist: robotframework-stacktrace>=0.4.1
22
22
  Requires-Dist: natsort>=8.2.0
23
+ Dynamic: download-url
23
24
 
24
25
  A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.
@@ -53,45 +53,45 @@ There are several ways you can help in improving this tool:
53
53
  - Contribute by programming and making a pull request (easiest way is to work on an issue from the issue tracker)
54
54
 
55
55
  ## Command-line options
56
-
57
- pabot [--verbose|--testlevelsplit|--command .. --end-command|
58
- --processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
59
- --processtimeout num|
60
- --shard i/n|
61
- --artifacts extensions|--artifactsinsubfolders|
62
- --resourcefile file|--argumentfile[num] file|--suitesfrom file]
63
- [robot options] [path ...]
56
+ <!-- START DOCSTRING -->
57
+ pabot [--verbose|--testlevelsplit|--command .. --end-command|
58
+ --processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
59
+ --processtimeout num|
60
+ --shard i/n|
61
+ --artifacts extensions|--artifactsinsubfolders|
62
+ --resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file
63
+ --chunk
64
+ --pabotprerunmodifier modifier]
65
+ [robot options] [path ...]
66
+
67
+ PabotLib remote server is started by default to enable locking and resource distribution between parallel test executions.
64
68
 
65
69
  Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following pabot options:
66
70
 
67
71
  --verbose
68
- more output from the parallel execution
72
+ More output from the parallel execution.
69
73
 
70
74
  --testlevelsplit
71
- Split execution on test level instead of default suite level.
72
- If .pabotsuitenames contains both tests and suites then this
73
- will only affect new suites and split only them.
74
- Leaving this flag out when both suites and tests in
75
- .pabotsuitenames file will also only affect new suites and
76
- add them as suite files.
75
+ Split execution on test level instead of default suite level. If .pabotsuitenames contains both tests and suites then
76
+ this will only affect new suites and split only them. Leaving this flag out when both suites and tests in
77
+ .pabotsuitenames file will also only affect new suites and add them as suite files.
77
78
 
78
79
  --command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
79
- RF script for situations where robot is not used directly
80
-
81
- --processes [NUMBER OF PROCESSES]
82
- How many parallel executors to use (default max of 2 and cpu count).
83
- Special option "all" will use as many processes as there are
84
- executable suites or tests.
80
+ RF script for situations where robot is not used directly.
85
81
 
86
- PabotLib remote server is started by default to enable locking and resource distribution
87
- between parallel test executions.
82
+ --processes [NUMBER OF PROCESSES]
83
+ How many parallel executors to use (default max of 2 and cpu count). Special option "all" will use as many processes as
84
+ there are executable suites or tests.
88
85
 
89
- --no-pabotlib
86
+ --no-pabotlib
90
87
  Disable the PabotLib remote server if you don't need locking or resource distribution features.
91
88
 
92
- --pabotlibhost [HOSTNAME]
93
- Connect to an already running instance of the PabotLib remote server at the given host
94
- (disables the local PabotLib server start).
89
+ --pabotlibhost [HOSTNAME]
90
+ Connect to an already running instance of the PabotLib remote server at the given host (disables the local PabotLib
91
+ server start). For example, to connect to a remote PabotLib server running on another machine:
92
+
93
+ pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
94
+
95
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>
@@ -99,21 +99,20 @@ between parallel test executions.
99
99
 
100
100
  This enables sharing a resource with multiple Robot Framework instances.
101
101
 
102
- --pabotlibport [PORT]
103
- Port number of the PabotLib remote server (default is 8270)
104
- See --pabotlibhost for more information
102
+ --pabotlibport [PORT]
103
+ Port number of the PabotLib remote server (default is 8270). See --pabotlibhost for more information.
105
104
 
106
- --processtimeout [TIMEOUT]
105
+ --processtimeout [TIMEOUT]
107
106
  Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
108
107
 
109
- --resourcefile [FILEPATH]
110
- Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
108
+ --shard [INDEX]/[TOTAL]
109
+ Optionally split execution into smaller pieces. This can be used for distributing testing to multiple machines.
111
110
 
112
111
  --artifacts [FILE EXTENSIONS]
113
- List of file extensions (comma separated).
114
- Defines which files (screenshots, videos etc.) from separate reporting directories would be copied and included in a final report.
115
- Possible links to copied files in RF log would be updated (only relative paths supported).
116
- The default value is `png`.
112
+ List of file extensions (comma separated). Defines which files (screenshots, videos etc.) from separate reporting
113
+ directories would be copied and included in a final report. Possible links to copied files in RF log would be updated
114
+ (only relative paths supported). The default value is `png`.
115
+
117
116
  Examples:
118
117
 
119
118
  --artifacts png,mp4,txt
@@ -121,22 +120,33 @@ between parallel test executions.
121
120
  --artifactsinsubfolders
122
121
  Copy artifacts located not only directly in the RF output dir, but also in it's sub-folders.
123
122
 
124
- --argumentfile[INTEGER] [FILEPATH]
123
+ --resourcefile [FILEPATH]
124
+ Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
125
+ pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
126
+
127
+ --argumentfile [INTEGER] [FILEPATH]
125
128
  Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
129
+
126
130
  For example:
127
131
 
128
132
  --argumentfile1 arg1.txt --argumentfile2 arg2.txt
129
133
 
130
- --suitesfrom [FILEPATH TO OUTPUTXML]
131
- Optionally read suites from output.xml file. Failed suites will run
132
- first and longer running ones will be executed before shorter ones.
134
+ --suitesfrom [FILEPATH TO OUTPUTXML]
135
+ Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
136
+ before shorter ones.
137
+
138
+ --ordering [FILE PATH]
139
+ Optionally give execution order from a file.
133
140
 
134
- --shard [INDEX]/[TOTAL]
135
- Optionally split execution into smaller pieces. This can
136
- be used for distributing testing to multiple machines.
141
+ --chunk
142
+ Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
143
+ setups and teardowns.
137
144
 
138
- --chunk
139
- Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same setups and teardowns.
145
+ --pabotprerunmodifier [PRERUNMODIFIER MODULE OR CLASS]
146
+ Like Robot Framework's --prerunmodifier, but executed only once in the pabot's main process after all other
147
+ --prerunmodifiers. But unlike the regular --prerunmodifier command, --pabotprerunmodifier is not executed again in each
148
+ pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
149
+ example, to modify the list of tests to be performed.
140
150
 
141
151
  Example usages:
142
152
 
@@ -149,6 +159,7 @@ Example usages:
149
159
  # To disable PabotLib:
150
160
  pabot --no-pabotlib tests
151
161
 
162
+ <!-- END DOCSTRING -->
152
163
  ### PabotLib
153
164
 
154
165
  pabot.PabotLib provides keywords that will help communication and data sharing between the executor processes.
@@ -219,7 +230,7 @@ There different possibilities to influence the execution:
219
230
  * If the base suite name is changing with robot option [```--name / -N```](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#setting-the-name) you can also give partial suite name without the base suite.
220
231
  * You can add a line with text `#WAIT` to force executor to wait until all previous suites have been executed.
221
232
  * You can group suites and tests together to same executor process by adding line `{` before the group and `}`after.
222
- * You can introduce dependencies using the word `#DEPENDS` after a test declaration. Please take care that in case of circular dependencies an exception will be thrown. An example could be.
233
+ * You can introduce dependencies using the word `#DEPENDS` after a test declaration. Can be used several times if it is necessary to refer to several different tests. Please take care that in case of circular dependencies an exception will be thrown. An example could be.
223
234
 
224
235
  ```
225
236
  --test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
@@ -2,4 +2,4 @@ from __future__ import absolute_import
2
2
 
3
3
  from .pabotlib import PabotLib
4
4
 
5
- __version__ = "4.0.6"
5
+ __version__ = "4.1.0"
@@ -106,8 +106,9 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
106
106
  "resourcefile": str,
107
107
  "pabotlibhost": str,
108
108
  "pabotlibport": int,
109
+ "pabotprerunmodifier": str,
109
110
  "processtimeout": int,
110
- "ordering": _parse_ordering,
111
+ "ordering": str,
111
112
  "suitesfrom": str,
112
113
  "artifacts": lambda x: x.split(","),
113
114
  "shard": _parse_shard,
@@ -195,17 +196,6 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
195
196
  return remaining_args, pabot_args
196
197
 
197
198
 
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
199
  def _delete_none_keys(d): # type: (Dict[str, Optional[object]]) -> Dict[str, object]
210
200
  keys = set()
211
201
  for k in d:
@@ -5,6 +5,7 @@ from robot import __version__ as ROBOT_VERSION
5
5
  from robot.errors import DataError
6
6
  from robot.utils import PY2, is_unicode
7
7
 
8
+ import re
8
9
 
9
10
  @total_ordering
10
11
  class ExecutionItem(object):
@@ -97,20 +98,31 @@ class GroupItem(ExecutionItem):
97
98
  class RunnableItem(ExecutionItem):
98
99
  pass
99
100
 
100
- depends = None # type: str
101
+ depends = None # type: List[str]
101
102
  depends_keyword = "#DEPENDS"
102
103
 
104
+ def _split_dependencies(self, line_name, depends_indexes):
105
+ depends_lst = [] if len(depends_indexes) < 2 else [line_name[i + len(self.depends_keyword) : j].strip() for i, j in zip(depends_indexes, depends_indexes[1:])]
106
+ depends_lst.append(line_name[depends_indexes[-1] + len(self.depends_keyword) : ].strip())
107
+ return depends_lst
108
+
109
+ def _merge_dependencies(self, line_start):
110
+ output_line = line_start
111
+ for d in self.depends:
112
+ output_line = output_line + " " + self.depends_keyword + " " + d
113
+ return output_line
114
+
103
115
  def set_name_and_depends(self, name):
104
116
  line_name = name.encode("utf-8") if PY2 and is_unicode(name) else name
105
- depends_begin_index = line_name.find(self.depends_keyword)
117
+ depends_indexes = [d.start() for d in re.finditer(self.depends_keyword, line_name)]
106
118
  self.name = (
107
119
  line_name
108
- if depends_begin_index == -1
109
- else line_name[0:depends_begin_index].strip()
120
+ if len(depends_indexes) == 0
121
+ else line_name[0:depends_indexes[0]].strip()
110
122
  )
111
123
  self.depends = (
112
- line_name[depends_begin_index + len(self.depends_keyword) :].strip()
113
- if depends_begin_index != -1
124
+ self._split_dependencies(line_name, depends_indexes)
125
+ if len(depends_indexes) != 0
114
126
  else None
115
127
  )
116
128
 
@@ -118,7 +130,7 @@ class RunnableItem(ExecutionItem):
118
130
  # type: () -> str
119
131
  line_without_depends = "--" + self.type + " " + self.name
120
132
  return (
121
- line_without_depends + " " + self.depends_keyword + " " + self.depends
133
+ self._merge_dependencies(line_without_depends)
122
134
  if self.depends
123
135
  else line_without_depends
124
136
  )
@@ -15,73 +15,12 @@
15
15
  # limitations under the License.
16
16
  #
17
17
  # partly based on work by Nokia Solutions and Networks Oyj
18
+
19
+ # Help documentation from README.md:
18
20
  """A parallel executor for Robot Framework test cases.
19
21
  Version [PABOT_VERSION]
20
22
 
21
- Supports all Robot Framework command line options and also following
22
- options (these must be before normal RF options):
23
-
24
- --verbose
25
- more output
26
-
27
- --command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
28
- RF script for situations where pybot is not used directly
29
-
30
- --processes [NUMBER OF PROCESSES]
31
- How many parallel executors to use (default max of 2 and cpu count).
32
- Special option "all" will use as many processes as there are
33
- executable suites or tests.
34
-
35
- --testlevelsplit
36
- Split execution on test level instead of default suite level.
37
- If .pabotsuitenames contains both tests and suites then this
38
- will only affect new suites and split only them.
39
- Leaving this flag out when both suites and tests in
40
- .pabotsuitenames file will also only affect new suites and
41
- add them as suite files.
42
-
43
- --resourcefile [FILEPATH]
44
- Indicator for a file that can contain shared variables for
45
- distributing resources.
46
-
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
-
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
61
-
62
- --pabotlibport [PORT]
63
- Port number of the PabotLib remote server (default is 8270)
64
-
65
- --processtimeout [TIMEOUT]
66
- Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
67
-
68
- --ordering [FILE PATH]
69
- Optionally give execution order from a file.
70
-
71
- --suitesfrom [FILEPATH TO OUTPUTXML]
72
- Optionally read suites from output.xml file. Failed suites will run
73
- first and longer running ones will be executed before shorter ones.
74
-
75
- --argumentfile[INTEGER] [FILEPATH]
76
- Run same suite with multiple argumentfile options.
77
- For example "--argumentfile1 arg1.txt --argumentfile2 arg2.txt".
78
-
79
- --shard [SHARD]/[SHARD COUNT]
80
- Optionally split execution into smaller pieces. This can
81
- be used for distributing testing to multiple machines.
82
-
83
- --chunk
84
- Optionally chunk tests to PROCESSES number of robot runs.
23
+ PLACEHOLDER_README.MD
85
24
 
86
25
  Copyright 2022 Mikko Korpela - Apache 2 License
87
26
  """
@@ -184,6 +123,27 @@ _ROBOT_EXTENSIONS = [
184
123
  _ALL_ELAPSED = [] # type: List[Union[int, float]]
185
124
 
186
125
 
126
+ def extract_section(filename, start_marker, end_marker):
127
+ with open(filename, "r", encoding="utf-8") as f:
128
+ lines = f.readlines()
129
+
130
+ inside_section = False
131
+ extracted_lines = []
132
+
133
+ for line in lines:
134
+ if start_marker in line:
135
+ inside_section = True
136
+ continue
137
+ if end_marker in line:
138
+ inside_section = False
139
+ break
140
+ if inside_section:
141
+ # Add line from README.md without [] and (https: address)
142
+ extracted_lines.append(re.sub(r'\[([^\]]+)\]\(https?://[^\)]+\)', r'\1', line))
143
+
144
+ return "".join(extracted_lines).strip()
145
+
146
+
187
147
  class Color:
188
148
  SUPPORTED_OSES = ["posix"]
189
149
 
@@ -880,6 +840,8 @@ def solve_shard_suites(suite_names, pabot_args):
880
840
 
881
841
 
882
842
  def solve_suite_names(outs_dir, datasources, options, pabot_args):
843
+ if pabot_args.get("pabotprerunmodifier"):
844
+ options['prerunmodifier'].append(pabot_args['pabotprerunmodifier'])
883
845
  h = Hashes(
884
846
  dirs=get_hash_of_dirs(datasources),
885
847
  cmd=get_hash_of_command(options, pabot_args),
@@ -915,7 +877,7 @@ def solve_suite_names(outs_dir, datasources, options, pabot_args):
915
877
  for l in lines[4:]
916
878
  )
917
879
  execution_item_lines = [parse_execution_item_line(l) for l in lines[4:]]
918
- if corrupted or h != file_h or file_hash != hash_of_file:
880
+ if corrupted or h != file_h or file_hash != hash_of_file or pabot_args.get("pabotprerunmodifier"):
919
881
  return _regenerate(
920
882
  file_h,
921
883
  h,
@@ -1949,7 +1911,11 @@ def main_program(args):
1949
1911
  _start_message_writer()
1950
1912
  options, datasources, pabot_args, opts_for_run = parse_args(args)
1951
1913
  if pabot_args["help"]:
1952
- print(__doc__.replace("[PABOT_VERSION]", PABOT_VERSION))
1914
+ help_print = __doc__.replace(
1915
+ "PLACEHOLDER_README.MD",
1916
+ extract_section("README.md", "<!-- START DOCSTRING -->", "<!-- END DOCSTRING -->")
1917
+ )
1918
+ print(help_print.replace("[PABOT_VERSION]", PABOT_VERSION))
1953
1919
  return 0
1954
1920
  if len(datasources) == 0:
1955
1921
  print("[ " + _wrap_with(Color.RED, "ERROR") + " ]: No datasources given.")
@@ -1988,7 +1954,8 @@ def main_program(args):
1988
1954
  )
1989
1955
  return result_code if not _ABNORMAL_EXIT_HAPPENED else 252
1990
1956
  except Information as i:
1991
- print(__doc__.replace("[PABOT_VERSION]", PABOT_VERSION))
1957
+ version_print = __doc__.replace("\nPLACEHOLDER_README.MD\n", "")
1958
+ print(version_print.replace("[PABOT_VERSION]", PABOT_VERSION))
1992
1959
  print(i.message)
1993
1960
  except DataError as err:
1994
1961
  print(err.message)
@@ -2010,10 +1977,24 @@ def main_program(args):
2010
1977
  _stop_message_writer()
2011
1978
 
2012
1979
 
1980
+ def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
1981
+ try:
1982
+ with open(filename, "r") as orderingfile:
1983
+ return [
1984
+ parse_execution_item_line(line.strip())
1985
+ for line in orderingfile.readlines()
1986
+ ]
1987
+ except FileNotFoundError:
1988
+ raise DataError("Error: File '%s' not found." % filename)
1989
+ except:
1990
+ raise DataError("Error parsing ordering file '%s'" % filename)
1991
+
1992
+
2013
1993
  def _group_suites(outs_dir, datasources, options, pabot_args):
2014
1994
  suite_names = solve_suite_names(outs_dir, datasources, options, pabot_args)
2015
1995
  _verify_depends(suite_names)
2016
- ordered_suites = _preserve_order(suite_names, pabot_args.get("ordering"))
1996
+ ordering_arg = _parse_ordering(pabot_args.get("ordering")) if (pabot_args.get("ordering")) is not None else None
1997
+ ordered_suites = _preserve_order(suite_names, ordering_arg)
2017
1998
  shard_suites = solve_shard_suites(ordered_suites, pabot_args)
2018
1999
  grouped_suites = (
2019
2000
  _chunked_suite_names(shard_suites, pabot_args["processes"])
@@ -2083,20 +2064,27 @@ def _group_by_depend(suite_names):
2083
2064
  return [suite_names]
2084
2065
  independent_tests = list(filter(lambda suite: not suite.depends, runnable_suites))
2085
2066
  dependency_tree = [independent_tests]
2086
- while True:
2087
- dependent_tests = list(filter(lambda suite: suite.depends, runnable_suites))
2088
- dependent_on_last_stage = list(
2089
- filter(
2090
- lambda suite: any(
2091
- test_in_tier_before.name == suite.depends
2092
- for test_in_tier_before in dependency_tree[-1]
2093
- ),
2094
- dependent_tests,
2095
- )
2096
- )
2097
- if not dependent_on_last_stage:
2098
- break
2099
- dependency_tree += [dependent_on_last_stage]
2067
+ dependent_tests = list(filter(lambda suite: suite.depends, runnable_suites))
2068
+ unknown_dependent_tests = dependent_tests
2069
+ while len(unknown_dependent_tests) > 0:
2070
+ run_in_this_stage, run_later = [], []
2071
+ for d in unknown_dependent_tests:
2072
+ stage_indexes = []
2073
+ for i, stage in enumerate(dependency_tree):
2074
+ for test in stage:
2075
+ if test.name in d.depends:
2076
+ stage_indexes.append(i)
2077
+ # All #DEPENDS test are already run:
2078
+ if len(stage_indexes) == len(d.depends):
2079
+ run_in_this_stage.append(d)
2080
+ else:
2081
+ run_later.append(d)
2082
+ unknown_dependent_tests = run_later
2083
+ if len(run_in_this_stage) == 0:
2084
+ text = "There are circular or unmet dependencies using #DEPENDS. Check this/these test(s): " + str(run_later)
2085
+ raise DataError(text)
2086
+ else:
2087
+ dependency_tree.append(run_in_this_stage)
2100
2088
  flattened_dependency_tree = sum(dependency_tree, [])
2101
2089
  if len(flattened_dependency_tree) != len(runnable_suites):
2102
2090
  raise DataError(
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: robotframework-pabot
3
- Version: 4.0.6
3
+ Version: 4.1.0
4
4
  Summary: Parallel test runner for Robot Framework
5
5
  Home-page: https://pabot.org
6
6
  Download-URL: https://pypi.python.org/pypi/robotframework-pabot
@@ -20,5 +20,6 @@ License-File: LICENSE.txt
20
20
  Requires-Dist: robotframework>=3.2
21
21
  Requires-Dist: robotframework-stacktrace>=0.4.1
22
22
  Requires-Dist: natsort>=8.2.0
23
+ Dynamic: download-url
23
24
 
24
25
  A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.
@@ -32,6 +32,7 @@ tests/test_functional.py
32
32
  tests/test_ordering.py
33
33
  tests/test_pabot.py
34
34
  tests/test_pabotlib.py
35
+ tests/test_pabotprerunmodifier.py
35
36
  tests/test_pabotsuitenames_io.py
36
37
  tests/test_prerunmodifier.py
37
38
  tests/test_resultmerger.py
@@ -0,0 +1,219 @@
1
+ import subprocess
2
+ import sys
3
+ import textwrap
4
+ import shutil
5
+ import tempfile
6
+ import unittest
7
+
8
+
9
+ def _string_convert(byte_string):
10
+ legacy_python = sys.version_info < (3, 0)
11
+ return byte_string.decode() if legacy_python else byte_string
12
+
13
+
14
+ class DependsTest(unittest.TestCase):
15
+ test_file = """
16
+ *** Settings ***
17
+ Test Template Test1
18
+ *** Test Cases ***
19
+ The Test S1Test 01 1
20
+ The Test S1Test 02 1
21
+ The Test S1Test 03 1
22
+ The Test S1Test 04 1
23
+ The Test S1Test 05 1
24
+ The Test S1Test 06 1
25
+ The Test S1Test 07 1
26
+ The Test S1Test 08 1
27
+ The Test S1Test 09 1
28
+ The Test S1Test 10 1
29
+ The Test S1Test 11 1
30
+ The Test S1Test 12 1
31
+ *** Keywords ***
32
+ Test1
33
+ [Arguments] ${arg}
34
+ Log Test
35
+ """
36
+ passed = _string_convert(b"PASSED")
37
+ failed = _string_convert(b"FAILED")
38
+ test_01 = _string_convert(b"S1Test 01")
39
+ test_02 = _string_convert(b"S1Test 02")
40
+ test_03 = _string_convert(b"S1Test 03")
41
+ test_04 = _string_convert(b"S1Test 04")
42
+ test_05 = _string_convert(b"S1Test 05")
43
+ test_06 = _string_convert(b"S1Test 06")
44
+ test_07 = _string_convert(b"S1Test 07")
45
+ test_08 = _string_convert(b"S1Test 08")
46
+ test_09 = _string_convert(b"S1Test 09")
47
+ test_10 = _string_convert(b"S1Test 10")
48
+ test_11 = _string_convert(b"S1Test 11")
49
+ test_12 = _string_convert(b"S1Test 12")
50
+
51
+ def setUp(self):
52
+ self.tmpdir = tempfile.mkdtemp()
53
+
54
+ def tearDown(self):
55
+ shutil.rmtree(self.tmpdir)
56
+
57
+ def _run_tests_with(self, testfile, orderfile):
58
+ robot_file = open("{}/test.robot".format(self.tmpdir), "w")
59
+ robot_file.write(textwrap.dedent(testfile))
60
+ robot_file.close()
61
+ with open("{}/order.dat".format(self.tmpdir), "w") as f:
62
+ f.write(textwrap.dedent(orderfile))
63
+ process = subprocess.Popen(
64
+ [
65
+ sys.executable,
66
+ "-m" "pabot.pabot",
67
+ "--testlevelsplit",
68
+ "--ordering",
69
+ "{}/order.dat".format(self.tmpdir),
70
+ "{}/test.robot".format(self.tmpdir),
71
+ ],
72
+ cwd=self.tmpdir,
73
+ stdout=subprocess.PIPE,
74
+ stderr=subprocess.PIPE,
75
+ )
76
+ return process.communicate()
77
+
78
+ def test_dependency_ok(self):
79
+ stdout, stderr = self._run_tests_with(
80
+ self.test_file,
81
+ """
82
+ --test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
83
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
84
+ --test Test.The Test S1Test 08
85
+ """,
86
+ )
87
+ self.assertIn(self.passed, stdout, stderr)
88
+ self.assertNotIn(self.failed, stdout, stderr)
89
+ self.assertEqual(stdout.count(self.passed), 12)
90
+ test_01_index = stdout.find(self.test_01)
91
+ test_02_index = stdout.find(self.test_02)
92
+ test_08_index = stdout.find(self.test_08)
93
+ self.assertNotEqual(test_01_index, -1)
94
+ self.assertNotEqual(test_02_index, -1)
95
+ self.assertNotEqual(test_08_index, -1)
96
+ self.assertTrue(test_08_index < test_02_index)
97
+ self.assertTrue(test_02_index < test_01_index)
98
+
99
+ def test_multiple_dependencies_ok(self):
100
+ stdout, stderr = self._run_tests_with(
101
+ self.test_file,
102
+ """
103
+ --test Test.The Test S1Test 01
104
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
105
+ --test Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 02
106
+ --test Test.The Test S1Test 04
107
+ --test Test.The Test S1Test 05 #DEPENDS Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 04
108
+ --test Test.The Test S1Test 06 #DEPENDS Test.The Test S1Test 05
109
+ --test Test.The Test S1Test 07
110
+ --test Test.The Test S1Test 08
111
+ --test Test.The Test S1Test 09 #DEPENDS Test.The Test S1Test 07 #DEPENDS Test.The Test S1Test 08 #DEPENDS Test.The Test S1Test 06
112
+ --test Test.The Test S1Test 10 #DEPENDS Test.The Test S1Test 12
113
+ --test Test.The Test S1Test 11 #DEPENDS Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 06 #DEPENDS Test.The Test S1Test 10 #DEPENDS Test.The Test S1Test 09
114
+ --test Test.The Test S1Test 12
115
+ """,
116
+ )
117
+ self.assertIn(self.passed, stdout, stderr)
118
+ self.assertNotIn(self.failed, stdout, stderr)
119
+ self.assertEqual(stdout.count(self.passed), 12)
120
+ test_01_index = stdout.find(self.test_01)
121
+ test_02_index = stdout.find(self.test_02)
122
+ test_03_index = stdout.find(self.test_03)
123
+ test_04_index = stdout.find(self.test_04)
124
+ test_05_index = stdout.find(self.test_05)
125
+ test_06_index = stdout.find(self.test_06)
126
+ test_07_index = stdout.find(self.test_07)
127
+ test_08_index = stdout.find(self.test_08)
128
+ test_09_index = stdout.find(self.test_09)
129
+ test_10_index = stdout.find(self.test_10)
130
+ test_11_index = stdout.find(self.test_11)
131
+ test_12_index = stdout.find(self.test_12)
132
+ self.assertNotEqual(test_01_index, -1)
133
+ self.assertNotEqual(test_02_index, -1)
134
+ self.assertNotEqual(test_03_index, -1)
135
+ self.assertNotEqual(test_04_index, -1)
136
+ self.assertNotEqual(test_05_index, -1)
137
+ self.assertNotEqual(test_06_index, -1)
138
+ self.assertNotEqual(test_07_index, -1)
139
+ self.assertNotEqual(test_08_index, -1)
140
+ self.assertNotEqual(test_09_index, -1)
141
+ self.assertNotEqual(test_10_index, -1)
142
+ self.assertNotEqual(test_11_index, -1)
143
+ self.assertNotEqual(test_12_index, -1)
144
+ self.assertTrue(test_02_index > test_01_index)
145
+ self.assertTrue(test_03_index > test_02_index)
146
+ self.assertTrue(test_05_index > test_01_index)
147
+ self.assertTrue(test_05_index > test_04_index)
148
+ self.assertTrue(test_06_index > test_05_index)
149
+ self.assertTrue(test_09_index > test_07_index)
150
+ self.assertTrue(test_09_index > test_08_index)
151
+ self.assertTrue(test_09_index > test_06_index)
152
+ self.assertTrue(test_10_index > test_12_index)
153
+ self.assertTrue(test_11_index > test_03_index)
154
+ self.assertTrue(test_11_index > test_06_index)
155
+ self.assertTrue(test_11_index > test_10_index)
156
+ self.assertTrue(test_11_index > test_09_index)
157
+
158
+ def test_circular_dependency(self):
159
+ stdout, stderr = self._run_tests_with(
160
+ self.test_file,
161
+ """
162
+ --test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
163
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
164
+ --test Test.The Test S1Test 08
165
+ """,
166
+ )
167
+ self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 01>, <test:Test.The Test S1Test 02>]", stdout)
168
+ self.assertEqual(b"", stderr)
169
+
170
+ def test_circular_dependency_with_multiple_depends(self):
171
+ stdout, stderr = self._run_tests_with(
172
+ self.test_file,
173
+ """
174
+ --test Test.The Test S1Test 01
175
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
176
+ --test Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 01
177
+ --test Test.The Test S1Test 08 #DEPENDS Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 09
178
+ --test Test.The Test S1Test 09 #DEPENDS Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 08
179
+ """,
180
+ )
181
+ self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 08>, <test:Test.The Test S1Test 09>]", stdout)
182
+ self.assertEqual(b"", stderr)
183
+
184
+ def test_unmet_dependency(self):
185
+ stdout, stderr = self._run_tests_with(
186
+ self.test_file,
187
+ """
188
+ --test Test.The Test S1Test 01
189
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 23
190
+ --test Test.The Test S1Test 08
191
+ """,
192
+ )
193
+ self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
194
+ self.assertEqual(b"", stderr)
195
+
196
+ def test_same_reference(self):
197
+ stdout, stderr = self._run_tests_with(
198
+ self.test_file,
199
+ """
200
+ --test Test.The Test S1Test 01
201
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 02
202
+ --test Test.The Test S1Test 08
203
+ """,
204
+ )
205
+ self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
206
+ self.assertEqual(b"", stderr)
207
+
208
+ def test_wait(self):
209
+ stdout, stderr = self._run_tests_with(
210
+ self.test_file,
211
+ """
212
+ --test Test.The Test S1Test 01
213
+ --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
214
+ #WAIT
215
+ --test Test.The Test S1Test 08
216
+ """,
217
+ )
218
+ self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
219
+ self.assertEqual(b"", stderr)
@@ -0,0 +1,212 @@
1
+ import unittest
2
+ import tempfile
3
+ import textwrap
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ import os
8
+ import re
9
+ import robot
10
+
11
+
12
+ def check_robot_version_and_return_name():
13
+ version = robot.__version__
14
+ major_version = int(version.split('.')[0])
15
+
16
+ if major_version >= 7:
17
+ return "full_name"
18
+ else:
19
+ return "longname"
20
+
21
+
22
+ class PabotPrerunModifierTests(unittest.TestCase):
23
+ @classmethod
24
+ def setUpClass(self):
25
+ self.tmpdir = tempfile.mkdtemp()
26
+ self.tmpdir_name = (re.sub(r'(\d)([a-z])', lambda m: m.group(1) + m.group(2).upper(),
27
+ " ".join(word.capitalize().strip() for word in os.path.basename(self.tmpdir).split("_")))).strip()
28
+
29
+ # robot case file 1
30
+ self.robot_file_path_1 = f'{self.tmpdir}/test_1.robot'
31
+ with open(self.robot_file_path_1, 'w') as robot_file:
32
+ robot_file.write(
33
+ textwrap.dedent("""
34
+ *** Test Cases ***
35
+ Testing 1
36
+ [Tags] tag test3
37
+ Log hello
38
+
39
+ Testing 2
40
+ [Tags] tag test test2 test3
41
+ Log world
42
+
43
+ Testing 3
44
+ [Tags] tag
45
+ Log world
46
+ """))
47
+
48
+ # robot case file 2
49
+ self.robot_file_path_2 = f'{self.tmpdir}/test_2.robot'
50
+ with open(self.robot_file_path_2, 'w') as robot_file:
51
+ robot_file.write(
52
+ textwrap.dedent("""
53
+ *** Test Cases ***
54
+ Testing 4
55
+ [Tags] tag
56
+ Log hello
57
+
58
+ Testing 5
59
+ [Tags] tag test2 test3
60
+ Log world
61
+
62
+ Testing 6
63
+ [Tags] tag
64
+ Log world
65
+ """))
66
+
67
+ # pabotprerunmodifier script. Works like Robot Framework --include command
68
+ self.modifier_file_path = f'{self.tmpdir}/Modifier.py'
69
+ correct_full_name_call = check_robot_version_and_return_name()
70
+ with open(self.modifier_file_path, 'w') as modifier_file:
71
+ modifier_file.write(
72
+ textwrap.dedent(f"""
73
+ from robot.api import SuiteVisitor
74
+
75
+ class Modifier(SuiteVisitor):
76
+ def __init__(self, tag):
77
+ self.list_of_test_names = []
78
+ self.tag = tag
79
+
80
+ def start_suite(self, suite):
81
+ if suite.tests:
82
+ for test in suite.tests:
83
+ if self.tag in test.tags:
84
+ self.list_of_test_names.append(test.{correct_full_name_call})
85
+
86
+ def end_suite(self, suite):
87
+ suite.tests = [t for t in suite.tests if t.{correct_full_name_call} in self.list_of_test_names]
88
+ suite.suites = [s for s in suite.suites if s.test_count > 0]
89
+ """))
90
+
91
+ # prerunmodifier script
92
+ self.modifier2_file_path = f'{self.tmpdir}/Modifier2.py'
93
+ with open(self.modifier2_file_path, 'w') as modifier_file:
94
+ modifier_file.write(
95
+ textwrap.dedent("""
96
+ from robot.api import SuiteVisitor
97
+
98
+ class Modifier2(SuiteVisitor):
99
+ def start_suite(self, suite):
100
+ if suite.tests:
101
+ for test in suite.tests:
102
+ if '2' in test.name:
103
+ test.name = 'new-name-2'
104
+ test.tags.add(['tag2'])
105
+ """))
106
+
107
+ # ordering file
108
+ self.ordering_file_path = f'{self.tmpdir}/ordering.txt'
109
+ with open(self.ordering_file_path, 'w') as ordering_file:
110
+ ordering_file.write(
111
+ textwrap.dedent(f"""
112
+ {{
113
+ --test {self.tmpdir_name}.Test 1.Testing 2
114
+ --test {self.tmpdir_name}.Test 2.Testing 5
115
+ }}
116
+ --test {self.tmpdir_name}.Test 1.Testing 1
117
+ """))
118
+
119
+
120
+ def test_pabotprerunmodifier(self):
121
+ process = subprocess.Popen(
122
+ [
123
+ sys.executable,
124
+ "-m", "pabot.pabot",
125
+ "--pabotprerunmodifier",
126
+ self.modifier_file_path + ":test",
127
+ self.tmpdir
128
+ ],
129
+ cwd=self.tmpdir,
130
+ stdout=subprocess.PIPE,
131
+ stderr=subprocess.PIPE
132
+ )
133
+
134
+ stdout, stderr = process.communicate()
135
+ # without testlevelsplit argument whole test suite 1 will be executed.
136
+ self.assertIn(f'PASSED {self.tmpdir_name}.Test 1'.encode('utf-8'), stdout)
137
+ self.assertIn(b'3 tests, 3 passed, 0 failed, 0 skipped.', stdout)
138
+ self.assertEqual(b"", stderr)
139
+
140
+
141
+ def test_pabotprerunmodifier_with_testlevelsplit(self):
142
+ process = subprocess.Popen(
143
+ [
144
+ sys.executable,
145
+ "-m", "pabot.pabot",
146
+ "--testlevelsplit",
147
+ "--pabotprerunmodifier",
148
+ self.modifier_file_path + ":test2",
149
+ self.tmpdir
150
+ ],
151
+ cwd=self.tmpdir,
152
+ stdout=subprocess.PIPE,
153
+ stderr=subprocess.PIPE
154
+ )
155
+
156
+ stdout, stderr = process.communicate()
157
+ self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.Testing 2'.encode('utf-8'), stdout)
158
+ self.assertIn(f'PASSED {self.tmpdir_name}.Test 2.Testing 5'.encode('utf-8'), stdout)
159
+ self.assertIn(b'2 tests, 2 passed, 0 failed, 0 skipped.', stdout)
160
+ self.assertEqual(b"", stderr)
161
+
162
+
163
+ def test_pabotprerunmodifier_with_testlevelsplit_and_ordering(self):
164
+ process = subprocess.Popen(
165
+ [
166
+ sys.executable,
167
+ "-m", "pabot.pabot",
168
+ "--testlevelsplit",
169
+ "--pabotprerunmodifier",
170
+ self.modifier_file_path + ":test3",
171
+ "--ordering",
172
+ self.ordering_file_path,
173
+ self.tmpdir
174
+ ],
175
+ cwd=self.tmpdir,
176
+ stdout=subprocess.PIPE,
177
+ stderr=subprocess.PIPE
178
+ )
179
+
180
+ stdout, stderr = process.communicate()
181
+ self.assertIn(f'PASSED Group_{self.tmpdir_name}.Test 1.Testing 2_{self.tmpdir_name}.Test 2.Testing 5'.encode('utf-8'), stdout)
182
+ self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.Testing 1'.encode('utf-8'), stdout)
183
+ self.assertIn(b'3 tests, 3 passed, 0 failed, 0 skipped.', stdout)
184
+ self.assertEqual(b"", stderr)
185
+
186
+
187
+ def test_pabotprerunmodifier_with_prerunmodifier(self):
188
+ process = subprocess.Popen(
189
+ [
190
+ sys.executable,
191
+ "-m", "pabot.pabot",
192
+ "--testlevelsplit",
193
+ "--pabotprerunmodifier",
194
+ self.modifier_file_path + ":tag2",
195
+ "--prerunmodifier",
196
+ self.modifier2_file_path,
197
+ self.tmpdir
198
+ ],
199
+ cwd=self.tmpdir,
200
+ stdout=subprocess.PIPE,
201
+ stderr=subprocess.PIPE
202
+ )
203
+
204
+ stdout, stderr = process.communicate()
205
+ self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.new-name-2'.encode('utf-8'), stdout)
206
+ self.assertIn(b'1 tests, 1 passed, 0 failed, 0 skipped.', stdout)
207
+ self.assertEqual(b"", stderr)
208
+
209
+
210
+ @classmethod
211
+ def tearDownClass(self):
212
+ shutil.rmtree(self.tmpdir)
@@ -1,145 +0,0 @@
1
- import subprocess
2
- import sys
3
- import textwrap
4
- import shutil
5
- import tempfile
6
- import unittest
7
-
8
-
9
- def _string_convert(byte_string):
10
- legacy_python = sys.version_info < (3, 0)
11
- return byte_string.decode() if legacy_python else byte_string
12
-
13
-
14
- class DependsTest(unittest.TestCase):
15
- test_file = """
16
- *** Settings ***
17
- Test Template Test1
18
- *** Test Cases ***
19
- The Test S1Test 01 1
20
- The Test S1Test 02 1
21
- The Test S1Test 03 1
22
- The Test S1Test 04 1
23
- The Test S1Test 05 1
24
- The Test S1Test 06 1
25
- The Test S1Test 07 1
26
- The Test S1Test 08 1
27
- The Test S1Test 09 1
28
- The Test S1Test 10 1
29
- The Test S1Test 11 1
30
- The Test S1Test 12 1
31
- *** Keywords ***
32
- Test1
33
- [Arguments] ${arg}
34
- Log Test
35
- """
36
- passed = _string_convert(b"PASSED")
37
- failed = _string_convert(b"FAILED")
38
- test_01 = _string_convert(b"S1Test 01")
39
- test_02 = _string_convert(b"S1Test 02")
40
- test_08 = _string_convert(b"S1Test 08")
41
-
42
- def setUp(self):
43
- self.tmpdir = tempfile.mkdtemp()
44
-
45
- def tearDown(self):
46
- shutil.rmtree(self.tmpdir)
47
-
48
- def _run_tests_with(self, testfile, orderfile):
49
- robot_file = open("{}/test.robot".format(self.tmpdir), "w")
50
- robot_file.write(textwrap.dedent(testfile))
51
- robot_file.close()
52
- with open("{}/order.dat".format(self.tmpdir), "w") as f:
53
- f.write(textwrap.dedent(orderfile))
54
- process = subprocess.Popen(
55
- [
56
- sys.executable,
57
- "-m" "pabot.pabot",
58
- "--testlevelsplit",
59
- "--ordering",
60
- "{}/order.dat".format(self.tmpdir),
61
- "{}/test.robot".format(self.tmpdir),
62
- ],
63
- cwd=self.tmpdir,
64
- stdout=subprocess.PIPE,
65
- stderr=subprocess.PIPE,
66
- )
67
- return process.communicate()
68
-
69
- def test_dependency_ok(self):
70
- stdout, stderr = self._run_tests_with(
71
- self.test_file,
72
- """
73
- --test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
74
- --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
75
- --test Test.The Test S1Test 08
76
- """,
77
- )
78
- self.assertIn(self.passed, stdout, stderr)
79
- self.assertNotIn(self.failed, stdout, stderr)
80
- self.assertEqual(stdout.count(self.passed), 12)
81
- test_01_index = stdout.find(self.test_01)
82
- test_02_index = stdout.find(self.test_02)
83
- test_08_index = stdout.find(self.test_08)
84
- self.assertNotEqual(test_01_index, -1)
85
- self.assertNotEqual(test_02_index, -1)
86
- self.assertNotEqual(test_08_index, -1)
87
- self.assertTrue(test_08_index < test_02_index)
88
- self.assertTrue(test_02_index < test_01_index)
89
-
90
- def test_circular_dependency(self):
91
- stdout, stderr = self._run_tests_with(
92
- self.test_file,
93
- """
94
- --test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
95
- --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
96
- --test Test.The Test S1Test 08
97
- """,
98
- )
99
- self.assertIn(
100
- b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
101
- stdout,
102
- )
103
-
104
- def test_unmet_dependency(self):
105
- stdout, stderr = self._run_tests_with(
106
- self.test_file,
107
- """
108
- --test Test.The Test S1Test 01
109
- --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 23
110
- --test Test.The Test S1Test 08
111
- """,
112
- )
113
- self.assertIn(
114
- b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
115
- stdout,
116
- )
117
-
118
- def test_same_reference(self):
119
- stdout, stderr = self._run_tests_with(
120
- self.test_file,
121
- """
122
- --test Test.The Test S1Test 01
123
- --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 02
124
- --test Test.The Test S1Test 08
125
- """,
126
- )
127
- self.assertIn(
128
- b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
129
- stdout,
130
- )
131
-
132
- def test_wait(self):
133
- stdout, stderr = self._run_tests_with(
134
- self.test_file,
135
- """
136
- --test Test.The Test S1Test 01
137
- --test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
138
- #WAIT
139
- --test Test.The Test S1Test 08
140
- """,
141
- )
142
- self.assertIn(
143
- b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
144
- stdout,
145
- )