/* Copyright 2020 Qiniu Cloud (qiniu.com) 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 qiniu import ( "encoding/json" "fmt" "log" "os" "path" "sort" "strconv" ) const ( //statusJSON is the JSON file that stores build success info statusJSON = "finished.json" // ArtifactsDirName is the name of directory defined in prow to store test artifacts ArtifactsDirName = "artifacts" //default prow coverage file PostSubmitCoverProfile = "filtered.cov" //default to save changed file related coverage profile ChangedProfileName = "changed-file-profile.cov" ) // sortBuilds converts all build from str to int and sorts all builds in descending order and // returns the sorted slice func sortBuilds(strBuilds []string) []int { var res []int for _, buildStr := range strBuilds { num, err := strconv.Atoi(buildStr) if err != nil { log.Printf("Non-int build number found: '%s'", buildStr) } else { res = append(res, num) } } sort.Sort(sort.Reverse(sort.IntSlice(res))) return res } type finishedStatus struct { Timestamp int Passed bool } func isBuildSucceeded(jsonText []byte) bool { var status finishedStatus err := json.Unmarshal(jsonText, &status) return err == nil && status.Passed } // FindBaseProfileFromQiniu finds the coverage profile file from the latest healthy build // stored in given gcs directory func FindBaseProfileFromQiniu(qc *Client, prowJobName, covProfileName string) ([]byte, error) { dirOfJob := path.Join("logs", prowJobName) prefix := dirOfJob + "/" strBuilds, err := qc.ListSubDirs(prefix) if err != nil { return nil, fmt.Errorf("error listing qiniu objects, prowjob:%v, err:%v", prowJobName, err) } if len(strBuilds) == 0 { log.Printf("no cover profiles found from remote, do nothing") return nil, nil } log.Printf("total sub dirs: %d", len(strBuilds)) builds := sortBuilds(strBuilds) profilePath := "" for _, build := range builds { buildDirPath := path.Join(dirOfJob, strconv.Itoa(build)) dirOfStatusJSON := path.Join(buildDirPath, statusJSON) statusText, err := qc.ReadObject(dirOfStatusJSON) if err != nil { log.Printf("Cannot read finished.json (%s) ", dirOfStatusJSON) } else if isBuildSucceeded(statusText) { artifactsDirPath := path.Join(buildDirPath, ArtifactsDirName) profilePath = path.Join(artifactsDirPath, covProfileName) break } } if profilePath == "" { log.Printf("no cover profiles found from remote job %s, do nothing", prowJobName) return nil, nil } log.Printf("base cover profile path: %s", profilePath) return qc.ReadObject(profilePath) } type Artifacts struct { Directory string ProfileName string ChangedProfileName string // create temporary to save changed file related coverage profile } func (a *Artifacts) ProfilePath() string { return path.Join(a.Directory, a.ProfileName) } func (a *Artifacts) CreateChangedProfile() *os.File { if a.ChangedProfileName == "" { log.Fatalf("param Artifacts.ChangedProfileName should not be empty") } p, err := os.Create(a.ChangedProfileName) log.Printf("os create: %s", a.ChangedProfileName) if err != nil { log.Fatalf("file(%s) create failed: %v", a.ChangedProfileName, err) } return p }