// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package coverage import ( "fmt" "internal/coverage" "internal/coverage/calloc" "internal/coverage/cformat" "internal/coverage/cmerge" "internal/coverage/decodecounter" "internal/coverage/decodemeta" "internal/coverage/pods" "io" "os" "strings" ) // processCoverTestDir is called (via a linknamed reference) from // testmain code when "go test -cover" is in effect. It is not // intended to be used other than internally by the Go command's // generated code. func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error { return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout) } // processCoverTestDirInternal is an io.Writer version of processCoverTestDir, // exposed for unit testing. func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error { cmode := coverage.ParseCounterMode(cm) if cmode == coverage.CtrModeInvalid { return fmt.Errorf("invalid counter mode %q", cm) } // Emit meta-data and counter data. ml := getCovMetaList() if len(ml) == 0 { // This corresponds to the case where we have a package that // contains test code but no functions (which is fine). In this // case there is no need to emit anything. } else { if err := emitMetaDataToDirectory(dir, ml); err != nil { return err } if err := emitCounterDataToDirectory(dir); err != nil { return err } } // Collect pods from test run. For the majority of cases we would // expect to see a single pod here, but allow for multiple pods in // case the test harness is doing extra work to collect data files // from builds that it kicks off as part of the testing. podlist, err := pods.CollectPods([]string{dir}, false) if err != nil { return fmt.Errorf("reading from %s: %v", dir, err) } // Open text output file if appropriate. var tf *os.File var tfClosed bool if cfile != "" { var err error tf, err = os.Create(cfile) if err != nil { return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err) } defer func() { if !tfClosed { tfClosed = true tf.Close() } }() } // Read/process the pods. ts := &tstate{ cm: &cmerge.Merger{}, cf: cformat.NewFormatter(cmode), cmode: cmode, } // Generate the expected hash string based on the final meta-data // hash for this test, then look only for pods that refer to that // hash (just in case there are multiple instrumented executables // in play). See issue #57924 for more on this. hashstring := fmt.Sprintf("%x", finalHash) for _, p := range podlist { if !strings.Contains(p.MetaFile, hashstring) { continue } if err := ts.processPod(p); err != nil { return err } } // Emit percent. if err := ts.cf.EmitPercent(w, cpkg, true); err != nil { return err } // Emit text output. if tf != nil { if err := ts.cf.EmitTextual(tf); err != nil { return err } tfClosed = true if err := tf.Close(); err != nil { return fmt.Errorf("closing %s: %v", cfile, err) } } return nil } type tstate struct { calloc.BatchCounterAlloc cm *cmerge.Merger cf *cformat.Formatter cmode coverage.CounterMode } // processPod reads coverage counter data for a specific pod. func (ts *tstate) processPod(p pods.Pod) error { // Open meta-data file f, err := os.Open(p.MetaFile) if err != nil { return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err) } defer func() { f.Close() }() var mfr *decodemeta.CoverageMetaFileReader mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil) if err != nil { return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err) } newmode := mfr.CounterMode() if newmode != ts.cmode { return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile) } newgran := mfr.CounterGranularity() if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil { return err } // A map to store counter data, indexed by pkgid/fnid tuple. pmm := make(map[pkfunc][]uint32) // Helper to read a single counter data file. readcdf := func(cdf string) error { cf, err := os.Open(cdf) if err != nil { return fmt.Errorf("opening counter data file %s: %s", cdf, err) } defer cf.Close() var cdr *decodecounter.CounterDataReader cdr, err = decodecounter.NewCounterDataReader(cdf, cf) if err != nil { return fmt.Errorf("reading counter data file %s: %s", cdf, err) } var data decodecounter.FuncPayload for { ok, err := cdr.NextFunc(&data) if err != nil { return fmt.Errorf("reading counter data file %s: %v", cdf, err) } if !ok { break } // NB: sanity check on pkg and func IDs? key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} if prev, found := pmm[key]; found { // Note: no overflow reporting here. if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil { return fmt.Errorf("processing counter data file %s: %v", cdf, err) } } c := ts.AllocateCounters(len(data.Counters)) copy(c, data.Counters) pmm[key] = c } return nil } // Read counter data files. for _, cdf := range p.CounterDataFiles { if err := readcdf(cdf); err != nil { return err } } // Visit meta-data file. np := uint32(mfr.NumPackages()) payload := []byte{} for pkIdx := uint32(0); pkIdx < np; pkIdx++ { var pd *decodemeta.CoverageMetaDataDecoder pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload) if err != nil { return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err) } ts.cf.SetPackage(pd.PackagePath()) var fd coverage.FuncDesc nf := pd.NumFuncs() for fnIdx := uint32(0); fnIdx < nf; fnIdx++ { if err := pd.ReadFunc(fnIdx, &fd); err != nil { return fmt.Errorf("reading meta-data file %s: %v", p.MetaFile, err) } key := pkfunc{pk: pkIdx, fcn: fnIdx} counters, haveCounters := pmm[key] for i := 0; i < len(fd.Units); i++ { u := fd.Units[i] // Skip units with non-zero parent (no way to represent // these in the existing format). if u.Parent != 0 { continue } count := uint32(0) if haveCounters { count = counters[i] } ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count) } } } return nil } type pkfunc struct { pk, fcn uint32 }