k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/bugs.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package framework 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "sync" 27 28 "github.com/onsi/ginkgo/v2/types" 29 ) 30 31 var ( 32 bugs []Bug 33 bugMutex sync.Mutex 34 ) 35 36 // RecordBug stores information about a bug in the E2E suite source code that 37 // cannot be reported through ginkgo.Fail because it was found outside of some 38 // test, for example during test registration. 39 // 40 // This can be used instead of raising a panic. Then all bugs can be reported 41 // together instead of failing after the first one. 42 func RecordBug(bug Bug) { 43 bugMutex.Lock() 44 defer bugMutex.Unlock() 45 46 bugs = append(bugs, bug) 47 } 48 49 type Bug struct { 50 FileName string 51 LineNumber int 52 Message string 53 } 54 55 // NewBug creates a new bug with a location that is obtained by skipping a certain number 56 // of stack frames. Passing zero will record the source code location of the direct caller 57 // of NewBug. 58 func NewBug(message string, skip int) Bug { 59 location := types.NewCodeLocation(skip + 1) 60 return Bug{FileName: location.FileName, LineNumber: location.LineNumber, Message: message} 61 } 62 63 // FormatBugs produces a report that includes all bugs recorded earlier via 64 // RecordBug. An error is returned with the report if there have been bugs. 65 func FormatBugs() error { 66 bugMutex.Lock() 67 defer bugMutex.Unlock() 68 69 if len(bugs) == 0 { 70 return nil 71 } 72 73 lines := make([]string, 0, len(bugs)) 74 wd, err := os.Getwd() 75 if err != nil { 76 return fmt.Errorf("get current directory: %v", err) 77 } 78 // Sort by file name, line number, message. For the sake of simplicity 79 // this uses the full file name even though the output the may use a 80 // relative path. Usually the result should be the same because full 81 // paths will all have the same prefix. 82 sort.Slice(bugs, func(i, j int) bool { 83 switch strings.Compare(bugs[i].FileName, bugs[j].FileName) { 84 case -1: 85 return true 86 case 1: 87 return false 88 } 89 if bugs[i].LineNumber < bugs[j].LineNumber { 90 return true 91 } 92 if bugs[i].LineNumber > bugs[j].LineNumber { 93 return false 94 } 95 return bugs[i].Message < bugs[j].Message 96 }) 97 for _, bug := range bugs { 98 // Use relative paths, if possible. 99 path := bug.FileName 100 if wd != "" { 101 if relpath, err := filepath.Rel(wd, bug.FileName); err == nil { 102 path = relpath 103 } 104 } 105 lines = append(lines, fmt.Sprintf("ERROR: %s:%d: %s\n", path, bug.LineNumber, strings.TrimSpace(bug.Message))) 106 } 107 return errors.New(strings.Join(lines, "")) 108 }