blob: 75eaf075f7695b5f40210b5ec20637a4ab607b6a [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
// daysSinceRecentCommit specifies how far to look back when determining
// who recently committed changes to a test.
const daysSinceRecentCommit = 60
// gitLogAuthorEmailRegexp captures an author's email address from git log.
var gitLogAuthorEmailRegexp = regexp.MustCompile(`Author:[^<]+<([^>]+)>`)
// controlFileOwnersRegexp captures the AUTHOR line from a test control file.
var controlFileOwnersRegexp = regexp.MustCompile(`AUTHOR = "([^"]+)"`)
// e2eTest contains metadata for a single end-to-end tests.
// End-to-end tests are typically found as directories within
// autotest/server/site_tests
type e2eTest struct {
Name, TestClass, Description string
Owners, RecentEditors []string
}
// e2eTest.println displays the test struct's field names and values.
func (t e2eTest) println() {
fmt.Printf("%+v\n", t)
}
// e2eTest.dir returns the test's folder within autotest/server/site_tests/
func (t e2eTest) dir() string {
return filepath.Join(siteTestsDir, t.Name)
}
const (
e2eTestClassFirmware = "FirmwareTest"
e2eTestClassCr50 = "Cr50Test"
)
var autotestDir string
var siteTestsDir string
// getDefaultAutotestDir attempts to find Autotest if the -autotest_dir flag is
// not provided.
func getDefaultAutotestDir() string {
defaultAutotestDir := "/"
thisDir, err := os.Getwd()
if err != nil {
log.Fatal(err, " when getting current working directory")
}
thisDir, err = filepath.Abs(thisDir)
if err != nil {
log.Fatal(err, " when parsing cwd as an absolute path")
}
thisDir = filepath.ToSlash(thisDir)
for _, dir := range strings.Split(thisDir, "/") {
defaultAutotestDir = filepath.Join(defaultAutotestDir, dir)
if dir == "src" {
break
}
}
defaultAutotestDir = filepath.Join(defaultAutotestDir, "third_party", "autotest", "files")
return defaultAutotestDir
}
// Init parses command-line args to find autotest and site_tests
func init() {
flag.StringVar(&autotestDir, "autotest_dir", getDefaultAutotestDir(), "The path to your autotest files")
flag.Parse()
if _, err := os.Stat(autotestDir); os.IsNotExist(err) {
log.Fatalf("Could not find autotest directory: <%s>. Try supplying -autotest_dir", autotestDir)
}
siteTestsDir = filepath.Join(autotestDir, "server", "site_tests")
if _, err := os.Stat(siteTestsDir); os.IsNotExist(err) {
log.Fatalf("Could not find site_tests directory: <%s>", siteTestsDir)
}
}
// e2eTest.containsStr determines whether server/site_tests/$(t.Name)/*.py contains searchString.
func (t e2eTest) containsStr(searchString string) (bool, error) {
testDir := filepath.Join(siteTestsDir, t.Name)
testDirContents, err := ioutil.ReadDir(testDir)
if err != nil {
return false, fmt.Errorf("%v when reading test directory %s", err, testDir)
}
for _, fo := range testDirContents {
testFile := filepath.Join(testDir, fo.Name())
if filepath.Ext(testFile) != ".py" {
continue
}
testFileContents, err := ioutil.ReadFile(testFile)
if err != nil {
return false, fmt.Errorf("%v when reading test file %s", err, testFile)
}
if strings.Contains(string(testFileContents), searchString) {
return true, nil
}
}
return false, nil
}
// contains determines whether an array of strings contains a given string.
func contains(arr []string, elem string) bool {
for _, x := range arr {
if x == elem {
return true
}
}
return false
}
// e2e.setRecentEditors finds all authors who have committed changes to the test
// within the past $(daysSinceRecentCommit) days.
func (t *e2eTest) setRecentEditors() error {
sinceDate := time.Now().AddDate(0, 0, -1*daysSinceRecentCommit)
gitLogCmd := exec.Command(
"git",
"--no-pager",
"log",
"--since",
sinceDate.Format(time.RFC3339), ".")
gitLogCmd.Dir = t.dir()
gitLogOutput, err := gitLogCmd.Output()
if err != nil {
return fmt.Errorf("%v when running %s", err, gitLogCmd)
}
authorMatches := gitLogAuthorEmailRegexp.FindAllSubmatch(gitLogOutput, -1)
authors := []string{}
for _, authorMatch := range authorMatches {
author := strings.TrimSpace(string(authorMatch[1]))
if !contains(authors, author) {
authors = append(authors, author)
}
}
t.RecentEditors = authors
return nil
}
// e2eTest.setOwners populates e2eTest.Owners based on the AUTHORS line of the control file
func (t *e2eTest) setOwners() error {
controlFile := t.findOneControlFile()
controlFileBytes, err := ioutil.ReadFile(controlFile)
if err != nil {
return fmt.Errorf("%v when reading control file %s", err, controlFile)
}
ownerMatch := controlFileOwnersRegexp.FindStringSubmatch(string(controlFileBytes))
for _, owner := range strings.Split(ownerMatch[1], ",") {
t.Owners = append(t.Owners, strings.TrimSpace(owner))
}
return nil
}
// e2eTest.findOneControlFile returns the path to the alphabetically first control file for t.
func (t e2eTest) findOneControlFile() string {
controlFiles := t.findAllControlFiles()
return controlFiles[0]
}
// e2eTest.findAllControlFiles returns a slice of filepaths to the control files for t.
func (t e2eTest) findAllControlFiles() []string {
controlFiles := []string{}
fileObjs, _ := ioutil.ReadDir(t.dir())
for _, fo := range fileObjs {
if strings.HasPrefix(fo.Name(), "control") {
controlFiles = append(controlFiles, filepath.Join(t.dir(), fo.Name()))
}
}
sort.Strings(controlFiles)
return controlFiles
}
// Collect and report info on all firmware end-to-end tests.
func main() {
siteTestFileObjs, err := ioutil.ReadDir(siteTestsDir)
if err != nil {
log.Fatal(err, " when reading site_tests dir: ", siteTestsDir)
}
var e2eTests []e2eTest
var t e2eTest
for _, fo := range siteTestFileObjs {
if !fo.IsDir() {
continue
}
t = e2eTest{Name: fo.Name()}
for _, testClass := range []string{e2eTestClassFirmware, e2eTestClassCr50} {
hasClass, err := t.containsStr(testClass)
if err != nil {
log.Fatalf("%v for test %s", err, t.Name)
}
if hasClass {
t.TestClass = testClass
break
}
}
if t.TestClass == "" {
continue
}
// TODO: Add description
for _, f := range []func() error{t.setRecentEditors, t.setOwners} {
err := f()
if err != nil {
log.Fatalf("%v for test %s", err, t.Name)
}
}
e2eTests = append(e2eTests, t)
}
for _, t := range e2eTests {
t.println()
}
}