// Copyright 2019 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 tests import ( "path/filepath" "strconv" "strings" "golang.org/x/tools/go/packages/packagestest" ) type Normalizer struct { path string slashed string escaped string fragment string } func CollectNormalizers(exported *packagestest.Exported) []Normalizer { // build the path normalizing patterns var normalizers []Normalizer for _, m := range exported.Modules { for fragment := range m.Files { n := Normalizer{ path: exported.File(m.Name, fragment), fragment: fragment, } if n.slashed = filepath.ToSlash(n.path); n.slashed == n.path { n.slashed = "" } quoted := strconv.Quote(n.path) if n.escaped = quoted[1 : len(quoted)-1]; n.escaped == n.path { n.escaped = "" } normalizers = append(normalizers, n) } } return normalizers } // NormalizePrefix normalizes a single path at the front of the input string. func NormalizePrefix(s string, normalizers []Normalizer) string { for _, n := range normalizers { if t := strings.TrimPrefix(s, n.path); t != s { return n.fragment + t } if t := strings.TrimPrefix(s, n.slashed); t != s { return n.fragment + t } if t := strings.TrimPrefix(s, n.escaped); t != s { return n.fragment + t } } return s } // Normalize replaces all paths present in s with just the fragment portion // this is used to make golden files not depend on the temporary paths of the files func Normalize(s string, normalizers []Normalizer) string { type entry struct { path string index int fragment string } var match []entry // collect the initial state of all the matchers for _, n := range normalizers { index := strings.Index(s, n.path) if index >= 0 { match = append(match, entry{n.path, index, n.fragment}) } if n.slashed != "" { index := strings.Index(s, n.slashed) if index >= 0 { match = append(match, entry{n.slashed, index, n.fragment}) } } if n.escaped != "" { index := strings.Index(s, n.escaped) if index >= 0 { match = append(match, entry{n.escaped, index, n.fragment}) } } } // result should be the same or shorter than the input var b strings.Builder last := 0 for { // find the nearest path match to the start of the buffer next := -1 nearest := len(s) for i, c := range match { if c.index >= 0 && nearest > c.index { nearest = c.index next = i } } // if there are no matches, we copy the rest of the string and are done if next < 0 { b.WriteString(s[last:]) return b.String() } // we have a match n := &match[next] // copy up to the start of the match b.WriteString(s[last:n.index]) // skip over the filename last = n.index + len(n.path) // Hack: In multi-module mode, we add a "testmodule/" prefix, so trim // it from the fragment. fragment := n.fragment if strings.HasPrefix(fragment, "testmodule") { split := strings.Split(filepath.ToSlash(fragment), "/") fragment = filepath.FromSlash(strings.Join(split[1:], "/")) } // add in the fragment instead b.WriteString(fragment) // see what the next match for this path is n.index = strings.Index(s[last:], n.path) if n.index >= 0 { n.index += last } } }