github.com/joelanford/operator-sdk@v0.8.2/internal/pkg/scorecard/test_definitions.go (about)

     1  // Copyright 2019 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package scorecard
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	scapiv1alpha1 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha1"
    22  )
    23  
    24  // Type Definitions
    25  
    26  // Test provides methods for running scorecard tests
    27  type Test interface {
    28  	GetName() string
    29  	GetDescription() string
    30  	IsCumulative() bool
    31  	Run(context.Context) *TestResult
    32  }
    33  
    34  // TestResult contains a test's points, suggestions, and errors
    35  type TestResult struct {
    36  	State         scapiv1alpha1.State
    37  	Test          Test
    38  	EarnedPoints  int
    39  	MaximumPoints int
    40  	Suggestions   []string
    41  	Errors        []error
    42  }
    43  
    44  // TestInfo contains information about the scorecard test
    45  type TestInfo struct {
    46  	Name        string
    47  	Description string
    48  	// If a test is set to cumulative, the scores of multiple runs of the same test on separate CRs are added together for the total score.
    49  	// If cumulative is false, if any test failed, the total score is 0/1. Otherwise 1/1.
    50  	Cumulative bool
    51  }
    52  
    53  // GetName return the test name
    54  func (i TestInfo) GetName() string { return i.Name }
    55  
    56  // GetDescription returns the test description
    57  func (i TestInfo) GetDescription() string { return i.Description }
    58  
    59  // IsCumulative returns true if the test's scores are intended to be cumulative
    60  func (i TestInfo) IsCumulative() bool { return i.Cumulative }
    61  
    62  // TestSuite contains a list of tests and results, along with the relative weights of each test. Also can optionally contain a log
    63  type TestSuite struct {
    64  	TestInfo
    65  	Tests       []Test
    66  	TestResults []TestResult
    67  	Weights     map[string]float64
    68  	Log         string
    69  }
    70  
    71  // Helper functions
    72  
    73  // AddTest adds a new Test to a TestSuite along with a relative weight for the new Test
    74  func (ts *TestSuite) AddTest(t Test, weight float64) {
    75  	ts.Tests = append(ts.Tests, t)
    76  	ts.Weights[t.GetName()] = weight
    77  }
    78  
    79  // TotalScore calculates and returns the total score of all run Tests in a TestSuite
    80  func (ts *TestSuite) TotalScore() (score int) {
    81  	floatScore := 0.0
    82  	for _, result := range ts.TestResults {
    83  		if result.MaximumPoints != 0 {
    84  			floatScore += (float64(result.EarnedPoints) / float64(result.MaximumPoints)) * ts.Weights[result.Test.GetName()]
    85  		}
    86  	}
    87  	// scale to a percentage
    88  	addedWeights := 0.0
    89  	for _, weight := range ts.Weights {
    90  		addedWeights += weight
    91  	}
    92  	// protect against divide by zero for failed plugins
    93  	if addedWeights == 0 {
    94  		return 0
    95  	}
    96  	return int(floatScore * (100 / addedWeights))
    97  }
    98  
    99  // Run runs all Tests in a TestSuite
   100  func (ts *TestSuite) Run(ctx context.Context) {
   101  	for _, test := range ts.Tests {
   102  		ts.TestResults = append(ts.TestResults, *test.Run(ctx))
   103  	}
   104  }
   105  
   106  // NewTestSuite returns a new TestSuite with a given name and description
   107  func NewTestSuite(name, description string) *TestSuite {
   108  	return &TestSuite{
   109  		TestInfo: TestInfo{
   110  			Name:        name,
   111  			Description: description,
   112  		},
   113  		Weights: make(map[string]float64),
   114  	}
   115  }
   116  
   117  // MergeSuites takes an array of TestSuites and combines all suites with the same name
   118  func MergeSuites(suites []TestSuite) ([]TestSuite, error) {
   119  	suiteMap := make(map[string][]TestSuite)
   120  	for _, suite := range suites {
   121  		suiteMap[suite.GetName()] = append(suiteMap[suite.GetName()], suite)
   122  	}
   123  	mergedSuites := []TestSuite{}
   124  	for _, suiteSlice := range suiteMap {
   125  		testMap := make(map[string][]TestResult)
   126  		for _, suite := range suiteSlice {
   127  			for _, result := range suite.TestResults {
   128  				testMap[result.Test.GetName()] = append(testMap[result.Test.GetName()], result)
   129  			}
   130  		}
   131  		mergedTestResults := []TestResult{}
   132  		for _, testSlice := range testMap {
   133  			if testSlice[0].Test.IsCumulative() {
   134  				newResult, err := ResultsCumulative(testSlice)
   135  				if err != nil {
   136  					return nil, fmt.Errorf("failed to combine test results: %s", err)
   137  				}
   138  				mergedTestResults = append(mergedTestResults, newResult)
   139  			} else {
   140  				newResult, err := ResultsPassFail(testSlice)
   141  				if err != nil {
   142  					return nil, fmt.Errorf("failed to combine test results: %s", err)
   143  				}
   144  				mergedTestResults = append(mergedTestResults, newResult)
   145  			}
   146  		}
   147  		newSuite := suiteSlice[0]
   148  		newSuite.TestResults = mergedTestResults
   149  		mergedSuites = append(mergedSuites, newSuite)
   150  	}
   151  	return mergedSuites, nil
   152  }