diff --git a/test/e2e/density.go b/test/e2e/density.go
index 0f76c8b1188..a79508e2870 100644
--- a/test/e2e/density.go
+++ b/test/e2e/density.go
@@ -450,15 +450,15 @@ var _ = framework.KubeDescribe("Density", func() {
}
for _, testArg := range densityTests {
- name := fmt.Sprintf("should allow starting %d pods per node", testArg.podsPerNode)
+ feature := "ManualPerformance"
switch testArg.podsPerNode {
case 30:
- name = "[Feature:Performance] " + name
+ feature = "Performance"
case 95:
- name = "[Feature:HighDensityPerformance]" + name
- default:
- name = "[Feature:ManualPerformance] " + name
+ feature = "HighDensityPerformance"
}
+
+ name := fmt.Sprintf("[Feature:%s] should allow starting %d pods per node", feature, testArg.podsPerNode)
itArg := testArg
It(name, func() {
podsPerNode := itArg.podsPerNode
diff --git a/test/e2e/load.go b/test/e2e/load.go
index 779764da487..9e674b5f19a 100644
--- a/test/e2e/load.go
+++ b/test/e2e/load.go
@@ -133,12 +133,11 @@ var _ = framework.KubeDescribe("Load capacity", func() {
}
for _, testArg := range loadTests {
- name := fmt.Sprintf("should be able to handle %v pods per node", testArg.podsPerNode)
+ feature := "ManualPerformance"
if testArg.podsPerNode == 30 {
- name = "[Feature:Performance] " + name
- } else {
- name = "[Feature:ManualPerformance] " + name
+ feature = "Performance"
}
+ name := fmt.Sprintf("[Feature:%s] should be able to handle %v pods per node", feature, testArg.podsPerNode)
itArg := testArg
It(name, func() {
diff --git a/test/e2e/networking_perf.go b/test/e2e/networking_perf.go
index b351df449c5..a849200fc53 100644
--- a/test/e2e/networking_perf.go
+++ b/test/e2e/networking_perf.go
@@ -43,12 +43,10 @@ var _ = framework.KubeDescribe("Networking IPerf [Experimental] [Slow] [Feature:
// A few simple bandwidth tests which are capped by nodes.
// TODO replace the 1 with the scale option implementation
- runClientServerBandwidthMeasurement(f, 1, gceBandwidthBitsEstimate)
-})
-
-func runClientServerBandwidthMeasurement(f *framework.Framework, numClient int, maxBandwidthBits int64) {
// TODO: Make this a function parameter, once we distribute iperf endpoints, possibly via session affinity.
+ numClient := 1
numServer := 1
+ maxBandwidthBits := gceBandwidthBitsEstimate
It(fmt.Sprintf("should transfer ~ 1GB onto the service endpoint %v servers (maximum of %v clients)", numServer, numClient), func() {
nodes := framework.GetReadySchedulableNodesOrDie(f.Client)
@@ -153,4 +151,4 @@ func runClientServerBandwidthMeasurement(f *framework.Framework, numClient int,
framework.Logf("%v had bandwidth %v. Ratio to expected (%v) was %f", ipClient, bandwidth, expectedBandwidth, float64(bandwidth)/float64(expectedBandwidth))
}
})
-}
+})
diff --git a/test/e2e/proxy.go b/test/e2e/proxy.go
index d836d7dd013..5e397fa9a69 100644
--- a/test/e2e/proxy.go
+++ b/test/e2e/proxy.go
@@ -37,11 +37,6 @@ import (
. "github.com/onsi/gomega"
)
-var _ = framework.KubeDescribe("Proxy", func() {
- version := registered.GroupOrDie(api.GroupName).GroupVersion.Version
- Context("version "+version, func() { proxyContext(version) })
-})
-
const (
// Try all the proxy tests this many times (to catch even rare flakes).
proxyAttempts = 20
@@ -53,227 +48,230 @@ const (
proxyHTTPCallTimeout = 30 * time.Second
)
-func proxyContext(version string) {
- options := framework.FrameworkOptions{
- ClientQPS: -1.0,
- }
- f := framework.NewFramework("proxy", options, nil)
- prefix := "/api/" + version
+var _ = framework.KubeDescribe("Proxy", func() {
+ version := registered.GroupOrDie(api.GroupName).GroupVersion.Version
+ Context("version "+version, func() {
+ options := framework.FrameworkOptions{
+ ClientQPS: -1.0,
+ }
+ f := framework.NewFramework("proxy", options, nil)
+ prefix := "/api/" + version
- // Port here has to be kept in sync with default kubelet port.
- It("should proxy logs on node with explicit kubelet port [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":10250/logs/") })
- It("should proxy logs on node [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", "/logs/") })
- It("should proxy to cadvisor", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":4194/containers/") })
+ // Port here has to be kept in sync with default kubelet port.
+ It("should proxy logs on node with explicit kubelet port [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":10250/logs/") })
+ It("should proxy logs on node [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", "/logs/") })
+ It("should proxy to cadvisor", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":4194/containers/") })
- It("should proxy logs on node with explicit kubelet port using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", ":10250/proxy/logs/") })
- It("should proxy logs on node using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", "/proxy/logs/") })
- It("should proxy to cadvisor using proxy subresource", func() { nodeProxyTest(f, prefix+"/nodes/", ":4194/proxy/containers/") })
+ It("should proxy logs on node with explicit kubelet port using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", ":10250/proxy/logs/") })
+ It("should proxy logs on node using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", "/proxy/logs/") })
+ It("should proxy to cadvisor using proxy subresource", func() { nodeProxyTest(f, prefix+"/nodes/", ":4194/proxy/containers/") })
- // using the porter image to serve content, access the content
- // (of multiple pods?) from multiple (endpoints/services?)
- It("should proxy through a service and a pod [Conformance]", func() {
- start := time.Now()
- labels := map[string]string{"proxy-service-target": "true"}
- service, err := f.Client.Services(f.Namespace.Name).Create(&api.Service{
- ObjectMeta: api.ObjectMeta{
- GenerateName: "proxy-service-",
- },
- Spec: api.ServiceSpec{
- Selector: labels,
- Ports: []api.ServicePort{
- {
- Name: "portname1",
- Port: 80,
- TargetPort: intstr.FromString("dest1"),
- },
- {
- Name: "portname2",
- Port: 81,
- TargetPort: intstr.FromInt(162),
- },
- {
- Name: "tlsportname1",
- Port: 443,
- TargetPort: intstr.FromString("tlsdest1"),
- },
- {
- Name: "tlsportname2",
- Port: 444,
- TargetPort: intstr.FromInt(462),
+ // using the porter image to serve content, access the content
+ // (of multiple pods?) from multiple (endpoints/services?)
+ It("should proxy through a service and a pod [Conformance]", func() {
+ start := time.Now()
+ labels := map[string]string{"proxy-service-target": "true"}
+ service, err := f.Client.Services(f.Namespace.Name).Create(&api.Service{
+ ObjectMeta: api.ObjectMeta{
+ GenerateName: "proxy-service-",
+ },
+ Spec: api.ServiceSpec{
+ Selector: labels,
+ Ports: []api.ServicePort{
+ {
+ Name: "portname1",
+ Port: 80,
+ TargetPort: intstr.FromString("dest1"),
+ },
+ {
+ Name: "portname2",
+ Port: 81,
+ TargetPort: intstr.FromInt(162),
+ },
+ {
+ Name: "tlsportname1",
+ Port: 443,
+ TargetPort: intstr.FromString("tlsdest1"),
+ },
+ {
+ Name: "tlsportname2",
+ Port: 444,
+ TargetPort: intstr.FromInt(462),
+ },
},
},
- },
- })
- Expect(err).NotTo(HaveOccurred())
+ })
+ Expect(err).NotTo(HaveOccurred())
- // Make an RC with a single pod. The 'porter' image is
- // a simple server which serves the values of the
- // environmental variables below.
- By("starting an echo server on multiple ports")
- pods := []*api.Pod{}
- cfg := testutils.RCConfig{
- Client: f.Client,
- Image: "gcr.io/google_containers/porter:cd5cb5791ebaa8641955f0e8c2a9bed669b1eaab",
- Name: service.Name,
- Namespace: f.Namespace.Name,
- Replicas: 1,
- PollInterval: time.Second,
- Env: map[string]string{
- "SERVE_PORT_80": `test`,
- "SERVE_PORT_1080": `test`,
- "SERVE_PORT_160": "foo",
- "SERVE_PORT_162": "bar",
+ // Make an RC with a single pod. The 'porter' image is
+ // a simple server which serves the values of the
+ // environmental variables below.
+ By("starting an echo server on multiple ports")
+ pods := []*api.Pod{}
+ cfg := testutils.RCConfig{
+ Client: f.Client,
+ Image: "gcr.io/google_containers/porter:cd5cb5791ebaa8641955f0e8c2a9bed669b1eaab",
+ Name: service.Name,
+ Namespace: f.Namespace.Name,
+ Replicas: 1,
+ PollInterval: time.Second,
+ Env: map[string]string{
+ "SERVE_PORT_80": `test`,
+ "SERVE_PORT_1080": `test`,
+ "SERVE_PORT_160": "foo",
+ "SERVE_PORT_162": "bar",
- "SERVE_TLS_PORT_443": `test`,
- "SERVE_TLS_PORT_460": `tls baz`,
- "SERVE_TLS_PORT_462": `tls qux`,
- },
- Ports: map[string]int{
- "dest1": 160,
- "dest2": 162,
-
- "tlsdest1": 460,
- "tlsdest2": 462,
- },
- ReadinessProbe: &api.Probe{
- Handler: api.Handler{
- HTTPGet: &api.HTTPGetAction{
- Port: intstr.FromInt(80),
- },
+ "SERVE_TLS_PORT_443": `test`,
+ "SERVE_TLS_PORT_460": `tls baz`,
+ "SERVE_TLS_PORT_462": `tls qux`,
},
- InitialDelaySeconds: 1,
- TimeoutSeconds: 5,
- PeriodSeconds: 10,
- },
- Labels: labels,
- CreatedPods: &pods,
- }
- Expect(framework.RunRC(cfg)).NotTo(HaveOccurred())
- defer framework.DeleteRCAndPods(f.Client, f.ClientSet, f.Namespace.Name, cfg.Name)
+ Ports: map[string]int{
+ "dest1": 160,
+ "dest2": 162,
- Expect(f.WaitForAnEndpoint(service.Name)).NotTo(HaveOccurred())
+ "tlsdest1": 460,
+ "tlsdest2": 462,
+ },
+ ReadinessProbe: &api.Probe{
+ Handler: api.Handler{
+ HTTPGet: &api.HTTPGetAction{
+ Port: intstr.FromInt(80),
+ },
+ },
+ InitialDelaySeconds: 1,
+ TimeoutSeconds: 5,
+ PeriodSeconds: 10,
+ },
+ Labels: labels,
+ CreatedPods: &pods,
+ }
+ Expect(framework.RunRC(cfg)).NotTo(HaveOccurred())
+ defer framework.DeleteRCAndPods(f.Client, f.ClientSet, f.Namespace.Name, cfg.Name)
- // table constructors
- // Try proxying through the service and directly to through the pod.
- svcProxyURL := func(scheme, port string) string {
- return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/services/" + net.JoinSchemeNamePort(scheme, service.Name, port)
- }
- subresourceServiceProxyURL := func(scheme, port string) string {
- return prefix + "/namespaces/" + f.Namespace.Name + "/services/" + net.JoinSchemeNamePort(scheme, service.Name, port) + "/proxy"
- }
- podProxyURL := func(scheme, port string) string {
- return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/pods/" + net.JoinSchemeNamePort(scheme, pods[0].Name, port)
- }
- subresourcePodProxyURL := func(scheme, port string) string {
- return prefix + "/namespaces/" + f.Namespace.Name + "/pods/" + net.JoinSchemeNamePort(scheme, pods[0].Name, port) + "/proxy"
- }
+ Expect(f.WaitForAnEndpoint(service.Name)).NotTo(HaveOccurred())
- // construct the table
- expectations := map[string]string{
- svcProxyURL("", "portname1") + "/": "foo",
- svcProxyURL("", "80") + "/": "foo",
- svcProxyURL("", "portname2") + "/": "bar",
- svcProxyURL("", "81") + "/": "bar",
+ // table constructors
+ // Try proxying through the service and directly to through the pod.
+ svcProxyURL := func(scheme, port string) string {
+ return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/services/" + net.JoinSchemeNamePort(scheme, service.Name, port)
+ }
+ subresourceServiceProxyURL := func(scheme, port string) string {
+ return prefix + "/namespaces/" + f.Namespace.Name + "/services/" + net.JoinSchemeNamePort(scheme, service.Name, port) + "/proxy"
+ }
+ podProxyURL := func(scheme, port string) string {
+ return prefix + "/proxy/namespaces/" + f.Namespace.Name + "/pods/" + net.JoinSchemeNamePort(scheme, pods[0].Name, port)
+ }
+ subresourcePodProxyURL := func(scheme, port string) string {
+ return prefix + "/namespaces/" + f.Namespace.Name + "/pods/" + net.JoinSchemeNamePort(scheme, pods[0].Name, port) + "/proxy"
+ }
- svcProxyURL("http", "portname1") + "/": "foo",
- svcProxyURL("http", "80") + "/": "foo",
- svcProxyURL("http", "portname2") + "/": "bar",
- svcProxyURL("http", "81") + "/": "bar",
+ // construct the table
+ expectations := map[string]string{
+ svcProxyURL("", "portname1") + "/": "foo",
+ svcProxyURL("", "80") + "/": "foo",
+ svcProxyURL("", "portname2") + "/": "bar",
+ svcProxyURL("", "81") + "/": "bar",
- svcProxyURL("https", "tlsportname1") + "/": "tls baz",
- svcProxyURL("https", "443") + "/": "tls baz",
- svcProxyURL("https", "tlsportname2") + "/": "tls qux",
- svcProxyURL("https", "444") + "/": "tls qux",
+ svcProxyURL("http", "portname1") + "/": "foo",
+ svcProxyURL("http", "80") + "/": "foo",
+ svcProxyURL("http", "portname2") + "/": "bar",
+ svcProxyURL("http", "81") + "/": "bar",
- subresourceServiceProxyURL("", "portname1") + "/": "foo",
- subresourceServiceProxyURL("http", "portname1") + "/": "foo",
- subresourceServiceProxyURL("", "portname2") + "/": "bar",
- subresourceServiceProxyURL("http", "portname2") + "/": "bar",
- subresourceServiceProxyURL("https", "tlsportname1") + "/": "tls baz",
- subresourceServiceProxyURL("https", "tlsportname2") + "/": "tls qux",
+ svcProxyURL("https", "tlsportname1") + "/": "tls baz",
+ svcProxyURL("https", "443") + "/": "tls baz",
+ svcProxyURL("https", "tlsportname2") + "/": "tls qux",
+ svcProxyURL("https", "444") + "/": "tls qux",
- podProxyURL("", "1080") + "/": `test`,
- podProxyURL("", "160") + "/": "foo",
- podProxyURL("", "162") + "/": "bar",
+ subresourceServiceProxyURL("", "portname1") + "/": "foo",
+ subresourceServiceProxyURL("http", "portname1") + "/": "foo",
+ subresourceServiceProxyURL("", "portname2") + "/": "bar",
+ subresourceServiceProxyURL("http", "portname2") + "/": "bar",
+ subresourceServiceProxyURL("https", "tlsportname1") + "/": "tls baz",
+ subresourceServiceProxyURL("https", "tlsportname2") + "/": "tls qux",
- podProxyURL("http", "1080") + "/": `test`,
- podProxyURL("http", "160") + "/": "foo",
- podProxyURL("http", "162") + "/": "bar",
+ podProxyURL("", "1080") + "/": `test`,
+ podProxyURL("", "160") + "/": "foo",
+ podProxyURL("", "162") + "/": "bar",
- subresourcePodProxyURL("", "") + "/": `test`,
- subresourcePodProxyURL("", "1080") + "/": `test`,
- subresourcePodProxyURL("http", "1080") + "/": `test`,
- subresourcePodProxyURL("", "160") + "/": "foo",
- subresourcePodProxyURL("http", "160") + "/": "foo",
- subresourcePodProxyURL("", "162") + "/": "bar",
- subresourcePodProxyURL("http", "162") + "/": "bar",
+ podProxyURL("http", "1080") + "/": `test`,
+ podProxyURL("http", "160") + "/": "foo",
+ podProxyURL("http", "162") + "/": "bar",
- subresourcePodProxyURL("https", "443") + "/": `test`,
- subresourcePodProxyURL("https", "460") + "/": "tls baz",
- subresourcePodProxyURL("https", "462") + "/": "tls qux",
+ subresourcePodProxyURL("", "") + "/": `test`,
+ subresourcePodProxyURL("", "1080") + "/": `test`,
+ subresourcePodProxyURL("http", "1080") + "/": `test`,
+ subresourcePodProxyURL("", "160") + "/": "foo",
+ subresourcePodProxyURL("http", "160") + "/": "foo",
+ subresourcePodProxyURL("", "162") + "/": "bar",
+ subresourcePodProxyURL("http", "162") + "/": "bar",
- // TODO: below entries don't work, but I believe we should make them work.
- // podPrefix + ":dest1": "foo",
- // podPrefix + ":dest2": "bar",
- }
+ subresourcePodProxyURL("https", "443") + "/": `test`,
+ subresourcePodProxyURL("https", "460") + "/": "tls baz",
+ subresourcePodProxyURL("https", "462") + "/": "tls qux",
- wg := sync.WaitGroup{}
- errs := []string{}
- errLock := sync.Mutex{}
- recordError := func(s string) {
- errLock.Lock()
- defer errLock.Unlock()
- errs = append(errs, s)
- }
- d := time.Since(start)
- framework.Logf("setup took %v, starting test cases", d)
- numberTestCases := len(expectations)
- totalAttempts := numberTestCases * proxyAttempts
- By(fmt.Sprintf("running %v cases, %v attempts per case, %v total attempts", numberTestCases, proxyAttempts, totalAttempts))
+ // TODO: below entries don't work, but I believe we should make them work.
+ // podPrefix + ":dest1": "foo",
+ // podPrefix + ":dest2": "bar",
+ }
- for i := 0; i < proxyAttempts; i++ {
- wg.Add(numberTestCases)
- for path, val := range expectations {
- go func(i int, path, val string) {
- defer wg.Done()
- // this runs the test case
- body, status, d, err := doProxy(f, path, i)
+ wg := sync.WaitGroup{}
+ errs := []string{}
+ errLock := sync.Mutex{}
+ recordError := func(s string) {
+ errLock.Lock()
+ defer errLock.Unlock()
+ errs = append(errs, s)
+ }
+ d := time.Since(start)
+ framework.Logf("setup took %v, starting test cases", d)
+ numberTestCases := len(expectations)
+ totalAttempts := numberTestCases * proxyAttempts
+ By(fmt.Sprintf("running %v cases, %v attempts per case, %v total attempts", numberTestCases, proxyAttempts, totalAttempts))
- if err != nil {
- if serr, ok := err.(*errors.StatusError); ok {
- recordError(fmt.Sprintf("%v (%v; %v): path %v gave status error: %+v",
- i, status, d, path, serr.Status()))
- } else {
- recordError(fmt.Sprintf("%v: path %v gave error: %v", i, path, err))
+ for i := 0; i < proxyAttempts; i++ {
+ wg.Add(numberTestCases)
+ for path, val := range expectations {
+ go func(i int, path, val string) {
+ defer wg.Done()
+ // this runs the test case
+ body, status, d, err := doProxy(f, path, i)
+
+ if err != nil {
+ if serr, ok := err.(*errors.StatusError); ok {
+ recordError(fmt.Sprintf("%v (%v; %v): path %v gave status error: %+v",
+ i, status, d, path, serr.Status()))
+ } else {
+ recordError(fmt.Sprintf("%v: path %v gave error: %v", i, path, err))
+ }
+ return
}
- return
- }
- if status != http.StatusOK {
- recordError(fmt.Sprintf("%v: path %v gave status: %v", i, path, status))
- }
- if e, a := val, string(body); e != a {
- recordError(fmt.Sprintf("%v: path %v: wanted %v, got %v", i, path, e, a))
- }
- if d > proxyHTTPCallTimeout {
- recordError(fmt.Sprintf("%v: path %v took %v > %v", i, path, d, proxyHTTPCallTimeout))
- }
- }(i, path, val)
- }
- wg.Wait()
- }
-
- if len(errs) != 0 {
- body, err := f.Client.Pods(f.Namespace.Name).GetLogs(pods[0].Name, &api.PodLogOptions{}).Do().Raw()
- if err != nil {
- framework.Logf("Error getting logs for pod %s: %v", pods[0].Name, err)
- } else {
- framework.Logf("Pod %s has the following error logs: %s", pods[0].Name, body)
+ if status != http.StatusOK {
+ recordError(fmt.Sprintf("%v: path %v gave status: %v", i, path, status))
+ }
+ if e, a := val, string(body); e != a {
+ recordError(fmt.Sprintf("%v: path %v: wanted %v, got %v", i, path, e, a))
+ }
+ if d > proxyHTTPCallTimeout {
+ recordError(fmt.Sprintf("%v: path %v took %v > %v", i, path, d, proxyHTTPCallTimeout))
+ }
+ }(i, path, val)
+ }
+ wg.Wait()
}
- Fail(strings.Join(errs, "\n"))
- }
+ if len(errs) != 0 {
+ body, err := f.Client.Pods(f.Namespace.Name).GetLogs(pods[0].Name, &api.PodLogOptions{}).Do().Raw()
+ if err != nil {
+ framework.Logf("Error getting logs for pod %s: %v", pods[0].Name, err)
+ } else {
+ framework.Logf("Pod %s has the following error logs: %s", pods[0].Name, body)
+ }
+
+ Fail(strings.Join(errs, "\n"))
+ }
+ })
})
-}
+})
func doProxy(f *framework.Framework, path string, i int) (body []byte, statusCode int, d time.Duration, err error) {
// About all of the proxy accesses in this file:
diff --git a/test/list/main.go b/test/list/main.go
new file mode 100644
index 00000000000..3bb421ac49d
--- /dev/null
+++ b/test/list/main.go
@@ -0,0 +1,275 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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.
+*/
+
+// list all unit and ginkgo test names that will be run
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+var (
+ dumpTree = flag.Bool("dump", false, "print AST")
+ dumpJson = flag.Bool("json", false, "output test list as JSON")
+ warn = flag.Bool("warn", false, "print warnings")
+)
+
+type Test struct {
+ Loc string
+ Name string
+ TestName string
+}
+
+// collect extracts test metadata from a file.
+// If src is nil, it reads filename for the code, otherwise it
+// uses src (which may be a string, byte[], or io.Reader).
+func collect(filename string, src interface{}) []Test {
+ // Create the AST by parsing src.
+ fset := token.NewFileSet() // positions are relative to fset
+ f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+
+ if *dumpTree {
+ ast.Print(fset, f)
+ }
+
+ tests := make([]Test, 0)
+
+ ast.Walk(makeWalker("[k8s.io]", fset, &tests), f)
+
+ // Unit tests are much simpler to enumerate!
+ if strings.HasSuffix(filename, "_test.go") {
+ packageName := f.Name.Name
+ dirName, _ := filepath.Split(filename)
+ if filepath.Base(dirName) != packageName && *warn {
+ log.Printf("Warning: strange path/package mismatch %s %s\n", filename, packageName)
+ }
+ testPath := "k8s.io/kubernetes/" + dirName[:len(dirName)-1]
+ for _, decl := range f.Decls {
+ funcdecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ name := funcdecl.Name.Name
+ if strings.HasPrefix(name, "Test") {
+ tests = append(tests, Test{fset.Position(funcdecl.Pos()).String(), testPath, name})
+ }
+ }
+ }
+
+ return tests
+}
+
+// funcName converts a selectorExpr with two idents into a string,
+// x.y -> "x.y"
+func funcName(n ast.Expr) string {
+ if sel, ok := n.(*ast.SelectorExpr); ok {
+ if x, ok := sel.X.(*ast.Ident); ok {
+ return x.String() + "." + sel.Sel.String()
+ }
+ }
+ return ""
+}
+
+// isSprintf returns whether the given node is a call to fmt.Sprintf
+func isSprintf(n ast.Expr) bool {
+ call, ok := n.(*ast.CallExpr)
+ return ok && funcName(call.Fun) == "fmt.Sprintf" && len(call.Args) != 0
+}
+
+type walker struct {
+ path string
+ fset *token.FileSet
+ tests *[]Test
+ vals map[string]string
+}
+
+func makeWalker(path string, fset *token.FileSet, tests *[]Test) *walker {
+ return &walker{path, fset, tests, make(map[string]string)}
+}
+
+// clone creates a new walker with the given string extending the path.
+func (w *walker) clone(ext string) *walker {
+ return &walker{w.path + " " + ext, w.fset, w.tests, w.vals}
+}
+
+// firstArg attempts to statically determine the value of the first
+// argument. It only handles strings, and converts any unknown values
+// (fmt.Sprintf interpolations) into *.
+func (w *walker) firstArg(n *ast.CallExpr) string {
+ if len(n.Args) == 0 {
+ return ""
+ }
+ var lit *ast.BasicLit
+ if isSprintf(n.Args[0]) {
+ return w.firstArg(n.Args[0].(*ast.CallExpr))
+ }
+ lit, ok := n.Args[0].(*ast.BasicLit)
+ if ok && lit.Kind == token.STRING {
+ v, err := strconv.Unquote(lit.Value)
+ if err != nil {
+ panic(err)
+ }
+ if strings.Contains(v, "%") {
+ v = strings.Replace(v, "%d", "*", -1)
+ v = strings.Replace(v, "%v", "*", -1)
+ v = strings.Replace(v, "%s", "*", -1)
+ }
+ return v
+ }
+ if ident, ok := n.Args[0].(*ast.Ident); ok {
+ if val, ok := w.vals[ident.String()]; ok {
+ return val
+ }
+ }
+ if *warn {
+ log.Printf("Warning: dynamic arg value: %v\n", w.fset.Position(n.Args[0].Pos()))
+ }
+ return "*"
+}
+
+// describeName returns the first argument of a function if it's
+// a Ginkgo-relevant function (Describe/KubeDescribe/Context),
+// and the empty string otherwise.
+func (w *walker) describeName(n *ast.CallExpr) string {
+ switch x := n.Fun.(type) {
+ case *ast.SelectorExpr:
+ if x.Sel.Name != "KubeDescribe" {
+ return ""
+ }
+ case *ast.Ident:
+ if x.Name != "Describe" && x.Name != "Context" {
+ return ""
+ }
+ default:
+ return ""
+ }
+ return w.firstArg(n)
+}
+
+// itName returns the first argument if it's a call to It(), else "".
+func (w *walker) itName(n *ast.CallExpr) string {
+ if fun, ok := n.Fun.(*ast.Ident); ok && fun.Name == "It" {
+ return w.firstArg(n)
+ }
+ return ""
+}
+
+// Visit walks the AST, following Ginkgo context and collecting tests.
+// See the documentation for ast.Walk for more details.
+func (w *walker) Visit(n ast.Node) ast.Visitor {
+ switch x := n.(type) {
+ case *ast.CallExpr:
+ name := w.describeName(x)
+ if name != "" && len(x.Args) >= 2 {
+ // If calling (Kube)Describe/Context, make a new
+ // walker to recurse with the description added.
+ return w.clone(name)
+ }
+ name = w.itName(x)
+ if name != "" {
+ // We've found an It() call, the full test name
+ // can be determined now.
+ if w.path == "[k8s.io]" && *warn {
+ log.Printf("It without matching Describe: %s\n", w.fset.Position(n.Pos()))
+ }
+ *w.tests = append(*w.tests, Test{w.fset.Position(n.Pos()).String(), w.path, name})
+ return nil // Stop walking
+ }
+ case *ast.AssignStmt:
+ // Attempt to track literals that might be used as
+ // arguments. This analysis is very unsound, and ignores
+ // both scope and program flow, but is sufficient for
+ // our minor use case.
+ ident, ok := x.Lhs[0].(*ast.Ident)
+ if ok {
+ if isSprintf(x.Rhs[0]) {
+ // x := fmt.Sprintf("something", args)
+ w.vals[ident.String()] = w.firstArg(x.Rhs[0].(*ast.CallExpr))
+ }
+ if lit, ok := x.Rhs[0].(*ast.BasicLit); ok && lit.Kind == token.STRING {
+ // x := "a literal string"
+ v, err := strconv.Unquote(lit.Value)
+ if err != nil {
+ panic(err)
+ }
+ w.vals[ident.String()] = v
+ }
+ }
+ }
+ return w // Continue walking
+}
+
+type testList struct {
+ tests []Test
+}
+
+// handlePath walks the filesystem recursively, collecting tests
+// from files with paths *e2e*.go and *_test.go, ignoring third_party
+// and staging directories.
+func (t *testList) handlePath(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if strings.Contains(path, "third_party") ||
+ strings.Contains(path, "staging") {
+ return filepath.SkipDir
+ }
+ if strings.HasSuffix(path, ".go") && strings.Contains(path, "e2e") ||
+ strings.HasSuffix(path, "_test.go") {
+ tests := collect(path, nil)
+ t.tests = append(t.tests, tests...)
+ }
+ return nil
+}
+
+func main() {
+ flag.Parse()
+ args := flag.Args()
+ if len(args) == 0 {
+ args = append(args, ".")
+ }
+ tests := testList{}
+ for _, arg := range args {
+ err := filepath.Walk(arg, tests.handlePath)
+ if err != nil {
+ log.Fatalf("Error walking: %v", err)
+ }
+ }
+ if *dumpJson {
+ json, err := json.Marshal(tests.tests)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(string(json))
+ } else {
+ for _, t := range tests.tests {
+ fmt.Println(t)
+ }
+ }
+}
diff --git a/test/list/main_test.go b/test/list/main_test.go
new file mode 100644
index 00000000000..4474f4ac6d7
--- /dev/null
+++ b/test/list/main_test.go
@@ -0,0 +1,111 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+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 (
+ "errors"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+var collectCases = []struct {
+ filename string
+ code string
+ output []Test
+}{
+ // Empty: no tests
+ {"e2e/util_test.go", "", []Test{}},
+ // Go unit test
+ {"test/list/main_test.go", `
+var num = 3
+func Helper(x int) { return x / 0 }
+func TestStuff(t *Testing.T) {
+}`, []Test{{"test/list/main_test.go:5:1", "k8s.io/kubernetes/test/list", "TestStuff"}},
+ },
+ // Describe + It
+ {"e2e/foo.go", `
+var _ = Describe("Feature", func() {
+ It("should work properly", func() {})
+})`, []Test{{"e2e/foo.go:4:2", "[k8s.io] Feature", "should work properly"}},
+ },
+ // KubeDescribe + It
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ It("should work properly", func() {})
+})`, []Test{{"e2e/foo.go:4:2", "[k8s.io] Feature", "should work properly"}},
+ },
+ // KubeDescribe + Context + It
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ Context("when offline", func() {
+ It("should work", func() {})
+ })
+})`, []Test{{"e2e/foo.go:5:3", "[k8s.io] Feature when offline", "should work"}},
+ },
+ // KubeDescribe + It(Sprintf)
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ It(fmt.Sprintf("handles %d nodes", num), func() {})
+})`, []Test{{"e2e/foo.go:4:2", "[k8s.io] Feature", "handles * nodes"}},
+ },
+ // KubeDescribe + Sprintf + It(var)
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ arg := fmt.Sprintf("does %s and %v at %d", task, desc, num)
+ It(arg, func() {})
+})`, []Test{{"e2e/foo.go:5:2", "[k8s.io] Feature", "does * and * at *"}},
+ },
+ // KubeDescribe + string + It(var)
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ arg := "does stuff"
+ It(arg, func() {})
+})`, []Test{{"e2e/foo.go:5:2", "[k8s.io] Feature", "does stuff"}},
+ },
+ // KubeDescribe + It(unknown)
+ {"e2e/foo.go", `
+var _ = framework.KubeDescribe("Feature", func() {
+ It(mysteryFunc(), func() {})
+})`, []Test{{"e2e/foo.go:4:2", "[k8s.io] Feature", "*"}},
+ },
+}
+
+func TestCollect(t *testing.T) {
+ for _, test := range collectCases {
+ code := "package test\n" + test.code
+ tests := collect(test.filename, code)
+ if !reflect.DeepEqual(tests, test.output) {
+ t.Errorf("code:\n%s\ngot %v\nwant %v",
+ code, tests, test.output)
+ }
+ }
+}
+
+func TestHandlePath(t *testing.T) {
+ tl := testList{}
+ e := errors.New("ex")
+ if tl.handlePath("foo", nil, e) != e {
+ t.Error("handlePath not returning errors")
+ }
+ if tl.handlePath("foo.txt", nil, nil) != nil {
+ t.Error("should skip random files")
+ }
+ if tl.handlePath("third_party/a_test.go", nil, nil) != filepath.SkipDir {
+ t.Error("should skip third_party")
+ }
+}