blob: 4b93dd92937a9dd3a0a8cad843bd566a95c3121f [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 codifier
import (
"errors"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
const (
groupTestsTemplate = `
group("tests") {
testonly = true
deps = [ "%s" ]
}
`
groupTestsFileHeader = `
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/test.gni")
`
)
// Do not add tests to these directories or above.
var defaultStopList = []string{"//zircon/system", "//zircon/third_party"}
// AddTestsGroup finds a group("tests") clause and adds the given testTarget to
// its dependencies.
func (p *Proc) AddTestsGroup(testTarget string) *Proc {
if p == nil {
log.Println(" AddTestsGroup() error: nil receiver")
return nil
}
log.Printf("AddTestsGroup(): checking %q is in test group for file %q", testTarget, p.filename)
// Look for sections like group("tests").
groupTestsScope := p.Scope("group", "tests")
if groupTestsScope == nil {
// No scope found, so no child added to p, so no ApplyChanges() needed.
log.Println(" did not find 'group(\"tests\")'; adding...")
p = p.Append(fmt.Sprintf(groupTestsTemplate, testTarget))
p.changedFiles.Add(p.filename)
return p
}
// Found a scope, adding a child, so ApplyChanges() is needed before exit.
p = groupTestsScope
p = p.ExtractList("deps")
if p == nil {
log.Print(" AddTestsGroup() error: couldn't find deps in tests group")
return nil
}
// Don't add if already present.
// TODO: The change to orderedStrings broke this check: ListContains() casts
// to orderedStrings, which fails because these are stored as just
// []strings. Same for the "tests:tests: check below. Fix.
if p.ListContains("deps", testTarget) {
log.Printf(" AddTestsGroup() error: %q already present in test group deps", testTarget)
return p
}
// There's a default where a dependency like "tests" will point to a
// directory named tests and a group named "tests".
if testTarget == "tests:tests" && p.ListContains("deps", "tests") {
return p
}
// Insert the new dependency by inserting at the front of the list. The
// indentation may not match, but the gn format step will correct it. Gn
// format will also correct the alphabetical ordering of the dep list.
// TODO(gboone@): Consider replacing this line with a function that will do
// a line insertion with matching indentation.
// Regular expression: the deps keywork, some space, an equals sign, some
// space an opening bracket.
re := regexp.MustCompile(`deps\s*=\s*\[`)
newStr := fmt.Sprintf("deps = [ \"%s\",\n", testTarget)
p.replacement = re.ReplaceAllString(p.replacement, newStr)
p.changedFiles.Add(p.filename)
p = p.ApplyChanges()
return p
}
// AddParentTests ensures that the group("tests") in the current file is linked
// to parent tests in all of the parent build files.
func (p *Proc) AddParentTests(buildFile string) *Proc {
if p == nil {
log.Println(" AddParentTests() error: nil receiver")
return nil
}
log.Printf(" AddParentTests() checking group tests in %q", buildFile)
dirParts := strings.Split(buildFile, string(os.PathSeparator))
if len(dirParts) <= 2 {
return p
}
stopDirs, err := stopDirs(p.fuchsiaDir)
if err != nil {
return nil
}
// The last path item is the BUILD.gn file, so start one up from that. The
// last is len - 1, so that would be len - 2.
for i := len(dirParts) - 2; i > 0; i-- {
dir := string(os.PathSeparator) + filepath.Join(filepath.Join(dirParts[:i]...))
if stopDirs.contains(dir) {
break
}
filename := filepath.Join(dir, "BUILD.gn")
testToAdd := dirParts[i] + ":tests"
var q *Proc
if fileDoesNotExist(filename) {
q = NewProcFromString(groupTestsFileHeader).setFilename(filename)
} else {
q = NewProcFromFile(filename)
}
q = q.AddTestsGroup(testToAdd).WriteFile()
if q == nil {
return nil
}
p.changedFiles.Add(q.changedFiles...)
}
return p
}
// Returns true if the given file does not exist.
func fileDoesNotExist(filename string) bool {
_, err := os.Stat(filename)
return errors.Is(err, fs.ErrNotExist)
}
// Returns a list of directories that should stop path climbing. That is, if the
// stop directory is "b", then path traversals up "a/b/c/d/e/f", which would be
// "a/b/c/d/e/f", "a/b/c/d/e", "a/b/c/d" and so on, would not include "a/b". The
// stop directories include the given Fuchsia directory. All stop directories
// are fully resolved to their absolute filepaths.
func stopDirs(fuchsiaDir string) (orderedStrings, error) {
stopDir, err := resolveGnPath("", fuchsiaDir)
if err != nil {
log.Println(" stopDirs() error: couldn't resolve fuchsia base directory")
return nil, err
}
stopDir = strings.TrimSuffix(stopDir, string(os.PathSeparator))
if stopDir == "" {
stopDir = "/"
}
stopDirs := []string{stopDir}
// Add the others.
for _, f := range defaultStopList {
d, err := resolveGnPath(fuchsiaDir, f)
if err != nil {
log.Printf(" stopDirs() error: couldn't resolve stop directory %q", d)
return nil, err
}
stopDirs = append(stopDirs, d)
}
return stopDirs, nil
}