mirror of https://github.com/knative/func.git
389 lines
13 KiB
Go
389 lines
13 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
fn "knative.dev/func/pkg/functions"
|
|
"knative.dev/func/pkg/mock"
|
|
. "knative.dev/func/pkg/testing"
|
|
)
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRun_Images ensures that runnning 'func run' with --image
|
|
// (and additional flags) works as intended
|
|
func TestRun_Images(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
buildInvoked bool
|
|
runInvoked bool
|
|
|
|
runError error
|
|
buildError error
|
|
}{
|
|
{
|
|
name: "image with digest",
|
|
args: []string{"--image", "exampleimage@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
|
|
runInvoked: true,
|
|
buildInvoked: false,
|
|
},
|
|
{
|
|
name: "image with tag direct deploy",
|
|
args: []string{"--image", "username/exampleimage:latest", "--build=false"},
|
|
runInvoked: true,
|
|
buildInvoked: false,
|
|
},
|
|
{
|
|
name: "digested image without container should fail",
|
|
args: []string{"--container=false", "--image", "exampleimage@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
|
|
runInvoked: false,
|
|
buildInvoked: false,
|
|
buildError: fmt.Errorf("cannot use digested image with --container=false"),
|
|
},
|
|
{
|
|
name: "image should build even with tagged image given",
|
|
args: []string{"--image", "username/exampleimage:latest"},
|
|
runInvoked: true,
|
|
buildInvoked: true,
|
|
},
|
|
}
|
|
|
|
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
|
|
_, err := fn.New().Init(fn.Function{Root: root, Runtime: "go"})
|
|
if 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRun_CorrectImage enusures that correct image gets passed through to the
|
|
// runner.
|
|
func TestRun_CorrectImage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
image string
|
|
args []string
|
|
buildInvoked bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "image with digest, auto build",
|
|
args: []string{"--image", "exampleimage@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
|
|
image: "exampleimage@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
buildInvoked: false,
|
|
},
|
|
{
|
|
name: "image with tag direct deploy",
|
|
args: []string{"--image", "username/exampleimage:latest", "--build=false"},
|
|
image: "username/exampleimage:latest",
|
|
buildInvoked: false,
|
|
},
|
|
{
|
|
name: "digested image without container should fail",
|
|
args: []string{"--container=false", "--image", "exampleimage@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"},
|
|
image: "",
|
|
buildInvoked: false,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "image should build even with tagged image given",
|
|
args: []string{"--image", "username/exampleimage:latest"},
|
|
image: "username/exampleimage:latest",
|
|
buildInvoked: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
root := FromTempDirectory(t)
|
|
runner := mock.NewRunner()
|
|
|
|
runner.RunFn = func(_ context.Context, f fn.Function, _ time.Duration) (*fn.Job, error) {
|
|
// TODO: add if for empty image? -- should fail beforehand
|
|
if f.Build.Image != tt.image {
|
|
return nil, fmt.Errorf("Expected image: %v but got: %v", tt.image, f.Build.Image)
|
|
}
|
|
errs := make(chan error, 1)
|
|
stop := func() error { return nil }
|
|
return fn.NewJob(f, "127.0.0.1", "8080", errs, stop, false)
|
|
}
|
|
|
|
builder := mock.NewBuilder()
|
|
if tt.expectError {
|
|
builder.BuildFn = func(f fn.Function) error { return fmt.Errorf("expected error") }
|
|
}
|
|
|
|
cmd := NewRunCmd(NewTestClient(
|
|
fn.WithRunner(runner),
|
|
fn.WithBuilder(builder),
|
|
fn.WithRegistry("ghcr.com/reg"),
|
|
))
|
|
cmd.SetArgs(tt.args)
|
|
|
|
// set test case's function instance
|
|
_, err := fn.New().Init(fn.Function{Root: root, Runtime: "go"})
|
|
if 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.expectError {
|
|
// 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.expectError {
|
|
runErrCh <- fmt.Errorf("Expected error but got '%v'\n", 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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|