github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/testing/testcheck/testcheck.go (about)

     1  // Copyright 2023 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package testcheck provides common functions to check test definitions.
     6  package testcheck
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	gotesting "testing"
    12  	"time"
    13  
    14  	"go.chromium.org/tast/core/internal/testing"
    15  )
    16  
    17  // SetAllTestsforTest sets all tests to use in this package. This is mainly used in unittest for testing purpose.
    18  func SetAllTestsforTest(tests []*testing.TestInstance) func() {
    19  	allTests = func() []*testing.TestInstance {
    20  		return tests
    21  	}
    22  	return func() {
    23  		allTests = testing.GlobalRegistry().AllTests
    24  	}
    25  }
    26  
    27  // TestFilter defines the condition whether or not the test should be checked.
    28  type TestFilter func(t *testing.TestInstance) bool
    29  
    30  var allTests func() []*testing.TestInstance = testing.GlobalRegistry().AllTests
    31  
    32  func getTests(t *gotesting.T, f TestFilter) []*testing.TestInstance {
    33  	var tests []*testing.TestInstance
    34  	for _, tst := range allTests() {
    35  		if f(tst) {
    36  			tests = append(tests, tst)
    37  		}
    38  	}
    39  	if len(tests) == 0 {
    40  		t.Fatalf("No tests matched")
    41  	}
    42  	return tests
    43  }
    44  
    45  // Glob returns a TestFilter which returns true for a test if the test name
    46  // matches with the given glob pattern.
    47  func Glob(t *gotesting.T, glob string) TestFilter {
    48  	re, err := testing.NewTestGlobRegexp(glob)
    49  	if err != nil {
    50  		t.Fatalf("Bad glob %q: %v", glob, err)
    51  	}
    52  	return func(t *testing.TestInstance) bool {
    53  		return re.MatchString(t.Name)
    54  	}
    55  }
    56  
    57  // Timeout checks that tests matched by f have timeout no less than minTimeout.
    58  func Timeout(t *gotesting.T, f TestFilter, minTimeout time.Duration) {
    59  	for _, tst := range getTests(t, f) {
    60  		if tst.Timeout < minTimeout {
    61  			t.Errorf("%s: timeout is too short (%v < %v)", tst.Name, tst.Timeout, minTimeout)
    62  		}
    63  	}
    64  }
    65  
    66  // Attr checks that tests matched by f declare requiredAttr as Attr.
    67  // requiredAttr is a list of items which the test's Attr must have.
    68  // Each item is one or '|'-connected multiple attr names, and Attr must contain at least one of them.
    69  func Attr(t *gotesting.T, f TestFilter, requiredAttr []string) {
    70  	for _, tst := range getTests(t, f) {
    71  		attr := make(map[string]struct{})
    72  		for _, at := range tst.Attr {
    73  			attr[at] = struct{}{}
    74  		}
    75  	CheckLoop:
    76  		for _, at := range requiredAttr {
    77  			for _, item := range strings.Split(at, "|") {
    78  				if _, ok := attr[item]; ok {
    79  					continue CheckLoop
    80  				}
    81  			}
    82  			t.Errorf("%s: missing attribute %q", tst.Name, at)
    83  		}
    84  	}
    85  }
    86  
    87  // IfAttr checks that tests matched by f declare requiredAttr as Attr if all Attr in criteriaAttr are present.
    88  // criteriaAttr is a list of items to apply to test's Attr.
    89  // requiredAttr is a list of items which the test's Attr must have if criteriaAttr are matched.
    90  // Each item is one or '|'-connected multiple attr names, and Attr must contain at least one of them.
    91  // Example, criteriaAttr=["A", "B|C"], requiredAttr=["D", "E|F"]
    92  // Any tests with Attr A and either B or C should define Attr D and either E or F.
    93  func IfAttr(t *gotesting.T, f TestFilter, criteriaAttr, requiredAttr []string) {
    94  TestLoop:
    95  	for _, tst := range getTests(t, f) {
    96  		attr := make(map[string]struct{})
    97  		for _, at := range tst.Attr {
    98  			attr[at] = struct{}{}
    99  		}
   100  	CriteriaLoop:
   101  		for _, at := range criteriaAttr {
   102  			for _, item := range strings.Split(at, "|") {
   103  				if _, ok := attr[item]; ok {
   104  					continue CriteriaLoop
   105  				}
   106  			}
   107  			// Any one of the criteria is not met.
   108  			continue TestLoop
   109  		}
   110  	CheckLoop:
   111  		for _, at := range requiredAttr {
   112  			for _, item := range strings.Split(at, "|") {
   113  				if _, ok := attr[item]; ok {
   114  					continue CheckLoop
   115  				}
   116  			}
   117  			t.Errorf("%s: missing attribute %q", tst.Name, at)
   118  		}
   119  	}
   120  }
   121  
   122  // SoftwareDeps checks that tests matched by f declare requiredDeps as software dependencies.
   123  // requiredDeps is a list of items which the test's SoftwareDeps needs to
   124  // satisfy. Each item is one or '|'-connected multiple software feature names,
   125  // and SoftwareDeps must contain at least one of them.
   126  // TODO: b/225978622 -- support multi-dut in test check.
   127  func SoftwareDeps(t *gotesting.T, f TestFilter, requiredDeps []string) {
   128  	for _, tst := range getTests(t, f) {
   129  		deps := make(map[string]struct{})
   130  		for _, d := range tst.SoftwareDeps[""] {
   131  			deps[d] = struct{}{}
   132  		}
   133  	CheckLoop:
   134  		for _, d := range requiredDeps {
   135  			for _, item := range strings.Split(d, "|") {
   136  				if _, ok := deps[item]; ok {
   137  					continue CheckLoop
   138  				}
   139  			}
   140  			t.Errorf("%s: missing software dependency %q", tst.Name, d)
   141  		}
   142  	}
   143  }
   144  
   145  // EntityType represents a type of an entity, such as a test or a fixture.
   146  type EntityType int
   147  
   148  const (
   149  	// Test represents that an entity is a test.
   150  	Test EntityType = iota
   151  	// Fixture represents that an entity is a fixture.
   152  	Fixture
   153  )
   154  
   155  func (e EntityType) String() string {
   156  	switch e {
   157  	case Test:
   158  		return "test"
   159  	case Fixture:
   160  		return "fixture"
   161  	default:
   162  		return fmt.Sprintf("unknown(%d)", e)
   163  	}
   164  }
   165  
   166  // Entity represents a node in the dependency graph of tests and fixtures.
   167  type Entity struct {
   168  	Type         EntityType
   169  	Name         string
   170  	Parent       string
   171  	PrivateAttrs map[string]struct{}
   172  	Attrs        map[string]struct{}
   173  }
   174  
   175  // HasPrivateAttr returns whether the node has a given private attribute.
   176  func (e *Entity) HasPrivateAttr(name string) bool {
   177  	_, ok := e.PrivateAttrs[name]
   178  	return ok
   179  }
   180  
   181  // HasAttr returns whether the node has given Attr.
   182  func (e *Entity) HasAttr(name string) bool {
   183  	_, ok := e.Attrs[name]
   184  	return ok
   185  }
   186  
   187  // Entities gives all dependency data of all tests.
   188  func Entities() map[string]Entity {
   189  	stringSet := func(list []string) map[string]struct{} {
   190  		m := make(map[string]struct{})
   191  		for _, v := range list {
   192  			m[v] = struct{}{}
   193  		}
   194  		return m
   195  	}
   196  	result := make(map[string]Entity)
   197  	for _, tst := range allTests() {
   198  		result[tst.Name] = Entity{
   199  			Type:         Test,
   200  			Name:         tst.Name,
   201  			Parent:       tst.Fixture,
   202  			PrivateAttrs: stringSet(tst.PrivateAttr),
   203  			Attrs:        stringSet(tst.Attr),
   204  		}
   205  	}
   206  	for n, f := range testing.GlobalRegistry().AllFixtures() {
   207  		result[n] = Entity{
   208  			Type:         Fixture,
   209  			Name:         n,
   210  			Parent:       f.Parent,
   211  			PrivateAttrs: stringSet(f.PrivateAttr),
   212  			Attrs:        nil,
   213  		}
   214  	}
   215  	return result
   216  }