123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- /*
- 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")
- )
- // Test holds test locations, package names, and test names.
- 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") ||
- strings.Contains(path, "_output") {
- 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)
- }
- }
- }
|