Skip to content

Commit 8cf21a0

Browse files
committed
add e2e framework
1 parent 8d744dc commit 8cf21a0

File tree

3 files changed

+346
-0
lines changed

3 files changed

+346
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package ginkgowrapper wraps Ginkgo Fail and Skip functions to panic
18+
// with structured data instead of a constant string.
19+
package ginkgowrapper
20+
21+
import (
22+
"bufio"
23+
"bytes"
24+
"regexp"
25+
"runtime"
26+
"runtime/debug"
27+
"strings"
28+
29+
"github.com/onsi/ginkgo"
30+
)
31+
32+
// FailurePanic is the value that will be panicked from Fail.
33+
type FailurePanic struct {
34+
Message string // The failure message passed to Fail
35+
Filename string // The filename that is the source of the failure
36+
Line int // The line number of the filename that is the source of the failure
37+
FullStackTrace string // A full stack trace starting at the source of the failure
38+
}
39+
40+
// String makes FailurePanic look like the old Ginkgo panic when printed.
41+
func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC }
42+
43+
// Fail wraps ginkgo.Fail so that it panics with more useful
44+
// information about the failure. This function will panic with a
45+
// FailurePanic.
46+
func Fail(message string, callerSkip ...int) {
47+
skip := 1
48+
if len(callerSkip) > 0 {
49+
skip += callerSkip[0]
50+
}
51+
52+
_, file, line, _ := runtime.Caller(skip)
53+
fp := FailurePanic{
54+
Message: message,
55+
Filename: file,
56+
Line: line,
57+
FullStackTrace: pruneStack(skip),
58+
}
59+
60+
defer func() {
61+
e := recover()
62+
if e != nil {
63+
panic(fp)
64+
}
65+
}()
66+
67+
ginkgo.Fail(message, skip)
68+
}
69+
70+
// SkipPanic is the value that will be panicked from Skip.
71+
type SkipPanic struct {
72+
Message string // The failure message passed to Fail
73+
Filename string // The filename that is the source of the failure
74+
Line int // The line number of the filename that is the source of the failure
75+
FullStackTrace string // A full stack trace starting at the source of the failure
76+
}
77+
78+
// String makes SkipPanic look like the old Ginkgo panic when printed.
79+
func (SkipPanic) String() string { return ginkgo.GINKGO_PANIC }
80+
81+
// Skip wraps ginkgo.Skip so that it panics with more useful
82+
// information about why the test is being skipped. This function will
83+
// panic with a SkipPanic.
84+
func Skip(message string, callerSkip ...int) {
85+
skip := 1
86+
if len(callerSkip) > 0 {
87+
skip += callerSkip[0]
88+
}
89+
90+
_, file, line, _ := runtime.Caller(skip)
91+
sp := SkipPanic{
92+
Message: message,
93+
Filename: file,
94+
Line: line,
95+
FullStackTrace: pruneStack(skip),
96+
}
97+
98+
defer func() {
99+
e := recover()
100+
if e != nil {
101+
panic(sp)
102+
}
103+
}()
104+
105+
ginkgo.Skip(message, skip)
106+
}
107+
108+
// ginkgo adds a lot of test running infrastructure to the stack, so
109+
// we filter those out
110+
var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`)
111+
112+
func pruneStack(skip int) string {
113+
skip += 2 // one for pruneStack and one for debug.Stack
114+
stack := debug.Stack()
115+
scanner := bufio.NewScanner(bytes.NewBuffer(stack))
116+
var prunedStack []string
117+
118+
// skip the top of the stack
119+
for i := 0; i < 2*skip+1; i++ {
120+
scanner.Scan()
121+
}
122+
123+
for scanner.Scan() {
124+
if stackSkipPattern.Match(scanner.Bytes()) {
125+
scanner.Scan() // these come in pairs
126+
} else {
127+
prunedStack = append(prunedStack, scanner.Text())
128+
scanner.Scan() // these come in pairs
129+
prunedStack = append(prunedStack, scanner.Text())
130+
}
131+
}
132+
133+
return strings.Join(prunedStack, "\n")
134+
}

test/e2e/framework/test_context.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package framework
18+
19+
import (
20+
"flag"
21+
"fmt"
22+
"os"
23+
24+
"github.com/onsi/ginkgo/config"
25+
26+
"k8s.io/client-go/tools/clientcmd"
27+
)
28+
29+
const (
30+
defaultHost = "http://127.0.0.1:8080"
31+
defaultBinariesDir = "/usr/local/kubebuilder/bin/"
32+
)
33+
34+
var TestContext TestContextType
35+
36+
type TestContextType struct {
37+
BinariesDir string
38+
ProjectDir string
39+
// kubectl related items
40+
KubectlPath string
41+
KubeConfig string
42+
KubeContext string
43+
KubeAPIContentType string
44+
CertDir string
45+
Host string
46+
}
47+
48+
// Register flags common to all e2e test suites.
49+
func RegisterFlags() {
50+
// Turn on verbose by default to get spec names
51+
config.DefaultReporterConfig.Verbose = true
52+
53+
// Turn on EmitSpecProgress to get spec progress (especially on interrupt)
54+
config.GinkgoConfig.EmitSpecProgress = true
55+
56+
// Randomize specs as well as suites
57+
config.GinkgoConfig.RandomizeAllSpecs = true
58+
59+
flag.StringVar(&TestContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to kubeconfig containing embedded authinfo.")
60+
flag.StringVar(&TestContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'")
61+
flag.StringVar(&TestContext.KubeAPIContentType, "kube-api-content-type", "application/vnd.kubernetes.protobuf", "ContentType used to communicate with apiserver")
62+
flag.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
63+
flag.StringVar(&TestContext.Host, "host", "", fmt.Sprintf("The host, or apiserver, to connect to. Will default to %s if this argument and --kubeconfig are not set", defaultHost))
64+
65+
flag.StringVar(&TestContext.BinariesDir, "binaries-dir", defaultBinariesDir, "The path of binaries.")
66+
flag.StringVar(&TestContext.ProjectDir, "project-dir", "", "Project root path, must under $GOPATH/src/.")
67+
}

test/e2e/framework/util.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package framework
18+
19+
import (
20+
"fmt"
21+
"io/ioutil"
22+
"math/rand"
23+
"os"
24+
"path/filepath"
25+
"regexp"
26+
"strings"
27+
"time"
28+
29+
"github.com/kubernetes-sigs/kubebuilder/test/e2e/framework/ginkgowrapper"
30+
. "github.com/onsi/ginkgo"
31+
32+
"k8s.io/client-go/tools/clientcmd"
33+
)
34+
35+
// Code originally copied from kubernetes/kubernetes at
36+
// https://github.com/kubernetes/kubernetes/blob/master/test/e2e/framework/util.go
37+
38+
// GetKubectlArgs wraps with default kubectl related args.
39+
func GetKubectlArgs(args []string) []string {
40+
defaultArgs := []string{}
41+
42+
// Reference a --server option so tests can run anywhere.
43+
if TestContext.Host != "" {
44+
defaultArgs = append(defaultArgs, "--"+clientcmd.FlagAPIServer+"="+TestContext.Host)
45+
}
46+
if TestContext.KubeConfig != "" {
47+
defaultArgs = append(defaultArgs, "--"+clientcmd.RecommendedConfigPathFlag+"="+TestContext.KubeConfig)
48+
49+
// Reference the KubeContext
50+
if TestContext.KubeContext != "" {
51+
defaultArgs = append(defaultArgs, "--"+clientcmd.FlagContext+"="+TestContext.KubeContext)
52+
}
53+
54+
} else {
55+
if TestContext.CertDir != "" {
56+
defaultArgs = append(defaultArgs,
57+
fmt.Sprintf("--certificate-authority=%s", filepath.Join(TestContext.CertDir, "ca.crt")),
58+
fmt.Sprintf("--client-certificate=%s", filepath.Join(TestContext.CertDir, "kubecfg.crt")),
59+
fmt.Sprintf("--client-key=%s", filepath.Join(TestContext.CertDir, "kubecfg.key")))
60+
}
61+
}
62+
kubectlArgs := append(defaultArgs, args...)
63+
64+
return kubectlArgs
65+
}
66+
67+
func NowStamp() string {
68+
return time.Now().Format(time.StampMilli)
69+
}
70+
71+
func log(level string, format string, args ...interface{}) {
72+
fmt.Fprintf(GinkgoWriter, NowStamp()+": "+level+": "+format+"\n", args...)
73+
}
74+
75+
func Logf(format string, args ...interface{}) {
76+
log("INFO", format, args...)
77+
}
78+
79+
func Failf(format string, args ...interface{}) {
80+
FailfWithOffset(1, format, args...)
81+
}
82+
83+
// FailfWithOffset calls "Fail" and logs the error at "offset" levels above its caller
84+
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
85+
func FailfWithOffset(offset int, format string, args ...interface{}) {
86+
msg := fmt.Sprintf(format, args...)
87+
log("INFO", msg)
88+
ginkgowrapper.Fail(NowStamp()+": "+msg, 1+offset)
89+
}
90+
91+
func Skipf(format string, args ...interface{}) {
92+
msg := fmt.Sprintf(format, args...)
93+
log("INFO", msg)
94+
ginkgowrapper.Skip(NowStamp() + ": " + msg)
95+
}
96+
97+
// RandomSuffix provides a random string to append to certain base name.
98+
func RandomSuffix() string {
99+
source := []rune("abcdefghijklmnopqrstuvwxyz")
100+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
101+
res := make([]rune, 4)
102+
for i := range res {
103+
res[i] = source[r.Intn(len(source))]
104+
}
105+
return string(res)
106+
}
107+
108+
// ParseCmdOutput converts given command output string into individual objects
109+
// according to line breakers, and ignores the empty elements in it.
110+
func ParseCmdOutput(output string) []string {
111+
res := []string{}
112+
elements := strings.Split(output, "\n")
113+
for _, element := range elements {
114+
if element != "" {
115+
res = append(res, element)
116+
}
117+
}
118+
119+
return res
120+
}
121+
122+
// ReplaceFileConent tries to replace the source content of given file
123+
// with the target concent, source content can be regex.
124+
func ReplaceFileConent(src, target string, filePath string) error {
125+
// Check if file exist
126+
if _, err := os.Stat(filePath); err != nil {
127+
return err
128+
}
129+
130+
// Read file content
131+
fileContent, err := ioutil.ReadFile(filePath)
132+
if err != nil {
133+
return err
134+
}
135+
136+
// Replace the content
137+
r := regexp.MustCompile(src)
138+
output := r.ReplaceAllString(string(fileContent), target)
139+
140+
if err := ioutil.WriteFile(filePath, []byte(output), os.ModePerm); err != nil {
141+
return err
142+
}
143+
144+
return nil
145+
}

0 commit comments

Comments
 (0)