From 57750ccb5186143bf1644945e6d31538168947ec Mon Sep 17 00:00:00 2001 From: Jeff Grafton Date: Mon, 27 Apr 2015 17:56:06 -0700 Subject: [PATCH] Use native Ginkgo test runner instead of cmd/e2e. This commit deletes cmd/e2e and updates hack/ginkgo-e2e.sh to use the 'ginkgo' command instead. All logic from cmd/e2e/e2e.go and test/e2e/driver.go have been combined into the new file test/e2e/e2e_test.go. Additionally, several tests which made poor assumptions about cwd or used testContext before it was set have been fixed. This change is generally intended to have no externally visible changes, aside from the following caveats: - The -t/--tests flag has been removed - Calling cmd/e2e/e2e directly obviously won't work, but that was never supported anyway - If the GINKGO_PARALLEL environment variable is set to y, then ginkgo will run test specs in parallel. (Currently defaults to n, since some tests are broken in this mode.) --- cmd/e2e/e2e.go | 89 ------------------------------ hack/ginkgo-e2e.sh | 82 +++++++++------------------- hack/lib/golang.sh | 2 +- hack/test-go.sh | 1 + test/e2e/driver.go | 98 --------------------------------- test/e2e/e2e_test.go | 127 +++++++++++++++++++++++++++++++++++++++++++ test/e2e/kubectl.go | 15 +++-- test/e2e/shell.go | 14 +++-- test/e2e/util.go | 9 +++ 9 files changed, 183 insertions(+), 254 deletions(-) delete mode 100644 cmd/e2e/e2e.go delete mode 100644 test/e2e/driver.go create mode 100644 test/e2e/e2e_test.go diff --git a/cmd/e2e/e2e.go b/cmd/e2e/e2e.go deleted file mode 100644 index 1135ef22e0b..00000000000 --- a/cmd/e2e/e2e.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2015 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "fmt" - "os" - goruntime "runtime" - "strings" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/GoogleCloudPlatform/kubernetes/test/e2e" - "github.com/golang/glog" - flag "github.com/spf13/pflag" -) - -var ( - context = &e2e.TestContextType{} - cloudConfig = &context.CloudConfig - - orderseed = flag.Int64("orderseed", 0, "If non-zero, seed of random test shuffle order. (Otherwise random.)") - reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.") - times = flag.Int("times", 1, "Number of times each test is eligible to be run. Individual order is determined by shuffling --times instances of each test using --orderseed (like a multi-deck shoe of cards).") - testList util.StringList -) - -func init() { - flag.VarP(&testList, "test", "t", "Test to execute (may be repeated or comma separated list of tests.) Defaults to running all tests.") - - flag.StringVar(&context.KubeConfig, clientcmd.RecommendedConfigPathFlag, "", "Path to kubeconfig containing embeded authinfo.") - flag.StringVar(&context.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'") - flag.StringVar(&context.AuthConfig, "auth-config", "", "Path to the auth info file.") - flag.StringVar(&context.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.") - flag.StringVar(&context.Host, "host", "", "The host, or apiserver, to connect to") - flag.StringVar(&context.RepoRoot, "repo-root", "./", "Root directory of kubernetes repository, for finding test files. Default assumes working directory is repository root") - flag.StringVar(&context.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, vagrant, etc.)") - - // TODO: Flags per provider? Rename gce_project/gce_zone? - flag.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke") - flag.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable") - flag.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable") -} - -func main() { - util.InitFlags() - goruntime.GOMAXPROCS(goruntime.NumCPU()) - if context.Provider == "" { - glog.Info("The --provider flag is not set. Treating as a conformance test. Some tests may not be run.") - os.Exit(1) - } - if *times <= 0 { - glog.Error("Invalid --times (negative or no testing requested)!") - os.Exit(1) - } - - if context.Provider == "aws" { - awsConfig := "[Global]\n" - if cloudConfig.Zone == "" { - glog.Error("--gce-zone must be specified for AWS") - os.Exit(1) - } - awsConfig += fmt.Sprintf("Zone=%s\n", cloudConfig.Zone) - - var err error - cloudConfig.Provider, err = cloudprovider.GetCloudProvider(context.Provider, strings.NewReader(awsConfig)) - if err != nil { - glog.Error("Error building AWS provider: ", err) - os.Exit(1) - } - } - - e2e.RunE2ETests(context, *orderseed, *times, *reportDir, testList) -} diff --git a/hack/ginkgo-e2e.sh b/hack/ginkgo-e2e.sh index 6044b54f546..e4d0411884f 100755 --- a/hack/ginkgo-e2e.sh +++ b/hack/ginkgo-e2e.sh @@ -18,54 +18,19 @@ set -o errexit set -o nounset set -o pipefail -KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +GINKGO_PARALLEL=${GINKGO_PARALLEL:-n} # set to 'y' to run tests in parallel +KUBE_ROOT=$(readlink -f $(dirname "${BASH_SOURCE}")/..) + source "${KUBE_ROOT}/cluster/common.sh" +source "${KUBE_ROOT}/hack/lib/init.sh" -# --- Find local test binaries. +# Ginkgo will build the e2e tests, so we need to make sure that the environment +# is set up correctly (including Godeps, etc). +kube::golang::setup_env -# Detect the OS name/arch so that we can find our binary -case "$(uname -s)" in - Darwin) - host_os=darwin - ;; - Linux) - host_os=linux - ;; - *) - echo "Unsupported host OS. Must be Linux or Mac OS X." >&2 - exit 1 - ;; -esac - -case "$(uname -m)" in - x86_64*) - host_arch=amd64 - ;; - i?86_64*) - host_arch=amd64 - ;; - amd64*) - host_arch=amd64 - ;; - arm*) - host_arch=arm - ;; - i?86*) - host_arch=x86 - ;; - *) - echo "Unsupported host arch. Must be x86_64, 386 or arm." >&2 - exit 1 - ;; -esac - -# Gather up the list of likely places and use ls to find the latest one. -locations=( - "${KUBE_ROOT}/_output/dockerized/bin/${host_os}/${host_arch}/e2e" - "${KUBE_ROOT}/_output/local/bin/${host_os}/${host_arch}/e2e" - "${KUBE_ROOT}/platforms/${host_os}/${host_arch}/e2e" -) -e2e=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 ) +# --- Build the Ginkgo test runner from Godeps +godep go install github.com/onsi/ginkgo/ginkgo +ginkgo=$(godep path)/bin/ginkgo # --- Setup some env vars. @@ -97,8 +62,8 @@ if [[ -z "${AUTH_CONFIG:-}" ]]; then ) elif [[ "${KUBERNETES_PROVIDER}" == "conformance_test" ]]; then auth_config=( - "--auth_config=${KUBERNETES_CONFORMANCE_TEST_AUTH_CONFIG:-}" - "--cert_dir=${KUBERNETES_CONFORMANCE_TEST_CERT_DIR:-}" + "--auth-config=${KUBERNETES_CONFORMANCE_TEST_AUTH_CONFIG:-}" + "--cert-dir=${KUBERNETES_CONFORMANCE_TEST_CERT_DIR:-}" ) else auth_config=( @@ -109,21 +74,26 @@ else echo "Conformance Test. No cloud-provider-specific preparation." KUBERNETES_PROVIDER="" auth_config=( - "--auth_config=${AUTH_CONFIG:-}" - "--cert_dir=${CERT_DIR:-}" + "--auth-config=${AUTH_CONFIG:-}" + "--cert-dir=${CERT_DIR:-}" ) fi -# Use the kubectl binary from the same directory as the e2e binary. +ginkgo_args=() +if [[ ${GINKGO_PARALLEL} =~ ^[yY]$ ]]; then + ginkgo_args+=("-p") +fi + # The --host setting is used only when providing --auth_config # If --kubeconfig is used, the host to use is retrieved from the .kubeconfig # file and the one provided with --host is ignored. -export PATH=$(dirname "${e2e}"):"${PATH}" -"${e2e}" "${auth_config[@]:+${auth_config[@]}}" \ +"${ginkgo}" "${ginkgo_args[@]:+${ginkgo_args[@]}}" test/e2e -- \ + "${auth_config[@]:+${auth_config[@]}}" \ --host="https://${KUBE_MASTER_IP-}" \ --provider="${KUBERNETES_PROVIDER}" \ - --gce_project="${PROJECT:-}" \ - --gce_zone="${ZONE:-}" \ - --kube_master="${KUBE_MASTER:-}" \ - ${E2E_REPORT_DIR+"--report_dir=${E2E_REPORT_DIR}"} \ + --gce-project="${PROJECT:-}" \ + --gce-zone="${ZONE:-}" \ + --kube-master="${KUBE_MASTER:-}" \ + --repo-root="${KUBE_VERSION_ROOT}" \ + ${E2E_REPORT_DIR+"--report-dir=${E2E_REPORT_DIR}"} \ "${@:-}" diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 00b13088729..45f3bad5bba 100644 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -44,7 +44,6 @@ readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}") # The set of test targets that we are building for all platforms readonly KUBE_TEST_TARGETS=( - cmd/e2e cmd/integration cmd/gendocs cmd/genman @@ -59,6 +58,7 @@ readonly KUBE_TEST_PORTABLE=( hack/e2e-suite hack/e2e-internal hack/ginkgo-e2e.sh + hack/lib ) # If we update this we need to also update the set of golang compilers we build diff --git a/hack/test-go.sh b/hack/test-go.sh index ddac88fe34c..c95ed5836ec 100755 --- a/hack/test-go.sh +++ b/hack/test-go.sh @@ -35,6 +35,7 @@ kube::test::find_dirs() { -o -wholename '*/third_party/*' \ -o -wholename '*/Godeps/*' \ -o -wholename '*/contrib/podex/*' \ + -o -wholename '*/test/e2e/*' \ -o -wholename '*/test/integration/*' \ \) -prune \ \) -name '*_test.go' -print0 | xargs -0n1 dirname | sed 's|^\./||' | sort -u diff --git a/test/e2e/driver.go b/test/e2e/driver.go deleted file mode 100644 index 13f518ebf1b..00000000000 --- a/test/e2e/driver.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package e2e - -import ( - "fmt" - "path" - "regexp" - "strings" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" - "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/config" - "github.com/onsi/ginkgo/reporters" - "github.com/onsi/gomega" -) - -type testResult bool - -type CloudConfig struct { - ProjectID string - Zone string - MasterName string - - Provider cloudprovider.Interface -} - -func init() { - // Turn on verbose by default to get spec names - config.DefaultReporterConfig.Verbose = true - - // Turn on EmitSpecProgress to get spec progress (especially on interrupt) - config.GinkgoConfig.EmitSpecProgress = true - - // Randomize specs as well as suites - config.GinkgoConfig.RandomizeAllSpecs = true -} - -func (t *testResult) Fail() { *t = false } - -// Run each Go end-to-end-test. This function assumes the -// creation of a test cluster. -func RunE2ETests(context *TestContextType, orderseed int64, times int, reportDir string, testList []string) { - testContext = *context - util.ReallyCrash = true - util.InitLogs() - defer util.FlushLogs() - - if len(testList) != 0 { - if config.GinkgoConfig.FocusString != "" || config.GinkgoConfig.SkipString != "" { - glog.Fatal("Either specify --test/-t or --ginkgo.focus/--ginkgo.skip but not both.") - } - var testRegexps []string - for _, t := range testList { - testRegexps = append(testRegexps, regexp.QuoteMeta(t)) - } - config.GinkgoConfig.FocusString = `\b(` + strings.Join(testRegexps, "|") + `)\b` - } - - // Disable density test unless it's explicitly requested. - if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" { - config.GinkgoConfig.SkipString = "Skipped" - } - - // TODO: Make orderseed work again. - var passed testResult = true - gomega.RegisterFailHandler(ginkgo.Fail) - // Run the existing tests with output to console + JUnit for Jenkins - for i := 0; i < times && passed; i++ { - var r []ginkgo.Reporter - if reportDir != "" { - r = append(r, reporters.NewJUnitReporter(path.Join(reportDir, fmt.Sprintf("junit_%d.xml", i+1)))) - } - ginkgo.RunSpecsWithDefaultAndCustomReporters(&passed, fmt.Sprintf("Kubernetes e2e Suite run %d of %d", i+1, times), r) - } - - if !passed { - glog.Fatalf("At least one test failed") - } else { - glog.Infof("All tests pass") - } -} diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go new file mode 100644 index 00000000000..dc392790df4 --- /dev/null +++ b/test/e2e/e2e_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "flag" + "fmt" + "os" + "path" + goruntime "runtime" + "strings" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/golang/glog" + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/reporters" + "github.com/onsi/gomega" +) + +type testResult bool + +var ( + cloudConfig = &testContext.CloudConfig + + orderseed = flag.Int64("orderseed", 0, "If non-zero, seed of random test shuffle order. (Otherwise random.)") + reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.") + times = flag.Int("times", 1, "Number of times each test is eligible to be run. Individual order is determined by shuffling --times instances of each test using --orderseed (like a multi-deck shoe of cards).") +) + +func init() { + // Turn on verbose by default to get spec names + config.DefaultReporterConfig.Verbose = true + + // Turn on EmitSpecProgress to get spec progress (especially on interrupt) + config.GinkgoConfig.EmitSpecProgress = true + + // Randomize specs as well as suites + config.GinkgoConfig.RandomizeAllSpecs = true + + flag.StringVar(&testContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, "", "Path to kubeconfig containing embeded authinfo.") + flag.StringVar(&testContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'") + flag.StringVar(&testContext.AuthConfig, "auth-config", "", "Path to the auth info file.") + flag.StringVar(&testContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.") + flag.StringVar(&testContext.Host, "host", "", "The host, or apiserver, to connect to") + flag.StringVar(&testContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.") + flag.StringVar(&testContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, vagrant, etc.)") + + // TODO: Flags per provider? Rename gce-project/gce-zone? + flag.StringVar(&cloudConfig.MasterName, "kube-master", "", "Name of the kubernetes master. Only required if provider is gce or gke") + flag.StringVar(&cloudConfig.ProjectID, "gce-project", "", "The GCE project being used, if applicable") + flag.StringVar(&cloudConfig.Zone, "gce-zone", "", "GCE zone being used, if applicable") +} + +func (t *testResult) Fail() { *t = false } + +func TestE2E(t *testing.T) { + defer util.FlushLogs() + + // Disable density test unless it's explicitly requested. + if config.GinkgoConfig.FocusString == "" && config.GinkgoConfig.SkipString == "" { + config.GinkgoConfig.SkipString = "Skipped" + } + + // TODO: Make orderseed work again. + gomega.RegisterFailHandler(ginkgo.Fail) + // Run the existing tests with output to console + JUnit for Jenkins + for i := 0; i < *times && !t.Failed(); i++ { + var r []ginkgo.Reporter + if *reportDir != "" { + r = append(r, reporters.NewJUnitReporter(path.Join(*reportDir, fmt.Sprintf("junit_%d_%02d.xml", i+1, config.GinkgoConfig.ParallelNode)))) + } + ginkgo.RunSpecsWithDefaultAndCustomReporters(t, fmt.Sprintf("Kubernetes e2e Suite run %d of %d", i+1, *times), r) + } +} + +func TestMain(m *testing.M) { + flag.Parse() + util.ReallyCrash = true + util.InitLogs() + goruntime.GOMAXPROCS(goruntime.NumCPU()) + + // TODO: possibly clean up or refactor this functionality. + if testContext.Provider == "" { + glog.Info("The --provider flag is not set. Treating as a conformance test. Some tests may not be run.") + os.Exit(1) + } + if *times <= 0 { + glog.Error("Invalid --times (negative or no testing requested)!") + os.Exit(1) + } + + if testContext.Provider == "aws" { + awsConfig := "[Global]\n" + if cloudConfig.Zone == "" { + glog.Error("gce-zone must be specified for AWS") + os.Exit(1) + } + awsConfig += fmt.Sprintf("Zone=%s\n", cloudConfig.Zone) + + var err error + cloudConfig.Provider, err = cloudprovider.GetCloudProvider(testContext.Provider, strings.NewReader(awsConfig)) + if err != nil { + glog.Error("Error building AWS provider: ", err) + os.Exit(1) + } + } + + os.Exit(m.Run()) +} diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go index 559c7b60b2b..2fc986c2550 100644 --- a/test/e2e/kubectl.go +++ b/test/e2e/kubectl.go @@ -44,6 +44,7 @@ const ( ) var _ = Describe("kubectl", func() { + defer GinkgoRecover() var c *client.Client BeforeEach(func() { @@ -53,11 +54,12 @@ var _ = Describe("kubectl", func() { }) Describe("update-demo", func() { - var ( + var updateDemoRoot, nautilusPath, kittenPath string + BeforeEach(func() { updateDemoRoot = filepath.Join(testContext.RepoRoot, "examples/update-demo") - nautilusPath = filepath.Join(updateDemoRoot, "nautilus-rc.yaml") - kittenPath = filepath.Join(updateDemoRoot, "kitten-rc.yaml") - ) + nautilusPath = filepath.Join(updateDemoRoot, "nautilus-rc.yaml") + kittenPath = filepath.Join(updateDemoRoot, "kitten-rc.yaml") + }) It("should create and stop a replication controller", func() { defer cleanup(nautilusPath, updateDemoSelector) @@ -95,7 +97,10 @@ var _ = Describe("kubectl", func() { }) Describe("guestbook", func() { - var guestbookPath = filepath.Join(testContext.RepoRoot, "examples/guestbook") + var guestbookPath string + BeforeEach(func() { + guestbookPath = filepath.Join(testContext.RepoRoot, "examples/guestbook") + }) It("should create and stop a working application", func() { if !providerIs("gce", "gke") { diff --git a/test/e2e/shell.go b/test/e2e/shell.go index 6640d0a4b8e..a05af72d404 100644 --- a/test/e2e/shell.go +++ b/test/e2e/shell.go @@ -22,20 +22,19 @@ import ( "io/ioutil" "os" "os/exec" - "path" "path/filepath" . "github.com/onsi/ginkgo" ) var ( - root = absOrDie(filepath.Clean(filepath.Join(path.Base(os.Args[0]), ".."))) + root = kubeRootOrDie() ) var _ = Describe("Shell", func() { - defer GinkgoRecover() // Slurp up all the tests in hack/e2e-suite + // This should be using testContext.RepoRoot, but this is evaluated before flags are evaluated, so we are stuck. bashE2ERoot := filepath.Join(root, "hack/e2e-suite") files, err := ioutil.ReadDir(bashE2ERoot) if err != nil { @@ -56,8 +55,13 @@ var _ = Describe("Shell", func() { } }) -func absOrDie(path string) string { - out, err := filepath.Abs(path) +// Returns the root directory of the Kubernetes source tree, assuming the test lives inside test/e2e. +func kubeRootOrDie() string { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + out, err := filepath.Abs(filepath.Join(cwd, "..", "..")) if err != nil { panic(err) } diff --git a/test/e2e/util.go b/test/e2e/util.go index 198b89ff7d6..3c9fe93ea9f 100644 --- a/test/e2e/util.go +++ b/test/e2e/util.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/clientauth" + "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" @@ -46,6 +47,14 @@ const ( podStartTimeout = 5 * time.Minute ) +type CloudConfig struct { + ProjectID string + Zone string + MasterName string + + Provider cloudprovider.Interface +} + type TestContextType struct { KubeConfig string KubeContext string