spl2-testing-framework 1.5.0__tar.gz → 1.5.1__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 (33) hide show
  1. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/PKG-INFO +3 -2
  2. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/README.md +2 -2
  3. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/pyproject.toml +1 -1
  4. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/performance.py +93 -12
  5. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/spl2test_runner.py +1 -0
  6. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/LICENSE +0 -0
  7. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/__init__.py +0 -0
  8. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/conftest.py +0 -0
  9. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/launch_remotely_spl2.py +0 -0
  10. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/logger_manager.py +0 -0
  11. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/pytest.ini.sample +0 -0
  12. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/single_spl2_file_runner.py +0 -0
  13. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/spl2_test_config.json +0 -0
  14. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/spl2_utils/assertions.spl2 +0 -0
  15. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/spl2_utils/logs_to_metrics.spl2 +0 -0
  16. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/test_runner.py +0 -0
  17. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/__init__.py +0 -0
  18. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/create_diff_sheet.py +0 -0
  19. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/__init__.py +0 -0
  20. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/cli_job.py +0 -0
  21. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/cloud_job.py +0 -0
  22. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/job.py +0 -0
  23. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/splunk_job.py +0 -0
  24. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/jobs/test_cloud_job.py +0 -0
  25. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/results.py +0 -0
  26. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/search_clients/__init__.py +0 -0
  27. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/search_clients/cli_search_client.py +0 -0
  28. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/search_clients/cloud_search_client.py +0 -0
  29. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/search_clients/search_client.py +0 -0
  30. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/search_clients/splunk_search_client.py +0 -0
  31. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/test_discovery.py +0 -0
  32. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/test_types.py +0 -0
  33. {spl2_testing_framework-1.5.0 → spl2_testing_framework-1.5.1}/spl2_testing_framework/tools/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spl2-testing-framework
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: SPL2 Testing Framework
5
5
  Home-page: https://github.com/splunk/spl2-testing-framework
6
6
  Keywords: SPL2 testing framework
@@ -207,8 +207,9 @@ This checks can be applied only to box tests, as assertions which are used in un
207
207
  performance.
208
208
 
209
209
  ## Format all files
210
- To format all files, run:
210
+ To format all files run:
211
211
 
212
212
  ```bash
213
213
  black spl2_testing_framework tests
214
214
  ```
215
+
@@ -175,8 +175,8 @@ This checks can be applied only to box tests, as assertions which are used in un
175
175
  performance.
176
176
 
177
177
  ## Format all files
178
- To format all files, run:
178
+ To format all files run:
179
179
 
180
180
  ```bash
181
181
  black spl2_testing_framework tests
182
- ```
182
+ ```
@@ -15,7 +15,7 @@
15
15
 
16
16
  [tool.poetry]
17
17
  name = "spl2-testing-framework"
18
- version = "1.5.0"
18
+ version = "1.5.1"
19
19
  description = "SPL2 Testing Framework"
20
20
  authors = ["mgazda-splunk <mgazda@splunk.com>"]
21
21
  readme = "README.md"
@@ -38,7 +38,7 @@ class PerformanceCheck:
38
38
  PIPELINE_PATTERN = re.compile(
39
39
  r"(?P<fullmatch>"
40
40
  r"^\s*?\$pipeline\s*?=\s*?\|?\s*?from\s*?\$source\s*?" # first line of pipeline
41
- r"(.|\n)*?" # anything in between
41
+ r"(.|\n)*" # greedy: match everything up to the LAST into $destination
42
42
  r"(\|\s*?into\s*?\$destination)" # last line of pipeline definition
43
43
  r")",
44
44
  flags=re.M | re.VERBOSE,
@@ -46,8 +46,8 @@ class PerformanceCheck:
46
46
 
47
47
  FUNCTION_PATTERN = re.compile(
48
48
  r"^(?P<fullmatch>"
49
- r"(export\s+)?function\s+(?P<f_name>\S+)\((?P<source>\S+):" # function header
50
- r".*{\n\s+return\s+(\|\s+)?from\s+(?P=source)\n*" # return statement
49
+ r"(export\s+)?function\s+(?P<f_name>\S+)\((?P<source>\$\w+)" # function header (source may lack type)
50
+ r"[^{]*{\n\s+return\s+(\|\s+)?(?:from|FROM)\s+(?P=source)\n*" # return statement
51
51
  r"(?P<statements>(.|\n)*?)" # anything before closing bracket
52
52
  r"(?P<closing_bracket>^\s*})" # closing bracket [ASSUMED IT'S IN THE NEW LINE]
53
53
  r")",
@@ -56,6 +56,20 @@ class PerformanceCheck:
56
56
 
57
57
  STATEMENT_PATTERN = re.compile(r"^(?P<lx>\s*?\|.*)$", re.M)
58
58
 
59
+ SCALAR_FUNCTION_PATTERN = re.compile(
60
+ r"^(?P<fullmatch>"
61
+ r"(?:export\s+)?function\s+(?P<f_name>\S+)\("
62
+ r"[^{]*{\n"
63
+ r"(?P<statements>[\s\S]*?)"
64
+ r"(?P<closing_bracket>^\s*})"
65
+ r")",
66
+ flags=re.M | re.VERBOSE,
67
+ )
68
+
69
+ SCALAR_STATEMENT_PATTERN = re.compile(
70
+ r"^(?P<lx>\s*(?:\$\w+\s*=(?!=)|return\s).+)$", re.M
71
+ )
72
+
59
73
  def __init__(self, test: Test):
60
74
  self._test = test
61
75
  self._performance_check_type = test.meta.get("performance", None)
@@ -135,13 +149,26 @@ class PerformanceCheck:
135
149
  output = copy(self.code_module)
136
150
 
137
151
  for timestamp_name, value, relative in calculated_diffs:
152
+ marker = f">>> {(value * 1000):.6f}ms [{relative:.2%}] <<< "
138
153
  output = re.sub(
139
154
  rf"\| eval {timestamp_name}=time\(\)",
140
- f">>> {(value * 1000):.6f}ms [{relative:.2%}] <<< ",
155
+ marker,
156
+ output,
157
+ )
158
+ output = re.sub(
159
+ rf"^\${timestamp_name}\s*=\s*time\(\)\s*\n",
160
+ marker,
141
161
  output,
162
+ flags=re.M,
142
163
  )
143
164
 
144
165
  output = re.sub(r"\| eval _timestamp_\w+_\d{3}=time\(\)", r"", output)
166
+ output = re.sub(
167
+ r"^\$_timestamp_\w+_\d{3}\s*=\s*time\(\)\s*\n?",
168
+ r"",
169
+ output,
170
+ flags=re.M,
171
+ )
145
172
 
146
173
  _LOGGER.info(output)
147
174
 
@@ -154,7 +181,10 @@ class PerformanceCheck:
154
181
  def _save_output(self, output: str, event_number: int) -> None:
155
182
  """Save output to text file"""
156
183
  os.makedirs(self.OUTPUT_DIR, exist_ok=True)
157
- filename = f"{self.OUTPUT_DIR}/performance_{self._test.name}_{event_number}_{datetime.now()}.txt"
184
+ safe_name = (
185
+ self._test.name.replace(os.sep, "_").replace("/", "_").replace("\\", "_")
186
+ )
187
+ filename = f"{self.OUTPUT_DIR}/performance_{safe_name}_{event_number}_{datetime.now()}.txt"
158
188
 
159
189
  with open(filename, "w") as f:
160
190
  f.write(output)
@@ -181,13 +211,18 @@ class PerformanceCheck:
181
211
  flags=re.M,
182
212
  )
183
213
 
184
- code_module = re.sub(
185
- r"(\|\s*?into\s*?\$destination)",
186
- r"| eval _timestamp_perf_stop = time()"
187
- r"| eval _timestamp_perf_exec = _timestamp_perf_stop - _timestamp_perf_start \1",
188
- code_module,
189
- flags=re.M,
190
- )
214
+ pattern = re.compile(r"(\|\s*?into\s*?\$destination)", re.M)
215
+ matches = list(pattern.finditer(code_module))
216
+ if matches:
217
+ last = matches[-1]
218
+ replacement = (
219
+ "| eval _timestamp_perf_stop = time()"
220
+ "| eval _timestamp_perf_exec = _timestamp_perf_stop - _timestamp_perf_start "
221
+ + last.group(1)
222
+ )
223
+ code_module = (
224
+ code_module[: last.start()] + replacement + code_module[last.end() :]
225
+ )
191
226
 
192
227
  return code_module
193
228
 
@@ -196,6 +231,7 @@ class PerformanceCheck:
196
231
 
197
232
  functions = self.__parse_functions(code_module)
198
233
  pipeline = self.__parse_pipeline(code_module)
234
+ scalar_functions = self.__parse_scalar_functions(code_module)
199
235
 
200
236
  functions.append(pipeline)
201
237
 
@@ -203,14 +239,37 @@ class PerformanceCheck:
203
239
  new_function = self.__add_timestamps_to_function(f)
204
240
  code_module = code_module.replace(f["fullmatch"], new_function)
205
241
 
242
+ for f in scalar_functions:
243
+ new_function = self.__add_timestamps_to_scalar_function(f)
244
+ code_module = code_module.replace(f["fullmatch"], new_function)
245
+
246
+ code_module = self._preserve_timestamps_through_fields(code_module)
247
+
206
248
  return code_module
207
249
 
250
+ @staticmethod
251
+ def _preserve_timestamps_through_fields(code_module: str) -> str:
252
+ """Replace '| fields' keep-mode with a no-op so _timestamp_perf* fields are not stripped."""
253
+ return re.sub(
254
+ r"\|\s*fields\s+(?!-)(.+)",
255
+ r"| eval _perf_fields_preserved=1",
256
+ code_module,
257
+ )
258
+
208
259
  def __parse_functions(self, code_module: str) -> List[Dict]:
209
260
  """Parse all functions from code module. The finding contains groups necessary for parsing it
210
261
  and adding timestamps"""
211
262
  functions = [m.groupdict() for m in self.FUNCTION_PATTERN.finditer(code_module)]
212
263
  return functions
213
264
 
265
+ def __parse_scalar_functions(self, code_module: str) -> List[Dict]:
266
+ """Parse scalar functions (no FROM $source) that use $variable = expr assignments."""
267
+ dataset_fn_names = {f["f_name"] for f in self.__parse_functions(code_module)}
268
+ all_fns = [
269
+ m.groupdict() for m in self.SCALAR_FUNCTION_PATTERN.finditer(code_module)
270
+ ]
271
+ return [f for f in all_fns if f["f_name"] not in dataset_fn_names]
272
+
214
273
  def __parse_pipeline(self, code_module: str) -> Dict:
215
274
  """Parse pipeline definition from code module. The finding contains groups necessary for parsing it
216
275
  and adding timestamps"""
@@ -249,3 +308,25 @@ class PerformanceCheck:
249
308
  )
250
309
 
251
310
  return new_function
311
+
312
+ def __add_timestamps_to_scalar_function(self, f: Dict) -> str:
313
+ """Add timestamps to scalar functions that use $variable = expr assignments.
314
+ Injects $-prefixed variable assignments instead of | eval commands."""
315
+ new_function = self.SCALAR_STATEMENT_PATTERN.sub(
316
+ rf"$_time_{f['f_name']}_placeholder = time()\n\g<lx>",
317
+ f["fullmatch"],
318
+ )
319
+
320
+ func_timestamps = re.findall(
321
+ rf"\$_time_{f['f_name']}_placeholder", new_function
322
+ )
323
+
324
+ for n, timestamp in enumerate(func_timestamps):
325
+ new_function = re.sub(
326
+ re.escape(timestamp),
327
+ rf"$_timestamp_perf{f['f_name']}_{n:03d}",
328
+ new_function,
329
+ count=1,
330
+ )
331
+
332
+ return new_function
@@ -177,6 +177,7 @@ class SPL2TestRunner:
177
177
  _LOGGER.debug("Expected: \n%s", str(box_test.output))
178
178
  else:
179
179
  _LOGGER.info(f"Output check skipped (expected empty): {box_test.name}")
180
+ _LOGGER.debug("Actual results received: \n%s", str(output_result))
180
181
 
181
182
  if box_test.metrics:
182
183
  # Remove fields from actual metrics that are not in expected metrics if configured