mirror of https://github.com/knative/func.git
345 lines
8.5 KiB
Go
345 lines
8.5 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package function_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
fn "knative.dev/kn-plugin-func"
|
|
"knative.dev/kn-plugin-func/buildpacks"
|
|
"knative.dev/kn-plugin-func/docker"
|
|
"knative.dev/kn-plugin-func/knative"
|
|
)
|
|
|
|
/*
|
|
NOTE: Running integration tests locally requires a configured test cluster.
|
|
Test failures may require manual removal of dangling resources.
|
|
|
|
## Integration Cluster
|
|
|
|
These integration tests require a properly configured cluster,
|
|
such as that which is setup and configured in CI (see .github/workflows).
|
|
A local KinD cluster can be started via:
|
|
./hack/allocate.sh && ./hack/configure.sh
|
|
|
|
## Integration Testing
|
|
|
|
These tests can be run via the make target:
|
|
make test-integration
|
|
or manually by specifying the tag
|
|
go test -v -tags integration ./...
|
|
|
|
## Teardown and Cleanup
|
|
|
|
Tests should clean up after themselves. In the event of failures, one may
|
|
need to manually remove files:
|
|
rm -rf ./testdata/example.com
|
|
The test cluster is not automatically removed, as it can be reused. To remove:
|
|
./hack/delete.sh
|
|
*/
|
|
|
|
const (
|
|
// DefaultRegistry must contain both the registry host and
|
|
// registry namespace at this time. This will likely be
|
|
// split and defaulted to the forthcoming in-cluster registry.
|
|
DefaultRegistry = "localhost:5000/func"
|
|
|
|
// DefaultNamespace for the underlying deployments. Must be the same
|
|
// as is set up and configured (see hack/configure.sh)
|
|
DefaultNamespace = "func"
|
|
)
|
|
|
|
func TestList(t *testing.T) {
|
|
verbose := true
|
|
|
|
// Assemble
|
|
lister, err := knative.NewLister(DefaultNamespace)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client := fn.New(
|
|
fn.WithLister(lister),
|
|
fn.WithVerbose(verbose))
|
|
|
|
// Act
|
|
names, err := client.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Assert
|
|
if len(names) != 0 {
|
|
t.Fatalf("Expected no Functions, got %v", names)
|
|
}
|
|
}
|
|
|
|
// TestNew creates
|
|
func TestNew(t *testing.T) {
|
|
defer within(t, "testdata/example.com/testnew")()
|
|
verbose := true
|
|
|
|
client := newClient(verbose)
|
|
|
|
// Act
|
|
if err := client.New(context.Background(), fn.Function{Name: "testnew", Root: ".", Runtime: "go"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer del(t, client, "testnew")
|
|
|
|
// Assert
|
|
items, err := client.List(context.Background())
|
|
names := []string{}
|
|
for _, item := range items {
|
|
names = append(names, item.Name)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(names, []string{"testnew"}) {
|
|
t.Fatalf("Expected function list ['testnew'], got %v", names)
|
|
}
|
|
}
|
|
|
|
// TestDeploy updates
|
|
func TestDeploy(t *testing.T) {
|
|
defer within(t, "testdata/example.com/deploy")()
|
|
verbose := true
|
|
|
|
client := newClient(verbose)
|
|
|
|
if err := client.New(context.Background(), fn.Function{Name: "deploy", Root: ".", Runtime: "go"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer del(t, client, "deploy")
|
|
|
|
if err := client.Deploy(context.Background(), "."); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestRemove deletes
|
|
func TestRemove(t *testing.T) {
|
|
defer within(t, "testdata/example.com/remove")()
|
|
verbose := true
|
|
|
|
client := newClient(verbose)
|
|
|
|
if err := client.New(context.Background(), fn.Function{Name: "remove", Root: ".", Runtime: "go"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitFor(t, client, "remove")
|
|
|
|
if err := client.Remove(context.Background(), fn.Function{Name: "remove"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
names, err := client.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(names) != 0 {
|
|
t.Fatalf("Expected empty Functions list, got %v", names)
|
|
}
|
|
}
|
|
|
|
// TestRemoteRepositories ensures that initializing a Function
|
|
// defined in a remote repository finds the template, writes
|
|
// the expected files, and retains the expected modes.
|
|
// NOTE: this test only succeeds due to an override in
|
|
// templates' copyNode which forces mode 755 for directories.
|
|
// See https://github.com/go-git/go-git/issues/364
|
|
func TestRemoteRepositories(t *testing.T) {
|
|
defer within(t, "testdata/example.com/remote")()
|
|
|
|
// Write the test template from the remote onto root
|
|
client := fn.New(
|
|
fn.WithRegistry(DefaultRegistry),
|
|
fn.WithRepository("https://github.com/boson-project/test-templates"),
|
|
)
|
|
err := client.Create(fn.Function{
|
|
Root: ".",
|
|
Runtime: "runtime",
|
|
Template: "template",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
Path string
|
|
Perm uint32
|
|
Dir bool
|
|
}{
|
|
{Path: "file", Perm: 0644},
|
|
{Path: "dir-a/file", Perm: 0644},
|
|
{Path: "dir-b/file", Perm: 0644},
|
|
{Path: "dir-b/executable", Perm: 0755},
|
|
{Path: "dir-b", Perm: 0755},
|
|
{Path: "dir-a", Perm: 0755},
|
|
}
|
|
|
|
// Note that .Perm() are used to only consider the least-signifigant 9 and
|
|
// thus not have to consider the directory bit.
|
|
for _, test := range tests {
|
|
file, err := os.Stat(test.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("%04o repository/%v", file.Mode().Perm(), test.Path)
|
|
if file.Mode().Perm() != os.FileMode(test.Perm) {
|
|
t.Fatalf("expected 'repository/%v' to have mode %04o, got %04o", test.Path, test.Perm, file.Mode().Perm())
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***********
|
|
// Helpers
|
|
// ***********
|
|
|
|
// newClient creates an instance of the func client whose concrete impls
|
|
// match those created by the kn func plugin CLI.
|
|
func newClient(verbose bool) *fn.Client {
|
|
builder := buildpacks.NewBuilder()
|
|
builder.Verbose = verbose
|
|
|
|
pusher, err := docker.NewPusher()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pusher.Verbose = verbose
|
|
|
|
deployer, err := knative.NewDeployer(DefaultNamespace)
|
|
if err != nil {
|
|
panic(err) // TODO: remove error from deployer constructor
|
|
}
|
|
deployer.Verbose = verbose
|
|
|
|
remover, err := knative.NewRemover(DefaultNamespace)
|
|
if err != nil {
|
|
panic(err) // TODO: remove error from remover constructor
|
|
}
|
|
remover.Verbose = verbose
|
|
|
|
lister, err := knative.NewLister(DefaultNamespace)
|
|
if err != nil {
|
|
panic(err) // TODO: remove error from lister constructor
|
|
}
|
|
lister.Verbose = verbose
|
|
|
|
return fn.New(
|
|
fn.WithRegistry(DefaultRegistry),
|
|
fn.WithVerbose(verbose),
|
|
fn.WithBuilder(builder),
|
|
fn.WithPusher(pusher),
|
|
fn.WithDeployer(deployer),
|
|
fn.WithRemover(remover),
|
|
fn.WithLister(lister),
|
|
)
|
|
}
|
|
|
|
// Del cleans up after a test by removing a function by name.
|
|
// (test fails if the named function does not exist)
|
|
//
|
|
// Intended to be run in a defer statement immediately after creation, del
|
|
// works around the asynchronicity of the underlying platform's creation
|
|
// step by polling the provider until the names function becomes available
|
|
// (or the test times out), before firing off a deletion request.
|
|
// Of course, ideally this would be replaced by the use of a synchronous
|
|
// method, or at a minimum a way to register a callback/listener for the
|
|
// creation event. This is what we have for now, and the show must go on.
|
|
func del(t *testing.T, c *fn.Client, name string) {
|
|
t.Helper()
|
|
waitFor(t, c, name)
|
|
if err := c.Remove(context.Background(), fn.Function{Name: name}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// waitFor the named Function to become available in List output.
|
|
// TODO: the API should be synchronous, but that depends first on
|
|
// Create returning the derived name such that we can bake polling in.
|
|
// Ideally the Boson provider's Creaet would be made syncrhonous.
|
|
func waitFor(t *testing.T, c *fn.Client, name string) {
|
|
t.Helper()
|
|
var pollInterval = 2 * time.Second
|
|
|
|
for { // ever (i.e. defer to global test timeout)
|
|
nn, err := c.List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, n := range nn {
|
|
if n.Name == name {
|
|
return
|
|
}
|
|
}
|
|
time.Sleep(pollInterval)
|
|
}
|
|
}
|
|
|
|
// Create the given directory, CD to it, and return a function which can be
|
|
// run in a defer statement to return to the original directory and cleanup.
|
|
// Note must be executed, not deferred itself
|
|
// NO: defer within(t, "somedir")
|
|
// YES: defer within(t, "somedir")()
|
|
func within(t *testing.T, root string) func() {
|
|
t.Helper()
|
|
cwd := pwd(t)
|
|
mkdir(t, root)
|
|
cd(t, root)
|
|
return func() {
|
|
cd(t, cwd)
|
|
rm(t, root)
|
|
}
|
|
}
|
|
|
|
func pwd(t *testing.T) string {
|
|
t.Helper()
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func mkdir(t *testing.T, dir string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func cd(t *testing.T, dir string) {
|
|
t.Helper()
|
|
if err := os.Chdir(dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func rm(t *testing.T, dir string) {
|
|
t.Helper()
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func touch(file string) {
|
|
_, err := os.Stat(file)
|
|
if os.IsNotExist(err) {
|
|
f, err := os.Create(file)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
}
|
|
t := time.Now().Local()
|
|
if err := os.Chtimes(file, t, t); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|