diff --git a/pkg/cmd/run/view/fixtures/run_log.zip b/pkg/cmd/run/view/fixtures/run_log.zip index 60701d925..425ba09dd 100644 Binary files a/pkg/cmd/run/view/fixtures/run_log.zip and b/pkg/cmd/run/view/fixtures/run_log.zip differ diff --git a/pkg/cmd/run/view/view.go b/pkg/cmd/run/view/view.go index 5b8a1da87..0dafbcc09 100644 --- a/pkg/cmd/run/view/view.go +++ b/pkg/cmd/run/view/view.go @@ -555,15 +555,25 @@ func getJobNameForLogFilename(name string) string { return sanitizedJobName } +// A job run log file is a top-level .txt file whose name starts with an ordinal +// number; e.g., "0_jobname.txt". func jobLogFilenameRegexp(job shared.Job) *regexp.Regexp { sanitizedJobName := getJobNameForLogFilename(job.Name) - re := fmt.Sprintf(`^-?\d+_%s\.txt`, regexp.QuoteMeta(sanitizedJobName)) + re := fmt.Sprintf(`^\d+_%s\.txt$`, regexp.QuoteMeta(sanitizedJobName)) + return regexp.MustCompile(re) +} + +// A legacy job run log file is a top-level .txt file whose name starts with a +// negative number which is the ID of the run; e.g., "-2147483648_jobname.txt". +func legacyJobLogFilenameRegexp(job shared.Job) *regexp.Regexp { + sanitizedJobName := getJobNameForLogFilename(job.Name) + re := fmt.Sprintf(`^-\d+_%s\.txt$`, regexp.QuoteMeta(sanitizedJobName)) return regexp.MustCompile(re) } func stepLogFilenameRegexp(job shared.Job, step shared.Step) *regexp.Regexp { sanitizedJobName := getJobNameForLogFilename(job.Name) - re := fmt.Sprintf(`^%s\/%d_.*\.txt`, regexp.QuoteMeta(sanitizedJobName), step.Number) + re := fmt.Sprintf(`^%s\/%d_.*\.txt$`, regexp.QuoteMeta(sanitizedJobName), step.Number) return regexp.MustCompile(re) } @@ -662,26 +672,31 @@ func truncateAsUTF16(str string, max int) string { // where the ID can apparently be negative. func attachRunLog(rlz *zip.Reader, jobs []shared.Job) { for i, job := range jobs { - re := jobLogFilenameRegexp(job) - for _, file := range rlz.File { - if re.MatchString(file.Name) { - jobs[i].Log = file - break - } + // As a highest priority, we try to use the step logs first. We have seen zips that surprisingly contain + // step logs, normal job logs and legacy job logs. In this case, both job logs would be ignored. We have + // never seen a zip containing both job logs and no step logs, however, it may be possible. In that case + // let's prioritise the normal log over the legacy one. + jobLog := matchFileInZIPArchive(rlz, jobLogFilenameRegexp(job)) + if jobLog == nil { + jobLog = matchFileInZIPArchive(rlz, legacyJobLogFilenameRegexp(job)) } + jobs[i].Log = jobLog for j, step := range job.Steps { - re := stepLogFilenameRegexp(job, step) - for _, file := range rlz.File { - if re.MatchString(file.Name) { - jobs[i].Steps[j].Log = file - break - } - } + jobs[i].Steps[j].Log = matchFileInZIPArchive(rlz, stepLogFilenameRegexp(job, step)) } } } +func matchFileInZIPArchive(zr *zip.Reader, re *regexp.Regexp) *zip.File { + for _, file := range zr.File { + if re.MatchString(file.Name) { + return file + } + } + return nil +} + func displayRunLog(w io.Writer, jobs []shared.Job, failed bool) error { for _, job := range jobs { // To display a run log, we first try to compile it from individual step diff --git a/pkg/cmd/run/view/view_test.go b/pkg/cmd/run/view/view_test.go index 2205a9031..2d150934f 100644 --- a/pkg/cmd/run/view/view_test.go +++ b/pkg/cmd/run/view/view_test.go @@ -2039,8 +2039,9 @@ func TestViewRun(t *testing.T) { // ├── 2_cool job with no step logs.txt // ├── 3_sad job with no step logs.txt // ├── -9999999999_legacy cool job with no step logs.txt -// └── -9999999999_legacy sad job with no step logs.txt - +// ├── -9999999999_legacy sad job with no step logs.txt +// ├── 4_cool job with both legacy and new logs.txt +// └── -9999999999_cool job with both legacy and new logs.txt func Test_attachRunLog(t *testing.T) { tests := []struct { name string @@ -2149,6 +2150,15 @@ func Test_attachRunLog(t *testing.T) { wantJobFilename: "-9999999999_legacy cool job with no step logs.txt", wantStepMatch: false, }, + { + name: "matching job name with both normal and legacy filename", + job: shared.Job{ + Name: "cool job with both legacy and new logs", + }, + wantJobMatch: true, + wantJobFilename: "4_cool job with both legacy and new logs.txt", + wantStepMatch: false, + }, { name: "one job name is a suffix of another", job: shared.Job{