spl2-testing-framework 1.5.1__tar.gz → 1.7.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 (38) hide show
  1. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/PKG-INFO +120 -2
  2. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/README.md +118 -1
  3. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/pyproject.toml +2 -1
  4. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/conftest.py +93 -4
  5. spl2_testing_framework-1.7.0/spl2_testing_framework/spl2_test_config.json +18 -0
  6. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/search_clients/cli_search_client.py +7 -2
  7. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/spl2test_runner.py +90 -28
  8. spl2_testing_framework-1.7.0/spl2_testing_framework/tools/splunk_integration/__init__.py +13 -0
  9. spl2_testing_framework-1.7.0/spl2_testing_framework/tools/splunk_integration/splunk_handler.py +119 -0
  10. spl2_testing_framework-1.7.0/spl2_testing_framework/tools/test_discovery.py +413 -0
  11. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/test_types.py +3 -0
  12. spl2_testing_framework-1.7.0/spl2_testing_framework/tools/utils.py +114 -0
  13. spl2_testing_framework-1.5.1/spl2_testing_framework/spl2_test_config.json +0 -9
  14. spl2_testing_framework-1.5.1/spl2_testing_framework/tools/test_discovery.py +0 -235
  15. spl2_testing_framework-1.5.1/spl2_testing_framework/tools/utils.py +0 -51
  16. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/LICENSE +0 -0
  17. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/__init__.py +0 -0
  18. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/launch_remotely_spl2.py +0 -0
  19. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/logger_manager.py +0 -0
  20. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/pytest.ini.sample +0 -0
  21. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/single_spl2_file_runner.py +0 -0
  22. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/spl2_utils/assertions.spl2 +0 -0
  23. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/spl2_utils/logs_to_metrics.spl2 +0 -0
  24. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/test_runner.py +0 -0
  25. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/__init__.py +0 -0
  26. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/create_diff_sheet.py +0 -0
  27. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/__init__.py +0 -0
  28. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/cli_job.py +0 -0
  29. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/cloud_job.py +0 -0
  30. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/job.py +0 -0
  31. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/splunk_job.py +0 -0
  32. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/jobs/test_cloud_job.py +0 -0
  33. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/performance.py +0 -0
  34. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/results.py +0 -0
  35. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/search_clients/__init__.py +0 -0
  36. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/search_clients/cloud_search_client.py +0 -0
  37. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/search_clients/search_client.py +0 -0
  38. {spl2_testing_framework-1.5.1 → spl2_testing_framework-1.7.0}/spl2_testing_framework/tools/search_clients/splunk_search_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spl2-testing-framework
3
- Version: 1.5.1
3
+ Version: 1.7.0
4
4
  Summary: SPL2 Testing Framework
5
5
  Home-page: https://github.com/splunk/spl2-testing-framework
6
6
  Keywords: SPL2 testing framework
@@ -25,6 +25,7 @@ Requires-Dist: pytest (==7.4.4)
25
25
  Requires-Dist: pytest-subtests (==0.13.1)
26
26
  Requires-Dist: pytest-xdist (==3.5.0)
27
27
  Requires-Dist: requests (>=2.31.0,<3.0.0)
28
+ Requires-Dist: splunk-sdk (>=2.1.1,<3.0.0)
28
29
  Requires-Dist: tabulate (==0.9.0)
29
30
  Requires-Dist: urllib3 (>=1.26.19,<2.0.0)
30
31
  Project-URL: Repository, https://github.com/splunk/spl2-testing-framework
@@ -130,12 +131,15 @@ It is possible to pass more options to the command, which works also with pytest
130
131
 
131
132
  Additionally, the following options are supported:
132
133
 
134
+ * `--test_dir` - directory where test files (`.test.json`, `.test.spl2`, `module.json`) are located. Defaults to the current working directory. Discovery is recursive.
135
+ * `--code_dir` - directory where SPL2 code modules (`.spl2`) are located. Defaults to the current working directory. Falls back to `--test_dir` if the module is not found. See [Separate code and test directories](#separate-code-and-test-directories) for details.
133
136
  * `--ignore_empty_strings` - to ignore empty strings in the results
134
137
  * `--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)
135
138
  * `--create_comparison_sheet` - to create a comparison sheet in `comparison_box_test` folder using actual and expected outputs (works only when running box tests)
136
139
  * `--cli_bench` - when running tests with CLI, this option enables benchmarking mode by adding `-b` and `-n` flags to the spl2-processor-cli command. The value specifies the number of events to test (e.g., `--cli_bench=1000` will add `-b -n 1000` to the CLI command). **In bench mode, all tests always succeed and CLI output is printed to logs** - this is useful for performance testing and benchmarking. This is only applicable when using `--type cli`.
140
+ * `--check_splunk_results` - option to validate box test output by sending events to a Splunk instance (using HEC) and querying them back, with support for **CIM** (Common Information Model) and **TA** (Technology Add-on) checks.
137
141
 
138
- Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
142
+ * Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
139
143
  file by removing the `.sample` extension, and execute the command.
140
144
 
141
145
  ### Run tests in IDE [PyCharm]
@@ -150,10 +154,53 @@ Sample configuration which may be used:
150
154
  - Parameters:
151
155
  `--type [cli | splunk | cloud] --test_dir /tests/resources -o log_cli=true --log-cli-level=INFO --verbose`
152
156
  - If test dir is not specified, current working directory will be used
157
+ - Use `--code_dir` if SPL2 code modules are in a separate directory from tests
153
158
  - If necessary another pytest options can be added
154
159
 
155
160
  Note: It's necessary to set "pytest" as default test runner in PyCharm settings
156
161
 
162
+ ### Separate code and test directories
163
+
164
+ By default, the framework expects SPL2 code modules (`.spl2` files) to live alongside their test files. The `--code_dir` option allows you to keep code and tests in separate directory trees.
165
+
166
+ #### How it works
167
+
168
+ | What is searched | Primary directory | Fallback directory |
169
+ |------------------|-------------------|--------------------|
170
+ | **Code modules** (`.spl2`) | `--code_dir` | `--test_dir` |
171
+ | **Test files** (`.test.json`, `.test.spl2`) | `--test_dir` | `--code_dir` |
172
+
173
+ Both searches are recursive. If the primary directory has no results, the framework falls back to the other directory and logs the action.
174
+
175
+ When multiple files with the same name are found within a directory, a warning is logged and the lexicographically first match is used.
176
+
177
+ #### Example project layout
178
+
179
+ ```
180
+ my-project/
181
+ ├── src/ # --code_dir
182
+ │ ├── network_traffic.spl2
183
+ │ └── dns_lookup.spl2
184
+ └── tests/ # --test_dir
185
+ ├── network_traffic.test.json
186
+ ├── dns_lookup.test.json
187
+ └── dns_lookup.test.spl2
188
+ ```
189
+
190
+ Run with:
191
+
192
+ ```bash
193
+ spl2_tests_run cli --test_dir tests --code_dir src
194
+ ```
195
+
196
+ #### Backward compatibility
197
+
198
+ When `--code_dir` is not specified (or both flags point to the same directory), the framework behaves exactly as before — code modules are found next to their test files via the same recursive search.
199
+
200
+ #### Applies to all test types
201
+
202
+ The `--code_dir` option works for box tests, unit tests, and single SPL2 file execution.
203
+
157
204
  ## Executing a spl2 file
158
205
 
159
206
  This framework also supports executing a single spl2 file and prints the results in command line as well as a log file.
@@ -177,6 +224,7 @@ It is possible to pass more options to the command, which works also with pytest
177
224
 
178
225
  * `--test_dir` - Path in which the template file and module.json are available. If not provided, it will look for the
179
226
  current directory for the template file and module.json file
227
+ * `--code_dir` - Path where SPL2 code modules are located, if different from `--test_dir`. Falls back to `--test_dir` when not set. See [Separate code and test directories](#separate-code-and-test-directories).
180
228
  * `--template_file` - The spl2 template file to execute
181
229
  * `--sample_file` - A file containing all the samples required for the template file. If not provided, it will look for
182
230
  the samples in module.json file of the corresponding template file
@@ -206,6 +254,76 @@ Content of this file will also be printed to stdout.
206
254
  This checks can be applied only to box tests, as assertions which are used in unit tests may impact spl2 pipeline
207
255
  performance.
208
256
 
257
+ ### Check splunk results
258
+
259
+ When set (`--check_splunk_results=<option>`), box tests (after running the SPL2 pipeline) send pipeline output to the configured Splunk instance and validate results there instead of comparing only in-memory expected vs actual.
260
+
261
+ | Value | Behavior |
262
+ |-------|----------|
263
+ | **CIM** | For each output event, push to Splunk, query by host, then validate that the returned event has the expected CIM fields (from `expected_cim_fields.cim_fields` in the box test). Uses `validate_compatibility()`. Skips the test if the box test has no `cim_fields`. |
264
+ | **TA** | For each output event, push to Splunk, query by host, then assert that the returned event equals the expected output event for that index. |
265
+
266
+ For **TA** only, you can name top-level fields to drop on **both** the queried event and each `expected_destination_result` row by adding **`ignore_fields_in_splunk_check`**: an object mapping **field name → short reason**. Use the same key under `test` in each entry in `*.test.json` (same file as `expected_destination_result`):
267
+
268
+ ```json
269
+ "test": {
270
+ "source": "...",
271
+ "expected_destination_result": [ ... ],
272
+ "ignore_fields_in_splunk_check": {
273
+ "_raw": "Raw event differs after Splunk indexing",
274
+ "_time": "Timestamp may vary due to ingestion latency"
275
+ }
276
+ }
277
+ ```
278
+
279
+
280
+ If the key is omitted, no fields are ignored. Used only when `--check_splunk_results=TA` (ignored for other modes and for the default
281
+ in-memory box test assert). If the file or key is missing, no fields are ignored.
282
+
283
+ #### Configuration:
284
+
285
+ `SPLUNK_INSTANCE` in `spl2_test_config.json`
286
+
287
+ New top-level key for the Splunk instance used by `--check_splunk_results`:
288
+
289
+ ```json
290
+ {
291
+ "SPLUNK_INSTANCE": {
292
+ "ip": "<hostname-or-ip>",
293
+ "port": 8088,
294
+ "api_port": 8089,
295
+ "username": "<splunk-user>",
296
+ "password": "<splunk-password>",
297
+ "index": "<index-name>",
298
+ "hec_token": "<hec-token>"
299
+ }
300
+ }
301
+ ```
302
+
303
+ - **ip** – Splunk server host (e.g. `localhost` or hostname).
304
+ - **port** – HEC port (e.g. 8088).
305
+ - **api_port** – Management/API port (e.g. 8089) for Search API and index creation.
306
+ - **username** / **password** – Used for Search API and index creation (HTTP Basic).
307
+ - **index** – Index where HEC events are sent and then searched.
308
+ - **hec_token** – Token for HEC (`Authorization: Splunk <token>`).
309
+
310
+ Do not commit real credentials. Prefer environment variables (below) or a local config that is not in version control.
311
+
312
+ #### Environment variables
313
+
314
+ Any `SPLUNK_INSTANCE` field can be set via the environment; config file values override these only when non-empty.
315
+
316
+ | Variable | Maps to |
317
+ |----------|--------|
318
+ | `SPL2_TF_SPLUNK_INSTANCE_IP` | `ip` |
319
+ | `SPL2_TF_SPLUNK_INSTANCE_PORT` | `port` |
320
+ | `SPL2_TF_SPLUNK_INSTANCE_API_PORT` | `api_port` |
321
+ | `SPL2_TF_SPLUNK_INSTANCE_USERNAME` | `username` |
322
+ | `SPL2_TF_SPLUNK_INSTANCE_PASSWORD` | `password` |
323
+ | `SPL2_TF_SPLUNK_INSTANCE_INDEX` | `index` |
324
+ | `SPL2_TF_SPLUNK_INSTANCE_HEC_TOKEN` | `hec_token` |
325
+
326
+
209
327
  ## Format all files
210
328
  To format all files run:
211
329
 
@@ -98,12 +98,15 @@ It is possible to pass more options to the command, which works also with pytest
98
98
 
99
99
  Additionally, the following options are supported:
100
100
 
101
+ * `--test_dir` - directory where test files (`.test.json`, `.test.spl2`, `module.json`) are located. Defaults to the current working directory. Discovery is recursive.
102
+ * `--code_dir` - directory where SPL2 code modules (`.spl2`) are located. Defaults to the current working directory. Falls back to `--test_dir` if the module is not found. See [Separate code and test directories](#separate-code-and-test-directories) for details.
101
103
  * `--ignore_empty_strings` - to ignore empty strings in the results
102
104
  * `--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)
103
105
  * `--create_comparison_sheet` - to create a comparison sheet in `comparison_box_test` folder using actual and expected outputs (works only when running box tests)
104
106
  * `--cli_bench` - when running tests with CLI, this option enables benchmarking mode by adding `-b` and `-n` flags to the spl2-processor-cli command. The value specifies the number of events to test (e.g., `--cli_bench=1000` will add `-b -n 1000` to the CLI command). **In bench mode, all tests always succeed and CLI output is printed to logs** - this is useful for performance testing and benchmarking. This is only applicable when using `--type cli`.
107
+ * `--check_splunk_results` - option to validate box test output by sending events to a Splunk instance (using HEC) and querying them back, with support for **CIM** (Common Information Model) and **TA** (Technology Add-on) checks.
105
108
 
106
- Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
109
+ * Note: The `pytest.ini.sample` file allows you to define command parameters. Just update the configurations, rename the
107
110
  file by removing the `.sample` extension, and execute the command.
108
111
 
109
112
  ### Run tests in IDE [PyCharm]
@@ -118,10 +121,53 @@ Sample configuration which may be used:
118
121
  - Parameters:
119
122
  `--type [cli | splunk | cloud] --test_dir /tests/resources -o log_cli=true --log-cli-level=INFO --verbose`
120
123
  - If test dir is not specified, current working directory will be used
124
+ - Use `--code_dir` if SPL2 code modules are in a separate directory from tests
121
125
  - If necessary another pytest options can be added
122
126
 
123
127
  Note: It's necessary to set "pytest" as default test runner in PyCharm settings
124
128
 
129
+ ### Separate code and test directories
130
+
131
+ By default, the framework expects SPL2 code modules (`.spl2` files) to live alongside their test files. The `--code_dir` option allows you to keep code and tests in separate directory trees.
132
+
133
+ #### How it works
134
+
135
+ | What is searched | Primary directory | Fallback directory |
136
+ |------------------|-------------------|--------------------|
137
+ | **Code modules** (`.spl2`) | `--code_dir` | `--test_dir` |
138
+ | **Test files** (`.test.json`, `.test.spl2`) | `--test_dir` | `--code_dir` |
139
+
140
+ Both searches are recursive. If the primary directory has no results, the framework falls back to the other directory and logs the action.
141
+
142
+ When multiple files with the same name are found within a directory, a warning is logged and the lexicographically first match is used.
143
+
144
+ #### Example project layout
145
+
146
+ ```
147
+ my-project/
148
+ ├── src/ # --code_dir
149
+ │ ├── network_traffic.spl2
150
+ │ └── dns_lookup.spl2
151
+ └── tests/ # --test_dir
152
+ ├── network_traffic.test.json
153
+ ├── dns_lookup.test.json
154
+ └── dns_lookup.test.spl2
155
+ ```
156
+
157
+ Run with:
158
+
159
+ ```bash
160
+ spl2_tests_run cli --test_dir tests --code_dir src
161
+ ```
162
+
163
+ #### Backward compatibility
164
+
165
+ When `--code_dir` is not specified (or both flags point to the same directory), the framework behaves exactly as before — code modules are found next to their test files via the same recursive search.
166
+
167
+ #### Applies to all test types
168
+
169
+ The `--code_dir` option works for box tests, unit tests, and single SPL2 file execution.
170
+
125
171
  ## Executing a spl2 file
126
172
 
127
173
  This framework also supports executing a single spl2 file and prints the results in command line as well as a log file.
@@ -145,6 +191,7 @@ It is possible to pass more options to the command, which works also with pytest
145
191
 
146
192
  * `--test_dir` - Path in which the template file and module.json are available. If not provided, it will look for the
147
193
  current directory for the template file and module.json file
194
+ * `--code_dir` - Path where SPL2 code modules are located, if different from `--test_dir`. Falls back to `--test_dir` when not set. See [Separate code and test directories](#separate-code-and-test-directories).
148
195
  * `--template_file` - The spl2 template file to execute
149
196
  * `--sample_file` - A file containing all the samples required for the template file. If not provided, it will look for
150
197
  the samples in module.json file of the corresponding template file
@@ -174,6 +221,76 @@ Content of this file will also be printed to stdout.
174
221
  This checks can be applied only to box tests, as assertions which are used in unit tests may impact spl2 pipeline
175
222
  performance.
176
223
 
224
+ ### Check splunk results
225
+
226
+ When set (`--check_splunk_results=<option>`), box tests (after running the SPL2 pipeline) send pipeline output to the configured Splunk instance and validate results there instead of comparing only in-memory expected vs actual.
227
+
228
+ | Value | Behavior |
229
+ |-------|----------|
230
+ | **CIM** | For each output event, push to Splunk, query by host, then validate that the returned event has the expected CIM fields (from `expected_cim_fields.cim_fields` in the box test). Uses `validate_compatibility()`. Skips the test if the box test has no `cim_fields`. |
231
+ | **TA** | For each output event, push to Splunk, query by host, then assert that the returned event equals the expected output event for that index. |
232
+
233
+ For **TA** only, you can name top-level fields to drop on **both** the queried event and each `expected_destination_result` row by adding **`ignore_fields_in_splunk_check`**: an object mapping **field name → short reason**. Use the same key under `test` in each entry in `*.test.json` (same file as `expected_destination_result`):
234
+
235
+ ```json
236
+ "test": {
237
+ "source": "...",
238
+ "expected_destination_result": [ ... ],
239
+ "ignore_fields_in_splunk_check": {
240
+ "_raw": "Raw event differs after Splunk indexing",
241
+ "_time": "Timestamp may vary due to ingestion latency"
242
+ }
243
+ }
244
+ ```
245
+
246
+
247
+ If the key is omitted, no fields are ignored. Used only when `--check_splunk_results=TA` (ignored for other modes and for the default
248
+ in-memory box test assert). If the file or key is missing, no fields are ignored.
249
+
250
+ #### Configuration:
251
+
252
+ `SPLUNK_INSTANCE` in `spl2_test_config.json`
253
+
254
+ New top-level key for the Splunk instance used by `--check_splunk_results`:
255
+
256
+ ```json
257
+ {
258
+ "SPLUNK_INSTANCE": {
259
+ "ip": "<hostname-or-ip>",
260
+ "port": 8088,
261
+ "api_port": 8089,
262
+ "username": "<splunk-user>",
263
+ "password": "<splunk-password>",
264
+ "index": "<index-name>",
265
+ "hec_token": "<hec-token>"
266
+ }
267
+ }
268
+ ```
269
+
270
+ - **ip** – Splunk server host (e.g. `localhost` or hostname).
271
+ - **port** – HEC port (e.g. 8088).
272
+ - **api_port** – Management/API port (e.g. 8089) for Search API and index creation.
273
+ - **username** / **password** – Used for Search API and index creation (HTTP Basic).
274
+ - **index** – Index where HEC events are sent and then searched.
275
+ - **hec_token** – Token for HEC (`Authorization: Splunk <token>`).
276
+
277
+ Do not commit real credentials. Prefer environment variables (below) or a local config that is not in version control.
278
+
279
+ #### Environment variables
280
+
281
+ Any `SPLUNK_INSTANCE` field can be set via the environment; config file values override these only when non-empty.
282
+
283
+ | Variable | Maps to |
284
+ |----------|--------|
285
+ | `SPL2_TF_SPLUNK_INSTANCE_IP` | `ip` |
286
+ | `SPL2_TF_SPLUNK_INSTANCE_PORT` | `port` |
287
+ | `SPL2_TF_SPLUNK_INSTANCE_API_PORT` | `api_port` |
288
+ | `SPL2_TF_SPLUNK_INSTANCE_USERNAME` | `username` |
289
+ | `SPL2_TF_SPLUNK_INSTANCE_PASSWORD` | `password` |
290
+ | `SPL2_TF_SPLUNK_INSTANCE_INDEX` | `index` |
291
+ | `SPL2_TF_SPLUNK_INSTANCE_HEC_TOKEN` | `hec_token` |
292
+
293
+
177
294
  ## Format all files
178
295
  To format all files run:
179
296
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  [tool.poetry]
17
17
  name = "spl2-testing-framework"
18
- version = "1.5.1"
18
+ version = "1.7.0"
19
19
  description = "SPL2 Testing Framework"
20
20
  authors = ["mgazda-splunk <mgazda@splunk.com>"]
21
21
  readme = "README.md"
@@ -52,6 +52,7 @@ numpy = [
52
52
  { version = ">=1.26,<2.0", python = ">=3.13" }
53
53
  ]
54
54
  openpyxl = "^3.1.2"
55
+ splunk-sdk = "^2.1.1"
55
56
 
56
57
  [tool.poetry.group.dev.dependencies]
57
58
  pre-commit = "*"
@@ -15,10 +15,13 @@
15
15
 
16
16
  import json
17
17
  import os
18
+ from collections import defaultdict
18
19
 
20
+ import pandas as pd
19
21
  import pytest
20
22
 
21
23
  from .tools.results import Results
24
+ from .tools.splunk_integration.splunk_handler import SplunkHandler
22
25
 
23
26
  pytest.register_assert_rewrite("spl2_testing_framework")
24
27
 
@@ -48,9 +51,11 @@ SINGLE_SPL2_FILE = "single_spl2_file"
48
51
  IGNORE_EMPTY_STRINGS = "ignore_empty_strings"
49
52
  IGNORE_ADDITIONAL_FIELDS_IN_ACTUAL = "ignore_additional_fields_in_actual"
50
53
  CREATE_COMPARISON_SHEET = "create_comparison_sheet"
54
+ CHECK_SPLUNK_RESULTS = "check_splunk_results"
51
55
  PERFORMANCE_CHECK = "performance_check"
52
56
  TEST_TYPE = "type"
53
57
  TEST_DIR = "test_dir"
58
+ CODE_DIR = "code_dir"
54
59
  LIMIT_TESTS = "limit_tests"
55
60
  CLI_BENCH = "cli_bench"
56
61
 
@@ -66,6 +71,7 @@ _LOGGER = logging.getLogger(__name__)
66
71
  def pytest_addoption(parser):
67
72
  parser.addoption(f"--{TEST_TYPE}", action="store", default="cli")
68
73
  parser.addoption(f"--{TEST_DIR}", action="store", default=".")
74
+ parser.addoption(f"--{CODE_DIR}", action="store", default=".")
69
75
  parser.addoption(
70
76
  f"--{PERFORMANCE_CHECK}",
71
77
  action="store",
@@ -75,6 +81,9 @@ def pytest_addoption(parser):
75
81
  parser.addoption("--template_file", action="store", default=None)
76
82
  parser.addoption("--sample_file", action="store", default=None)
77
83
  parser.addoption("--sample_delimiter", action="store", default="\n")
84
+ parser.addoption(
85
+ f"--{CHECK_SPLUNK_RESULTS}", action="store", type=str, default=None
86
+ )
78
87
  parser.addoption(f"--{CREATE_COMPARISON_SHEET}", action="store_true", default=False)
79
88
  parser.addoption(f"--{IGNORE_EMPTY_STRINGS}", action="store_true")
80
89
  parser.addoption(f"--{IGNORE_ADDITIONAL_FIELDS_IN_ACTUAL}", action="store_true")
@@ -86,13 +95,16 @@ def pytest_generate_tests(metafunc):
86
95
  """This function is used for dynamic generation of tests based on the test files in the directory specified by the
87
96
  --test_dir option"""
88
97
  test_dir = metafunc.config.getoption(TEST_DIR)
98
+ code_dir = metafunc.config.getoption(CODE_DIR)
89
99
  performance_check = metafunc.config.getoption(PERFORMANCE_CHECK)
90
100
  limit_tests = metafunc.config.getoption(LIMIT_TESTS)
91
101
  _set_results_options(metafunc)
92
102
 
93
103
  if BOX_TEST in metafunc.fixturenames:
94
104
  _LOGGER.info("Collecting Box Tests")
95
- module_tests = BoxTestDiscovery(test_dir, performance_check, limit_tests)
105
+ module_tests = BoxTestDiscovery(
106
+ test_dir, performance_check, limit_tests, code_dir=code_dir
107
+ )
96
108
  module_tests.discover_tests()
97
109
  metafunc.parametrize(
98
110
  "%s" % BOX_TEST, module_tests.tests, ids=module_tests.tests.get_ids()
@@ -112,6 +124,7 @@ def pytest_generate_tests(metafunc):
112
124
  sample_delimiter,
113
125
  performance_check=performance_check,
114
126
  limit_tests=limit_tests,
127
+ code_dir=code_dir,
115
128
  )
116
129
  try:
117
130
  spl2_pipelines.discover_tests()
@@ -125,7 +138,7 @@ def pytest_generate_tests(metafunc):
125
138
 
126
139
  if UNIT_TEST in metafunc.fixturenames:
127
140
  _LOGGER.info("Collecting Unit Tests")
128
- unit_tests = UTDiscovery(test_dir, limit_tests)
141
+ unit_tests = UTDiscovery(test_dir, limit_tests, code_dir=code_dir)
129
142
  unit_tests.discover_tests()
130
143
  metafunc.parametrize(
131
144
  UNIT_TEST, unit_tests.tests, ids=unit_tests.tests.get_ids()
@@ -139,6 +152,8 @@ def spl2_test_runner(
139
152
  basic_authentication,
140
153
  bearer_token_authentication,
141
154
  run_configurations,
155
+ splunk_handler,
156
+ stats,
142
157
  ):
143
158
  """This fixture is used to create a test runner object based on the test type specified by the --type option
144
159
  3 types of tests are supported: 'splunk', 'cloud', 'cli'
@@ -160,7 +175,7 @@ def spl2_test_runner(
160
175
  )
161
176
  )
162
177
 
163
- x = SPL2TestRunner(search_client, run_configurations)
178
+ x = SPL2TestRunner(search_client, run_configurations, splunk_handler, stats)
164
179
  return x
165
180
 
166
181
 
@@ -172,11 +187,35 @@ def run_configurations(request):
172
187
  f"--{CREATE_COMPARISON_SHEET}"
173
188
  ),
174
189
  CLI_BENCH: request.config.getoption(f"--{CLI_BENCH}"),
190
+ CHECK_SPLUNK_RESULTS: request.config.getoption(f"--{CHECK_SPLUNK_RESULTS}"),
175
191
  }
176
192
 
177
193
 
178
194
  @pytest.fixture(scope="session")
179
- def config():
195
+ def splunk_instance_config():
196
+ splunk_instance_ip = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_IP", None)
197
+ splunk_instance_port = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_PORT", None)
198
+ splunk_instance_api_port = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_API_PORT", None)
199
+ splunk_instance_username = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_USERNAME", None)
200
+ splunk_instance_password = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_PASSWORD", None)
201
+ splunk_instance_index = os.environ.get("SPL2_TF_SPLUNK_INSTANCE_INDEX", None)
202
+ splunk_instance_hec_token = os.environ.get(
203
+ "SPL2_TF_SPLUNK_INSTANCE_HEC_TOKEN", None
204
+ )
205
+
206
+ return {
207
+ "ip": splunk_instance_ip,
208
+ "port": splunk_instance_port,
209
+ "api_port": splunk_instance_api_port,
210
+ "username": splunk_instance_username,
211
+ "password": splunk_instance_password,
212
+ "index": splunk_instance_index,
213
+ "hec_token": splunk_instance_hec_token,
214
+ }
215
+
216
+
217
+ @pytest.fixture(scope="session")
218
+ def config(splunk_instance_config):
180
219
  """This fixture is used to read the configuration from the environment variables or from the spl2_test_config.json
181
220
  Configuration set using environment variables will be overwritten by settings from spl2_test_config.json file,
182
221
  however empty values in spl2_test_config.json will be ignored.
@@ -219,6 +258,12 @@ def config():
219
258
  if conf_file.get("cloud_instance", None):
220
259
  cloud_instance = conf_file["cloud_instance"]
221
260
 
261
+ for k, v in splunk_instance_config.items():
262
+ if v is None:
263
+ splunk_instance_config[k] = conf_file.get("SPLUNK_INSTANCE", {}).get(
264
+ k, None
265
+ )
266
+
222
267
  return {
223
268
  "host": host,
224
269
  "port": port,
@@ -227,6 +272,7 @@ def config():
227
272
  "bearer_token": bearer_token,
228
273
  "tenant": tenant,
229
274
  "cloud_instance": cloud_instance,
275
+ "SPLUNK_INSTANCE": splunk_instance_config,
230
276
  }
231
277
 
232
278
 
@@ -251,6 +297,49 @@ def bearer_token_authentication(config):
251
297
  return HTTPBearerTokenAuth(token)
252
298
 
253
299
 
300
+ @pytest.fixture(scope="session")
301
+ def splunk_token_authentication(config):
302
+ class SplunkTokenAuth(AuthBase):
303
+ """Attaches HTTP Basic Authentication to the given Request object."""
304
+
305
+ def __init__(self, token):
306
+ self.token = token
307
+
308
+ def __call__(self, r):
309
+ r.headers["Authorization"] = f"Splunk {self.token}"
310
+ return r
311
+
312
+ token = config["SPLUNK_INSTANCE"].get("hec_token", "")
313
+ splunk_auth = SplunkTokenAuth(token)
314
+ return splunk_auth
315
+
316
+
317
+ @pytest.fixture(scope="session")
318
+ def splunk_handler(config, splunk_token_authentication):
319
+ config["SPLUNK_INSTANCE"][
320
+ "url"
321
+ ] = f"https://{config['SPLUNK_INSTANCE']['ip']}:{config['SPLUNK_INSTANCE']['port']}/services/collector/raw"
322
+ config["SPLUNK_INSTANCE"][
323
+ "index_url"
324
+ ] = f"https://{config['SPLUNK_INSTANCE']['ip']}:{config['SPLUNK_INSTANCE']['api_port']}/services/data/indexes"
325
+
326
+ sh = SplunkHandler(config, splunk_token_authentication)
327
+ return sh
328
+
329
+
330
+ @pytest.fixture(scope="session")
331
+ def stats():
332
+ s = defaultdict(lambda: defaultdict(list))
333
+ yield s
334
+
335
+ for template, v in s.items():
336
+ x = pd.DataFrame(v)
337
+ _LOGGER.info(" *** *** ***")
338
+ _LOGGER.info("Collected statistics: ")
339
+ _LOGGER.info("Template name: %s ", template)
340
+ _LOGGER.info(x.describe())
341
+
342
+
254
343
  def _set_results_options(metafunc):
255
344
  ignore_empty_strings = metafunc.config.getoption(IGNORE_EMPTY_STRINGS)
256
345
  Results._ignore_empty_strings = ignore_empty_strings
@@ -0,0 +1,18 @@
1
+ {
2
+ "host": "",
3
+ "cloud_instance": "",
4
+ "tenant": "",
5
+ "bearer_token": "",
6
+ "port": "",
7
+ "user": "",
8
+ "password": "",
9
+ "SPLUNK_INSTANCE": {
10
+ "ip": "",
11
+ "port": "",
12
+ "api_port": "",
13
+ "username": "",
14
+ "password": "",
15
+ "index": "",
16
+ "hec_token": ""
17
+ }
18
+ }
@@ -75,8 +75,13 @@ class CLISearchClient(SearchClient):
75
75
  # Normal mode - raise errors as before
76
76
  if result.stderr:
77
77
  raise RuntimeError(result.stderr)
78
-
79
- job.result = json.loads(result.stdout)
78
+ try:
79
+ job.result = json.loads(result.stdout)
80
+ except json.decoder.JSONDecodeError:
81
+ _LOGGER.error(
82
+ "Can't decode output for %s. Details: %s ", job.test_name, result
83
+ )
84
+ raise
80
85
  return
81
86
 
82
87
  def _print_bench_results(self, job, result):