Skip to content

Commit 23c5148

Browse files
committed
internal/testrunner/script: add script testing package
1 parent beb06af commit 23c5148

File tree

33 files changed

+3319
-4
lines changed

33 files changed

+3319
-4
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ test-check-packages-with-kind:
9797
test-check-packages-other:
9898
PACKAGE_TEST_TYPE=other ./scripts/test-check-packages.sh
9999

100+
test-check-packages-independent-script:
101+
elastic-package test script -C test/packages/other/with_script --external-stack=false --defer-cleanup 1s
102+
100103
test-check-packages-false-positives:
101104
PACKAGE_TEST_TYPE=false_positives ./scripts/test-check-false-positives.sh
102105

@@ -133,7 +136,7 @@ test-profiles-command:
133136
test-check-update-version:
134137
./scripts/test-check-update-version.sh
135138

136-
test: test-go test-stack-command test-check-packages test-profiles-command test-build-install-zip test-build-zip test-build-install-zip-file test-build-install-zip-file-shellinit test-check-update-version test-profiles-command test-system-test-flags
139+
test: test-go test-stack-command test-check-packages test-check-packages-independent-script test-profiles-command test-build-install-zip test-build-zip test-build-install-zip-file test-build-install-zip-file-shellinit test-check-update-version test-profiles-command test-system-test-flags
137140

138141
check-git-clean:
139142
git update-index --really-refresh

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,12 @@ _Context: package_
624624

625625
Run policy tests for the package.
626626

627+
### `elastic-package test script`
628+
629+
_Context: package_
630+
631+
Run script tests for the package.
632+
627633
### `elastic-package test static`
628634

629635
_Context: package_

cmd/testrunner.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/elastic/elastic-package/internal/testrunner/runners/policy"
3131
"github.com/elastic/elastic-package/internal/testrunner/runners/static"
3232
"github.com/elastic/elastic-package/internal/testrunner/runners/system"
33+
"github.com/elastic/elastic-package/internal/testrunner/script"
3334
)
3435

3536
const testLongDescription = `Use this command to run tests on a package. Currently, the following types of tests are available:
@@ -94,6 +95,9 @@ func setupTestCommand() *cobraext.Command {
9495
systemCmd := getTestRunnerSystemCommand()
9596
cmd.AddCommand(systemCmd)
9697

98+
scriptCmd := getTestRunnerScriptCommand()
99+
cmd.AddCommand(scriptCmd)
100+
97101
policyCmd := getTestRunnerPolicyCommand()
98102
cmd.AddCommand(policyCmd)
99103

@@ -593,6 +597,47 @@ func testRunnerSystemCommandAction(cmd *cobra.Command, args []string) error {
593597
return nil
594598
}
595599

600+
func getTestRunnerScriptCommand() *cobra.Command {
601+
cmd := &cobra.Command{
602+
Use: "script",
603+
Short: "Run script tests",
604+
Long: "Run script tests for the package.",
605+
Args: cobra.NoArgs,
606+
RunE: testRunnerScriptCommandAction,
607+
}
608+
609+
cmd.Flags().String(cobraext.ScriptsFlagName, "", cobraext.ScriptsFlagDescription)
610+
cmd.Flags().Bool(cobraext.ExternalStackFlagName, true, cobraext.ExternalStackFlagDescription)
611+
cmd.Flags().StringSliceP(cobraext.DataStreamsFlagName, "d", nil, cobraext.DataStreamsFlagDescription)
612+
cmd.Flags().String(cobraext.RunPatternFlagName, "", cobraext.RunPatternFlagDescription)
613+
cmd.Flags().BoolP(cobraext.UpdateScriptTestArchiveFlagName, "u", false, cobraext.UpdateScriptTestArchiveFlagDescription)
614+
cmd.Flags().BoolP(cobraext.WorkScriptTestFlagName, "w", false, cobraext.WorkScriptTestFlagDescription)
615+
cmd.Flags().Bool(cobraext.ContinueOnErrorFlagName, false, cobraext.ContinueOnErrorFlagDescription)
616+
cmd.Flags().Bool(cobraext.VerboseScriptFlagName, false, cobraext.VerboseScriptFlagDescription)
617+
618+
cmd.MarkFlagsMutuallyExclusive(cobraext.DataStreamsFlagName, cobraext.DataStreamsFlagName)
619+
620+
return cmd
621+
}
622+
623+
func testRunnerScriptCommandAction(cmd *cobra.Command, args []string) error {
624+
cmd.Println("Run script tests for the package")
625+
var ok bool
626+
pkgRoot, ok, err := packages.FindPackageRoot()
627+
if err != nil {
628+
return fmt.Errorf("locating package root failed: %w", err)
629+
}
630+
if !ok {
631+
return errors.New("package root not found")
632+
}
633+
pkg := filepath.Base(pkgRoot)
634+
cmd.Printf("--- Test results for package: %s - START ---\n", pkg)
635+
err = script.Run(cmd.OutOrStderr(), cmd, args)
636+
cmd.Printf("--- Test results for package: %s - END ---\n", pkg)
637+
cmd.Println("Done")
638+
return err
639+
}
640+
596641
func getTestRunnerPolicyCommand() *cobra.Command {
597642
cmd := &cobra.Command{
598643
Use: "policy",

docs/howto/script_testing.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# HOWTO: Writing script tests for a package
2+
3+
Script testing is an advanced topic that assumes knowledge of [pipeline](./pipeline_testing.md)
4+
and [system](./system_testing.md) testing.
5+
6+
Testing packages with script testing is only intended for testing cases that
7+
cannot be adequately covered by the pipeline and system testing tools such as
8+
testing failure paths and package upgrades. It can also be used for debugging
9+
integrations stack issues.
10+
11+
## Introduction
12+
13+
The script testing system is build on the Go testscript package with extensions
14+
provided to allow scripting of stack and integration operations such as
15+
bringing up a stack, installing packages and running agents. For example, using
16+
these commands it is possible to express a system test as described in the
17+
system testing [Conceptual Process](./system_testing.md#conceptual-process) section.
18+
19+
20+
## Expressing tests
21+
22+
Tests are written as [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar#hdr-Txtar_format)
23+
files in a data stream's \_dev/test/scripts directory. The logic for the test is
24+
written in the txtar file's initial comment section and any additional resource
25+
files are included in the txtar file's files sections.
26+
27+
The standard commands and behaviors for testscript scripts are documented in
28+
the [testscript package documentation](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript).
29+
30+
31+
## Extension commands
32+
33+
The test script command provides additional commands to aid in interacting with
34+
a stack, starting agents and services and validating results.
35+
36+
- `sleep`: sleep for a duration (Go `time.Duration` parse syntax)
37+
- `date`: print the current time in RFC3339, optionally setting a variable with the value
38+
- `GET`: perform an HTTP GET request, emitting the response body to stdout
39+
- `POST`: perform an HTTP POST request, emitting the response body to stdout
40+
- `match_file`: perform a grep pattern match between a pattern file and a data file
41+
42+
- stack commands:
43+
- `stack_up`: bring up a version of the Elastic stack
44+
- `use_stack`: use a running Elastic stack
45+
- `stack_down`: take down a started Elastic stack
46+
- `dump_logs`: dump the logs from the stack into a directory
47+
- `get_policy`: print the details for a policy
48+
49+
- agent commands:
50+
- `install_agent`: install an Elastic Agent policy
51+
- `uninstall_agent`: remove an installed Elastic Agent policy
52+
53+
- package commands:
54+
- `add_package`: add the current package's assets
55+
- `remove_package`: remove assets for the current package
56+
- `add_package_zip`: add assets from a Zip-packaged integration package
57+
- `remove_package_zip`: remove assets for Zip-packaged integration package
58+
- `upgrade_package_latest`: upgrade the current package or another named package to the latest version
59+
60+
- data stream commands:
61+
- `add_data_stream`: add a data stream policy
62+
- `remove_data_stream`: remove a data stream policy
63+
- `get_docs`: get documents from a data stream
64+
65+
- docker commands:
66+
- `docker_up`: start a docker service
67+
- `docker_down`: stop a started docker service and print the docker logs to stdout
68+
- `docker_signal`: send a signal to a running docker service
69+
- `docker_wait_exit`: wait for a docker service to exit
70+
71+
- pipeline commands:
72+
- `install_pipelines`: install ingest pipelines from a path
73+
- `simulate`: run a pipeline test
74+
- `uninstall_pipelines`: remove installed ingest pipelines
75+
76+
77+
## Environment variables
78+
79+
- `CONFIG_ROOT`: the `elastic-package` configuration root path
80+
- `CONFIG_PROFILES`: the `elastic-package` profiles configuration root path
81+
- `HOME`: the user's home directory path
82+
- `PKG`: the name of the running package
83+
- `PKG_ROOT`: the path to the root of the running package
84+
- `CURRENT_VERSION`: the current version of the package
85+
- `PREVIOUS_VERSION`: the previous version of the package
86+
- `DATA_STREAM`: the name of the data stream
87+
- `DATA_STREAM_ROOT`: the path to the root of the data stream
88+
89+
90+
## Conditions
91+
92+
The testscript package allows conditions to be set that allow conditional
93+
execution of commands. The test script command adds a condition that reflects
94+
the state of the `--external-stack` flag. This allows tests to be written that
95+
conditionally use either an externally managed stack, or a stack that has been
96+
started by the test script.
97+
98+
99+
## Example
100+
101+
As an example, a basic system test could be expressed as follows.
102+
```
103+
# Only run the test if --external-stack=true.
104+
[!external_stack] skip 'Skipping external stack test.'
105+
# Only run the test if the jq executable is in $PATH. This is needed for a test below.
106+
[!exec:jq] skip 'Skipping test requiring absent jq command'
107+
108+
# Register running stack.
109+
use_stack -profile ${CONFIG_PROFILES}/default
110+
111+
# Install an agent.
112+
install_agent -profile ${CONFIG_PROFILES}/default NETWORK_NAME
113+
114+
# Bring up a docker container.
115+
#
116+
# The service is described in the test-hits/docker-compose.yml below with
117+
# its logs in test-hits/logs/generated.log.
118+
docker_up -profile ${CONFIG_PROFILES}/default -network ${NETWORK_NAME} test-hits
119+
120+
# Add the package resources.
121+
add_package -profile ${CONFIG_PROFILES}/default
122+
123+
# Add the data stream.
124+
#
125+
# The configuration for the test is described in test_config.yaml below.
126+
add_data_stream -profile ${CONFIG_PROFILES}/default test_config.yaml DATA_STREAM_NAME
127+
128+
# Start the service.
129+
docker_signal test-hits SIGHUP
130+
131+
# Wait for the service to exit.
132+
docker_wait_exit -timeout 5m test-hits
133+
134+
# Check that we can see our policy.
135+
get_policy -profile ${CONFIG_PROFILES}/default -timeout 1m ${DATA_STREAM_NAME}
136+
cp stdout got_policy.json
137+
exec jq '.name=="'${DATA_STREAM_NAME}'"' got_policy.json
138+
stdout true
139+
140+
# Take down the service and check logs for our message.
141+
docker_down test-hits
142+
! stderr .
143+
stdout '"total_lines":10'
144+
145+
# Get documents from the data stream.
146+
get_docs -profile ${CONFIG_PROFILES}/default -want 10 -timeout 5m ${DATA_STREAM_NAME}
147+
cp stdout got_docs.json
148+
149+
# Remove the data stream.
150+
remove_data_stream -profile ${CONFIG_PROFILES}/default ${DATA_STREAM_NAME}
151+
152+
# Uninstall the agent.
153+
uninstall_agent -profile ${CONFIG_PROFILES}/default -timeout 1m
154+
155+
# Remove the package resources.
156+
remove_package -profile ${CONFIG_PROFILES}/default
157+
158+
-- test-hits/docker-compose.yml --
159+
version: '2.3'
160+
services:
161+
test-hits:
162+
image: docker.elastic.co/observability/stream:v0.20.0
163+
volumes:
164+
- ./logs:/logs:ro
165+
command: log --start-signal=SIGHUP --delay=5s --addr elastic-agent:9999 -p=tcp /logs/generated.log
166+
-- test-hits/logs/generated.log --
167+
ntpd[1001]: kernel time sync enabled utl
168+
restorecond: : Reset file context quasiarc: liqua
169+
auditd[5699]: Audit daemon rotating log files
170+
anacron[5066]: Normal exit ehend
171+
restorecond: : Reset file context vol: luptat
172+
heartbeat: : <<eumiu.medium> Processing command: accept
173+
restorecond: : Reset file context nci: ofdeFin
174+
auditd[6668]: Audit daemon rotating log files
175+
anacron[1613]: Normal exit mvolu
176+
ntpd[2959]: ntpd gelit-r tatno
177+
-- test_config.yaml --
178+
input: tcp
179+
vars: ~
180+
data_stream:
181+
vars:
182+
tcp_host: 0.0.0.0
183+
tcp_port: 9999
184+
```
185+
186+
Other complete examples can be found in the [with_script test package](https://github.com/elastic/elastic-package/blob/main/test/packages/other/with_script/data_stream/first/_dev/test/scripts).
187+
188+
189+
## Running script tests
190+
191+
The `elastic-package test script` command has the following sub-command-specific
192+
flags:
193+
194+
- `--continue`: continue running the script if an error occurs
195+
- `--data-streams`: comma-separated data streams to test
196+
- `--external-stack`: use external stack for script tests (default true)
197+
- `--run`: run only tests matching the regular expression
198+
- `--scripts`: path to directory containing test scripts (advanced use only)
199+
- `--update`: update archive file if a cmp fails
200+
- `--verbose-scripts`: verbose script test output (show all script logging)
201+
- `--work`: print temporary work directory and do not remove when done
202+
203+
204+
## Limitations
205+
206+
While the testscript package allows reference to paths outside the configuration
207+
root and the package's root, the backing `elastic-package` infrastructure does
208+
not, so it is advised that tests only refer to paths within the `$WORK` and
209+
`$PKG_ROOT` directories.

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ require (
3232
github.com/mholt/archives v0.1.5
3333
github.com/olekukonko/tablewriter v1.1.0
3434
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
35+
github.com/rogpeppe/go-internal v1.13.1
3536
github.com/shirou/gopsutil/v3 v3.24.5
3637
github.com/spf13/cobra v1.10.1
3738
github.com/stretchr/testify v1.11.1
@@ -183,7 +184,6 @@ require (
183184
golang.org/x/term v0.36.0 // indirect
184185
golang.org/x/text v0.30.0 // indirect
185186
golang.org/x/time v0.12.0 // indirect
186-
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
187187
google.golang.org/protobuf v1.36.5 // indirect
188188
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
189189
gopkg.in/inf.v0 v0.9.1 // indirect
@@ -202,3 +202,5 @@ require (
202202
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
203203
sigs.k8s.io/yaml v1.6.0 // indirect
204204
)
205+
206+
replace github.com/elastic/package-spec/v3 => github.com/elastic/package-spec/v3 v3.5.1-0.20251010124158-cdc2a03341e3

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ github.com/elastic/gojsonschema v1.2.1 h1:cUMbgsz0wyEB4x7xf3zUEvUVDl6WCz2RKcQPul
132132
github.com/elastic/gojsonschema v1.2.1/go.mod h1:biw5eBS2Z4T02wjATMRSfecfjCmwaDPvuaqf844gLrg=
133133
github.com/elastic/kbncontent v0.1.4 h1:GoUkJkqkn2H6iJTnOHcxEqYVVYyjvcebLQVaSR1aSvU=
134134
github.com/elastic/kbncontent v0.1.4/go.mod h1:kOPREITK9gSJsiw/WKe7QWSO+PRiZMyEFQCw+CMLAHI=
135-
github.com/elastic/package-spec/v3 v3.5.0 h1:rvB+lWXXoUkSVx4TaHerV/eO6uN0NH1E5sPW1kW74Lk=
136-
github.com/elastic/package-spec/v3 v3.5.0/go.mod h1:dH//Q1geKx3fxC0lwPrVmnjN6RMqyDf5tnsw7trwqWE=
135+
github.com/elastic/package-spec/v3 v3.5.1-0.20251010124158-cdc2a03341e3 h1:OrnmIF32MoU99Ob2rwDW+A5kLFnYKX1rNtflspmTn+U=
136+
github.com/elastic/package-spec/v3 v3.5.1-0.20251010124158-cdc2a03341e3/go.mod h1:vWBPhVjiL8XoZ85lyCSMjKqSwzeb41zEmd6lElJsrk0=
137137
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
138138
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
139139
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

internal/cobraext/flags.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ const (
112112
CheckConditionFlagName = "check-condition"
113113
CheckConditionFlagDescription = "check if the condition is met for the package, but don't install the package (e.g. kibana.version=7.10.0)"
114114

115+
ContinueOnErrorFlagName = "continue"
116+
ContinueOnErrorFlagDescription = "continue running the script if an error occurs"
117+
115118
DaemonModeFlagName = "daemon"
116119
DaemonModeFlagDescription = "daemon mode"
117120

@@ -130,6 +133,9 @@ const (
130133
DumpOutputFlagName = "output"
131134
DumpOutputFlagDescription = "path to directory where exported assets will be stored"
132135

136+
ExternalStackFlagName = "external-stack"
137+
ExternalStackFlagDescription = "use external stack for script tests"
138+
133139
FailOnMissingFlagName = "fail-on-missing"
134140
FailOnMissingFlagDescription = "fail if tests are missing"
135141

@@ -165,6 +171,12 @@ const (
165171
ReportOutputPathFlagName = "report-output-path"
166172
ReportOutputPathFlagDescription = "output path for test report (defaults to %q in build directory)"
167173

174+
RunPatternFlagName = "run"
175+
RunPatternFlagDescription = "run only tests matching the regular expression"
176+
177+
ScriptsFlagName = "scripts"
178+
ScriptsFlagDescription = "path to directory containing test scripts"
179+
168180
ShowAllFlagName = "all"
169181
ShowAllFlagDescription = "show all deployed package revisions"
170182

@@ -224,6 +236,15 @@ const (
224236
NoProvisionFlagName = "no-provision"
225237
NoProvisionFlagDescription = "trigger just system tests wihout setup nor teardown"
226238

239+
UpdateScriptTestArchiveFlagName = "update"
240+
UpdateScriptTestArchiveFlagDescription = "update archive file if a cmp fails"
241+
242+
VerboseScriptFlagName = "verbose-scripts"
243+
VerboseScriptFlagDescription = "verbose script test output"
244+
245+
WorkScriptTestFlagName = "work"
246+
WorkScriptTestFlagDescription = "print temporary work directory and do not remove when done"
247+
227248
ZipPackageFilePathFlagName = "zip"
228249
ZipPackageFilePathFlagShorthand = "z"
229250
ZipPackageFilePathFlagDescription = "path to the zip package file (*.zip)"

0 commit comments

Comments
 (0)