github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 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 }