package paasio import ( "bytes" "crypto/rand" "io" "runtime" "strings" "sync" "testing" "time" ) // testVersion identifies the API tested by the test program. const targetTestVersion = 3 func TestMultiThreaded(t *testing.T) { if testVersion != targetTestVersion { t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion) } mincpu := 2 minproc := 2 ncpu := runtime.NumCPU() if ncpu < mincpu { t.Fatalf("at least %d cpu cores are required", mincpu) } nproc := runtime.GOMAXPROCS(0) if nproc < minproc { t.Errorf("at least %d threads are required; rerun the tests", minproc) t.Errorf("") t.Errorf("\tgo test -cpu %d ...", minproc) } } // this test could be improved to test that error conditions are preserved. func testWrite(t *testing.T, writer func(io.Writer) WriteCounter) { for i, test := range []struct { writes []string }{ {nil}, {[]string{""}}, {[]string{"I", " ", "never met ", "", "a gohper"}}, } { var buf bytes.Buffer buft := writer(&buf) for _, s := range test.writes { n, err := buft.Write([]byte(s)) if err != nil { t.Errorf("test %d: Write(%q) unexpected error: %v", i, s, err) continue } if n != len(s) { t.Errorf("test %d: Write(%q) unexpected number of bytes written: %v", i, s, n) continue } } out := buf.String() if out != strings.Join(test.writes, "") { t.Errorf("test %d: unexpected content in underlying writer: %q", i, out) } } } func TestWriteWriter(t *testing.T) { testWrite(t, NewWriteCounter) } func TestWriteReadWriter(t *testing.T) { testWrite(t, func(w io.Writer) WriteCounter { var r nopReader return NewReadWriteCounter(readWriter{r, w}) }) } // this test could be improved to test exact number of operations as well as // ensure that error conditions are preserved. func testRead(t *testing.T, reader func(io.Reader) ReadCounter) { chunkLen := 10 << 20 // 10MB orig := make([]byte, 10<<20) _, err := rand.Read(orig) if err != nil { t.Fatalf("error reading random data") } buf := bytes.NewBuffer(orig) rc := reader(buf) var obuf bytes.Buffer ncopy, err := io.Copy(&obuf, rc) if err != nil { t.Fatalf("error reading: %v", err) } if ncopy != int64(chunkLen) { t.Fatalf("copied %d bytes instead of %d", ncopy, chunkLen) } if string(orig) != obuf.String() { t.Fatalf("unexpected output from Read()") } n, nops := rc.ReadCount() if n != int64(chunkLen) { t.Fatalf("reported %d bytes read instead of %d", n, chunkLen) } if nops < 2 { t.Fatalf("unexpected number of reads: %v", nops) } } func TestReadReader(t *testing.T) { testRead(t, NewReadCounter) } func TestReadReadWriter(t *testing.T) { testRead(t, func(r io.Reader) ReadCounter { var w nopWriter return NewReadWriteCounter(readWriter{r, w}) }) } func testReadTotal(t *testing.T, rc ReadCounter) { numGo := 8000 numBytes := 50 totalBytes := int64(numGo) * int64(numBytes) p := make([]byte, numBytes) t.Logf("Calling Read() for %d*%d=%d bytes", numGo, numBytes, totalBytes) wg := new(sync.WaitGroup) wg.Add(numGo) start := make(chan struct{}) for i := 0; i < numGo; i++ { go func() { <-start rc.Read(p) wg.Done() }() } close(start) wg.Wait() n, nops := rc.ReadCount() if n != totalBytes { t.Errorf("expected %d bytes read; %d bytes reported", totalBytes, n) } if nops != numGo { t.Errorf("expected %d read operations; %d operations reported", numGo, nops) } } func TestReadTotalReader(t *testing.T) { var r nopReader testReadTotal(t, NewReadCounter(r)) } func TestReadTotalReadWriter(t *testing.T) { var rw nopReadWriter testReadTotal(t, NewReadWriteCounter(rw)) } func testWriteTotal(t *testing.T, wt WriteCounter) { numGo := 8000 numBytes := 50 totalBytes := int64(numGo) * int64(numBytes) p := make([]byte, numBytes) t.Logf("Calling Write() with %d*%d=%d bytes", numGo, numBytes, totalBytes) wg := new(sync.WaitGroup) wg.Add(numGo) start := make(chan struct{}) for i := 0; i < numGo; i++ { go func() { <-start wt.Write(p) wg.Done() }() } close(start) wg.Wait() n, nops := wt.WriteCount() if n != totalBytes { t.Errorf("expected %d bytes written; %d bytes reported", totalBytes, n) } if nops != numGo { t.Errorf("expected %d write operations; %d operations reported", numGo, nops) } } func TestWriteTotalWriter(t *testing.T) { var w nopWriter testWriteTotal(t, NewWriteCounter(w)) } func TestWriteTotalReadWriter(t *testing.T) { var rw nopReadWriter testWriteTotal(t, NewReadWriteCounter(rw)) } type nopWriter struct{ error } func (w nopWriter) Write(p []byte) (int, error) { time.Sleep(1) if w.error != nil { return 0, w.error } return len(p), nil } type nopReader struct{ error } func (r nopReader) Read(p []byte) (int, error) { time.Sleep(1) if r.error != nil { return 0, r.error } return len(p), nil } type nopReadWriter struct { nopReader nopWriter } type readWriter struct { io.Reader io.Writer }