Merge pull request #10740 from cli/babakks/fallback-to-job-run-logs
Fallback to job run logs when step logs are missing
This commit is contained in:
commit
83bd23b080
5 changed files with 941 additions and 46 deletions
|
|
@ -230,6 +230,8 @@ type Job struct {
|
|||
CompletedAt time.Time `json:"completed_at"`
|
||||
URL string `json:"html_url"`
|
||||
RunID int64 `json:"run_id"`
|
||||
|
||||
Log *zip.File
|
||||
}
|
||||
|
||||
type Step struct {
|
||||
|
|
@ -239,7 +241,8 @@ type Step struct {
|
|||
Number int
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
Log *zip.File
|
||||
|
||||
Log *zip.File
|
||||
}
|
||||
|
||||
type Steps []Step
|
||||
|
|
|
|||
|
|
@ -104,6 +104,60 @@ var SuccessfulJob Job = Job{
|
|||
},
|
||||
}
|
||||
|
||||
// Note that this run *has* steps, but in the ZIP archive the step logs are not
|
||||
// included.
|
||||
var SuccessfulJobWithoutStepLogs Job = Job{
|
||||
ID: 11,
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Name: "cool job with no step logs",
|
||||
StartedAt: TestRunStartTime,
|
||||
CompletedAt: TestRunStartTime.Add(time.Minute*4 + time.Second*34),
|
||||
URL: "https://github.com/jobs/11",
|
||||
RunID: 3,
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "fob the barz",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 1,
|
||||
},
|
||||
{
|
||||
Name: "barz the fob",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Note that this run *has* steps, but in the ZIP archive the step logs are not
|
||||
// included.
|
||||
var LegacySuccessfulJobWithoutStepLogs Job = Job{
|
||||
ID: 12,
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Name: "legacy cool job with no step logs",
|
||||
StartedAt: TestRunStartTime,
|
||||
CompletedAt: TestRunStartTime.Add(time.Minute*4 + time.Second*34),
|
||||
URL: "https://github.com/jobs/12",
|
||||
RunID: 3,
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "fob the barz",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 1,
|
||||
},
|
||||
{
|
||||
Name: "barz the fob",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var FailedJob Job = Job{
|
||||
ID: 20,
|
||||
Status: Completed,
|
||||
|
|
@ -129,6 +183,60 @@ var FailedJob Job = Job{
|
|||
},
|
||||
}
|
||||
|
||||
// Note that this run *has* steps, but in the ZIP archive the step logs are not
|
||||
// included.
|
||||
var FailedJobWithoutStepLogs Job = Job{
|
||||
ID: 21,
|
||||
Status: Completed,
|
||||
Conclusion: Failure,
|
||||
Name: "sad job with no step logs",
|
||||
StartedAt: TestRunStartTime,
|
||||
CompletedAt: TestRunStartTime.Add(time.Minute*4 + time.Second*34),
|
||||
URL: "https://github.com/jobs/21",
|
||||
RunID: 1234,
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "barf the quux",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 1,
|
||||
},
|
||||
{
|
||||
Name: "quux the barf",
|
||||
Status: Completed,
|
||||
Conclusion: Failure,
|
||||
Number: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Note that this run *has* steps, but in the ZIP archive the step logs are not
|
||||
// included.
|
||||
var LegacyFailedJobWithoutStepLogs Job = Job{
|
||||
ID: 22,
|
||||
Status: Completed,
|
||||
Conclusion: Failure,
|
||||
Name: "legacy sad job with no step logs",
|
||||
StartedAt: TestRunStartTime,
|
||||
CompletedAt: TestRunStartTime.Add(time.Minute*4 + time.Second*34),
|
||||
URL: "https://github.com/jobs/22",
|
||||
RunID: 1234,
|
||||
Steps: []Step{
|
||||
{
|
||||
Name: "barf the quux",
|
||||
Status: Completed,
|
||||
Conclusion: Success,
|
||||
Number: 1,
|
||||
},
|
||||
{
|
||||
Name: "quux the barf",
|
||||
Status: Completed,
|
||||
Conclusion: Failure,
|
||||
Number: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var SuccessfulJobAnnotations []Annotation = []Annotation{
|
||||
{
|
||||
JobName: "cool job",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -533,7 +533,7 @@ func promptForJob(prompter shared.Prompter, cs *iostreams.ColorScheme, jobs []sh
|
|||
|
||||
const JOB_NAME_MAX_LENGTH = 90
|
||||
|
||||
func logFilenameRegexp(job shared.Job, step shared.Step) *regexp.Regexp {
|
||||
func getJobNameForLogFilename(name string) string {
|
||||
// As described in https://github.com/cli/cli/issues/5011#issuecomment-1570713070, there are a number of steps
|
||||
// the server can take when producing the downloaded zip file that can result in a mismatch between the job name
|
||||
// and the filename in the zip including:
|
||||
|
|
@ -545,9 +545,20 @@ func logFilenameRegexp(job shared.Job, step shared.Step) *regexp.Regexp {
|
|||
// * Strip `/` which occur when composite action job names are constructed of the form `<JOB_NAME`> / <ACTION_NAME>`
|
||||
// * Truncate long job names
|
||||
//
|
||||
sanitizedJobName := strings.ReplaceAll(job.Name, "/", "")
|
||||
sanitizedJobName := strings.ReplaceAll(name, "/", "")
|
||||
sanitizedJobName = strings.ReplaceAll(sanitizedJobName, ":", "")
|
||||
sanitizedJobName = truncateAsUTF16(sanitizedJobName, JOB_NAME_MAX_LENGTH)
|
||||
return sanitizedJobName
|
||||
}
|
||||
|
||||
func jobLogFilenameRegexp(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)
|
||||
return regexp.MustCompile(re)
|
||||
}
|
||||
|
|
@ -627,17 +638,36 @@ func truncateAsUTF16(str string, max int) string {
|
|||
// │ ├── 2_anotherstepname.txt
|
||||
// │ ├── 3_stepstepname.txt
|
||||
// │ └── 4_laststepname.txt
|
||||
// └── jobname2/
|
||||
// ├── 1_stepname.txt
|
||||
// └── 2_somestepname.txt
|
||||
// ├── jobname2/
|
||||
// | ├── 1_stepname.txt
|
||||
// | └── 2_somestepname.txt
|
||||
// ├── 0_jobname1.txt
|
||||
// ├── 1_jobname2.txt
|
||||
// └── -9999999999_jobname3.txt
|
||||
//
|
||||
// It iterates through the list of jobs and tries to find the matching
|
||||
// log in the zip file. If the matching log is found it is attached
|
||||
// to the job.
|
||||
//
|
||||
// The top-level .txt files include the logs for an entire job run. Note that
|
||||
// the prefixed number is either:
|
||||
// - An ordinal and cannot be mapped to the corresponding job's ID.
|
||||
// - A negative integer which is the ID of the job in the old Actions service.
|
||||
// The service right now tries to get logs and use an ordinal in a loop.
|
||||
// However, if it doesn't get the logs, it falls back to an old service
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
for j, step := range job.Steps {
|
||||
re := logFilenameRegexp(job, step)
|
||||
re := stepLogFilenameRegexp(job, step)
|
||||
for _, file := range rlz.File {
|
||||
if re.MatchString(file.Name) {
|
||||
jobs[i].Steps[j].Log = file
|
||||
|
|
@ -650,6 +680,13 @@ func attachRunLog(rlz *zip.Reader, jobs []shared.Job) {
|
|||
|
||||
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
|
||||
// logs, because this way we can prepend lines with the corresponding
|
||||
// step name. However, at the time of writing, logs are sometimes being
|
||||
// served by a service that doesn’t include the step logs (none of them),
|
||||
// in which case we fall back to print the entire job run log.
|
||||
var hasStepLogs bool
|
||||
|
||||
steps := job.Steps
|
||||
sort.Sort(steps)
|
||||
for _, step := range steps {
|
||||
|
|
@ -659,18 +696,49 @@ func displayRunLog(w io.Writer, jobs []shared.Job, failed bool) error {
|
|||
if step.Log == nil {
|
||||
continue
|
||||
}
|
||||
hasStepLogs = true
|
||||
prefix := fmt.Sprintf("%s\t%s\t", job.Name, step.Name)
|
||||
f, err := step.Log.Open()
|
||||
if err != nil {
|
||||
if err := printZIPFile(w, step.Log, prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
fmt.Fprintf(w, "%s%s\n", prefix, scanner.Text())
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
if hasStepLogs {
|
||||
continue
|
||||
}
|
||||
|
||||
if failed && !shared.IsFailureState(job.Conclusion) {
|
||||
continue
|
||||
}
|
||||
|
||||
if job.Log == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Here, we fall back to the job run log, which means we do not know
|
||||
// the step name of lines. However, we want to keep the same line
|
||||
// formatting to avoid breaking any code or script that rely on the
|
||||
// tab-delimited formatting. So, an unknown-step placeholder is used
|
||||
// instead of the actual step name.
|
||||
prefix := fmt.Sprintf("%s\tUNKNOWN STEP\t", job.Name)
|
||||
if err := printZIPFile(w, job.Log, prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printZIPFile(w io.Writer, file *zip.File, prefix string) error {
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
fmt.Fprintf(w, "%s%s\n", prefix, scanner.Text())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -990,6 +990,619 @@ func TestViewRun(t *testing.T) {
|
|||
},
|
||||
wantOut: quuxTheBarfLogOutput,
|
||||
},
|
||||
{
|
||||
name: "interactive with log, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ cool commit, CI [trunk] Feb 23, 2021")
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ cool job with no step logs", "X sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ cool job with no step logs")
|
||||
})
|
||||
},
|
||||
wantOut: coolJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with log, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
JobID: "11",
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/11"),
|
||||
httpmock.JSONResponse(shared.SuccessfulJobWithoutStepLogs))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: coolJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "interactive with log-failed, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return 4, nil
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ cool job with no step logs", "X sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "X sad job with no step logs")
|
||||
})
|
||||
},
|
||||
wantOut: sadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with log-failed, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
JobID: "21",
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/21"),
|
||||
httpmock.JSONResponse(shared.FailedJobWithoutStepLogs))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: sadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "interactive with run log, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ cool commit, CI [trunk] Feb 23, 2021")
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ cool job with no step logs", "X sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "View all jobs in this run")
|
||||
})
|
||||
},
|
||||
wantOut: expectedRunLogOutputWithNoSteps,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with run log, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
RunID: "3",
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: expectedRunLogOutputWithNoSteps,
|
||||
},
|
||||
{
|
||||
name: "interactive with run log-failed, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return 4, nil
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ cool job with no step logs", "X sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "View all jobs in this run")
|
||||
})
|
||||
},
|
||||
wantOut: sadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with run log-failed, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
RunID: "1234",
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.SuccessfulJobWithoutStepLogs,
|
||||
shared.FailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: sadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "interactive with log, legacy service data, with no step logs available",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ cool commit, CI [trunk] Feb 23, 2021")
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ legacy cool job with no step logs", "X legacy sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ legacy cool job with no step logs")
|
||||
})
|
||||
},
|
||||
wantOut: legacyCoolJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with log, legacy service data, with no step logs available",
|
||||
opts: &ViewOptions{
|
||||
JobID: "12",
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/12"),
|
||||
httpmock.JSONResponse(shared.LegacySuccessfulJobWithoutStepLogs))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: legacyCoolJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
|
||||
{
|
||||
name: "interactive with log-failed, legacy service data, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return 4, nil
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ legacy cool job with no step logs", "X legacy sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "X legacy sad job with no step logs")
|
||||
})
|
||||
},
|
||||
wantOut: legacySadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with log-failed, legacy service data, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
JobID: "22",
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/22"),
|
||||
httpmock.JSONResponse(shared.LegacyFailedJobWithoutStepLogs))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: legacySadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "interactive with run log, legacy service data, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "✓ cool commit, CI [trunk] Feb 23, 2021")
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ legacy cool job with no step logs", "X legacy sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "View all jobs in this run")
|
||||
})
|
||||
},
|
||||
wantOut: expectedLegacyRunLogOutputWithNoSteps,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with run log, legacy service data, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
RunID: "3",
|
||||
Log: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: expectedLegacyRunLogOutputWithNoSteps,
|
||||
},
|
||||
{
|
||||
name: "interactive with run log-failed, legacy service data, with no step logs available (#10551)",
|
||||
tty: true,
|
||||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: shared.TestRuns,
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
promptStubs: func(pm *prompter.MockPrompter) {
|
||||
pm.RegisterSelect("Select a workflow run",
|
||||
[]string{"X cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "✓ cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "- cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "* cool commit, CI [trunk] Feb 23, 2021", "X cool commit, CI [trunk] Feb 23, 2021"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return 4, nil
|
||||
})
|
||||
pm.RegisterSelect("View a specific job in this run?",
|
||||
[]string{"View all jobs in this run", "✓ legacy cool job with no step logs", "X legacy sad job with no step logs"},
|
||||
func(_, _ string, opts []string) (int, error) {
|
||||
return prompter.IndexFor(opts, "View all jobs in this run")
|
||||
})
|
||||
},
|
||||
wantOut: legacySadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "noninteractive with run log-failed, legacy service data, with no step logs available (#10551)",
|
||||
opts: &ViewOptions{
|
||||
RunID: "1234",
|
||||
LogFailed: true,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/1234/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{
|
||||
shared.LegacySuccessfulJobWithoutStepLogs,
|
||||
shared.LegacyFailedJobWithoutStepLogs,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: legacySadJobRunWithNoStepLogsLogOutput,
|
||||
},
|
||||
{
|
||||
name: "run log but run is not done",
|
||||
tty: true,
|
||||
|
|
@ -1419,14 +2032,23 @@ func TestViewRun(t *testing.T) {
|
|||
// ├── sad job/
|
||||
// │ ├── 1_barf the quux.txt
|
||||
// │ └── 2_quux the barf.txt
|
||||
// └── ad job/
|
||||
// └── 1_barf the quux.txt
|
||||
// ├── ad job/
|
||||
// | └── 1_barf the quux.txt
|
||||
// ├── 0_cool job.txt
|
||||
// ├── 1_sad job.txt
|
||||
// ├── 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
|
||||
|
||||
func Test_attachRunLog(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
job shared.Job
|
||||
wantMatch bool
|
||||
wantFilename string
|
||||
name string
|
||||
job shared.Job
|
||||
wantJobMatch bool
|
||||
wantJobFilename string
|
||||
wantStepMatch bool
|
||||
wantStepFilename string
|
||||
}{
|
||||
{
|
||||
name: "matching job name and step number 1",
|
||||
|
|
@ -1437,8 +2059,10 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "cool job/1_fob the barz.txt",
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "0_cool job.txt",
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "cool job/1_fob the barz.txt",
|
||||
},
|
||||
{
|
||||
name: "matching job name and step number 2",
|
||||
|
|
@ -1449,8 +2073,10 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 2,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "cool job/2_barz the fob.txt",
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "0_cool job.txt",
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "cool job/2_barz the fob.txt",
|
||||
},
|
||||
{
|
||||
name: "matching job name and step number and mismatch step name",
|
||||
|
|
@ -1461,8 +2087,10 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "cool job/1_fob the barz.txt",
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "0_cool job.txt",
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "cool job/1_fob the barz.txt",
|
||||
},
|
||||
{
|
||||
name: "matching job name and mismatch step number",
|
||||
|
|
@ -1473,7 +2101,53 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 3,
|
||||
}},
|
||||
},
|
||||
wantMatch: false,
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "0_cool job.txt",
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "matching job name with no step logs",
|
||||
job: shared.Job{
|
||||
Name: "cool job with no step logs",
|
||||
Steps: []shared.Step{{
|
||||
Name: "fob the barz",
|
||||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "2_cool job with no step logs.txt",
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "matching job name with no step data",
|
||||
job: shared.Job{
|
||||
Name: "cool job with no step logs",
|
||||
},
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "2_cool job with no step logs.txt",
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "matching job name with legacy filename and no step logs",
|
||||
job: shared.Job{
|
||||
Name: "legacy cool job with no step logs",
|
||||
Steps: []shared.Step{{
|
||||
Name: "fob the barz",
|
||||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "-9999999999_legacy cool job with no step logs.txt",
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "matching job name with legacy filename and no step data",
|
||||
job: shared.Job{
|
||||
Name: "legacy cool job with no step logs",
|
||||
},
|
||||
wantJobMatch: true,
|
||||
wantJobFilename: "-9999999999_legacy cool job with no step logs.txt",
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "one job name is a suffix of another",
|
||||
|
|
@ -1484,8 +2158,8 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "ad job/1_barf the quux.txt",
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "ad job/1_barf the quux.txt",
|
||||
},
|
||||
{
|
||||
name: "escape metacharacters in job name",
|
||||
|
|
@ -1496,7 +2170,8 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 0,
|
||||
}},
|
||||
},
|
||||
wantMatch: false,
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "mismatching job name",
|
||||
|
|
@ -1507,7 +2182,8 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: false,
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: false,
|
||||
},
|
||||
{
|
||||
name: "job name with forward slash matches dir with slash removed",
|
||||
|
|
@ -1518,9 +2194,10 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: true,
|
||||
// not the double space in the dir name, as the slash has been removed
|
||||
wantFilename: "cool job with slash/1_fob the barz.txt",
|
||||
wantStepFilename: "cool job with slash/1_fob the barz.txt",
|
||||
},
|
||||
{
|
||||
name: "job name with colon matches dir with colon removed",
|
||||
|
|
@ -1531,8 +2208,9 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "cool job with colon/1_fob the barz.txt",
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "cool job with colon/1_fob the barz.txt",
|
||||
},
|
||||
{
|
||||
name: "Job name with really long name (over the ZIP limit)",
|
||||
|
|
@ -1543,8 +2221,9 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "thisisnineteenchars_thisisnineteenchars_thisisnineteenchars_thisisnineteenchars_thisisnine/1_Long Name Job.txt",
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "thisisnineteenchars_thisisnineteenchars_thisisnineteenchars_thisisnineteenchars_thisisnine/1_Long Name Job.txt",
|
||||
},
|
||||
{
|
||||
name: "Job name that would be truncated by the C# server to split a grapheme",
|
||||
|
|
@ -1555,8 +2234,9 @@ func Test_attachRunLog(t *testing.T) {
|
|||
Number: 1,
|
||||
}},
|
||||
},
|
||||
wantMatch: true,
|
||||
wantFilename: "Emoji Test 😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅<F09F9885>/1_Emoji Job.txt",
|
||||
wantJobMatch: false,
|
||||
wantStepMatch: true,
|
||||
wantStepFilename: "Emoji Test 😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅<F09F9885>/1_Emoji Job.txt",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1566,17 +2246,27 @@ func Test_attachRunLog(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
jobs := []shared.Job{tt.job}
|
||||
|
||||
attachRunLog(&run_log_zip_reader.Reader, []shared.Job{tt.job})
|
||||
attachRunLog(&run_log_zip_reader.Reader, jobs)
|
||||
|
||||
t.Logf("Job details: ")
|
||||
|
||||
for _, step := range tt.job.Steps {
|
||||
log := step.Log
|
||||
logPresent := log != nil
|
||||
require.Equal(t, tt.wantMatch, logPresent, "log not present")
|
||||
if logPresent {
|
||||
require.Equal(t, tt.wantFilename, log.Name, "Filename mismatch")
|
||||
job := jobs[0]
|
||||
|
||||
jobLog := job.Log
|
||||
jobLogPresent := jobLog != nil
|
||||
require.Equal(t, tt.wantJobMatch, jobLogPresent, "job log not present")
|
||||
if jobLogPresent {
|
||||
require.Equal(t, tt.wantJobFilename, jobLog.Name, "job log filename mismatch")
|
||||
}
|
||||
|
||||
for _, step := range job.Steps {
|
||||
stepLog := step.Log
|
||||
stepLogPresent := stepLog != nil
|
||||
require.Equal(t, tt.wantStepMatch, stepLogPresent, "step log not present")
|
||||
if stepLogPresent {
|
||||
require.Equal(t, tt.wantStepFilename, stepLog.Name, "step log filename mismatch")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1607,9 +2297,35 @@ sad job quux the barf log line 2
|
|||
sad job quux the barf log line 3
|
||||
`)
|
||||
|
||||
var coolJobRunWithNoStepLogsLogOutput = heredoc.Doc(`
|
||||
cool job with no step logs UNKNOWN STEP log line 1
|
||||
cool job with no step logs UNKNOWN STEP log line 2
|
||||
cool job with no step logs UNKNOWN STEP log line 3
|
||||
`)
|
||||
|
||||
var legacyCoolJobRunWithNoStepLogsLogOutput = heredoc.Doc(`
|
||||
legacy cool job with no step logs UNKNOWN STEP log line 1
|
||||
legacy cool job with no step logs UNKNOWN STEP log line 2
|
||||
legacy cool job with no step logs UNKNOWN STEP log line 3
|
||||
`)
|
||||
|
||||
var sadJobRunWithNoStepLogsLogOutput = heredoc.Doc(`
|
||||
sad job with no step logs UNKNOWN STEP log line 1
|
||||
sad job with no step logs UNKNOWN STEP log line 2
|
||||
sad job with no step logs UNKNOWN STEP log line 3
|
||||
`)
|
||||
|
||||
var legacySadJobRunWithNoStepLogsLogOutput = heredoc.Doc(`
|
||||
legacy sad job with no step logs UNKNOWN STEP log line 1
|
||||
legacy sad job with no step logs UNKNOWN STEP log line 2
|
||||
legacy sad job with no step logs UNKNOWN STEP log line 3
|
||||
`)
|
||||
|
||||
var coolJobRunLogOutput = fmt.Sprintf("%s%s", fobTheBarzLogOutput, barfTheFobLogOutput)
|
||||
var sadJobRunLogOutput = fmt.Sprintf("%s%s", barfTheQuuxLogOutput, quuxTheBarfLogOutput)
|
||||
var expectedRunLogOutput = fmt.Sprintf("%s%s", coolJobRunLogOutput, sadJobRunLogOutput)
|
||||
var expectedRunLogOutputWithNoSteps = fmt.Sprintf("%s%s", coolJobRunWithNoStepLogsLogOutput, sadJobRunWithNoStepLogsLogOutput)
|
||||
var expectedLegacyRunLogOutputWithNoSteps = fmt.Sprintf("%s%s", legacyCoolJobRunWithNoStepLogsLogOutput, legacySadJobRunWithNoStepLogsLogOutput)
|
||||
|
||||
func TestRunLog(t *testing.T) {
|
||||
t.Run("when the cache dir doesn't exist, exists return false", func(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue