spl2-testing-framework 1.0.0__py3-none-any.whl

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 (32) hide show
  1. spl2_testing_framework/__init__.py +15 -0
  2. spl2_testing_framework/conftest.py +217 -0
  3. spl2_testing_framework/launch_remotely_spl2.py +85 -0
  4. spl2_testing_framework/logger_manager.py +26 -0
  5. spl2_testing_framework/pytest.ini.sample +16 -0
  6. spl2_testing_framework/single_spl2_file_runner.py +52 -0
  7. spl2_testing_framework/spl2_test_config.json +9 -0
  8. spl2_testing_framework/spl2_utils/assertions.spl2 +157 -0
  9. spl2_testing_framework/spl2_utils/logs_to_metrics.spl2 +36 -0
  10. spl2_testing_framework/test_runner.py +59 -0
  11. spl2_testing_framework/tools/__init__.py +15 -0
  12. spl2_testing_framework/tools/jobs/__init__.py +15 -0
  13. spl2_testing_framework/tools/jobs/cli_job.py +72 -0
  14. spl2_testing_framework/tools/jobs/cloud_job.py +107 -0
  15. spl2_testing_framework/tools/jobs/job.py +65 -0
  16. spl2_testing_framework/tools/jobs/splunk_job.py +112 -0
  17. spl2_testing_framework/tools/performance.py +252 -0
  18. spl2_testing_framework/tools/results.py +135 -0
  19. spl2_testing_framework/tools/search_clients/__init__.py +28 -0
  20. spl2_testing_framework/tools/search_clients/cli_search_client.py +87 -0
  21. spl2_testing_framework/tools/search_clients/cloud_search_client.py +84 -0
  22. spl2_testing_framework/tools/search_clients/search_client.py +150 -0
  23. spl2_testing_framework/tools/search_clients/splunk_search_client.py +81 -0
  24. spl2_testing_framework/tools/spl2test_runner.py +143 -0
  25. spl2_testing_framework/tools/test_discovery.py +220 -0
  26. spl2_testing_framework/tools/test_types.py +84 -0
  27. spl2_testing_framework/tools/utils.py +52 -0
  28. spl2_testing_framework-1.0.0.dist-info/LICENSE +201 -0
  29. spl2_testing_framework-1.0.0.dist-info/METADATA +190 -0
  30. spl2_testing_framework-1.0.0.dist-info/RECORD +32 -0
  31. spl2_testing_framework-1.0.0.dist-info/WHEEL +4 -0
  32. spl2_testing_framework-1.0.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,15 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
@@ -0,0 +1,217 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import json
18
+ import os
19
+
20
+ import pytest
21
+
22
+ pytest.register_assert_rewrite("spl2_testing_framework")
23
+
24
+ from requests.auth import AuthBase, HTTPBasicAuth
25
+
26
+ from spl2_testing_framework.tools.search_clients.cli_search_client import (
27
+ CLISearchClient,
28
+ )
29
+ from spl2_testing_framework.tools.search_clients.cloud_search_client import (
30
+ CloudSearchClient,
31
+ )
32
+ from spl2_testing_framework.tools.search_clients.splunk_search_client import (
33
+ SplunkSearchClient,
34
+ )
35
+ from spl2_testing_framework.tools.spl2test_runner import SPL2TestRunner
36
+ from spl2_testing_framework.tools.test_discovery import (
37
+ BoxTestDiscovery,
38
+ SamplesNotFoundException,
39
+ SingleSPL2Discovery,
40
+ UTDiscovery,
41
+ )
42
+
43
+ UNIT_TEST = "unit_test"
44
+ BOX_TEST = "box_test"
45
+ SINGLE_SPL2_FILE = "single_spl2_file"
46
+
47
+ import logging
48
+
49
+ from .logger_manager import setup_logging
50
+
51
+ setup_logging()
52
+
53
+ _LOGGER = logging.getLogger(__name__)
54
+
55
+
56
+ def pytest_addoption(parser):
57
+ parser.addoption("--type", action="store", default="local")
58
+ parser.addoption("--test_dir", action="store", default=".")
59
+ parser.addoption(
60
+ "--performance_check",
61
+ action="store",
62
+ default="no",
63
+ choices=["no", "time", "detailed_time"],
64
+ )
65
+ parser.addoption("--template_file", action="store", default=None)
66
+ parser.addoption("--sample_file", action="store", default=None)
67
+ parser.addoption("--sample_delimiter", action="store", default="\n")
68
+
69
+
70
+ def pytest_generate_tests(metafunc):
71
+ """This function is used for dynamic generation of tests based on the test files in the directory specified by the
72
+ --test_dir option"""
73
+ test_dir = metafunc.config.getoption("test_dir")
74
+ performance_check = metafunc.config.getoption("performance_check")
75
+
76
+ if BOX_TEST in metafunc.fixturenames:
77
+ _LOGGER.info("Collecting Box Tests")
78
+ module_tests = BoxTestDiscovery(test_dir, performance_check)
79
+ module_tests.discover_tests()
80
+ metafunc.parametrize(
81
+ "%s" % BOX_TEST, module_tests.tests, ids=module_tests.tests.get_ids()
82
+ )
83
+
84
+ if SINGLE_SPL2_FILE in metafunc.fixturenames:
85
+ _LOGGER.info("Collecting the single spl2 file")
86
+ template_file = metafunc.config.getoption("template_file")
87
+ if template_file is None:
88
+ pytest.skip("No template file found to execute. Exiting..")
89
+ sample_file = metafunc.config.getoption("sample_file")
90
+ sample_delimiter = metafunc.config.getoption("sample_delimiter")
91
+ spl2_pipelines = SingleSPL2Discovery(
92
+ test_dir,
93
+ template_file,
94
+ sample_file,
95
+ sample_delimiter,
96
+ performance_check=performance_check,
97
+ )
98
+ try:
99
+ spl2_pipelines.discover_tests()
100
+ except SamplesNotFoundException as err:
101
+ pytest.exit(err)
102
+ metafunc.parametrize(
103
+ "%s" % SINGLE_SPL2_FILE,
104
+ spl2_pipelines.tests,
105
+ ids=spl2_pipelines.tests.get_ids(),
106
+ )
107
+
108
+ if UNIT_TEST in metafunc.fixturenames:
109
+ _LOGGER.info("Collecting Unit Tests")
110
+ unit_tests = UTDiscovery(test_dir)
111
+ unit_tests.discover_tests()
112
+ metafunc.parametrize(
113
+ UNIT_TEST, unit_tests.tests, ids=unit_tests.tests.get_ids()
114
+ )
115
+
116
+
117
+ @pytest.fixture()
118
+ def spl2_test_runner(
119
+ request, config, basic_authentication, bearer_token_authentication
120
+ ):
121
+ """This fixture is used to create a test runner object based on the test type specified by the --type option
122
+ 3 types of tests are supported: 'splunk', 'cloud', 'cli'
123
+ """
124
+ test_type = request.config.option.type.lower()
125
+ if test_type == "splunk":
126
+ url = f"{config['host']}:{config['port']}"
127
+ search_client = SplunkSearchClient(url=url, auth=basic_authentication)
128
+ elif test_type == "cli":
129
+ search_client = CLISearchClient()
130
+ elif test_type == "cloud":
131
+ url = f"https://{config['tenant']}.api.{config['cloud_instance']}/{config['tenant']}"
132
+ search_client = CloudSearchClient(url=url, auth=bearer_token_authentication)
133
+ else:
134
+ raise Exception(
135
+ "Unknown test type: {}. Supported tests types: 'splunk', 'cloud', 'cli'".format(
136
+ test_type
137
+ )
138
+ )
139
+
140
+ x = SPL2TestRunner(search_client)
141
+ return x
142
+
143
+
144
+ @pytest.fixture(scope="session")
145
+ def config():
146
+ """This fixture is used to read the configuration from the environment variables or from the spl2_test_config.json
147
+ Configuration set using environment variables will be overwritten by settings from spl2_test_config.json file,
148
+ however empty values in spl2_test_config.json will be ignored.
149
+ """
150
+ host = os.environ.get("SPL2_TF_HOST", None)
151
+ port = os.environ.get("SPL2_TF_PORT", None)
152
+ user = os.environ.get("SPL2_TF_USER", None)
153
+ password = os.environ.get("SPL2_TF_PASSWORD", None)
154
+ bearer_token = os.environ.get("SPL2_TF_BEARER_TOKEN", None)
155
+ tenant = os.environ.get("SPL2_TF_TENANT", None)
156
+ cloud_instance = os.environ.get("SPL2_TF_CLOUD_INSTANCE", None)
157
+
158
+ try:
159
+ with open("spl2_test_config.json", "rb") as f:
160
+ conf_file = json.load(fp=f)
161
+ except FileNotFoundError:
162
+ from spl2_testing_framework.tools.utils import get_framework_root
163
+
164
+ with open(f"{get_framework_root()}/spl2_test_config.json", "rb") as f:
165
+ conf_file = json.load(fp=f)
166
+
167
+ if conf_file.get("user", None):
168
+ user = conf_file["user"]
169
+
170
+ if conf_file.get("host", None):
171
+ host = conf_file["host"]
172
+
173
+ if conf_file.get("port", None):
174
+ port = conf_file["port"]
175
+
176
+ if conf_file.get("password", None):
177
+ password = conf_file["password"]
178
+
179
+ if conf_file.get("bearer_token", None):
180
+ bearer_token = conf_file["bearer_token"]
181
+
182
+ if conf_file.get("tenant", None):
183
+ tenant = conf_file["tenant"]
184
+
185
+ if conf_file.get("cloud_instance", None):
186
+ cloud_instance = conf_file["cloud_instance"]
187
+
188
+ return {
189
+ "host": host,
190
+ "port": port,
191
+ "user": user,
192
+ "password": password,
193
+ "bearer_token": bearer_token,
194
+ "tenant": tenant,
195
+ "cloud_instance": cloud_instance,
196
+ }
197
+
198
+
199
+ @pytest.fixture(scope="session")
200
+ def basic_authentication(config):
201
+ return HTTPBasicAuth(config.get("user", ""), config.get("password", ""))
202
+
203
+
204
+ @pytest.fixture(scope="session")
205
+ def bearer_token_authentication(config):
206
+ class HTTPBearerTokenAuth(AuthBase):
207
+ """Attaches HTTP Basic Authentication to the given Request object."""
208
+
209
+ def __init__(self, token):
210
+ self.token = token
211
+
212
+ def __call__(self, r):
213
+ r.headers["Authorization"] = f"Bearer {self.token}"
214
+ return r
215
+
216
+ token = config.get("bearer_token", "")
217
+ return HTTPBearerTokenAuth(token)
@@ -0,0 +1,85 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import argparse
18
+ import json
19
+
20
+ from spl2_testing_framework.tools.modules_reader import get_template_file
21
+ from spl2_testing_framework.tools.payload_functions import create_pipeline_job
22
+ from spl2_testing_framework.tools.cloud_search_client import CloudSearchClient
23
+
24
+
25
+ class Spl2Runner:
26
+ def _run_job_and_get_response(self, job):
27
+ response = self._search_client.run_job(job)
28
+ if response.status_code == 401:
29
+ print(
30
+ f"Unauthorised, may need to refresh token. Additional info: {response.text}"
31
+ )
32
+ exit(-1)
33
+ if response.status_code != 201:
34
+ print(f"Message: {response.text}. Status code: {response.status_code}.")
35
+ return response
36
+
37
+ def main(self):
38
+ parser = argparse.ArgumentParser("spl2_launcher")
39
+ parser.add_argument(
40
+ "spl2file",
41
+ help="Name of file to launch like: `pan_traffic_reduction.spl2`",
42
+ type=str,
43
+ )
44
+ parser.add_argument(
45
+ "-s",
46
+ "--source",
47
+ help="$source definition if it is not in the code.",
48
+ type=str,
49
+ default=None,
50
+ )
51
+ parser.add_argument(
52
+ "-e", "--events", help="Events definition txt file.", type=str, default=None
53
+ )
54
+ args = parser.parse_args()
55
+ self._search_client = CloudSearchClient()
56
+ template_module = get_template_file(args.spl2file).read_text(encoding="utf-8")
57
+ if args.source is not None:
58
+ template_module = template_module + f"\n$source = from {args.source};"
59
+ if args.events is not None:
60
+ events = list(
61
+ filter(lambda ev: len(ev) > 0, open(args.events).read().split("\n"))
62
+ )
63
+ template_module = template_module + f"\n$source = from ["
64
+ for event in events:
65
+ template_module = template_module + f"{{_raw: {json.dumps(event)} }}"
66
+ if event != events[-1]:
67
+ template_module = template_module + ","
68
+ template_module = template_module + "];"
69
+ job = create_pipeline_job(
70
+ template_module, "ingestProcessor", False, None
71
+ ) # allways launch in IP profile to use IP runner for tests
72
+ response = self._run_job_and_get_response(job)
73
+ # TODO check both destinations
74
+ destination_job_sid = response.json()["queryParameters"]["destination"]["sid"]
75
+
76
+ self._search_client.wait_for_job_status(destination_job_sid)
77
+
78
+ job_result = self._search_client.get_job_results(destination_job_sid)
79
+
80
+ print(json.dumps(job_result, indent=2))
81
+
82
+
83
+ if __name__ == "__main__":
84
+ spl2runner = Spl2Runner()
85
+ spl2runner.main()
@@ -0,0 +1,26 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import logging
18
+
19
+
20
+ def setup_logging():
21
+ logging.basicConfig(
22
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
23
+ handlers=[
24
+ logging.FileHandler("spl2_testing_framework.log"),
25
+ ],
26
+ )
@@ -0,0 +1,16 @@
1
+ [pytest]
2
+
3
+ log_cli = true
4
+ log-cli-level=INFO
5
+ log_level = INFO
6
+
7
+
8
+ addopts =
9
+ -rA
10
+ --verbose
11
+ -k <single_spl2_file|unit_test|box_test> ; Used to run a specific kind of test
12
+ --type=<cloud|cli|local> ; Any preferable method can be used to run the tests
13
+ --test_dir=<test_directory> ; Directory in which your tests and spl2 templates are kept
14
+ --template_file=<template_file_name> ; Name of the .spl2 file for which you want to get the results. Only used when running single_spl2_test_runner.py
15
+ --sample_file=<sample_file_name> ; Path to the sample file. Only used when running single_spl2_test_runner.py
16
+ --sample_delimiter=<sample_file_delimiter> ; Delimiter to separate events in sample_file. Only used when running single_spl2_test_runner.py
@@ -0,0 +1,52 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import argparse
18
+ import logging
19
+ import sys
20
+
21
+ import pytest
22
+
23
+ from spl2_testing_framework.tools.test_types import SingleSPL2
24
+
25
+ _LOGGER = logging.getLogger(__name__)
26
+
27
+
28
+ def test_single_spl2_file(spl2_test_runner, single_spl2_file: SingleSPL2):
29
+ _LOGGER.info(f"Executing single SPL2 File: '{single_spl2_file.name}'")
30
+ _LOGGER.debug(f"Single SPL2 File Details: {single_spl2_file}")
31
+ spl2_test_runner.run_single_spl2_file(single_spl2_file)
32
+
33
+
34
+ def run():
35
+ parser = argparse.ArgumentParser()
36
+ parser.add_argument("type")
37
+
38
+ arguments, other = parser.parse_known_args()
39
+
40
+ args = [__file__, "--type", arguments.type]
41
+ args.extend(other)
42
+
43
+ # INFO log level is required for result printing
44
+ if "--log-cli-level" not in args:
45
+ args.append("--log-cli-level")
46
+ args.append("INFO")
47
+
48
+ sys.exit(pytest.main(args))
49
+
50
+
51
+ if __name__ == "__main__":
52
+ run()
@@ -0,0 +1,9 @@
1
+ {
2
+ "host": "",
3
+ "cloud_instance": "",
4
+ "tenant": "",
5
+ "bearer_token": "",
6
+ "port": "",
7
+ "user": "",
8
+ "password": ""
9
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ # SPDX-FileCopyrightText: 2021 Splunk, Inc. <sales@splunk.com>
3
+ # SPDX-License-Identifier: LicenseRef-Splunk-8-2021
4
+ */
5
+
6
+ /**
7
+ * Utility function to compare a result with an expected value.
8
+ *
9
+ * @param $source events
10
+ * @param $expected_value value the test expects
11
+ * @param $test_field name of field that has the result to check
12
+ * @param $test_name optional, name the test to make it easier to parse results with multiple tests
13
+ * @param $expected_field optional, expected value is in this field
14
+
15
+ */
16
+ export function expect($source: dataset, $expected_value: any="", $test_field: any=_raw, $test_name: string="test", $expected_field: any=expected): dataset {
17
+ return from $source
18
+ | eval _testExpectedField = if(isnotnull($expected_field), $expected_field, $expected_value)
19
+ | eval _testResult = if($test_field == _testExpectedField, "true", "false")
20
+ | _testAddResult $test_name _testResult _testExpectedField, $test_field
21
+ | fields - _testExpectedField, _testResult
22
+ }
23
+
24
+ /**
25
+ * Test utility to ensure a given field exists on an event.
26
+ *
27
+ * @param $source events
28
+ * @param $field_name the field name to ensure exists
29
+ */
30
+ export function expect_field($source: dataset, $field_name: any): dataset {
31
+ return | from $source
32
+ | eval _expect = if(isnotnull(_expect), _expect, "true")
33
+ | eval _original_event_json = tojson()
34
+ | eval _expectHasField=if(isnotnull(json_extract_exact(_original_event_json, $field_name)), "true", "false")
35
+ | eval _result=if(_expectHasField == _expect, "true", "false")
36
+ | _testAddResult name="Has field: ${$field_name}" result=_result expected=_expectHasField received=_expectHasField
37
+ | fields - _expectHasField, _original_event_json, _result
38
+ }
39
+
40
+ /**
41
+ * Test utility to ensure given fields exists on an event.
42
+ *
43
+ * _expect field is not considered
44
+ *
45
+ * @param $source events
46
+ * @param $field_names the field name that are required
47
+ * @param $delimeter delimeter for the field names
48
+ */
49
+ export function expect_fields($source: dataset, $field_names: string): dataset {
50
+ $delimiter = ","
51
+ return | from $source
52
+ | eval _expect = if(isnotnull(_expect),_expect, "false")
53
+ // Convert the original event into json
54
+ | eval _original_event_json = tojson()
55
+ // Remove the _expect field
56
+ | eval _original_event_json=json_delete(_original_event_json, "_expect")
57
+ // Set the expected fields
58
+ | eval _expected_fields = mv_to_json_array(split($field_names, $delimiter))
59
+ // Test all the expected fields
60
+ | eval _test_result = json_object("result", "true", "received", ""),
61
+ _test_result = reduce(
62
+ _expected_fields,
63
+ _test_result,
64
+ ($results, $field) -> if(isnotnull(json_extract_exact(_original_event_json, $field)),
65
+ json_set($results, "", {"result": json_extract($results, "result"), "received": json_extract_exact($results, "received")+$field+": true\n"}),
66
+ json_set($results, "", {"result": "false", "received": json_extract($results, "received")+$field+": false\n"})
67
+ )
68
+ )
69
+ | eval _result=if(_test_result.result == _expect, "true", "false")
70
+ // Create a test result
71
+ | _testAddResult name="expect_fields" result=_result expected=_expect received=_test_result.result
72
+ | fields - _original_event_json, _expected_fields, _test_result, _result
73
+ }
74
+
75
+
76
+
77
+ /**
78
+ * Utility to ensure a given field exists on an event.
79
+ *
80
+ * @param $source events
81
+ * @param $field_names comma seperated list of fields to validate
82
+ * @param $delimeter the delimeter to split the fields list
83
+ * @param $ignore_testResults ignore the _testResult field if it exists. Useful when chaining tests.
84
+ */
85
+ export function expect_exact_fields($source: dataset, $field_names: string, $delimiter: string=",", $ignore_testResults: boolean=true): dataset {
86
+ return | from $source
87
+ | eval _expect = if(isnotnull(_expect),_expect, "false")
88
+ // Convert the original event into json
89
+ | eval _original_event_json = tojson()
90
+ // Remove the _expect field
91
+ | eval _original_event_json=json_delete(_original_event_json, "_expect")
92
+ // Get and sort the names of all the fields in the event into a MV field
93
+ | eval _original_event_fields = json_keys(_original_event_json)
94
+ // Get and sort all of the expected fields into a MV field
95
+ | eval _expected_fields = split($field_names, $delimiter)
96
+
97
+ // Match the expected fields against the event fields
98
+ | eval _test_result = json_object("fields", json_object()),
99
+ _test_result = reduce(
100
+ mv_to_json_array(_expected_fields),
101
+ _test_result,
102
+ ($results, $field) ->
103
+ json_set($results, "fields."+$field,
104
+ if(isnotnull(json_extract_exact(_original_event_json, $field)), "present", "missing")
105
+ )
106
+ )
107
+ // Match event fields against expected fields to find unexpected fields
108
+ | eval _test_result = reduce(
109
+ _original_event_fields,
110
+ _test_result,
111
+ ($results, $field) ->
112
+ json_set($results, "fields."+$field,
113
+ if(in($field, _expected_fields), "present", "not expected")
114
+ )
115
+ )
116
+ // Determine the test result
117
+ | eval _result = "true",
118
+ _result = reduce(
119
+ json_keys(_test_result.fields),
120
+ _result,
121
+ ($results, $field) ->
122
+ if(json_extract_exact(_test_result.fields, $field) != "present", "false", _result)
123
+ )
124
+
125
+ | eval _output=if(_result == _expect, "true", "false")
126
+ | _testAddResult name="expect_exact_fields", result=_output, expected=_expect, received=_result
127
+ | fields - _original_event_json, _original_event_fields, _expected_fields, _result, _test_result, _output
128
+ }
129
+
130
+
131
+ // Test result object
132
+ type testResult = {
133
+ test: string,
134
+ result: string,
135
+ expected: any,
136
+ received: any
137
+ };
138
+
139
+ /**
140
+ * Utility function to add a test result object to the test result field.
141
+ *
142
+ * @param $source events
143
+ * @param $name test name
144
+ * @param $result test result
145
+ * @param $expected expected result of the test
146
+ * @param $received the received result of the test
147
+ */
148
+ function _testAddResult($source: dataset, $name: string, $result: any, $expected: any, $received: any): dataset {
149
+ return from $source
150
+ // Create the test result object
151
+ | eval _testResultObject = json_object("result", $result, "test", $name, "expected", $expected, "received", $received)
152
+ | eval _testResults = if(isnotnull(_testResults),
153
+ json_append(_testResults, "results", json_extract(_testResultObject)),
154
+ json_object("results", json_array(json_extract(_testResultObject)))
155
+ )
156
+ | fields - _testResultObject
157
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ # SPDX-FileCopyrightText: 2021 Splunk, Inc. <sales@splunk.com>
3
+ # SPDX-License-Identifier: LicenseRef-Splunk-8-2021
4
+ */
5
+
6
+ type metrict=string where $value in (["gauge", "counter", "cumulative", "histogram"]);
7
+
8
+ /**
9
+ * This is a MOCK implementation of logs_to_metrics function. It is meant to be used just for testing.
10
+ *
11
+ * @param $source events
12
+ * @param $time time of the metric
13
+ * @param $name name of the metric
14
+ * @param $value value of the metric
15
+ * @param $metrictype type of the metric
16
+ * @param $dimensions dimensions of the metric
17
+ */
18
+ function logs_to_metrics($source, $time:time, $name:string, $value:number, $metrictype:metrict = "gauge", $dimensions:object = {}): dataset {
19
+ return from $source
20
+ | eval _has_time=if(isnotnull($time), 1, 0)
21
+ | eval _time=if(_has_time=1, $time, time())
22
+ | eval _time=floor(_time / 10) * 10
23
+ | select _time as _time,
24
+ $name as metric_name,
25
+ $value as metric_value,
26
+ $metrictype as metric_type,
27
+ $dimensions as dimensions,
28
+ source as source,
29
+ sourcetype as sourcetype,
30
+ index as index,
31
+ host as host,
32
+ _has_time as _has_time
33
+ | fields - _raw
34
+ }
35
+
36
+ export logs_to_metrics;
@@ -0,0 +1,59 @@
1
+ #
2
+ # Copyright 2025 Splunk Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import argparse
18
+ import sys
19
+ from logging import getLogger
20
+
21
+ import pytest
22
+
23
+ from spl2_testing_framework.tools.test_types import BoxTest
24
+
25
+ _LOGGER = getLogger(__name__)
26
+
27
+
28
+ def test_box_tests(spl2_test_runner, box_test: BoxTest):
29
+ _LOGGER.info("Running box test: %s from \n %s", box_test.name, box_test.module_path)
30
+ _LOGGER.debug("Test details: %s", box_test)
31
+
32
+ spl2_test_runner.run_box_test(box_test)
33
+
34
+
35
+ def test_spl2_unit_tests(spl2_test_runner, unit_test):
36
+ _LOGGER.info("Running unit test: %s", unit_test.name)
37
+ _LOGGER.debug("Test details: %s", unit_test)
38
+
39
+ spl2_test_runner.run_unit_test(unit_test)
40
+
41
+
42
+ def run():
43
+ parser = argparse.ArgumentParser()
44
+ parser.add_argument("type")
45
+
46
+ arguments, other = parser.parse_known_args()
47
+
48
+ args = [__file__, "--type", arguments.type]
49
+
50
+ if "--log-cli-level" not in args:
51
+ args.append("--log-cli-level")
52
+ args.append("INFO")
53
+
54
+ args.extend(other)
55
+ sys.exit(pytest.main(args))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ run()