github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/findflakes/logs.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	"golang.org/x/build/types"
    17  )
    18  
    19  type Revision struct {
    20  	types.BuildRevision
    21  	Date time.Time
    22  
    23  	Builds []*Build
    24  
    25  	path string
    26  }
    27  
    28  func (r *Revision) String() string {
    29  	// Use time format from dashboard, plus year.
    30  	return fmt.Sprintf("%s %s", r.Revision[:7], r.Date.Format("02 Jan 15:04 2006"))
    31  }
    32  
    33  func (r *Revision) Subject() string {
    34  	subject := r.Desc
    35  	if i := strings.Index(subject, "\n"); i >= 0 {
    36  		subject = subject[:i]
    37  	}
    38  	return subject
    39  }
    40  
    41  func (r *Revision) OneLine() string {
    42  	return fmt.Sprintf("%s %s", r.Revision[:7], r.Subject())
    43  }
    44  
    45  type Build struct {
    46  	Revision *Revision
    47  	Builder  string
    48  	Status   BuildStatus
    49  	LogURL   string
    50  }
    51  
    52  type BuildStatus int
    53  
    54  const (
    55  	BuildOK BuildStatus = iota
    56  	BuildRunning
    57  	BuildFailed
    58  )
    59  
    60  func (b *Build) LogPath() string {
    61  	return filepath.Join(b.Revision.path, b.Builder)
    62  }
    63  
    64  func (b *Build) ReadLog() ([]byte, error) {
    65  	return ioutil.ReadFile(b.LogPath())
    66  }
    67  
    68  // LoadRevisions loads all saved build revisions from revDir, which
    69  // must be the "rev" directory written by fetchlogs. The returned
    70  // revisions are ordered from oldest to newest.
    71  func LoadRevisions(revDir string) ([]*Revision, error) {
    72  	revFiles, err := ioutil.ReadDir(revDir)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	revs := []*Revision{}
    78  	for _, revFile := range revFiles {
    79  		if !revFile.IsDir() {
    80  			continue
    81  		}
    82  
    83  		rev := &Revision{path: filepath.Join(revDir, revFile.Name())}
    84  
    85  		// Load revision metadata.
    86  		var builders []string
    87  		err1 := readJSONFile(filepath.Join(rev.path, ".rev.json"), &rev.BuildRevision)
    88  		err2 := readJSONFile(filepath.Join(rev.path, ".builders.json"), &builders)
    89  		if os.IsNotExist(err1) || os.IsNotExist(err2) {
    90  			continue
    91  		} else if err1 != nil {
    92  			return nil, err1
    93  		} else if err2 != nil {
    94  			return nil, err2
    95  		}
    96  
    97  		rev.Date, err = time.Parse(time.RFC3339, rev.BuildRevision.Date)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  
   102  		rev.Builds = make([]*Build, len(builders))
   103  		for i, builder := range builders {
   104  			var status BuildStatus
   105  			var logURL string
   106  			s := rev.Results[i]
   107  			switch s {
   108  			case "ok":
   109  				status = BuildOK
   110  			case "":
   111  				status = BuildRunning
   112  			default:
   113  				status = BuildFailed
   114  				logURL = s
   115  			}
   116  			rev.Builds[i] = &Build{
   117  				Revision: rev,
   118  				Builder:  builder,
   119  				Status:   status,
   120  				LogURL:   logURL,
   121  			}
   122  		}
   123  
   124  		revs = append(revs, rev)
   125  	}
   126  
   127  	return revs, nil
   128  }
   129  
   130  func readJSONFile(path string, v interface{}) error {
   131  	r, err := os.Open(path)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	defer r.Close()
   136  
   137  	return json.NewDecoder(r).Decode(&v)
   138  }