Add script/rac-random-seek-test.go
diff --git a/script/rac-random-seek-test.go b/script/rac-random-seek-test.go
new file mode 100644
index 0000000..b7d8f2d
--- /dev/null
+++ b/script/rac-random-seek-test.go
@@ -0,0 +1,122 @@
+// Copyright 2019 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build ignore
+
+package main
+
+// rac-random-seek-test.go compresses stdin to the RAC file format, then checks
+// that repeatedly randomly accessing that RAC file recovers the original input.
+//
+// Usage: go run rac-random-seek-test.go < input
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"os"
+
+	"github.com/google/wuffs/lib/rac"
+	"github.com/google/wuffs/lib/raczlib"
+)
+
+func main() {
+	if err := main1(); err != nil {
+		os.Stderr.WriteString(err.Error() + "\n")
+		os.Exit(1)
+	}
+}
+
+func main1() error {
+	input, err := ioutil.ReadAll(os.Stdin)
+	if err != nil {
+		return err
+	}
+	buf := &bytes.Buffer{}
+	const dChunkSize = 256
+	w := &raczlib.Writer{
+		Writer:     buf,
+		DChunkSize: dChunkSize,
+	}
+	if _, err := w.Write(input); err != nil {
+		return err
+	}
+	if err := w.Close(); err != nil {
+		return err
+	}
+	compressed := buf.Bytes()
+	r := &raczlib.Reader{
+		ReadSeeker:     bytes.NewReader(compressed),
+		CompressedSize: int64(len(compressed)),
+	}
+	numChunks, err := countRACChunks(compressed)
+	if err != nil {
+		return err
+	}
+	if want := (len(input) + dChunkSize - 1) / dChunkSize; numChunks != want {
+		return fmt.Errorf("numChunks: got %d, want %d", numChunks, want)
+	}
+
+	fmt.Printf("%8d uncompressed bytes\n", len(input))
+	fmt.Printf("%8d   compressed bytes\n", len(compressed))
+	fmt.Printf("%8d          RAC chunks\n", numChunks)
+
+	got := make([]byte, len(input))
+	if _, err := io.ReadFull(r, got); err != nil {
+		return fmt.Errorf("ReadFull: %v", err)
+	} else if !bytes.Equal(got, input) {
+		return fmt.Errorf("ReadFull: mismatch")
+	}
+
+	for i, rng := uint64(0), rand.New(rand.NewSource(1)); ; i++ {
+		m := rng.Intn(len(input) + 1)
+		n := rng.Intn(len(input) + 1)
+		if m > n {
+			m, n = n, m
+		}
+
+		got = got[:n-m]
+		if _, err := r.Seek(int64(m), io.SeekStart); err != nil {
+			return fmt.Errorf("i=%d, m=%d, n=%d: Seek: %v", i, m, n, err)
+		}
+		if _, err := io.ReadFull(r, got); err != nil {
+			return fmt.Errorf("i=%d, m=%d, n=%d: ReadFull: %v", i, m, n, err)
+		}
+
+		want := input[m:n]
+		if !bytes.Equal(got, want) {
+			return fmt.Errorf("i=%d, m=%d, n=%d: mismatch", i, m, n)
+		}
+
+		if i%1024 == 0 {
+			fmt.Printf("i=%d: ok\n", i)
+		}
+	}
+}
+
+func countRACChunks(compressed []byte) (int, error) {
+	p := &rac.Parser{
+		ReadSeeker:     bytes.NewReader(compressed),
+		CompressedSize: int64(len(compressed)),
+	}
+	for n := 0; ; n++ {
+		if _, err := p.NextChunk(); err == io.EOF {
+			return n, nil
+		} else if err != nil {
+			return 0, err
+		}
+	}
+}