1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package main_test 6 7import ( 8 "cmd/internal/cov/covcmd" 9 "encoding/json" 10 "fmt" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "strings" 15 "testing" 16) 17 18func writeFile(t *testing.T, path string, contents []byte) { 19 if err := os.WriteFile(path, contents, 0666); err != nil { 20 t.Fatalf("os.WriteFile(%s) failed: %v", path, err) 21 } 22} 23 24func writePkgConfig(t *testing.T, outdir, tag, ppath, pname string, gran string, mpath string) string { 25 incfg := filepath.Join(outdir, tag+"incfg.txt") 26 outcfg := filepath.Join(outdir, "outcfg.txt") 27 p := covcmd.CoverPkgConfig{ 28 PkgPath: ppath, 29 PkgName: pname, 30 Granularity: gran, 31 OutConfig: outcfg, 32 EmitMetaFile: mpath, 33 } 34 data, err := json.Marshal(p) 35 if err != nil { 36 t.Fatalf("json.Marshal failed: %v", err) 37 } 38 writeFile(t, incfg, data) 39 return incfg 40} 41 42func writeOutFileList(t *testing.T, infiles []string, outdir, tag string) ([]string, string) { 43 outfilelist := filepath.Join(outdir, tag+"outfilelist.txt") 44 var sb strings.Builder 45 cv := filepath.Join(outdir, "covervars.go") 46 outfs := []string{cv} 47 fmt.Fprintf(&sb, "%s\n", cv) 48 for _, inf := range infiles { 49 base := filepath.Base(inf) 50 of := filepath.Join(outdir, tag+".cov."+base) 51 outfs = append(outfs, of) 52 fmt.Fprintf(&sb, "%s\n", of) 53 } 54 if err := os.WriteFile(outfilelist, []byte(sb.String()), 0666); err != nil { 55 t.Fatalf("writing %s: %v", outfilelist, err) 56 } 57 return outfs, outfilelist 58} 59 60func runPkgCover(t *testing.T, outdir string, tag string, incfg string, mode string, infiles []string, errExpected bool) ([]string, string, string) { 61 // Write the pkgcfg file. 62 outcfg := filepath.Join(outdir, "outcfg.txt") 63 64 // Form up the arguments and run the tool. 65 outfiles, outfilelist := writeOutFileList(t, infiles, outdir, tag) 66 args := []string{"-pkgcfg", incfg, "-mode=" + mode, "-var=var" + tag, "-outfilelist", outfilelist} 67 args = append(args, infiles...) 68 cmd := testenv.Command(t, testcover(t), args...) 69 if errExpected { 70 errmsg := runExpectingError(cmd, t) 71 return nil, "", errmsg 72 } else { 73 run(cmd, t) 74 return outfiles, outcfg, "" 75 } 76} 77 78func TestCoverWithCfg(t *testing.T) { 79 testenv.MustHaveGoRun(t) 80 81 t.Parallel() 82 83 // Subdir in testdata that has our input files of interest. 84 tpath := filepath.Join("testdata", "pkgcfg") 85 dir := tempDir(t) 86 instdira := filepath.Join(dir, "insta") 87 if err := os.Mkdir(instdira, 0777); err != nil { 88 t.Fatal(err) 89 } 90 91 scenarios := []struct { 92 mode, gran string 93 }{ 94 { 95 mode: "count", 96 gran: "perblock", 97 }, 98 { 99 mode: "set", 100 gran: "perfunc", 101 }, 102 { 103 mode: "regonly", 104 gran: "perblock", 105 }, 106 } 107 108 var incfg string 109 apkgfiles := []string{filepath.Join(tpath, "a", "a.go")} 110 for _, scenario := range scenarios { 111 // Instrument package "a", producing a set of instrumented output 112 // files and an 'output config' file to pass on to the compiler. 113 ppath := "cfg/a" 114 pname := "a" 115 mode := scenario.mode 116 gran := scenario.gran 117 tag := mode + "_" + gran 118 incfg = writePkgConfig(t, instdira, tag, ppath, pname, gran, "") 119 ofs, outcfg, _ := runPkgCover(t, instdira, tag, incfg, mode, 120 apkgfiles, false) 121 t.Logf("outfiles: %+v\n", ofs) 122 123 // Run the compiler on the files to make sure the result is 124 // buildable. 125 bargs := []string{"tool", "compile", "-p", "a", "-coveragecfg", outcfg} 126 bargs = append(bargs, ofs...) 127 cmd := testenv.Command(t, testenv.GoToolPath(t), bargs...) 128 cmd.Dir = instdira 129 run(cmd, t) 130 } 131 132 // Do some error testing to ensure that various bad options and 133 // combinations are properly rejected. 134 135 // Expect error if config file inaccessible/unreadable. 136 mode := "atomic" 137 errExpected := true 138 tag := "errors" 139 _, _, errmsg := runPkgCover(t, instdira, tag, "/not/a/file", mode, 140 apkgfiles, errExpected) 141 want := "error reading pkgconfig file" 142 if !strings.Contains(errmsg, want) { 143 t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg) 144 } 145 146 // Expect err if config file contains unknown stuff. 147 t.Logf("mangling in config") 148 writeFile(t, incfg, []byte("blah=foo\n")) 149 _, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode, 150 apkgfiles, errExpected) 151 want = "error reading pkgconfig file" 152 if !strings.Contains(errmsg, want) { 153 t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg) 154 } 155 156 // Expect error on empty config file. 157 t.Logf("writing empty config") 158 writeFile(t, incfg, []byte("\n")) 159 _, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode, 160 apkgfiles, errExpected) 161 if !strings.Contains(errmsg, want) { 162 t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg) 163 } 164} 165 166func TestCoverOnPackageWithNoTestFiles(t *testing.T) { 167 testenv.MustHaveGoRun(t) 168 169 // For packages with no test files, the new "go test -cover" 170 // strategy is to run cmd/cover on the package in a special 171 // "EmitMetaFile" mode. When running in this mode, cmd/cover walks 172 // the package doing instrumentation, but when finished, instead of 173 // writing out instrumented source files, it directly emits a 174 // meta-data file for the package in question, essentially 175 // simulating the effect that you would get if you added a dummy 176 // "no-op" x_test.go file and then did a build and run of the test. 177 178 t.Run("YesFuncsNoTests", func(t *testing.T) { 179 testCoverNoTestsYesFuncs(t) 180 }) 181 t.Run("NoFuncsNoTests", func(t *testing.T) { 182 testCoverNoTestsNoFuncs(t) 183 }) 184} 185 186func testCoverNoTestsYesFuncs(t *testing.T) { 187 t.Parallel() 188 dir := tempDir(t) 189 190 // Run the cover command with "emit meta" enabled on a package 191 // with functions but no test files. 192 tpath := filepath.Join("testdata", "pkgcfg") 193 pkg1files := []string{filepath.Join(tpath, "yesFuncsNoTests", "yfnt.go")} 194 ppath := "cfg/yesFuncsNoTests" 195 pname := "yesFuncsNoTests" 196 mode := "count" 197 gran := "perblock" 198 tag := mode + "_" + gran 199 instdir := filepath.Join(dir, "inst") 200 if err := os.Mkdir(instdir, 0777); err != nil { 201 t.Fatal(err) 202 } 203 mdir := filepath.Join(dir, "meta") 204 if err := os.Mkdir(mdir, 0777); err != nil { 205 t.Fatal(err) 206 } 207 mpath := filepath.Join(mdir, "covmeta.xxx") 208 incfg := writePkgConfig(t, instdir, tag, ppath, pname, gran, mpath) 209 _, _, errmsg := runPkgCover(t, instdir, tag, incfg, mode, 210 pkg1files, false) 211 if errmsg != "" { 212 t.Fatalf("runPkgCover err: %q", errmsg) 213 } 214 215 // Check for existence of meta-data file. 216 if inf, err := os.Open(mpath); err != nil { 217 t.Fatalf("meta-data file not created: %v", err) 218 } else { 219 inf.Close() 220 } 221 222 // Make sure it is digestible. 223 cdargs := []string{"tool", "covdata", "percent", "-i", mdir} 224 cmd := testenv.Command(t, testenv.GoToolPath(t), cdargs...) 225 run(cmd, t) 226} 227 228func testCoverNoTestsNoFuncs(t *testing.T) { 229 t.Parallel() 230 dir := tempDir(t) 231 232 // Run the cover command with "emit meta" enabled on a package 233 // with no functions and no test files. 234 tpath := filepath.Join("testdata", "pkgcfg") 235 pkgfiles := []string{filepath.Join(tpath, "noFuncsNoTests", "nfnt.go")} 236 pname := "noFuncsNoTests" 237 mode := "count" 238 gran := "perblock" 239 ppath := "cfg/" + pname 240 tag := mode + "_" + gran 241 instdir := filepath.Join(dir, "inst2") 242 if err := os.Mkdir(instdir, 0777); err != nil { 243 t.Fatal(err) 244 } 245 mdir := filepath.Join(dir, "meta2") 246 if err := os.Mkdir(mdir, 0777); err != nil { 247 t.Fatal(err) 248 } 249 mpath := filepath.Join(mdir, "covmeta.yyy") 250 incfg := writePkgConfig(t, instdir, tag, ppath, pname, gran, mpath) 251 _, _, errmsg := runPkgCover(t, instdir, tag, incfg, mode, 252 pkgfiles, false) 253 if errmsg != "" { 254 t.Fatalf("runPkgCover err: %q", errmsg) 255 } 256 257 // We expect to see an empty meta-data file in this case. 258 if inf, err := os.Open(mpath); err != nil { 259 t.Fatalf("opening meta-data file: error %v", err) 260 } else { 261 defer inf.Close() 262 fi, err := inf.Stat() 263 if err != nil { 264 t.Fatalf("stat meta-data file: %v", err) 265 } 266 if fi.Size() != 0 { 267 t.Fatalf("want zero-sized meta-data file got size %d", 268 fi.Size()) 269 } 270 } 271} 272