github.com/bir3/gocompiler@v0.9.2202/src/internal/coverage/pods/pods.go (about) 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 pods 6 7 import ( 8 "fmt" 9 "github.com/bir3/gocompiler/src/internal/coverage" 10 "os" 11 "path/filepath" 12 "regexp" 13 "sort" 14 "strconv" 15 ) 16 17 // Pod encapsulates a set of files emitted during the executions of a 18 // coverage-instrumented binary. Each pod contains a single meta-data 19 // file, and then 0 or more counter data files that refer to that 20 // meta-data file. Pods are intended to simplify processing of 21 // coverage output files in the case where we have several coverage 22 // output directories containing output files derived from more 23 // than one instrumented executable. In the case where the files that 24 // make up a pod are spread out across multiple directories, each 25 // element of the "Origins" field below will be populated with the 26 // index of the originating directory for the corresponding counter 27 // data file (within the slice of input dirs handed to CollectPods). 28 // The ProcessIDs field will be populated with the process ID of each 29 // data file in the CounterDataFiles slice. 30 type Pod struct { 31 MetaFile string 32 CounterDataFiles []string 33 Origins []int 34 ProcessIDs []int 35 } 36 37 // CollectPods visits the files contained within the directories in 38 // the list 'dirs', collects any coverage-related files, partitions 39 // them into pods, and returns a list of the pods to the caller, along 40 // with an error if something went wrong during directory/file 41 // reading. 42 // 43 // CollectPods skips over any file that is not related to coverage 44 // (e.g. avoids looking at things that are not meta-data files or 45 // counter-data files). CollectPods also skips over 'orphaned' counter 46 // data files (e.g. counter data files for which we can't find the 47 // corresponding meta-data file). If "warn" is true, CollectPods will 48 // issue warnings to stderr when it encounters non-fatal problems (for 49 // orphans or a directory with no meta-data files). 50 func CollectPods(dirs []string, warn bool) ([]Pod, error) { 51 files := []string{} 52 dirIndices := []int{} 53 for k, dir := range dirs { 54 dents, err := os.ReadDir(dir) 55 if err != nil { 56 return nil, err 57 } 58 for _, e := range dents { 59 if e.IsDir() { 60 continue 61 } 62 files = append(files, filepath.Join(dir, e.Name())) 63 dirIndices = append(dirIndices, k) 64 } 65 } 66 return collectPodsImpl(files, dirIndices, warn), nil 67 } 68 69 // CollectPodsFromFiles functions the same as "CollectPods" but 70 // operates on an explicit list of files instead of a directory. 71 func CollectPodsFromFiles(files []string, warn bool) []Pod { 72 return collectPodsImpl(files, nil, warn) 73 } 74 75 type fileWithAnnotations struct { 76 file string 77 origin int 78 pid int 79 } 80 81 type protoPod struct { 82 mf string 83 elements []fileWithAnnotations 84 } 85 86 // collectPodsImpl examines the specified list of files and picks out 87 // subsets that correspond to coverage pods. The first stage in this 88 // process is collecting a set { M1, M2, ... MN } where each M_k is a 89 // distinct coverage meta-data file. We then create a single pod for 90 // each meta-data file M_k, then find all of the counter data files 91 // that refer to that meta-data file (recall that the counter data 92 // file name incorporates the meta-data hash), and add the counter 93 // data file to the appropriate pod. 94 // 95 // This process is complicated by the fact that we need to keep track 96 // of directory indices for counter data files. Here is an example to 97 // motivate: 98 // 99 // directory 1: 100 // 101 // M1 covmeta.9bbf1777f47b3fcacb05c38b035512d6 102 // C1 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677673.1662138360208416486 103 // C2 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677637.1662138359974441782 104 // 105 // directory 2: 106 // 107 // M2 covmeta.9bbf1777f47b3fcacb05c38b035512d6 108 // C3 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677445.1662138360208416480 109 // C4 covcounters.9bbf1777f47b3fcacb05c38b035512d6.1677677.1662138359974441781 110 // M3 covmeta.a723844208cea2ae80c63482c78b2245 111 // C5 covcounters.a723844208cea2ae80c63482c78b2245.3677445.1662138360208416480 112 // C6 covcounters.a723844208cea2ae80c63482c78b2245.1877677.1662138359974441781 113 // 114 // In these two directories we have three meta-data files, but only 115 // two are distinct, meaning that we'll wind up with two pods. The 116 // first pod (with meta-file M1) will have four counter data files 117 // (C1, C2, C3, C4) and the second pod will have two counter data files 118 // (C5, C6). 119 func collectPodsImpl(files []string, dirIndices []int, warn bool) []Pod { 120 metaRE := regexp.MustCompile(fmt.Sprintf(`^%s\.(\S+)$`, coverage.MetaFilePref)) 121 mm := make(map[string]protoPod) 122 for _, f := range files { 123 base := filepath.Base(f) 124 if m := metaRE.FindStringSubmatch(base); m != nil { 125 tag := m[1] 126 // We need to allow for the possibility of duplicate 127 // meta-data files. If we hit this case, use the 128 // first encountered as the canonical version. 129 if _, ok := mm[tag]; !ok { 130 mm[tag] = protoPod{mf: f} 131 } 132 // FIXME: should probably check file length and hash here for 133 // the duplicate. 134 } 135 } 136 counterRE := regexp.MustCompile(fmt.Sprintf(coverage.CounterFileRegexp, coverage.CounterFilePref)) 137 for k, f := range files { 138 base := filepath.Base(f) 139 if m := counterRE.FindStringSubmatch(base); m != nil { 140 tag := m[1] // meta hash 141 pid, err := strconv.Atoi(m[2]) 142 if err != nil { 143 continue 144 } 145 if v, ok := mm[tag]; ok { 146 idx := -1 147 if dirIndices != nil { 148 idx = dirIndices[k] 149 } 150 fo := fileWithAnnotations{file: f, origin: idx, pid: pid} 151 v.elements = append(v.elements, fo) 152 mm[tag] = v 153 } else { 154 if warn { 155 warning("skipping orphaned counter file: %s", f) 156 } 157 } 158 } 159 } 160 if len(mm) == 0 { 161 if warn { 162 warning("no coverage data files found") 163 } 164 return nil 165 } 166 pods := make([]Pod, 0, len(mm)) 167 for _, p := range mm { 168 sort.Slice(p.elements, func(i, j int) bool { 169 if p.elements[i].origin != p.elements[j].origin { 170 return p.elements[i].origin < p.elements[j].origin 171 } 172 return p.elements[i].file < p.elements[j].file 173 }) 174 pod := Pod{ 175 MetaFile: p.mf, 176 CounterDataFiles: make([]string, 0, len(p.elements)), 177 Origins: make([]int, 0, len(p.elements)), 178 ProcessIDs: make([]int, 0, len(p.elements)), 179 } 180 for _, e := range p.elements { 181 pod.CounterDataFiles = append(pod.CounterDataFiles, e.file) 182 pod.Origins = append(pod.Origins, e.origin) 183 pod.ProcessIDs = append(pod.ProcessIDs, e.pid) 184 } 185 pods = append(pods, pod) 186 } 187 sort.Slice(pods, func(i, j int) bool { 188 return pods[i].MetaFile < pods[j].MetaFile 189 }) 190 return pods 191 } 192 193 func warning(s string, a ...interface{}) { 194 fmt.Fprintf(os.Stderr, "warning: ") 195 fmt.Fprintf(os.Stderr, s, a...) 196 fmt.Fprintf(os.Stderr, "\n") 197 }