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
+ }
+ }
+}