Documentation ¶
Index ¶
- func DeleteValue(obj *unstructured.Unstructured, path ...string) error
- func LoadInto(r io.Reader, into interface{}) error
- func LoadUnstructured(r io.Reader) (*unstructured.Unstructured, error)
- func ReadValue(obj *unstructured.Unstructured, path ...string) (interface{}, error)
- func Start(options Options, sinks *Sinks, scheme *runtime.Scheme)
- func WaitFor(wanted int, timeout time.Duration, from WaitForFn) error
- func WriteValue(obj *unstructured.Unstructured, value interface{}, path ...string) error
- type Handler
- type Harness
- type Options
- type ParseOptionFn
- func DefaultEnvAlways() ParseOptionFn
- func DefaultMakeDir(makedir string) ParseOptionFn
- func DefaultMakefile(makefile string) ParseOptionFn
- func DefaultManifests(manifests string) ParseOptionFn
- func DefaultNoCleanup() ParseOptionFn
- func DefaultOperatorDelay(opdelay time.Duration) ParseOptionFn
- func DefaultPrefix(prefix string) ParseOptionFn
- func OverrideCmdLine(cmdline *flag.FlagSet) ParseOptionFn
- func OverrideOsArgs(osargs []string) ParseOptionFn
- type ParseOptions
- type Sinks
- type Test
- type WaitForFn
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DeleteValue ¶
func DeleteValue(obj *unstructured.Unstructured, path ...string) error
DeleteValue (element, array or section) from the unstructured object
Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if nesting element is not a section. There is no error if the element being deleted does not exist, because that would be a no-op anyway. By "section" I mean map[string]interface{} i.e. map capable of nesting elements. No support for addressing individual elements in an array (slice).
func LoadInto ¶
LoadInto load an object from the textual data into an existing K8s object
This function supports both strong-typed object and Unstructured object. For example to load a deployment file the following can be used:
dep := appsv1.Deployment{} if err := framework.LoadInto(bufio.NewReader(file), &dep); err != nil { . . .
The benefits of using strong-typed objects (rather than unstructured.Unstructured) are:
- K8s will validate the input file while loading it
- Individual elements of the service definition will be easier to work with
- Function framework.WaitFor() provides special handling waiting on pods for types which can own them
Both JSON and YAML formats are supported.
func LoadUnstructured ¶
func LoadUnstructured(r io.Reader) (*unstructured.Unstructured, error)
LoadUnstructured load an object from the textual data
Using standard Reader interface (i.e. could be a file, hardcoded text, something else). Note: Unstructured object supports conversion to UnstructuredList for any object which contains "items" map directly under the object root. Both JSON and YAML formats are supported.
func ReadValue ¶
func ReadValue(obj *unstructured.Unstructured, path ...string) (interface{}, error)
ReadValue (element, array or section) from an unstructured object
Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if element is not found or if a nesting element is not a section. By "section" I mean map[string]interface{} i.e. map capable of nesting elements. No support for addressing individual elements in an array (slice).
func Start ¶
Start function will prepare and start the test cluster and create the K8s client for operating on it
This is arguably the key function of the test framework, since it performs most work:
- validation of all "glue" targets
- shutting the previously running cluster (if there was one and "no cleanup" is not set)
- starting up a test Kubernetes cluster
- creating a controller-runtime/client.Client object for manipulating the test cluster
- this client object will be made available for use in test code as framework.Kube.Client
Aside from the regular options, test code may also set:
- Sinks, to programmatically receive and handle the output of "glue" targets
- runtime.Scheme for CRD used by the test controller-runtime/client.Client object
func WaitFor ¶
WaitFor waits until "from" function returns a given number of instances
If the "from" function returns a strongly-typed K8s object of a type which can own pods, this number of instances refers to the number of ready pods. If the "from" function returns any of K8s List objects (including unstructured.UnstructuredList) this number refers to the number of items in the list. For Unstructured object, the returned number is a hardcoded 1. Function "from" can also return an integer number, which is useful if it needs to perform some specific check e.g. on data inside an object read from the test cluster. Specifics for individual K8s types are inside getReady function below.
If the "from" function does not return an expected number of instances within "timeout", this function will panic, hence failing test.
func WriteValue ¶
func WriteValue(obj *unstructured.Unstructured, value interface{}, path ...string) error
WriteValue (element, array or section) from an unstructured object
Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if nesting element is not found or is not a section. By "section" I mean map[string]interface{} i.e. map capable of nesting elements. No support for addressing individual elements in an array (slice).
Types ¶
type Handler ¶
This interface is used to handle the process after it's been started It is expected to call *FIRST* wg.Wait() and *THEN* cmd.Wait() This helps to ensure that the output scanners will read the full output before the pipes are closed inside cmd.Wait()
type Harness ¶
type Harness struct { harness.Harness // Options provided to Start function Options Options // Sinks provided to Start function. If "nil" was given, then a default // empty instance of Sinks will be stored here. Sinks Sinks // Scheme provided to Start function. This will be "nil" if Start function // received a "nil" parameter (i.e there is no default Scheme) Scheme *runtime.Scheme // Kubernetes client initialised and authenticated for operations on the // test cluster, created inside Start function. Client client.Client // contains filtered or unexported fields }
Harness represents the global state of the test framework.
The single global Harness object will be created by Start function (below) and made available through the global Kube object. Fields inside this object are meant for read only and should not be modified by the user.
var Kube *Harness
Kube is the global Harness object, created inside Start function.
func (*Harness) Close ¶
Close function will stop the test cluster
The call to this function is deferred inside Run, so users do not need to call it directly.
func (*Harness) NewTest ¶
NewTest will prepare new test case
Each test case needs a small number of extra data:
- test namespace, to be used in the K8s cluster by this test case
- sequential test number, to aid parallel test execution
This function takes care to prepare both of these. Note, the user should also the call test.Setup function to actually create namespace object in the K8s cluster and populate the environment variables specific to this test case (which will be used to pass the above data to the "glue" targets)
func (*Harness) OpenManifest ¶
OpenManifest can be used to read files bundled with the acceptance tests
These files are expected to reside inside manifests subdirectory. The actual location of this directory can be set from the command line with -k8s.manifests option or set with DefaultManifests functional option. If not set it will default to "manifests". This path is relative to where the main_test.go file is.
This function is borrowed from github.com/dlespiau/kube-test-harness/blob/master/harness.go
func (*Harness) Run ¶
Run function will apply test options and run the Go test cases, using m.Run()
Users are expected to use the return value from this function as a result code of the Go tests, e.g.:
os.Exit(framework.Kube.Run(m))
It should be called after Start function, which sets the options and starts the test cluster.
type Options ¶
type Options struct { harness.Options // Makefile is the name of the makefile used for "glue" targets required by the test framework. // The default name is "Makefile" Makefile string // MakeDir is the path to the directory containing the Makefile. It should be set relative to main_test.go // file by the DefaultMakeDir functional option; this path will be converted inside Parse to absolute path // and then stored here. Note, the conversion to absolute path is idempotent and also performed inside // Start, which allows the user to pass a relative path to Start here (e.g. if they are not using Parse) MakeDir string // Prefix is the prefix of the make targets used for "glue" targets executed by the test framework. // It defaults to "test". Parse function will "sanitize" this name if it does not end with either of // underscore or minus sign by appending a minus sign. Such sanitized name will be stored here. // Note, this operation is idempotent and also performed inside Start. Prefix string // OperatorDelay is the delay for starting operator during tests, during which the test framework // will wait for the operator to fail or exit, so it can fail the test case immediately. If the // operator continues to run beyond this interval, it is considered to have started successfully and // the framework will stop paying attention to its exit status. The default value is 2s OperatorDelay time.Duration // EnvAlways is a flag used by the operator to decide what to do if the "env" glue target returned // environment variables which collide with the environment variables already set before the tests // have started. If this flag is set then the variables from the glue target will take priority. // It is recommended that tests use the DefaultEnvAlways functional option to enable this functionality. EnvAlways bool }
Options for test framework
These options can be set from the command line, in which case they will be applied inside Parse function.
It is recommended that MakeDir and EnvAlways are set to sane defaults programmatically using functional options DefaultMakeDir and DefaultEnvAlways respectively.
If desired, test code may ignore the command line (by skipping Parse function) and populate Options entirely in code, before the call to Start function.
This also includes harness.Options from github.com/dlespiau/kube-test-harness/
func Parse ¶
func Parse(opts ...ParseOptionFn) *Options
Parse function should be called at the start of test suite to parse the command line options provided by the user
Test code may set the default values of the test options using Default... functional options above, e.g.:
func TestMain(m *testing.M) { options := framework.Parse( framework.DefaultMakeDir(".."), framework.DefaultEnvAlways(), )
In particular, DefaultMakeDir should be used to point to the Makefile directory where "glue" targets are defined.
This function does not have to be called, e.g. if the test code does not accept command line options. In this case MakeDir should be set directly inside the Options object and it will be transformed into absolute path inside the Start function.
We are making use of Functional Options pattern here, which works as follows:
- Parse function takes a variadic slice of functions matching ParseOptionFn signature
- each of these functions is responsible for adjusting a different field of ParseOptions structure
- the Default... functional options documented directly above can be used to create the required functions
- Parse will execute all these functions, hence adjusting default values of the command line parameters
- when command line parameters are parsed inside Parse, such adjusted default values will be applied
type ParseOptionFn ¶
type ParseOptionFn func(a *ParseOptions)
func DefaultEnvAlways ¶
func DefaultEnvAlways() ParseOptionFn
DefaultEnvAlways should be used to give the "env" glue target priority to override inherited environment variables
Normally if the environment variables returned by the "env" glue target are already set in the inherited environment (e.g. in the shell where tests are run) then the framework will ignore values set by the target. This behaviour might not be desired, since it makes test results dependent on the environment set by the user (e.g. they might have KUBECONFIG already set). When "env always" option is set, then the test framework will ignore inherited environment variables when parsing "env" glue target output. It is recommended that test code should use DefaultEnvAlways(). This can be overridden by the user with command line option -k8s.env-always
func DefaultMakeDir ¶
func DefaultMakeDir(makedir string) ParseOptionFn
DefaultMakeDir should be called by the test code to set the relative path of the "glue" makefile
For example, assuming that the "glue" targets are defined inside the Makefile residing in the project root directory and acceptance tests are in the "acceptance" directory, as demonstrated below:
| Makefile + acceptance | main_test.go
then the TestMain function defined inside main_test.go should pass DefaultMakeDir("..") to Parse function. This will ensure that framework will be able to find the Makefile in root project directory to execute the "glue" targets. The user can override this value using command line option -k8s.makedir
func DefaultMakefile ¶
func DefaultMakefile(makefile string) ParseOptionFn
DefaultMakefile can be used to override the default name of the "glue" makefile
The default name is "Makefile", however if the "glue" targets are defined in a different makefile, the test code can set a different default using this function. The user can override this option using command line option -k8s.makefile
func DefaultManifests ¶
func DefaultManifests(manifests string) ParseOptionFn
DefaultManifests can be called to override the default location of manifest files relative to main_test.go
If not set inside the test code, hardcoded subdirectory "manifests" will be used. The user can override this value using command line option -k8s.manifests
func DefaultNoCleanup ¶
func DefaultNoCleanup() ParseOptionFn
DefaultNoCleanup can be called to disable cleanup after tests
This function has the same effect as command line option -k8s.no-cleanup=true , however it can be overridden by the user setting -k8s.no-cleanup=false . If "no cleanup" option is set, then the test framework will not destroy test objects in the test K8s cluster and will not shutdown the cluster. This may lead to excessive resource utilization by the test cluster and in turn to transient test failures, so it is recommended that this function is not used by the test code.
func DefaultOperatorDelay ¶
func DefaultOperatorDelay(opdelay time.Duration) ParseOptionFn
DefaultOperatorDelay is how long the framework will wait for the operator being tested to report an error
If not set inside the test code, hardcoded 2 seconds will be used. Larger value means that operator can do more work before failing, if we want that failure to be detected by the tests immediately inside the StartOperator function; it also means that starting the operator will take longer. This can be overridden by the user with -k8s.op-delay command line option.
func DefaultPrefix ¶
func DefaultPrefix(prefix string) ParseOptionFn
DefaultPrefix can be called to override the default prefix of "glue" targets inside Makefile
If not set inside the test code, hardcoded prefix "test" will be used. The user can override this value using command line option -k8s.prefix. If the the prefix does not end with either of minus or underscore sign then the framework will automatically add minus character at the end. Empty prefix is also valid.
The purpose of prefix is to separate targets used as "glue" by tests and other targets which might be defined in the same Makefile. For example, in order to prepare environment variables before test cluster is started the test framework will invoke "env" glue target, which together with the default prefix makes "test-env" target in the Makefile.
func OverrideCmdLine ¶
func OverrideCmdLine(cmdline *flag.FlagSet) ParseOptionFn
OverrideCmdLine can be used by test code to override the default FlagSet used for tests
Normally tests should not use this function. It is only meant for testing of the Parse function.
func OverrideOsArgs ¶
func OverrideOsArgs(osargs []string) ParseOptionFn
OverrideOsArgs can be used by the test code to override command line parameters passed to tests
Normally tests should not use this function, unless they really want to ignore command line parameters, but still want to call Parse function (rather than prepare Options object explicitly in code). This function is mainly used for testing of the Parse function.
type ParseOptions ¶
type ParseOptions struct { MakeDir string Makefile string Manifests string Prefix string NoCleanup bool OperatorDelay time.Duration EnvAlways bool OsArgs []string CmdLine *flag.FlagSet }
ParseOptions is used by Parse function to enable the test code to set own defaults
We are applying Functional Options pattern, described by Rob Pike at https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html (first half only) and further documented by Dave Cheney at https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis A short version of this pattern is described with Parse function below.
The purpose of functional options in Parse is to customise the default parameters before command line parsing. This helps the users to avoid long "go test" invocations with many "-k8s..." parameters, by enabling the test code to provide the most useful defaults instead.
Each of the options below is set by some of the Default... functions below, and then applied inside Parse together with command line options to create an Options object which can be passed to Start function.
type Sinks ¶
Sinks can be used to capture the "console" output from the spawned sub-processes (e.g. glue targets or custom targets) for the purpose of testing. Capturing this output may be useful in tests.
type Test ¶
func (*Test) Close ¶
func (t *Test) Close()
Close should be called at the end of each test case
Ideally this function should be "deferred" right after test.Setup, as demonstrated above.
func (*Test) DeleteDeployment ¶
func (t *Test) DeleteDeployment(d *appsv1.Deployment, timeout time.Duration)
DeleteDeployment is a shortcut function for deleting a deployment and waiting until it is deleted
Note: this function is considered "alpha" and may get deleted or replaced with a different "helper" function.
func (*Test) RunTarget ¶
RunTarget will invoke arbitrary target from "glue" makefile
Note, the actual target name will be prefixed like all other "glue" targets, so assuming default "test" prefix this call test.RunTarget("stuff") will run "test-stuff" target. Extra environment variables can be passed to the invoked target, using the map parameters. These parameters are applied from left to right and never override variables set earlier. In particular, environment variables set inside test.Setup() cannot be overridden. RunTarget function will return an error if the target cannot be found or if it failed execution for any reason.
func (*Test) Setup ¶
Setup performs necessary initialisation of test case
This function should be called at the start of every test case and followed by "defer test.Close()", e.g:
test := framework.Kube.NewTest(t).Setup() defer test.Close()
func (*Test) StartOperator ¶
StartOperator starts the operator process for test case
This function will invoke "operator start" glue target, which will be next monitored in a dedicated Go routine. Test code can receive console output from the operator by setting Operator element of Sinks type when the test framework is initialized with framework.Start function. If the operator process terminates before DefaultOperatorDelay (or -k8s.op-delay) has elapsed, StartOperator function will return an error.
func (*Test) StopOperator ¶
func (t *Test) StopOperator()
StopOperator stops the operator process
The operator does not have to be running - trying to stop an operator when it is not running is an no-op. It is OK to start the operator again after it's been stopped, within the scope of same test. There is no need to stop operator explicitly at the end of test using this function, since Close function will do the same.