k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/prune-junit-xml/prunexml.go (about) 1 /* 2 Copyright 2022 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 main 18 19 import ( 20 "encoding/xml" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "regexp" 26 27 "k8s.io/kubernetes/third_party/forked/gotestsum/junitxml" 28 ) 29 30 func main() { 31 maxTextSize := flag.Int("max-text-size", 1, "maximum size of attribute or text (in MB)") 32 pruneTests := flag.Bool("prune-tests", true, 33 "prune's xml files to display only top level tests and failed sub-tests") 34 flag.Parse() 35 for _, path := range flag.Args() { 36 fmt.Printf("processing junit xml file : %s\n", path) 37 xmlReader, err := os.Open(path) 38 if err != nil { 39 panic(err) 40 } 41 defer xmlReader.Close() 42 suites, err := fetchXML(xmlReader) // convert MB into bytes (roughly!) 43 if err != nil { 44 panic(err) 45 } 46 47 pruneXML(suites, *maxTextSize*1e6) // convert MB into bytes (roughly!) 48 if *pruneTests { 49 pruneTESTS(suites) 50 } 51 52 xmlWriter, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 53 if err != nil { 54 panic(err) 55 } 56 defer xmlWriter.Close() 57 err = streamXML(xmlWriter, suites) 58 if err != nil { 59 panic(err) 60 } 61 fmt.Println("done.") 62 } 63 } 64 65 func pruneXML(suites *junitxml.JUnitTestSuites, maxBytes int) { 66 for _, suite := range suites.Suites { 67 for _, testcase := range suite.TestCases { 68 if testcase.SkipMessage != nil { 69 if len(testcase.SkipMessage.Message) > maxBytes { 70 fmt.Printf("clipping skip message in test case : %s\n", testcase.Name) 71 head := testcase.SkipMessage.Message[:maxBytes/2] 72 tail := testcase.SkipMessage.Message[len(testcase.SkipMessage.Message)-maxBytes/2:] 73 testcase.SkipMessage.Message = head + "[...clipped...]" + tail 74 } 75 } 76 if testcase.Failure != nil { 77 if len(testcase.Failure.Contents) > maxBytes { 78 fmt.Printf("clipping failure message in test case : %s\n", testcase.Name) 79 head := testcase.Failure.Contents[:maxBytes/2] 80 tail := testcase.Failure.Contents[len(testcase.Failure.Contents)-maxBytes/2:] 81 testcase.Failure.Contents = head + "[...clipped...]" + tail 82 } 83 } 84 } 85 } 86 } 87 88 // This function condenses the junit xml to have package name as top level identifier 89 // and nesting under that. 90 func pruneTESTS(suites *junitxml.JUnitTestSuites) { 91 var updatedTestsuites []junitxml.JUnitTestSuite 92 93 for _, suite := range suites.Suites { 94 var updatedTestcases []junitxml.JUnitTestCase 95 var updatedTestcase junitxml.JUnitTestCase 96 var updatedTestcaseFailure junitxml.JUnitFailure 97 failflag := false 98 name := suite.Name 99 regex := regexp.MustCompile(`^(.*?)/([^/]+)/?$`) 100 match := regex.FindStringSubmatch(name) 101 updatedTestcase.Classname = match[1] 102 updatedTestcase.Name = match[2] 103 updatedTestcase.Time = suite.Time 104 for _, testcase := range suite.TestCases { 105 // The top level testcase element in a JUnit xml file does not have the / character. 106 if testcase.Failure != nil { 107 failflag = true 108 updatedTestcaseFailure.Message = updatedTestcaseFailure.Message + testcase.Failure.Message + ";" 109 updatedTestcaseFailure.Contents = updatedTestcaseFailure.Contents + testcase.Failure.Contents + ";" 110 updatedTestcaseFailure.Type = updatedTestcaseFailure.Type + testcase.Failure.Type 111 } 112 } 113 if failflag { 114 updatedTestcase.Failure = &updatedTestcaseFailure 115 } 116 suite.TestCases = append(updatedTestcases, updatedTestcase) 117 updatedTestsuites = append(updatedTestsuites, suite) 118 } 119 suites.Suites = updatedTestsuites 120 } 121 122 func fetchXML(xmlReader io.Reader) (*junitxml.JUnitTestSuites, error) { 123 decoder := xml.NewDecoder(xmlReader) 124 var suites junitxml.JUnitTestSuites 125 err := decoder.Decode(&suites) 126 if err != nil { 127 return nil, err 128 } 129 return &suites, nil 130 } 131 132 func streamXML(writer io.Writer, in *junitxml.JUnitTestSuites) error { 133 _, err := writer.Write([]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")) 134 if err != nil { 135 return err 136 } 137 encoder := xml.NewEncoder(writer) 138 encoder.Indent("", "\t") 139 err = encoder.Encode(in) 140 if err != nil { 141 return err 142 } 143 return encoder.Flush() 144 }