| package ioutils // import "github.com/docker/docker/pkg/ioutils" | |
| import ( | |
| "crypto/sha256" | |
| "encoding/hex" | |
| "math/rand" | |
| "testing" | |
| "time" | |
| ) | |
| func TestBytesPipeRead(t *testing.T) { | |
| buf := NewBytesPipe() | |
| _, _ = buf.Write([]byte("12")) | |
| _, _ = buf.Write([]byte("34")) | |
| _, _ = buf.Write([]byte("56")) | |
| _, _ = buf.Write([]byte("78")) | |
| _, _ = buf.Write([]byte("90")) | |
| rd := make([]byte, 4) | |
| n, err := buf.Read(rd) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if n != 4 { | |
| t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) | |
| } | |
| if string(rd) != "1234" { | |
| t.Fatalf("Read %s, but must be %s", rd, "1234") | |
| } | |
| n, err = buf.Read(rd) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if n != 4 { | |
| t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) | |
| } | |
| if string(rd) != "5678" { | |
| t.Fatalf("Read %s, but must be %s", rd, "5679") | |
| } | |
| n, err = buf.Read(rd) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if n != 2 { | |
| t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 2) | |
| } | |
| if string(rd[:n]) != "90" { | |
| t.Fatalf("Read %s, but must be %s", rd, "90") | |
| } | |
| } | |
| func TestBytesPipeWrite(t *testing.T) { | |
| buf := NewBytesPipe() | |
| _, _ = buf.Write([]byte("12")) | |
| _, _ = buf.Write([]byte("34")) | |
| _, _ = buf.Write([]byte("56")) | |
| _, _ = buf.Write([]byte("78")) | |
| _, _ = buf.Write([]byte("90")) | |
| if buf.buf[0].String() != "1234567890" { | |
| t.Fatalf("Buffer %q, must be %q", buf.buf[0].String(), "1234567890") | |
| } | |
| } | |
| // Regression test for #41941. | |
| func TestBytesPipeDeadlock(t *testing.T) { | |
| bp := NewBytesPipe() | |
| bp.buf = []*fixedBuffer{getBuffer(blockThreshold)} | |
| rd := make(chan error) | |
| go func() { | |
| n, err := bp.Read(make([]byte, 1)) | |
| t.Logf("Read n=%d, err=%v", n, err) | |
| if n != 1 { | |
| t.Errorf("short read: got %d, want 1", n) | |
| } | |
| rd <- err | |
| }() | |
| wr := make(chan error) | |
| go func() { | |
| const writeLen int = blockThreshold + 1 | |
| time.Sleep(time.Millisecond) | |
| n, err := bp.Write(make([]byte, writeLen)) | |
| t.Logf("Write n=%d, err=%v", n, err) | |
| if n != writeLen { | |
| t.Errorf("short write: got %d, want %d", n, writeLen) | |
| } | |
| wr <- err | |
| }() | |
| timer := time.NewTimer(time.Second) | |
| defer timer.Stop() | |
| select { | |
| case <-timer.C: | |
| t.Fatal("deadlock! Neither Read() nor Write() returned.") | |
| case rerr := <-rd: | |
| if rerr != nil { | |
| t.Fatal(rerr) | |
| } | |
| select { | |
| case <-timer.C: | |
| t.Fatal("deadlock! Write() did not return.") | |
| case werr := <-wr: | |
| if werr != nil { | |
| t.Fatal(werr) | |
| } | |
| } | |
| case werr := <-wr: | |
| if werr != nil { | |
| t.Fatal(werr) | |
| } | |
| select { | |
| case <-timer.C: | |
| t.Fatal("deadlock! Read() did not return.") | |
| case rerr := <-rd: | |
| if rerr != nil { | |
| t.Fatal(rerr) | |
| } | |
| } | |
| } | |
| } | |
| // Write and read in different speeds/chunk sizes and check valid data is read. | |
| func TestBytesPipeWriteRandomChunks(t *testing.T) { | |
| tests := []struct{ iterations, writesPerLoop, readsPerLoop int }{ | |
| {iterations: 100, writesPerLoop: 10, readsPerLoop: 1}, | |
| {iterations: 1000, writesPerLoop: 10, readsPerLoop: 5}, | |
| {iterations: 1000, writesPerLoop: 100}, | |
| {iterations: 1000, writesPerLoop: 5, readsPerLoop: 6}, | |
| {iterations: 10000, writesPerLoop: 50, readsPerLoop: 25}, | |
| } | |
| testMessage := []byte("this is a random string for testing") | |
| // random slice sizes to read and write | |
| writeChunks := []int{25, 35, 15, 20} | |
| readChunks := []int{5, 45, 20, 25} | |
| for _, tc := range tests { | |
| // first pass: write directly to hash | |
| hash := sha256.New() | |
| for i := 0; i < tc.iterations*tc.writesPerLoop; i++ { | |
| if _, err := hash.Write(testMessage[:writeChunks[i%len(writeChunks)]]); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| expected := hex.EncodeToString(hash.Sum(nil)) | |
| // write/read through buffer | |
| buf := NewBytesPipe() | |
| hash.Reset() | |
| done := make(chan struct{}) | |
| go func() { | |
| // random delay before read starts | |
| <-time.After(time.Duration(rand.Intn(10)) * time.Millisecond) | |
| for i := 0; ; i++ { | |
| p := make([]byte, readChunks[(tc.iterations*tc.readsPerLoop+i)%len(readChunks)]) | |
| n, _ := buf.Read(p) | |
| if n == 0 { | |
| break | |
| } | |
| hash.Write(p[:n]) | |
| } | |
| close(done) | |
| }() | |
| for i := 0; i < tc.iterations; i++ { | |
| for w := 0; w < tc.writesPerLoop; w++ { | |
| buf.Write(testMessage[:writeChunks[(i*tc.writesPerLoop+w)%len(writeChunks)]]) | |
| } | |
| } | |
| _ = buf.Close() | |
| <-done | |
| actual := hex.EncodeToString(hash.Sum(nil)) | |
| if expected != actual { | |
| t.Fatalf("BytesPipe returned invalid data. Expected checksum %v, got %v", expected, actual) | |
| } | |
| } | |
| } | |
| func BenchmarkBytesPipeWrite(b *testing.B) { | |
| b.ReportAllocs() | |
| testData := []byte("pretty short line, because why not?") | |
| for i := 0; i < b.N; i++ { | |
| readBuf := make([]byte, 1024) | |
| buf := NewBytesPipe() | |
| go func() { | |
| var err error | |
| for err == nil { | |
| _, err = buf.Read(readBuf) | |
| } | |
| }() | |
| for j := 0; j < 1000; j++ { | |
| _, _ = buf.Write(testData) | |
| } | |
| _ = buf.Close() | |
| } | |
| } | |
| func BenchmarkBytesPipeRead(b *testing.B) { | |
| b.ReportAllocs() | |
| rd := make([]byte, 512) | |
| for i := 0; i < b.N; i++ { | |
| b.StopTimer() | |
| buf := NewBytesPipe() | |
| for j := 0; j < 500; j++ { | |
| _, _ = buf.Write(make([]byte, 1024)) | |
| } | |
| b.StartTimer() | |
| for j := 0; j < 1000; j++ { | |
| if n, _ := buf.Read(rd); n != 512 { | |
| b.Fatalf("Wrong number of bytes: %d", n) | |
| } | |
| } | |
| } | |
| } |