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