func/cmd/run_test.go

176 lines
5.8 KiB
Go

package cmd
import (
"context"
"fmt"
"testing"
"time"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/mock"
)
func TestRun_Run(t *testing.T) {
tests := []struct {
name string // name of the test
desc string // description of the test
setup func(fn.Function, *testing.T) error // Optionally mutate function
args []string // args for the test case
buildError error // Set the builder to yield this error
runError error // Set the runner to yield this error
buildInvoked bool // should Builder.Build be invoked?
runInvoked bool // should Runner.Run be invoked?
}{
{
name: "run and build by default",
desc: "Should run and build when build flag is not specified",
args: []string{},
buildInvoked: true,
runInvoked: true,
},
{
name: "run and build flag",
desc: "Should run and build when build is merely provided (defaults to true on presence)",
args: []string{"--build"},
buildInvoked: true,
runInvoked: true,
},
{
name: "run and build",
desc: "Should run and build when build is specifically requested",
args: []string{"--build=true"},
buildInvoked: true,
runInvoked: true,
},
{
name: "run and build with builder pack",
desc: "Should run and build when build is specifically requested with builder pack",
args: []string{"--build=true", "--builder=pack"},
buildInvoked: true,
runInvoked: true,
},
{
name: "run and build with builder s2i",
desc: "Should run and build when build is specifically requested with builder s2i",
args: []string{"--build=true", "--builder=s2i"},
buildInvoked: true,
runInvoked: true,
},
{
name: "run and build with builder invalid",
desc: "Should run and build when build is specifically requested with builder invalid",
args: []string{"--build=true", "--builder=invalid"},
buildError: fmt.Errorf("\"invalid\" is not a known builder. Available builders are \"pack\" and \"s2i\""),
buildInvoked: true,
runInvoked: true,
},
{
name: "run without build when disabled",
desc: "Should run but not build when build is expressly disabled",
args: []string{"--build=false"}, // can be any truthy value: 0, 'false' etc.
buildInvoked: false,
runInvoked: true,
},
{
name: "run and build on auto",
desc: "Should run and buil when build flag set to auto",
args: []string{"--build=auto"}, // can be any truthy value: 0, 'false' etc.
buildInvoked: true,
runInvoked: true,
},
{
name: "image existence builds",
desc: "Should build when image tag exists",
// The existence of an image tag value does not mean the function
// is built; that is the purvew of the buld stamp staleness check.
setup: func(f fn.Function, t *testing.T) error {
f.Image = "exampleimage"
return f.Write()
},
args: []string{},
buildInvoked: true,
runInvoked: true,
},
{
name: "Build errors return",
desc: "Errors building cause an immediate return with error",
args: []string{},
buildError: fmt.Errorf("generic build error"),
buildInvoked: true,
runInvoked: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := fromTempDirectory(t)
runner := mock.NewRunner()
if tt.runError != nil {
runner.RunFn = func(context.Context, fn.Function, time.Duration) (*fn.Job, error) { return nil, tt.runError }
}
builder := mock.NewBuilder()
if tt.buildError != nil {
builder.BuildFn = func(f fn.Function) error { return tt.buildError }
}
// using a command whose client will be populated with mock
// builder and mock runner, each of which may be set to error if the
// test has an error defined.
cmd := NewRunCmd(NewTestClient(
fn.WithRunner(runner),
fn.WithBuilder(builder),
fn.WithRegistry("ghcr.com/reg"),
))
cmd.SetArgs(tt.args) // Do not use test command args
// set test case's function instance
f, err := fn.New().Init(fn.Function{Root: root, Runtime: "go"})
if err != nil {
t.Fatal(err)
}
if tt.setup != nil {
if err := tt.setup(f, t); err != nil {
t.Fatal(err)
}
}
ctx, cancel := context.WithCancel(context.Background())
runErrCh := make(chan error, 1)
go func() {
t0 := tt // capture tt into closure
_, err := cmd.ExecuteContextC(ctx)
if err != nil && t0.buildError != nil {
// This is an expected error, so simply continue execution ignoring
// the error (send nil on the channel to release the parent routine
runErrCh <- nil
return
} else if err != nil {
runErrCh <- err // error not expected
return
}
// No errors, but an error was expected:
if t0.buildError != nil {
runErrCh <- fmt.Errorf("Expected error: %v but got %v\n", t0.buildError, err)
}
// Ensure invocations match expectations
if builder.BuildInvoked != tt.buildInvoked {
runErrCh <- fmt.Errorf("Function was expected to build is: %v but build execution was: %v", tt.buildInvoked, builder.BuildInvoked)
}
if runner.RunInvoked != tt.runInvoked {
runErrCh <- fmt.Errorf("Function was expected to run is: %v but run execution was: %v", tt.runInvoked, runner.RunInvoked)
}
close(runErrCh) // release the waiting parent process
}()
cancel() // trigger the return of cmd.ExecuteContextC in the routine
<-ctx.Done()
if err := <-runErrCh; err != nil { // wait for completion of assertions
t.Fatal(err)
}
})
}
}