diff --git a/tools/apicoverage/apicoverage.go b/tools/apicoverage/apicoverage.go index c2e60fd602..6e007a7270 100644 --- a/tools/apicoverage/apicoverage.go +++ b/tools/apicoverage/apicoverage.go @@ -27,10 +27,20 @@ import ( "log" "os" "reflect" - "strconv" "strings" "github.com/knative/serving/pkg/apis/serving/v1alpha1" + "github.com/knative/test-infra/tools/gcs" + "github.com/knative/test-infra/tools/testgrid" +) + +const ( + logDir = "logs/ci-knative-serving-continuous/" + buildFile = "build-log.txt" + apiCoverage = "api_coverage" + overallRoute = "OverallRoute" + overallConfig = "OverallConfiguration" + overallService = "OverallService" ) // ResourceObjects defines the resource objects in knative-serving @@ -168,6 +178,45 @@ func initCoverage() *OverallAPICoverage { return &coverage } +func getRelevantLogs(fields []string) *string { + // I0727 16:23:30.055] 2018-10-12T18:18:06.835-0700 info TestRouteCreation test/configuration.go:34 resource {: }"} + if len(fields) == 8 && fields[3] == "info" && fields[6] == "resource" { + s := strings.Join(fields[7:], " ") + return &s + } + return nil +} + +func createCases(tcName string, covered map[string]int, notCovered map[string]int) []testgrid.TestCase { + var tc []testgrid.TestCase + + var percentCovered = float32(100 * len(covered) / (len(covered) + len(notCovered))) + tp := []testgrid.TestProperty{testgrid.TestProperty{Name: apiCoverage, Value: percentCovered}} + tc = append(tc, testgrid.TestCase{Name: tcName, Properties: testgrid.TestProperties{Property: tp}, Fail: false}) + + for key, value := range covered { + tp := []testgrid.TestProperty{testgrid.TestProperty{Name: apiCoverage, Value: float32(value)}} + tc = append(tc, testgrid.TestCase{Name: tcName + "/" + key, Properties: testgrid.TestProperties{Property: tp}, Fail: false}) + } + + for key, value := range notCovered { + tp := []testgrid.TestProperty{testgrid.TestProperty{Name: apiCoverage, Value: float32(value)}} + tc = append(tc, testgrid.TestCase{Name: tcName + "/" + key, Properties: testgrid.TestProperties{Property: tp}, Fail: true}) + } + return tc +} + +func createTestgridXML(coverage *OverallAPICoverage, artifactsDir string) { + tc := createCases(overallRoute, coverage.RouteAPICovered, coverage.RouteAPINotCovered) + tc = append(tc, createCases(overallConfig, coverage.ConfigurationAPICovered, coverage.ConfigurationAPINotCovered)...) + tc = append(tc, createCases(overallService, coverage.ServiceAPICovered, coverage.ServiceAPINotCovered)...) + ts := testgrid.TestSuite{TestCases: tc} + + if err := testgrid.CreateXMLOutput(ts, artifactsDir); err != nil { + log.Fatalf("Cannot create the xml output file: %v", err) + } +} + func main() { artifactsDir := flag.String("artifacts-dir", "./artifacts", "Directory to store the generated XML file") @@ -176,18 +225,16 @@ func main() { // Read the latest-build.txt file to get the latest build number ctx := context.Background() - contents, err := readGcsFile(ctx, logDir+sourceDir+"/latest-build.txt", *serviceAccount) - if err != nil { - log.Fatalf("Cannot get latest build number. %s: %v", contents, err) - } - latestBuild, err := strconv.Atoi(string(contents)) + num, err := gcs.GetLatestBuildNumber(ctx, logDir, *serviceAccount) if err != nil { - log.Fatalf("Cannot convert %s to string to get latest build %v", string(contents), err) + log.Fatalf("Cannot get latest build number: %v", err) } // Calculate coverage coverage := initCoverage() - calculateCoverage(parseLog(ctx, fmt.Sprintf("%s/%d", sourceDir, latestBuild), false, coverage), coverage) + calculateCoverage( + gcs.ParseLog(ctx, fmt.Sprintf("%s/%d/%s", logDir, num, buildFile), getRelevantLogs), + coverage) // Write the testgrid xml to artifacts createTestgridXML(coverage, *artifactsDir) diff --git a/tools/apicoverage/testgrid.go b/tools/apicoverage/testgrid.go deleted file mode 100644 index 06901e6e21..0000000000 --- a/tools/apicoverage/testgrid.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2018 The Knative 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. -*/ - -// testgrid.go provides methods to perform action on testgrid. - -package main - -import ( - "encoding/xml" - "log" - "os" -) - -const ( - apiCoverage = "api_coverage" - overallRoute = "OverallRoute" - overallConfig = "OverallConfiguration" - overallService = "OverallService" -) - -// TestProperty defines a property of the test -type TestProperty struct { - Name string `xml:"name,attr"` - Value float32 `xml:"value,attr"` -} - -// TestProperties is an array of test properties -type TestProperties struct { - Property []TestProperty `xml:"property"` -} - -// TestCase defines a test case that was executed -type TestCase struct { - ClassName string `xml:"class_name,attr"` - Name string `xml:"name,attr"` - Time int `xml:"time,attr"` - Properties TestProperties `xml:"properties"` - Fail bool `xml:"failure,omitempty"` -} - -// TestSuite defines the set of relevant test cases -type TestSuite struct { - XMLName xml.Name `xml:"testsuite"` - TestCases []TestCase `xml:"testcase"` -} - -func createTestProperty(value float32) TestProperty { - return TestProperty{Name: apiCoverage, Value: value} -} - -func createTestCase(name string, properties []TestProperty, fail bool) TestCase { - tc := TestCase{ClassName: apiCoverage, Name: name, Properties: TestProperties{Property: properties}, Time: 0} - if fail { - tc.Fail = true - } - return tc -} - -func createTestSuite(cases []TestCase) TestSuite { - return TestSuite{TestCases: cases} -} - -func createCases(tcName string, covered map[string]int, notCovered map[string]int) []TestCase { - var tc []TestCase - - var percentCovered = float32(100 * len(covered) / (len(covered) + len(notCovered))) - tp := []TestProperty{createTestProperty(percentCovered)} - tc = append(tc, createTestCase(tcName, tp, false)) - - for key, value := range covered { - tp := []TestProperty{createTestProperty(float32(value))} - tc = append(tc, createTestCase(tcName+"/"+key, tp, false)) - } - - for key, value := range notCovered { - tp := []TestProperty{createTestProperty(float32(value))} - tc = append(tc, createTestCase(tcName+"/"+key, tp, true)) - } - return tc -} - -func createTestgridXML(coverage *OverallAPICoverage, artifactsDir string) { - tc := createCases(overallRoute, coverage.RouteAPICovered, coverage.RouteAPINotCovered) - tc = append(tc, createCases(overallConfig, coverage.ConfigurationAPICovered, coverage.ConfigurationAPINotCovered)...) - tc = append(tc, createCases(overallService, coverage.ServiceAPICovered, coverage.ServiceAPINotCovered)...) - ts := createTestSuite(tc) - - op, err := xml.MarshalIndent(ts, "", " ") - if err != nil { - log.Fatalf("Error creating xml: %v", err) - } - - outputFile := artifactsDir + "/junit_bazel.xml" - f, err := os.Create(outputFile) - if err != nil { - log.Fatalf("Cannot create '%s': %v", outputFile, err) - } - defer f.Close() - if _, err := f.WriteString(string(op) + "\n"); err != nil { - log.Fatalf("Cannot write to '%s': %v", f.Name(), err) - } -} diff --git a/tools/apicoverage/gcs.go b/tools/gcs/gcs.go similarity index 60% rename from tools/apicoverage/gcs.go rename to tools/gcs/gcs.go index 3a6b8a324e..a41fbbb21a 100644 --- a/tools/apicoverage/gcs.go +++ b/tools/gcs/gcs.go @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// gcs.go defines functions on gcs +// gcs.go defines functions to use GCS -package main +package gcs import ( "bufio" @@ -24,6 +24,7 @@ import ( "fmt" "io/ioutil" "log" + "strconv" "strings" "cloud.google.com/go/storage" @@ -31,9 +32,8 @@ import ( ) const ( - logDir = "logs/" - sourceDir = "ci-knative-serving-continuous" bucketName = "knative-prow" + latest = "latest-build.txt" ) var client *storage.Client @@ -48,7 +48,22 @@ func createStorageObject(filename string) *storage.ObjectHandle { return client.Bucket(bucketName).Object(filename) } -func readGcsFile(ctx context.Context, filename string, sa string) ([]byte, error) { +// GetLatestBuildNumber gets the latest build number for the specified log directory +func GetLatestBuildNumber(ctx context.Context, logDir string, sa string) (int, error) { + contents, err := ReadGcsFile(ctx, logDir+latest, sa) + if err != nil { + return 0, err + } + latestBuild, err := strconv.Atoi(string(contents)) + if err != nil { + return 0, err + } + + return latestBuild, nil +} + +//ReadGcsFile reads the specified file using the provided service account +func ReadGcsFile(ctx context.Context, filename string, sa string) ([]byte, error) { // Create a new GCS client if err := createStorageClient(ctx, sa); err != nil { log.Fatalf("Failed to create GCS client: %v", err) @@ -69,33 +84,29 @@ func readGcsFile(ctx context.Context, filename string, sa string) ([]byte, error return contents, nil } -func parseLog(ctx context.Context, dir string, isLocal bool, coverage *OverallAPICoverage) []string { - var covLogs []string +// ParseLog parses the log and returns the lines where the checkLog func does not return an empty slice. +// checkLog function should take in the log statement and return a part from that statement that should be in the log output. +func ParseLog(ctx context.Context, filename string, checkLog func(s []string) *string) []string { + var logs []string - buildDir := logDir + dir - log.Printf("Parsing '%s'", buildDir) - logFile := buildDir + "/build-log.txt" - log.Printf("Parsing '%s'", logFile) - o := createStorageObject(logFile) + log.Printf("Parsing '%s'", filename) + o := createStorageObject(filename) if _, err := o.Attrs(ctx); err != nil { - log.Printf("Cannot get attributes of '%s', assuming not ready yet: %v", logFile, err) + log.Printf("Cannot get attributes of '%s', assuming not ready yet: %v", filename, err) return nil } f, err := o.NewReader(ctx) if err != nil { - log.Fatalf("Error opening '%s': %v", logFile, err) + log.Fatalf("Error opening '%s': %v", filename, err) } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - - // I0727 16:23:30.055] info TestRouteCreation test/configuration.go:34 resource {: }"} - if len(fields) == 7 && fields[2] == "info" && fields[5] == "resource" { - covLogs = append(covLogs, strings.Join(fields[6:], " ")) + if s := checkLog(strings.Fields(scanner.Text())); s != nil { + logs = append(logs, *s) } } - return covLogs + return logs } diff --git a/tools/testgrid/testgrid.go b/tools/testgrid/testgrid.go new file mode 100644 index 0000000000..30d7ff2c13 --- /dev/null +++ b/tools/testgrid/testgrid.go @@ -0,0 +1,69 @@ +/* +Copyright 2018 The Knative 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. +*/ + +// testgrid.go provides methods to perform action on testgrid. + +package testgrid + +import ( + "encoding/xml" + "os" +) + +// TestProperty defines a property of the test +type TestProperty struct { + Name string `xml:"name,attr"` + Value float32 `xml:"value,attr"` +} + +// TestProperties is an array of test properties +type TestProperties struct { + Property []TestProperty `xml:"property"` +} + +// TestCase defines a test case that was executed +type TestCase struct { + ClassName string `xml:"class_name,attr"` + Name string `xml:"name,attr"` + Time int `xml:"time,attr"` + Properties TestProperties `xml:"properties"` + Fail bool `xml:"failure,omitempty"` +} + +// TestSuite defines the set of relevant test cases +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + TestCases []TestCase `xml:"testcase"` +} + +// CreateXMLOutput creates the junit xml file in the provided artifacts directory +func CreateXMLOutput(ts TestSuite, artifactsDir string) error { + op, err := xml.MarshalIndent(ts, "", " ") + if err != nil { + return err + } + + outputFile := artifactsDir + "/junit_bazel.xml" + f, err := os.Create(outputFile) + if err != nil { + return err + } + defer f.Close() + if _, err := f.WriteString(string(op) + "\n"); err != nil { + return err + } + return nil +}