github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/daemon/command_counter_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon_test
    21  
    22  import (
    23  	"fmt"
    24  	"go/ast"
    25  	"go/parser"
    26  	"go/token"
    27  	"io/ioutil"
    28  	"path/filepath"
    29  	"strings"
    30  
    31  	"gopkg.in/check.v1"
    32  )
    33  
    34  func countCommandDecls(c *check.C, comment check.CommentInterface) int {
    35  	n := 0
    36  	fns, _ := filepath.Glob("*.go")
    37  	for _, fn := range fns {
    38  		if !strings.HasSuffix(fn, "_test.go") {
    39  			n += countCommandDeclsIn(c, fn, comment)
    40  		}
    41  	}
    42  	return n
    43  }
    44  
    45  func countCommandDeclsIn(c *check.C, filename string, comment check.CommentInterface) int {
    46  	// NOTE: there's probably a
    47  	// better/easier way of doing this (patches welcome)
    48  	//
    49  	// Another note: the code below will find any and all variable
    50  	// declaration that have a &Command{} on the right hand. This
    51  	// is what we're currently using to declare the handlers that
    52  	// fill the api list; it also counts Command{}, and
    53  	// multi-variable (var foo, ... = Command{}, ...) just to
    54  	// future-proof it a little bit, but it's still supposed to be
    55  	// very restrictive. In particular I can think of different
    56  	// ways of doing things that won't be counted by the code
    57  	// below, i.e. the code below can still give false positives
    58  	// by counting too few command instances, e.g. if they're
    59  	// added directly to the api list, or if they're declared in a
    60  	// function or secondary slice or ... but as it stands I can't
    61  	// think of a way for it to give false negatives.
    62  	fset := token.NewFileSet()
    63  	f, err := parser.ParseFile(fset, filename, nil, 0)
    64  	c.Assert(err, check.IsNil, comment)
    65  
    66  	found := 0
    67  
    68  	ast.Inspect(f, func(n ast.Node) bool {
    69  		var vs *ast.ValueSpec
    70  		switch n := n.(type) {
    71  		case *ast.ValueSpec:
    72  			// a ValueSpec is a constant or variable
    73  			// child of GenDecl
    74  			vs = n
    75  		case *ast.File:
    76  			// yes we want to recurse into the file
    77  			return true
    78  		case *ast.GenDecl:
    79  			// and we recurse into the toplevel GenDecls
    80  			// (note a GenDecl can't contain a GenDecl)
    81  			return true
    82  		default:
    83  			// don't recurse into anything else
    84  			return false
    85  		}
    86  		// foo, bar = Command{}, Command{} -> two v.Values
    87  		for i, v := range vs.Values {
    88  			// note we loop over values, so empty declarations aren't counted
    89  			if vs.Names[i].Name == "_" {
    90  				// don't count "var _ = &Command{}"
    91  				continue
    92  			}
    93  			// a Command{} is a composite literal; check for that
    94  			x, ok := v.(*ast.CompositeLit)
    95  			if !ok {
    96  				// it might be a &Command{} instead
    97  				// the & in &foo{} is an unary expression
    98  				y, ok := v.(*ast.UnaryExpr)
    99  				// (and yes the & in &foo{} is token.AND)
   100  				if !ok || y.Op != token.AND {
   101  					continue
   102  				}
   103  				// again check for Command{} (composite literal)
   104  				x, ok = y.X.(*ast.CompositeLit)
   105  				if !ok {
   106  					continue
   107  				}
   108  			}
   109  			// ok, x is a composite literal, ie foo{}.
   110  			// the foo in foo{} is an Ident
   111  			z, ok := x.Type.(*ast.Ident)
   112  			if !ok {
   113  				continue
   114  			}
   115  			if z.Name == "Command" {
   116  				// gotcha!
   117  				found++
   118  			}
   119  		}
   120  		return false
   121  	})
   122  
   123  	return found
   124  }
   125  
   126  type cmdCounterSuite struct{}
   127  
   128  var _ = check.Suite(&cmdCounterSuite{})
   129  
   130  type commandDeclCounterTableT struct {
   131  	desc    string
   132  	count   int
   133  	content string
   134  }
   135  
   136  var commandDeclCounterTable = []commandDeclCounterTableT{
   137  	{"counts top-level vars", 4, `
   138  var won, too = &Command{}, Command{}
   139  var tri = &Command{}
   140  var foh = Command{}
   141  `},
   142  	{"count top-level vars in groups", 4, `
   143  var (
   144      won, too = &Command{}, Command{}
   145      tri = &Command{}
   146      foh = Command{}
   147  )
   148  `},
   149  	{"does *not* count these (should it?)", 0, `
   150  var wonP, tooP *Command
   151  var wonD, tooD Command
   152  var triP *Command
   153  var triD Command
   154  `},
   155  	{"not in groups either", 0, `
   156  var (
   157      wonP, tooP *Command
   158      wonD, tooD Command
   159      triP *Command
   160      triD Command
   161  )
   162  `},
   163  
   164  	{"does not count empty decls", 0, `
   165  var _, _ = &Command{}, Command{}
   166  var _ = &Command{}
   167  var _ = Command{}
   168  `},
   169  	{"does not count empty decls in groups", 0, `
   170  var (
   171      _, _ = &Command{}, Command{}
   172      _ = &Command{}
   173      _ = Command{}
   174  )
   175  `},
   176  	{"does not count things built in functions", 0, `
   177  func won() *Command {
   178      return &Command{}
   179  }
   180  func too() *Command {
   181      var x = &Command{}
   182      return x
   183  }
   184  func tri() Command {
   185      return Command{}
   186  }
   187  func foh() Command {
   188      var x = Command{}
   189      return x
   190  }
   191  `},
   192  	{"does not count things built in lists", 0, `
   193  var won = []Command{{}, {}, {}}
   194  var too = []Command{Command{}, Command{}}
   195  var tri = []*Command{nil, nil, nil}
   196  var foh = []*Command{{}, {}, {}}
   197  var fai = []*Command{&Command{}, &Command{}}
   198  `},
   199  	{"does not count things built in lists in groups", 0, `
   200  var (
   201      won = []Command{{}, {}, {}}
   202      too = []Command{Command{}, Command{}}
   203      tri = []*Command{nil, nil, nil}
   204      foh = []*Command{{}, {}, {}}
   205      fai = []*Command{&Command{}, &Command{}}
   206  )
   207  `},
   208  }
   209  
   210  func (cmdCounterSuite) TestCommandDeclCounter(c *check.C) {
   211  	d := c.MkDir()
   212  
   213  	for i, t := range commandDeclCounterTable {
   214  		fn := filepath.Join(d, fmt.Sprintf("a_%02d.go", i))
   215  		comm := check.Commentf(t.desc)
   216  		c.Assert(ioutil.WriteFile(fn, []byte("package huh"+t.content), 0644), check.IsNil, comm)
   217  		n := countCommandDeclsIn(c, fn, comm)
   218  		c.Check(n, check.Equals, t.count, comm)
   219  	}
   220  }