-
Notifications
You must be signed in to change notification settings - Fork 486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
flowtest: introduce script-based testing framework for Flow #6401
Conversation
This commit introduces a new package, flow/flowtest, which can be used for running script-based test using Flow components. flowtest accepts a txtar archive, where the comment of the txtar archive denotes the script to run. Files in the txtar archive are unpacked to a temporary directory as part of the test; that temporary directory is also used as the working directory of the Flow controller. See <rsc.io/script> for more information about the scripting language and available default commands. In addition to the default commands, the following Flow-specific commands are made available to scripts: - `controller.load [file]`: Load a file into the Flow controller. - `controller.start`: Start the Flow controller. - `assert.health [component] [health]`: Assert the health of a specific component. Note that `controller.start` should almost always be run as a background command to allow the controller to run for the duration of the test. Users of flowtest can provide custom commands on top of these by passing `WithExtraCommands` to `TestScript`. The following is an example script using these commands: # This file performs a basic test of loading a component and asserting its # health. controller.load main.river controller.start & assert.health local.file.example healthy -- main.river -- local.file "example" { filename = "./hello.txt" } -- hello.txt -- Hello, world! See the testdata/ directory for other test examples.
Summary: "start the Flow controller", | ||
Async: true, | ||
Detail: []string{ | ||
"This command should almost always be run in the background by appending a &, otherwise the controller will exit immediately after the command finishes'", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if & is missing, wouldn't the script block execution until controller exits (which could be forever)?
return cmds | ||
} | ||
|
||
func registerControllerCommands(f *flow.Flow, cmds map[string]script.Cmd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite a long function, maybe we should put each command in its own function.
}, | ||
) | ||
|
||
cmds["controller.start"] = script.Command( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would make most sense to be as close to user experience as possible with those tests so things like quirks of CLI parsing or conflicting flag combinations or environment variables can be tested. Also, we can completely refactor things inside and validate that the users' interface remains the same.
The users call grafana-agent run <config file>
and they don't start a controller themselves, so I would think that instead of operating on the controller level we should operate on agent's CLI level? We'd still need a way to extract controller from a running agent for the purposes of asserting on conditions like 'healthy', which is fine.
} | ||
|
||
func registerControllerCommands(f *flow.Flow, cmds map[string]script.Cmd) { | ||
cmds["controller.load"] = script.Command( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would testing of two agents work? for example a proxy scenario or a cluster of agents?
|
||
assert.health local.file.example healthy | ||
|
||
-- main.river -- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like the embedded files :)
// In addition to the default commands provided by [rsc.io/script], the | ||
// following Flow-specific commands are available: | ||
// | ||
// - `controller.load [file]`: Load a file into the Flow controller. | ||
// - `controller.start`: Start the Flow controller. | ||
// - `assert.health [component] [health]`: Assert the health of a specific component. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should just point to the file that registers all these commands. This is likely to get outdated fast.
return nil | ||
} | ||
t.Run(d.Name(), func(t *testing.T) { | ||
require.NoError(t, flowtest.TestScript(path)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we instead pass the testing.T
to TestScript
and further, so that we can use the rich assert/require library? These can facilitate stuff like comparing maps, checking numbers are within a range, checking something is contained in the other thing, using the 'eventually' functions... the error messages produced by these are usually quite clear and useful. So I think it's worth to make it possible to use them - especially will be useful for all the asserting commands.
|
||
// Create a test controller for the script to interact with. The test | ||
// controller's logs are buffered and printed to stderr on exit. This | ||
// prevents log mangling when both the script engine and the Flow controller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could put prefixes in logs and print at the same time? Splitting them becomes a problem when we need to correlate the logs of script with the logs of the controller to see what happened in what order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, this looks interesting and I look forward to using it! I wonder how convenient it will be with debuggers too. E.g. I guess there won't be any issues with the temp folders if I terminate the process mid-session while debugging.
@@ -583,7 +583,7 @@ require ( | |||
golang.org/x/mod v0.14.0 // indirect | |||
golang.org/x/sync v0.5.0 // indirect | |||
golang.org/x/term v0.16.0 // indirect | |||
golang.org/x/tools v0.15.0 // indirect | |||
golang.org/x/tools v0.15.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice if this this goes in the first import block with direct dependencies
} | ||
|
||
// Create a temporary directory for the scope of our test to operate in. | ||
tmpDir, err := os.MkdirTemp("", "flowtest-*") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are those directories cleaned up?
This PR has not had any activity in the past 30 days, so the |
This PR has not had any activity in the past 30 days, so the |
I'm going to close this for now. I'm still interested in a script-based testing framework, but it sounds like we need more thoughts around what the scripts should look like (and how to use them to test things like clustering). @thampiotr expressed interest in taking inspiration from this exploration, so I'm handing it over to him for future work on this :) |
This commit introduces a new package, flow/flowtest, which can be used for running script-based test using Flow components.
flowtest accepts a txtar archive, where the comment of the txtar archive denotes the script to run. Files in the txtar archive are unpacked to a temporary directory as part of the test; that temporary directory is also used as the working directory of the Flow controller.
See https://rsc.io/script for more information about the scripting language and available default commands.
The true value of this package is unknown, but it is expected to be useful in some cases as an alternative to writing pure Go tests. It is likely that more commands will be needed over time to cover more use cases, and we may find ourselves replacing existing tests with script-based tests where appropriate.
Flow-specific script commands
In addition to the default commands (
cd
,cat
,exec
, etc.), the following Flow-specific commands are made available to scripts:controller.load [file]
: Load a file into the Flow controller.controller.start
: Start the Flow controller.assert.health [component] [health]
: Assert the health of a specific component.Note that
controller.start
should almost always be run as a background command to allow the controller to run for the duration of the test.Users of flowtest can provide custom commands on top of these by passing
WithExtraCommands
toTestScript
.Example test script
The following is an example script using these commands:
See the testdata/ directory for other test examples.