github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/gunit/cmd/global.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  
    22  	"github.com/palantir/amalgomate/amalgomated"
    23  	"github.com/palantir/pkg/cli"
    24  	"github.com/palantir/pkg/cli/flag"
    25  	"github.com/palantir/pkg/matcher"
    26  	"github.com/palantir/pkg/pkgpath"
    27  	"github.com/pkg/errors"
    28  
    29  	"github.com/palantir/godel/apps/gunit/generated_src"
    30  	"github.com/palantir/godel/apps/gunit/params"
    31  )
    32  
    33  var Library = amalgomated.NewCmdLibrary(amalgomatedtesters.Instance())
    34  
    35  const (
    36  	junitOutputPathFlagName = "junit-output"
    37  	raceFlagName            = "race"
    38  	tagsFlagName            = "tags"
    39  	verboseFlagName         = "verbose"
    40  	verboseFlagAlias        = "v"
    41  )
    42  
    43  var (
    44  	GlobalFlags = []flag.Flag{
    45  		flag.StringFlag{
    46  			Name:  tagsFlagName,
    47  			Usage: "Run tests that are part of the provided tags (use commas to separate multiple tags)",
    48  		},
    49  		flag.BoolFlag{
    50  			Name:  verboseFlagName,
    51  			Alias: verboseFlagAlias,
    52  			Usage: "Enable verbose output for tests",
    53  		},
    54  		flag.BoolFlag{
    55  			Name:  raceFlagName,
    56  			Usage: "Enable race detector for tests",
    57  		},
    58  		flag.StringFlag{
    59  			Name:  junitOutputPathFlagName,
    60  			Usage: "Path to JUnit XML output (if provided, verbose flag is set to true)",
    61  		},
    62  	}
    63  )
    64  
    65  func Tags(ctx cli.Context) []string {
    66  	if !ctx.Has(tagsFlagName) {
    67  		return nil
    68  	}
    69  	return strings.Split(strings.ToLower(ctx.String(tagsFlagName)), ",")
    70  }
    71  
    72  func Verbose(ctx cli.Context) bool {
    73  	return ctx.Bool(verboseFlagName)
    74  }
    75  
    76  func Race(ctx cli.Context) bool {
    77  	return ctx.Bool(raceFlagName)
    78  }
    79  
    80  func JUnitOutputPath(ctx cli.Context) string {
    81  	return ctx.String(junitOutputPathFlagName)
    82  }
    83  
    84  // TagsMatcher returns a Matcher that matches all packages that are matched by the provided tags. If no tags are
    85  // provided, returns nil. If the tags consist of a single tag named "all", the returned matcher matches the union of all
    86  // known tags. If the tags consist of a single tag named "none", the returned matcher matches everything except the
    87  // union of all known tags (untagged tests).
    88  func TagsMatcher(tags []string, cfg params.GUnit) (matcher.Matcher, error) {
    89  	if len(tags) == 0 {
    90  		// if no tags were provided, does not match anything
    91  		return nil, nil
    92  	}
    93  
    94  	if len(tags) == 1 {
    95  		var allMatchers []matcher.Matcher
    96  		for _, matcher := range cfg.Tags {
    97  			allMatchers = append(allMatchers, matcher)
    98  		}
    99  		anyTagMatcher := matcher.Any(allMatchers...)
   100  		switch tags[0] {
   101  		case params.AllTagName:
   102  			// if tags contains only a single tag that is the "all" tag, return matcher that matches union of all tags
   103  			return anyTagMatcher, nil
   104  		case params.NoneTagName:
   105  			// if tags contains only a single tag that is the "none" tag, return matcher that matches not of union of all tags
   106  			return matcher.Not(anyTagMatcher), nil
   107  		}
   108  	}
   109  
   110  	// due to previous check, if "all" or "none" tag exists at this point it means that it was one of multiple tags
   111  	for _, tag := range tags {
   112  		switch tag {
   113  		case params.AllTagName, params.NoneTagName:
   114  			return nil, errors.Errorf("if %q tag is specified, it must be the only tag specified", tag)
   115  		}
   116  	}
   117  
   118  	var tagMatchers []matcher.Matcher
   119  	var missingTags []string
   120  	for _, tag := range tags {
   121  		if include, ok := cfg.Tags[tag]; ok {
   122  			tagMatchers = append(tagMatchers, include)
   123  		} else {
   124  			missingTags = append(missingTags, fmt.Sprintf("%q", tag))
   125  		}
   126  	}
   127  
   128  	if len(missingTags) > 0 {
   129  		var allTags []string
   130  		for tag := range cfg.Tags {
   131  			allTags = append(allTags, fmt.Sprintf("%q", tag))
   132  		}
   133  		sort.Strings(allTags)
   134  		validTagsOutput := fmt.Sprintf("Valid tags: %v", strings.Join(allTags, ", "))
   135  		if len(allTags) == 0 {
   136  			validTagsOutput = "No tags are defined."
   137  		}
   138  		return nil, fmt.Errorf("Tags %v not defined in configuration. %s", strings.Join(missingTags, ", "), validTagsOutput)
   139  	}
   140  
   141  	// not possible: if initial tags were empty then should have already returned, if specified tags did not match then
   142  	// missing block should have executed and returned, so at this point matchers must exist
   143  	if len(tagMatchers) == 0 {
   144  		panic("no matching tags found")
   145  	}
   146  
   147  	// OR of tags
   148  	return matcher.Any(tagMatchers...), nil
   149  }
   150  
   151  // PkgPaths returns a slice that contains the relative package paths for the packages "pkgPaths" relative to the
   152  // project directory "wd" excluding any of the paths that match the provided "exclude" Matcher. If "pkgPaths" is an
   153  // empty slice, then all of the packages in "wd" (except those that match the "exclude" matcher) are returned.
   154  func PkgPaths(pkgPaths []string, wd string, exclude matcher.Matcher) ([]string, error) {
   155  	var pkgs pkgpath.Packages
   156  	var err error
   157  	if len(pkgPaths) == 0 {
   158  		// if input slice is empty, return all matching packages
   159  		pkgs, err = pkgpath.PackagesInDir(wd, exclude)
   160  		if err != nil {
   161  			return nil, errors.Wrapf(err, "failed to list packages in %s", wd)
   162  		}
   163  	} else {
   164  		// otherwise, filter provided packages and return those that are not excluded
   165  		var nonExcludedPkgs []string
   166  		for _, currPkg := range pkgPaths {
   167  			if !exclude.Match(currPkg) {
   168  				nonExcludedPkgs = append(nonExcludedPkgs, currPkg)
   169  			}
   170  		}
   171  		pkgs, err = pkgpath.PackagesFromPaths(wd, nonExcludedPkgs)
   172  		if err != nil {
   173  			return nil, errors.Wrapf(err, "failed to parse %v as packages", pkgPaths)
   174  		}
   175  	}
   176  	resultPkgPaths, err := pkgs.Paths(pkgpath.Relative)
   177  	if err != nil {
   178  		return nil, errors.Wrapf(err, "failed to get relative paths for packages %v", pkgs)
   179  	}
   180  	return resultPkgPaths, nil
   181  }