blob: 1083427d51cfed0b733cd48f0980441b4ebc7274 [file] [edit]
// Copyright 2017 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 testing
import (
"fmt"
"runtime"
"strings"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"chromiumos/tast/errors"
"chromiumos/tast/internal/packages"
)
// Registry holds tests and services.
type Registry struct {
name string
errors []error
allTests []*TestInstance
testNames map[string]struct{} // names of registered tests
allServices []*Service
allPres map[string]Precondition
allFixtures map[string]*FixtureInstance
allVars map[string]Var // all registered global runtime variables
varRawValues map[string]string // raw values of global runtime variables
}
// NewRegistry returns a new test registry.
func NewRegistry(name string) *Registry {
return &Registry{
name: name,
testNames: make(map[string]struct{}),
allPres: make(map[string]Precondition),
allFixtures: make(map[string]*FixtureInstance),
allVars: make(map[string]Var),
}
}
// Name returns the name of the registry.
func (r *Registry) Name() string {
return r.name
}
// Errors returns errors generated by registration method calls.
func (r *Registry) Errors() []error {
return append([]error(nil), r.errors...)
}
// RecordError records err as a registration error if it is not nil.
func (r *Registry) RecordError(err error) {
if err == nil {
return
}
file, line := userCaller()
r.errors = append(r.errors, errors.Wrapf(err, "%s:%d", file, line))
}
// AddTest adds t to the registry.
func (r *Registry) AddTest(t *Test) {
r.RecordError(func() error {
tis, err := instantiate(t)
if err != nil {
return err
}
for _, ti := range tis {
r.AddTestInstance(ti)
}
return nil
}())
}
// AddTestInstance adds t to the registry.
// In contrast to AddTest, AddTestInstance registers an instantiated test almost
// as-is. It just associates the test with this registry so that the test cannot
// be registered to other registries.
// AddTestInstance should be called only by unit tests. It must not be
// accessible by user code.
func (r *Registry) AddTestInstance(t *TestInstance) {
r.RecordError(func() error {
if _, ok := r.testNames[t.Name]; ok {
return fmt.Errorf("test %q already registered", t.Name)
}
if t.Bundle != "" {
return fmt.Errorf("TestInstance %q cannot be added to bundle %q since it is already added to bundle %q", t.Name, r.name, t.Bundle)
}
t.Bundle = r.name
// Ensure equality of preconditions with the same name.
if t.Pre != nil {
name := t.Pre.String()
if pre, ok := r.allPres[name]; !ok {
r.allPres[name] = t.Pre
} else if pre != t.Pre {
return fmt.Errorf("precondition %s has multiple instances", name)
}
}
r.allTests = append(r.allTests, t)
r.testNames[t.Name] = struct{}{}
return nil
}())
}
// AddService adds s to the registry.
func (r *Registry) AddService(s *Service) {
r.allServices = append(r.allServices, s)
}
// AddFixture adds f to the registry.
func (r *Registry) AddFixture(f *Fixture, pkg string) {
r.RecordError(func() error {
fi, err := f.instantiate(pkg)
if err != nil {
return err
}
r.AddFixtureInstance(fi)
return nil
}())
}
// AddFixtureInstance adds f to the registry.
// In contrast to AddFixture, AddFixtureInstance registers an instantiated
// fixture almost as-is. It just associates the fixture with this registry so
// that the fixture cannot be registered to other registries.
// AddFixtureInstance should be called only by unit tests. It must not be
// accessible by user code.
func (r *Registry) AddFixtureInstance(f *FixtureInstance) {
r.RecordError(func() error {
if _, ok := r.allFixtures[f.Name]; ok {
return fmt.Errorf("fixture %q already registered", f.Name)
}
if f.Bundle != "" {
return fmt.Errorf("FixtureInstance %q cannot be added to bundle %q since it is already added to bundle %q", f.Name, r.name, f.Bundle)
}
f.Bundle = r.name
r.allFixtures[f.Name] = f
return nil
}())
}
// AddVar adds global variables to the registry.
func (r *Registry) AddVar(v Var) {
r.RecordError(func() error {
name := v.Name()
if _, ok := r.allVars[name]; ok {
return fmt.Errorf("global runtime variable %q has already been registered", v.Name())
}
r.allVars[name] = v
return nil
}())
}
// AllTests returns copies of all registered tests.
func (r *Registry) AllTests() []*TestInstance {
ts := make([]*TestInstance, len(r.allTests))
for i, t := range r.allTests {
ts[i] = t.clone()
}
return ts
}
// AllServices returns copies of all registered services.
func (r *Registry) AllServices() []*Service {
return append(([]*Service)(nil), r.allServices...)
}
// AllFixtures returns copies of all registered fixtures.
func (r *Registry) AllFixtures() map[string]*FixtureInstance {
fs := make(map[string]*FixtureInstance)
for name, f := range r.allFixtures {
fs[name] = f
}
return fs
}
// userCaller finds the caller of a registration function. It ignores framework
// packages.
func userCaller() (file string, line int) {
for skip := 1; ; skip++ {
pc, file, line, _ := runtime.Caller(skip)
f := runtime.FuncForPC(pc)
name := packages.Normalize(f.Name())
if strings.HasPrefix(name, packages.FrameworkPrefix+"internal/") ||
strings.HasPrefix(name, packages.FrameworkPrefix+"testing.") {
continue
}
return file, line
}
}
// InitializeVars initializes all registered global variables.
func (r *Registry) InitializeVars(values map[string]string) error {
if r.varRawValues != nil {
if !cmp.Equal(values, r.varRawValues, cmpopts.EquateEmpty()) {
return errors.New("global runtime variables can only be initialized once")
}
return nil
}
// Save raw values for future comparison.
r.varRawValues = make(map[string]string)
for k, v := range values {
r.varRawValues[k] = v
}
// Set value for each variable.
for _, v := range r.allVars {
if stringValue, ok := values[v.Name()]; ok {
if err := v.Unmarshal(stringValue); err != nil {
return err
}
}
}
return nil
}