spl2-testing-framework 1.2.2__tar.gz → 1.4.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 (33) hide show
  1. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/PKG-INFO +4 -1
  2. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/README.md +3 -0
  3. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/pyproject.toml +1 -1
  4. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/conftest.py +13 -3
  5. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/results.py +48 -0
  6. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/spl2test_runner.py +10 -0
  7. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/test_discovery.py +22 -6
  8. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/LICENSE +0 -0
  9. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/__init__.py +0 -0
  10. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/launch_remotely_spl2.py +0 -0
  11. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/logger_manager.py +0 -0
  12. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/pytest.ini.sample +0 -0
  13. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/single_spl2_file_runner.py +0 -0
  14. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/spl2_test_config.json +0 -0
  15. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/spl2_utils/assertions.spl2 +0 -0
  16. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/spl2_utils/logs_to_metrics.spl2 +0 -0
  17. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/test_runner.py +0 -0
  18. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/__init__.py +0 -0
  19. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/create_diff_sheet.py +0 -0
  20. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/__init__.py +0 -0
  21. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/cli_job.py +0 -0
  22. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/cloud_job.py +0 -0
  23. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/job.py +0 -0
  24. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/splunk_job.py +0 -0
  25. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/jobs/test_cloud_job.py +0 -0
  26. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/performance.py +0 -0
  27. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/search_clients/__init__.py +0 -0
  28. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/search_clients/cli_search_client.py +0 -0
  29. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/search_clients/cloud_search_client.py +0 -0
  30. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/search_clients/search_client.py +0 -0
  31. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/search_clients/splunk_search_client.py +0 -0
  32. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/spl2_testing_framework/tools/test_types.py +0 -0
  33. {spl2_testing_framework-1.2.2 → spl2_testing_framework-1.4.0}/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.2.2
3
+ Version: 1.4.0
4
4
  Summary: SPL2 Testing Framework
5
5
  Home-page: https://github.com/splunk/spl2-testing-framework
6
6
  Keywords: SPL2 testing framework
@@ -131,6 +131,7 @@ It is possible to pass more options to the command, which works also with pytest
131
131
  Additionally, the following options are supported:
132
132
 
133
133
  * `--ignore_empty_strings` - to ignore empty strings in the results
134
+ * `--ignore_additional_fields_in_actual` - to ignore fields present in actual results but not in expected results (useful when actual results contain extra fields that should not affect comparison)
134
135
  * `--create_comparison_sheet` - to create a comparison sheet in `comparison_box_test` folder using actual and expected outputs (works only when running box tests)
135
136
 
136
137
  Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
@@ -180,6 +181,8 @@ It is possible to pass more options to the command, which works also with pytest
180
181
  the samples in module.json file of the corresponding template file
181
182
  * `--sample_delimiter` - Separator for separating the samples provided in the sample file. If not provided, it will use
182
183
  newline as a default separator.
184
+ * `--limit_tests` - Limit number of tests to execute from the unit, sample file or module.json. If not provided, all will be executed.
185
+ This can be used for quick testing of spl2 files during development.
183
186
 
184
187
  * ... and much more, whatever is supported by pytest
185
188
 
@@ -99,6 +99,7 @@ It is possible to pass more options to the command, which works also with pytest
99
99
  Additionally, the following options are supported:
100
100
 
101
101
  * `--ignore_empty_strings` - to ignore empty strings in the results
102
+ * `--ignore_additional_fields_in_actual` - to ignore fields present in actual results but not in expected results (useful when actual results contain extra fields that should not affect comparison)
102
103
  * `--create_comparison_sheet` - to create a comparison sheet in `comparison_box_test` folder using actual and expected outputs (works only when running box tests)
103
104
 
104
105
  Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
@@ -148,6 +149,8 @@ It is possible to pass more options to the command, which works also with pytest
148
149
  the samples in module.json file of the corresponding template file
149
150
  * `--sample_delimiter` - Separator for separating the samples provided in the sample file. If not provided, it will use
150
151
  newline as a default separator.
152
+ * `--limit_tests` - Limit number of tests to execute from the unit, sample file or module.json. If not provided, all will be executed.
153
+ This can be used for quick testing of spl2 files during development.
151
154
 
152
155
  * ... and much more, whatever is supported by pytest
153
156
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  [tool.poetry]
18
18
  name = "spl2-testing-framework"
19
- version = "1.2.2"
19
+ version = "1.4.0"
20
20
  description = "SPL2 Testing Framework"
21
21
  authors = ["mgazda-splunk <mgazda@splunk.com>"]
22
22
  readme = "README.md"
@@ -47,10 +47,12 @@ BOX_TEST = "box_test"
47
47
  SINGLE_SPL2_FILE = "single_spl2_file"
48
48
 
49
49
  IGNORE_EMPTY_STRINGS = "ignore_empty_strings"
50
+ IGNORE_ADDITIONAL_FIELDS_IN_ACTUAL = "ignore_additional_fields_in_actual"
50
51
  CREATE_COMPARISON_SHEET = "create_comparison_sheet"
51
52
  PERFORMANCE_CHECK = "performance_check"
52
53
  TEST_TYPE = "type"
53
54
  TEST_DIR = "test_dir"
55
+ LIMIT_TESTS = "limit_tests"
54
56
 
55
57
  import logging
56
58
 
@@ -62,7 +64,7 @@ _LOGGER = logging.getLogger(__name__)
62
64
 
63
65
 
64
66
  def pytest_addoption(parser):
65
- parser.addoption(f"--{TEST_TYPE}", action="store", default="local")
67
+ parser.addoption(f"--{TEST_TYPE}", action="store", default="cli")
66
68
  parser.addoption(f"--{TEST_DIR}", action="store", default=".")
67
69
  parser.addoption(
68
70
  f"--{PERFORMANCE_CHECK}",
@@ -75,6 +77,8 @@ def pytest_addoption(parser):
75
77
  parser.addoption("--sample_delimiter", action="store", default="\n")
76
78
  parser.addoption(f"--{CREATE_COMPARISON_SHEET}", action="store_true", default=False)
77
79
  parser.addoption(f"--{IGNORE_EMPTY_STRINGS}", action="store_true")
80
+ parser.addoption(f"--{IGNORE_ADDITIONAL_FIELDS_IN_ACTUAL}", action="store_true")
81
+ parser.addoption(f"--{LIMIT_TESTS}", action="store", type=int, default=None)
78
82
 
79
83
 
80
84
  def pytest_generate_tests(metafunc):
@@ -82,11 +86,12 @@ def pytest_generate_tests(metafunc):
82
86
  --test_dir option"""
83
87
  test_dir = metafunc.config.getoption(TEST_DIR)
84
88
  performance_check = metafunc.config.getoption(PERFORMANCE_CHECK)
89
+ limit_tests = metafunc.config.getoption(LIMIT_TESTS)
85
90
  _set_results_options(metafunc)
86
91
 
87
92
  if BOX_TEST in metafunc.fixturenames:
88
93
  _LOGGER.info("Collecting Box Tests")
89
- module_tests = BoxTestDiscovery(test_dir, performance_check)
94
+ module_tests = BoxTestDiscovery(test_dir, performance_check, limit_tests)
90
95
  module_tests.discover_tests()
91
96
  metafunc.parametrize(
92
97
  "%s" % BOX_TEST, module_tests.tests, ids=module_tests.tests.get_ids()
@@ -105,6 +110,7 @@ def pytest_generate_tests(metafunc):
105
110
  sample_file,
106
111
  sample_delimiter,
107
112
  performance_check=performance_check,
113
+ limit_tests=limit_tests,
108
114
  )
109
115
  try:
110
116
  spl2_pipelines.discover_tests()
@@ -118,7 +124,7 @@ def pytest_generate_tests(metafunc):
118
124
 
119
125
  if UNIT_TEST in metafunc.fixturenames:
120
126
  _LOGGER.info("Collecting Unit Tests")
121
- unit_tests = UTDiscovery(test_dir)
127
+ unit_tests = UTDiscovery(test_dir, limit_tests)
122
128
  unit_tests.discover_tests()
123
129
  metafunc.parametrize(
124
130
  UNIT_TEST, unit_tests.tests, ids=unit_tests.tests.get_ids()
@@ -245,3 +251,7 @@ def bearer_token_authentication(config):
245
251
  def _set_results_options(metafunc):
246
252
  ignore_empty_strings = metafunc.config.getoption(IGNORE_EMPTY_STRINGS)
247
253
  Results._ignore_empty_strings = ignore_empty_strings
254
+ ignore_fields_in_actual = metafunc.config.getoption(
255
+ IGNORE_ADDITIONAL_FIELDS_IN_ACTUAL
256
+ )
257
+ Results._ignore_additional_fields_in_actual = ignore_fields_in_actual
@@ -24,6 +24,7 @@ import pytest
24
24
 
25
25
  class Results(UserDict):
26
26
  _ignore_empty_strings = False
27
+ _ignore_additional_fields_in_actual = False
27
28
  """Class responsible for parsing and storing results from Splunk, IP and CLI.
28
29
  It's necessary to parse nested jsons and other python types to strings in order to compare them
29
30
  """
@@ -121,6 +122,53 @@ class Results(UserDict):
121
122
  if self._ignore_empty_strings:
122
123
  self.data = self._remove_field_by_value(self.data, "")
123
124
 
125
+ @staticmethod
126
+ def _remove_extra_fields(actual_data, expected_data):
127
+ """Remove fields from actual_data that are not present in expected_data.
128
+ This is useful when actual results may have additional fields that should be ignored in comparison.
129
+ Works recursively for nested dicts and lists.
130
+ """
131
+ if isinstance(expected_data, dict) and isinstance(actual_data, dict):
132
+ # Get the keys that are in actual but not in expected
133
+ keys_to_remove = set(actual_data.keys()) - set(expected_data.keys())
134
+ for key in keys_to_remove:
135
+ actual_data.pop(key, None)
136
+
137
+ # Recursively process nested structures
138
+ for key in actual_data.keys():
139
+ if key in expected_data:
140
+ if isinstance(actual_data[key], dict) and isinstance(
141
+ expected_data[key], dict
142
+ ):
143
+ Results._remove_extra_fields(
144
+ actual_data[key], expected_data[key]
145
+ )
146
+ elif isinstance(actual_data[key], list) and isinstance(
147
+ expected_data[key], list
148
+ ):
149
+ # For lists, we need to handle them carefully
150
+ if len(actual_data[key]) > 0 and len(expected_data[key]) > 0:
151
+ if isinstance(actual_data[key][0], dict) and isinstance(
152
+ expected_data[key][0], dict
153
+ ):
154
+ # If both are lists of dicts, process each element
155
+ for i in range(
156
+ min(len(actual_data[key]), len(expected_data[key]))
157
+ ):
158
+ Results._remove_extra_fields(
159
+ actual_data[key][i], expected_data[key][i]
160
+ )
161
+
162
+ return actual_data
163
+
164
+ def remove_fields_not_in_expected(self, expected):
165
+ """Remove fields from this Results object that are not present in the expected Results object.
166
+ This is the public method to be called when comparing results.
167
+ """
168
+ if self._ignore_additional_fields_in_actual and isinstance(expected, Results):
169
+ self.data = self._remove_extra_fields(self.data, expected.data)
170
+ return self
171
+
124
172
  def __lt__(self, other):
125
173
  if "_raw" in self.data:
126
174
  s = self.data.get("_raw")
@@ -131,6 +131,11 @@ class SPL2TestRunner:
131
131
  perf.check_performance(output_result)
132
132
 
133
133
  if box_test.output:
134
+ # Remove fields from actual results that are not in expected results if configured
135
+ for i, actual_result in enumerate(output_result):
136
+ if i < len(box_test.output):
137
+ actual_result.remove_fields_not_in_expected(box_test.output[i])
138
+
134
139
  if self.create_comparison_sheet:
135
140
  self.comp_obj.create_comparison_sheet(output_result, box_test)
136
141
  assert sorted(output_result) == sorted(box_test.output)
@@ -141,6 +146,11 @@ class SPL2TestRunner:
141
146
  _LOGGER.info(f"Output check skipped (expected empty): {box_test.name}")
142
147
 
143
148
  if box_test.metrics:
149
+ # Remove fields from actual metrics that are not in expected metrics if configured
150
+ for i, actual_metric in enumerate(metrics_result):
151
+ if i < len(box_test.metrics):
152
+ actual_metric.remove_fields_not_in_expected(box_test.metrics[i])
153
+
144
154
  assert sorted(metrics_result) == sorted(box_test.metrics)
145
155
  _LOGGER.info(f"Metric check passed: {box_test.name}")
146
156
  _LOGGER.debug("Received: \n%s", str(metrics_result))
@@ -41,9 +41,10 @@ class SamplesNotFoundException(Exception):
41
41
  class TestDiscovery(abc.ABC):
42
42
  """Base class for test discovery classes"""
43
43
 
44
- def __init__(self, path: str, performance_check: str):
44
+ def __init__(self, path: str, performance_check: str, limit_tests: int = None):
45
45
  self.path = pathlib.Path(path)
46
46
  self._performance_check = performance_check
47
+ self.limit_tests = limit_tests
47
48
  self.tests = TestSuite()
48
49
 
49
50
  def _read_tests(self, test_name: str) -> None:
@@ -56,8 +57,8 @@ class TestDiscovery(abc.ABC):
56
57
  class BoxTestDiscovery(TestDiscovery):
57
58
  """Discovers tests for box tests, looking into module.test.json files"""
58
59
 
59
- def __init__(self, path: str, performance_check: str):
60
- super().__init__(path, performance_check)
60
+ def __init__(self, path: str, performance_check: str, limit_tests: int = None):
61
+ super().__init__(path, performance_check, limit_tests)
61
62
 
62
63
  def discover_tests(self) -> None:
63
64
  self._read_tests("module.test.json")
@@ -70,7 +71,13 @@ class BoxTestDiscovery(TestDiscovery):
70
71
 
71
72
  for module_path, content in self._parsed_modules.items():
72
73
  path = module_path.parent
74
+ tests_added = 0
73
75
  for test in content:
76
+ if self.limit_tests is not None and tests_added >= self.limit_tests:
77
+ _LOGGER.info(
78
+ f"Limit of {self.limit_tests} tests reached for {module_path}. Stopping."
79
+ )
80
+ break
74
81
  name = test["filename"].replace(".spl2", "")
75
82
  test_input = test["test"]["source"]
76
83
  code_module_path = path / test["filename"]
@@ -92,6 +99,7 @@ class BoxTestDiscovery(TestDiscovery):
92
99
  )
93
100
  test.meta["performance"] = self._performance_check
94
101
  self.tests.add(test)
102
+ tests_added += 1
95
103
 
96
104
 
97
105
  class SingleSPL2Discovery(TestDiscovery):
@@ -104,8 +112,9 @@ class SingleSPL2Discovery(TestDiscovery):
104
112
  sample_file=None,
105
113
  sample_delimiter="\n",
106
114
  performance_check="no",
115
+ limit_tests: int = None,
107
116
  ):
108
- super().__init__(path, performance_check)
117
+ super().__init__(path, performance_check, limit_tests)
109
118
  self.template_file = template_file
110
119
  self.sample_file = sample_file
111
120
  self.sample_delimiter = sample_delimiter
@@ -188,8 +197,8 @@ class SingleSPL2Discovery(TestDiscovery):
188
197
  class UTDiscovery(TestDiscovery):
189
198
  """Discovers tests for unit tests, looking into .test.spl2 files"""
190
199
 
191
- def __init__(self, path: str):
192
- super().__init__(path, performance_check="no")
200
+ def __init__(self, path: str, limit_tests: int = None):
201
+ super().__init__(path, performance_check="no", limit_tests=limit_tests)
193
202
 
194
203
  def discover_tests(self) -> None:
195
204
  self._read_tests("*.test.spl2")
@@ -208,7 +217,13 @@ class UTDiscovery(TestDiscovery):
208
217
  code_module_name = file_name.replace(".test.", ".")
209
218
  code_module_path = path.parent / code_module_name
210
219
 
220
+ tests_added = 0
211
221
  for test in tests:
222
+ if self.limit_tests is not None and tests_added >= self.limit_tests:
223
+ _LOGGER.info(
224
+ f"Limit of {self.limit_tests} tests reached for {file}. Stopping."
225
+ )
226
+ break
212
227
  self.tests.add(
213
228
  UnitTest(
214
229
  name=test,
@@ -218,3 +233,4 @@ class UTDiscovery(TestDiscovery):
218
233
  code_module_path=code_module_path,
219
234
  )
220
235
  )
236
+ tests_added += 1