pkg/testing/define/define_autodiscovery.go (227 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package define import ( "fmt" "sync" "gopkg.in/yaml.v3" ) // Default platforms. Overridable using InitAutodiscovery() var defaultPlatforms = []TestPlatform{ {OS: Windows, Arch: AMD64}, // Not supported by default // {OS: Windows, Arch: ARM64}, // Current batching mechanism support this, not sure it's correct {OS: Darwin, Arch: AMD64}, {OS: Darwin, Arch: ARM64}, {OS: Linux, Arch: AMD64}, {OS: Linux, Arch: ARM64}, } var defaultTestOS = TestOS{ Name: "", Version: "", } var defaultTestPlatform = TestPlatform{ OS: "", Arch: "", } // YAML/JSON output structs type OutputRunner struct { OSFamily string `json:"os_family" yaml:"os_family" ` Arch string `json:"arch,omitempty"` OS string `json:"os,omitempty"` Version string `json:"version,omitempty"` Groups []OutputGroup `json:"groups,omitempty"` } type OutputGroup struct { Name string Tests []OutputTest } type OutputTest struct { Name string Metadata TestMetadata } // structs to aggregate test information type TestPlatform struct { OS string `json:"os" yaml:"os"` Arch string `json:"arch" yaml:"arch"` } type TestMetadata struct { Local bool `json:"local" yaml:"local"` Sudo bool `json:"sudo" yaml:"sudo"` } type TestOS struct { Name string `json:"name" yaml:"name"` Version string `json:"version" yaml:"version"` } type TestGroup struct { Tests map[string]TestMetadata } func NewTestGroup() TestGroup { return TestGroup{ Tests: map[string]TestMetadata{}, } } type TestByOS struct { Groups map[string]TestGroup } func NewTestByOS() TestByOS { return TestByOS{ Groups: map[string]TestGroup{}, } } type TestsByPlatform struct { OperatingSystems map[TestOS]TestByOS `json:"os" yaml:"os"` } func NewTestsByPlatform() TestsByPlatform { return TestsByPlatform{OperatingSystems: map[TestOS]TestByOS{}} } type DiscoveredTests struct { Discovered map[TestPlatform]TestsByPlatform } // test autodiscovery aggregator var testAutodiscovery *DiscoveredTests var testAutodiscoveryMx sync.Mutex type Named interface { Name() string } func InitAutodiscovery(initDefaultPlatforms []TestPlatform) { testAutodiscoveryMx.Lock() defer testAutodiscoveryMx.Unlock() testAutodiscovery = &DiscoveredTests{ Discovered: map[TestPlatform]TestsByPlatform{}, } if initDefaultPlatforms != nil { defaultPlatforms = initDefaultPlatforms } } func DumpAutodiscoveryYAML() ([]byte, error) { testAutodiscoveryMx.Lock() defer testAutodiscoveryMx.Unlock() err := testAutodiscovery.normalizeDiscoveredTests() if err != nil { return nil, fmt.Errorf("normalizing discovered tests: %w", err) } runners := mapToRunners(testAutodiscovery) return yaml.Marshal(runners) } func mapToRunners(autodiscovery *DiscoveredTests) []OutputRunner { var mapped []OutputRunner for pltf, testsByOS := range autodiscovery.Discovered { for testOS, testsByOS := range testsByOS.OperatingSystems { or := OutputRunner{ OSFamily: pltf.OS, Arch: pltf.Arch, OS: testOS.Name, Version: testOS.Version, Groups: make([]OutputGroup, 0, len(testsByOS.Groups)), } for groupName, groupTests := range testsByOS.Groups { or.Groups = append(or.Groups, mapGroup(groupName, groupTests)) } mapped = append(mapped, or) } } return mapped } func mapGroup(name string, group TestGroup) OutputGroup { og := OutputGroup{Name: name, Tests: make([]OutputTest, 0, len(group.Tests))} for testName, testMetadata := range group.Tests { og.Tests = append(og.Tests, OutputTest{ Name: testName, Metadata: testMetadata, }) } return og } func discoverTest(test Named, reqs Requirements) { testAutodiscoveryMx.Lock() defer testAutodiscoveryMx.Unlock() for _, p := range getPlatforms(reqs.OS) { if testAutodiscovery == nil { panic("testAutodiscovery is nil. Check that InitAutodiscovery() has been called properly") } mappedOSesForPlatform := ensureMapping(testAutodiscovery.Discovered, p, NewTestsByPlatform) osForPlatform := getOSForPlatform(reqs.OS, p) for _, o := range osForPlatform { testsByOS := ensureMapping(mappedOSesForPlatform.OperatingSystems, o, NewTestByOS) testGroup := ensureMapping(testsByOS.Groups, reqs.Group, NewTestGroup) testGroup.Tests[test.Name()] = TestMetadata{ Local: reqs.Local, Sudo: reqs.Sudo, } } } } func ensureMapping[K comparable, V any](mappings map[K]V, k K, newValueCreateFunc func() V) V { if existingValue, ok := mappings[k]; ok { return existingValue } newValue := newValueCreateFunc() mappings[k] = newValue return newValue } func getOSForPlatform(os []OS, p TestPlatform) []TestOS { var matchingOSes []TestOS for _, o := range os { if o.Type == p.OS && o.Arch == p.Arch { matchingOSes = append(matchingOSes, getTestOS(o)) } } if len(matchingOSes) > 0 { return matchingOSes } // no other OS has matched, return the default OS return []TestOS{ defaultTestOS, } } func getTestOS(o OS) TestOS { switch { case o.Type == Linux: return TestOS{ Name: o.Distro, Version: o.Version, } default: return TestOS{ Name: o.Type, Version: o.Version, } } } func getPlatforms(os []OS) []TestPlatform { if len(os) == 0 { return []TestPlatform{defaultTestPlatform} } platforms := make([]TestPlatform, 0, len(os)) for _, o := range os { platforms = append(platforms, TestPlatform{ OS: o.Type, Arch: o.Arch, }) } return platforms } // Normalization functions func (dt *DiscoveredTests) normalizeDiscoveredTests() error { normalized := map[TestPlatform]TestsByPlatform{} for pltf, oses := range dt.Discovered { if pltf.OS == "" && pltf.Arch != "" { return fmt.Errorf("platform not supported: %v", pltf) } if pltf.OS != "" && pltf.Arch != "" { existingOSes := ensureMapping(normalized, pltf, NewTestsByPlatform) // normal case, append to normalized and go to the next platform existingOSes.mergeOSes(oses) continue } // Arch and/or OS is not specified: fill in the supported archs for the OS type (potentially for all OSes) for i, dp := range defaultPlatforms { if pltf.OS == "" || pltf.OS == dp.OS { existingOSes := ensureMapping(normalized, defaultPlatforms[i], NewTestsByPlatform) existingOSes.mergeOSes(oses) } } } dt.Discovered = normalized return nil } func (tbp *TestsByPlatform) mergeOSes(from TestsByPlatform) { for testOS, testsByOS := range from.OperatingSystems { // iterate over all the OS definitions, ensuring that the entry exists in the destination map existingTestsByOS := ensureMapping(tbp.OperatingSystems, testOS, NewTestByOS) // iterate over source groups for this OS and merge for grp, tests := range testsByOS.Groups { // iterate over all the OS definitions, ensuring that the entry exists in the destination map existingGroup := ensureMapping(existingTestsByOS.Groups, grp, NewTestGroup) // add all the tests for testName, testMeta := range tests.Tests { existingGroup.Tests[testName] = testMeta } } } }