package imageprogress import ( "encoding/json" "io" "reflect" "strconv" "testing" ) func TestReports(t *testing.T) { tests := []struct { name string gen func(*progressGenerator) errExpected bool expected report }{ { name: "simple report", gen: func(p *progressGenerator) { p.status("1", "Extracting") p.status("2", "Downloading") p.status("1", "Downloading") p.status("2", "Pull complete") }, expected: report{ statusDownloading: &layerDetail{Count: 1}, statusComplete: &layerDetail{Count: 1}, }, }, { name: "ignore invalid layer id", gen: func(p *progressGenerator) { p.status("1", "Downloading") p.status("hello", "testing") p.status("1", "Downloading") }, expected: report{ statusDownloading: &layerDetail{Count: 1}, }, }, { name: "ignore retrying status", gen: func(p *progressGenerator) { p.status("1", "Downloading") p.status("2", "Pull complete") p.status("1", "Downloading") p.status("3", "Retrying") }, expected: report{ statusDownloading: &layerDetail{Count: 1}, statusComplete: &layerDetail{Count: 1}, }, }, { name: "detect error", gen: func(p *progressGenerator) { p.status("1", "Downloading") p.err("an error") }, errExpected: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { pipeIn, pipeOut := io.Pipe() go func() { p := newProgressGenerator(pipeOut) test.gen(p) pipeOut.Close() }() var lastReport report w := newWriter( func(r report) { lastReport = r }, func(a report, b report) bool { return true }, ) w.(*imageProgressWriter).stableThreshhold = 0 _, err := io.Copy(w, pipeIn) if err == nil { err = w.Close() } if err != nil { if !test.errExpected { t.Fatalf("%s: unexpected: %v", test.name, err) } return } if test.errExpected { t.Fatalf("%s: did not get expected error", test.name) } // w.Close() waits until the goroutine inside of the progress generator finishes if !compareReport(lastReport, test.expected) { t.Errorf("%s: unexpected report, got: %v, expected: %v", test.name, lastReport, test.expected) } }) } } func TestErrorOnCopy(t *testing.T) { // Producer pipe genIn, genOut := io.Pipe() p := newProgressGenerator(genOut) // generate some data go func() { for i := 0; i < 100; i++ { p.status("1", "Downloading") p.status("2", "Downloading") p.status("3", "Downloading") } p.err("data error") genOut.Close() }() w := newWriter(func(r report) {}, func(a, b report) bool { return true }) // Ensure that the error is propagated to the copy _, err := io.Copy(w, genIn) if err == nil { err = w.Close() } if err == nil { t.Errorf("Did not get an error when copying to writer") } if err.Error() != "data error" { t.Errorf("Did not get expected error: %v", err) } } func TestStableLayerCount(t *testing.T) { tests := []struct { name string lastLayerCount int layerStatusCount int stableThreshhold int callCount int expectStable bool }{ { name: "increasing layer count", lastLayerCount: 3, layerStatusCount: 4, callCount: 1, expectStable: false, }, { name: "has not met stable threshhold", lastLayerCount: 3, layerStatusCount: 3, callCount: 2, stableThreshhold: 3, expectStable: false, }, { name: "met stable threshhold", lastLayerCount: 3, layerStatusCount: 3, callCount: 4, stableThreshhold: 3, expectStable: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { w := newWriter(func(report) {}, func(a, b report) bool { return true }).(*imageProgressWriter) w.lastLayerCount = test.lastLayerCount w.layerStatus = map[string]*progressLine{} w.stableThreshhold = test.stableThreshhold for i := 0; i < test.layerStatusCount; i++ { w.layerStatus[strconv.Itoa(i)] = &progressLine{} } var result bool for i := 0; i < test.callCount; i++ { result = w.isStableLayerCount() } if result != test.expectStable { t.Errorf("%s: expected %v, got %v", test.name, test.expectStable, result) } }) } } func compareReport(a, b report) bool { if len(a) != len(b) { return false } for k := range a { if _, ok := b[k]; !ok { return false } if !reflect.DeepEqual(*a[k], *b[k]) { return false } } return true } type progressGenerator json.Encoder func newProgressGenerator(w io.Writer) *progressGenerator { return (*progressGenerator)(json.NewEncoder(w)) } func (p *progressGenerator) status(id, status string) { (*json.Encoder)(p).Encode(&progressLine{ID: id, Status: status}) } func (p *progressGenerator) err(msg string) { (*json.Encoder)(p).Encode(&progressLine{Error: msg}) }