Source file src/cmd/cover/cfg_test.go

     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  
     5  package main_test
     6  
     7  import (
     8  	"cmd/internal/cov/covcmd"
     9  	"encoding/json"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  func 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  
    24  func 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  
    42  func 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  
    60  func 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  
    78  func 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  
   166  func 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 instrumention, 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  
   186  func 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  
   228  func 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  

View as plain text