blob: 71ea11c7bc19447e1301bf9028741d254ebe1161 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package framework
import (
"fmt"
"os"
"path"
"reflect"
"strings"
"sync"
)
import (
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/cluster"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/features"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/label"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
"github.com/apache/dubbo-go-pixiu/pkg/test/scopes"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/yml"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
// suiteContext contains suite-level items used during runtime.
type SuiteContext interface {
resource.Context
}
var _ SuiteContext = &suiteContext{}
// suiteContext contains suite-level items used during runtime.
type suiteContext struct {
settings *resource.Settings
environment resource.Environment
skipped bool
workDir string
yml.FileWriter
// context-level resources
globalScope *scope
contextMu sync.Mutex
contextNames sets.Set
suiteLabels label.Set
outcomeMu sync.RWMutex
testOutcomes []TestOutcome
traces sync.Map
}
func newSuiteContext(s *resource.Settings, envFn resource.EnvironmentFactory, labels label.Set) (*suiteContext, error) {
scopeID := fmt.Sprintf("[suite(%s)]", s.TestID)
workDir := path.Join(s.RunDir(), "_suite_context")
if err := os.MkdirAll(workDir, os.ModePerm); err != nil {
return nil, err
}
c := &suiteContext{
settings: s,
globalScope: newScope(scopeID, nil),
workDir: workDir,
FileWriter: yml.NewFileWriter(workDir),
suiteLabels: labels,
contextNames: sets.New(),
}
env, err := envFn(c)
if err != nil {
return nil, err
}
c.environment = env
c.globalScope.add(env, &resourceID{id: scopeID})
return c, nil
}
// allocateContextID allocates a unique context id for TestContexts. Useful for creating unique names to help with
// debugging
func (s *suiteContext) allocateContextID(prefix string) string {
s.contextMu.Lock()
defer s.contextMu.Unlock()
candidate := prefix
discriminator := 0
for {
if !s.contextNames.Contains(candidate) {
s.contextNames.Insert(candidate)
return candidate
}
candidate = fmt.Sprintf("%s-%d", prefix, discriminator)
discriminator++
}
}
func (s *suiteContext) allocateResourceID(contextID string, r resource.Resource) string {
s.contextMu.Lock()
defer s.contextMu.Unlock()
t := reflect.TypeOf(r)
candidate := fmt.Sprintf("%s/[%s]", contextID, t.String())
discriminator := 0
for {
if !s.contextNames.Contains(candidate) {
s.contextNames.Insert(candidate)
return candidate
}
candidate = fmt.Sprintf("%s/[%s-%d]", contextID, t.String(), discriminator)
discriminator++
}
}
func (s *suiteContext) ConditionalCleanup(fn func()) {
s.globalScope.addCloser(&closer{fn: func() error {
fn()
return nil
}, noskip: true})
}
func (s *suiteContext) Cleanup(fn func()) {
s.globalScope.addCloser(&closer{fn: func() error {
fn()
return nil
}})
}
// TrackResource adds a new resource to track to the context at this level.
func (s *suiteContext) TrackResource(r resource.Resource) resource.ID {
id := s.allocateResourceID(s.globalScope.id, r)
rid := &resourceID{id: id}
s.globalScope.add(r, rid)
return rid
}
func (s *suiteContext) GetResource(ref interface{}) error {
return s.globalScope.get(ref)
}
func (s *suiteContext) Environment() resource.Environment {
return s.environment
}
func (s *suiteContext) Clusters() cluster.Clusters {
return s.Environment().Clusters()
}
func (s *suiteContext) AllClusters() cluster.Clusters {
return s.Environment().AllClusters()
}
// Settings returns the current runtime.Settings.
func (s *suiteContext) Settings() *resource.Settings {
return s.settings
}
// CreateDirectory creates a new subdirectory within this context.
func (s *suiteContext) CreateDirectory(name string) (string, error) {
dir, err := os.MkdirTemp(s.workDir, name)
if err != nil {
scopes.Framework.Errorf("Error creating temp dir: runID='%s', prefix='%s', workDir='%v', err='%v'",
s.settings.RunID, name, s.workDir, err)
} else {
scopes.Framework.Debugf("Created a temp dir: runID='%s', name='%s'", s.settings.RunID, dir)
}
return dir, err
}
// CreateTmpDirectory creates a new temporary directory with the given prefix.
func (s *suiteContext) CreateTmpDirectory(prefix string) (string, error) {
if len(prefix) != 0 && !strings.HasSuffix(prefix, "-") {
prefix += "-"
}
dir, err := os.MkdirTemp(s.workDir, prefix)
if err != nil {
scopes.Framework.Errorf("Error creating temp dir: runID='%s', prefix='%s', workDir='%v', err='%v'",
s.settings.RunID, prefix, s.workDir, err)
} else {
scopes.Framework.Debugf("Created a temp dir: runID='%s', Name='%s'", s.settings.RunID, dir)
}
return dir, err
}
func (s *suiteContext) ConfigKube(clusters ...cluster.Cluster) resource.ConfigManager {
return newConfigManager(s, clusters)
}
func (s *suiteContext) ConfigIstio() resource.ConfigManager {
return newConfigManager(s, s.Clusters().Configs())
}
type Outcome string
const (
Passed Outcome = "Passed"
Failed Outcome = "Failed"
Skipped Outcome = "Skipped"
NotImplemented Outcome = "NotImplemented"
)
type TestOutcome struct {
Name string
Type string
Outcome Outcome
FeatureLabels map[features.Feature][]string
}
func (s *suiteContext) registerOutcome(test *testImpl) {
s.outcomeMu.Lock()
defer s.outcomeMu.Unlock()
o := Passed
if test.notImplemented {
o = NotImplemented
} else if test.goTest.Failed() {
o = Failed
} else if test.goTest.Skipped() {
o = Skipped
}
newOutcome := TestOutcome{
Name: test.goTest.Name(),
Type: "integration",
Outcome: o,
FeatureLabels: test.featureLabels,
}
s.contextMu.Lock()
defer s.contextMu.Unlock()
s.testOutcomes = append(s.testOutcomes, newOutcome)
}
func (s *suiteContext) RecordTraceEvent(key string, value interface{}) {
s.traces.Store(key, value)
}
func (s *suiteContext) marshalTraceEvent() []byte {
kvs := map[string]interface{}{}
s.traces.Range(func(key, value interface{}) bool {
kvs[key.(string)] = value
return true
})
outer := map[string]interface{}{
fmt.Sprintf("suite/%s", s.settings.TestID): kvs,
}
d, _ := yaml.Marshal(outer)
return d
}