1package ioutils // import "github.com/docker/docker/pkg/ioutils"
2
3import (
4	"crypto/sha1"
5	"encoding/hex"
6	"math/rand"
7	"testing"
8	"time"
9)
10
11func TestBytesPipeRead(t *testing.T) {
12	buf := NewBytesPipe()
13	buf.Write([]byte("12"))
14	buf.Write([]byte("34"))
15	buf.Write([]byte("56"))
16	buf.Write([]byte("78"))
17	buf.Write([]byte("90"))
18	rd := make([]byte, 4)
19	n, err := buf.Read(rd)
20	if err != nil {
21		t.Fatal(err)
22	}
23	if n != 4 {
24		t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4)
25	}
26	if string(rd) != "1234" {
27		t.Fatalf("Read %s, but must be %s", rd, "1234")
28	}
29	n, err = buf.Read(rd)
30	if err != nil {
31		t.Fatal(err)
32	}
33	if n != 4 {
34		t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4)
35	}
36	if string(rd) != "5678" {
37		t.Fatalf("Read %s, but must be %s", rd, "5679")
38	}
39	n, err = buf.Read(rd)
40	if err != nil {
41		t.Fatal(err)
42	}
43	if n != 2 {
44		t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 2)
45	}
46	if string(rd[:n]) != "90" {
47		t.Fatalf("Read %s, but must be %s", rd, "90")
48	}
49}
50
51func TestBytesPipeWrite(t *testing.T) {
52	buf := NewBytesPipe()
53	buf.Write([]byte("12"))
54	buf.Write([]byte("34"))
55	buf.Write([]byte("56"))
56	buf.Write([]byte("78"))
57	buf.Write([]byte("90"))
58	if buf.buf[0].String() != "1234567890" {
59		t.Fatalf("Buffer %q, must be %q", buf.buf[0].String(), "1234567890")
60	}
61}
62
63// Write and read in different speeds/chunk sizes and check valid data is read.
64func TestBytesPipeWriteRandomChunks(t *testing.T) {
65	cases := []struct{ iterations, writesPerLoop, readsPerLoop int }{
66		{100, 10, 1},
67		{1000, 10, 5},
68		{1000, 100, 0},
69		{1000, 5, 6},
70		{10000, 50, 25},
71	}
72
73	testMessage := []byte("this is a random string for testing")
74	// random slice sizes to read and write
75	writeChunks := []int{25, 35, 15, 20}
76	readChunks := []int{5, 45, 20, 25}
77
78	for _, c := range cases {
79		// first pass: write directly to hash
80		hash := sha1.New()
81		for i := 0; i < c.iterations*c.writesPerLoop; i++ {
82			if _, err := hash.Write(testMessage[:writeChunks[i%len(writeChunks)]]); err != nil {
83				t.Fatal(err)
84			}
85		}
86		expected := hex.EncodeToString(hash.Sum(nil))
87
88		// write/read through buffer
89		buf := NewBytesPipe()
90		hash.Reset()
91
92		done := make(chan struct{})
93
94		go func() {
95			// random delay before read starts
96			<-time.After(time.Duration(rand.Intn(10)) * time.Millisecond)
97			for i := 0; ; i++ {
98				p := make([]byte, readChunks[(c.iterations*c.readsPerLoop+i)%len(readChunks)])
99				n, _ := buf.Read(p)
100				if n == 0 {
101					break
102				}
103				hash.Write(p[:n])
104			}
105
106			close(done)
107		}()
108
109		for i := 0; i < c.iterations; i++ {
110			for w := 0; w < c.writesPerLoop; w++ {
111				buf.Write(testMessage[:writeChunks[(i*c.writesPerLoop+w)%len(writeChunks)]])
112			}
113		}
114		buf.Close()
115		<-done
116
117		actual := hex.EncodeToString(hash.Sum(nil))
118
119		if expected != actual {
120			t.Fatalf("BytesPipe returned invalid data. Expected checksum %v, got %v", expected, actual)
121		}
122
123	}
124}
125
126func BenchmarkBytesPipeWrite(b *testing.B) {
127	testData := []byte("pretty short line, because why not?")
128	for i := 0; i < b.N; i++ {
129		readBuf := make([]byte, 1024)
130		buf := NewBytesPipe()
131		go func() {
132			var err error
133			for err == nil {
134				_, err = buf.Read(readBuf)
135			}
136		}()
137		for j := 0; j < 1000; j++ {
138			buf.Write(testData)
139		}
140		buf.Close()
141	}
142}
143
144func BenchmarkBytesPipeRead(b *testing.B) {
145	rd := make([]byte, 512)
146	for i := 0; i < b.N; i++ {
147		b.StopTimer()
148		buf := NewBytesPipe()
149		for j := 0; j < 500; j++ {
150			buf.Write(make([]byte, 1024))
151		}
152		b.StartTimer()
153		for j := 0; j < 1000; j++ {
154			if n, _ := buf.Read(rd); n != 512 {
155				b.Fatalf("Wrong number of bytes: %d", n)
156			}
157		}
158	}
159}
160