1// Copyright 2019 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package raclz4_test
16
17import (
18	"bytes"
19	"fmt"
20	"io"
21	"log"
22
23	"github.com/google/wuffs/lib/rac"
24	"github.com/google/wuffs/lib/raclz4"
25)
26
27// Example_roundTrip demonstrates compressing (using a rac.Writer and a
28// raclz4.CodecWriter) and decompressing (using a rac.Reader and a
29// raclz4.CodecReader). This includes decompressing an excerpt of the original
30// data, exercising the "random access" part of RAC.
31func Example_roundTrip() {
32	// Create some test data.
33	oBuf := &bytes.Buffer{}
34	for i := 99; i > 0; i-- {
35		fmt.Fprintf(oBuf, "%d bottles of beer on the wall, %d bottles of beer.\n"+
36			"Take one down, pass it around, %d bottles of beer on the wall.\n",
37			i, i, i-1)
38	}
39	original := oBuf.Bytes()
40
41	// Create the RAC file.
42	cBuf := &bytes.Buffer{}
43	w := &rac.Writer{
44		Writer:      cBuf,
45		CodecWriter: &raclz4.CodecWriter{},
46		// It's not necessary to explicitly declare the DChunkSize. The zero
47		// value implies a reasonable default. Nonetheless, using a 1 KiB
48		// DChunkSize (which is relatively small) makes for a more interesting
49		// test, as the resultant RAC file then contains more than one chunk.
50		DChunkSize: 1024,
51		// We also use the default IndexLocation value, which makes for a
52		// simpler example, but if you're copy/pasting this code, note that
53		// using an explicit IndexLocationAtStart can result in slightly more
54		// efficient RAC files, at the cost of using more memory to encode.
55	}
56	if _, err := w.Write(original); err != nil {
57		log.Fatalf("Write: %v", err)
58	}
59	if err := w.Close(); err != nil {
60		log.Fatalf("Close: %v", err)
61	}
62	compressed := cBuf.Bytes()
63
64	// The exact compression ratio depends on the lz4 encoder's algorithm,
65	// which can change across Go standard library releases, but it should be
66	// at least a 3x ratio. It'd be larger if we didn't specify an explicit
67	// (but relatively small) DChunkSize.
68	if ratio := len(original) / len(compressed); ratio < 3 {
69		log.Fatalf("compression ratio (%dx) was too small", ratio)
70	}
71
72	// Prepare to decompress.
73	r := &rac.Reader{
74		ReadSeeker:     bytes.NewReader(compressed),
75		CompressedSize: int64(len(compressed)),
76		CodecReaders:   []rac.CodecReader{&raclz4.CodecReader{}},
77	}
78	defer r.Close()
79
80	// Read the whole file.
81	wBuf := &bytes.Buffer{}
82	if _, err := io.Copy(wBuf, r); err != nil {
83		log.Fatal(err)
84	}
85	wholeFile := wBuf.Bytes()
86	if !bytes.Equal(wholeFile, original) {
87		log.Fatal("round trip did not preserve whole file")
88	} else {
89		fmt.Printf("Whole file preserved (%d bytes).\n", len(wholeFile))
90	}
91
92	// Read an excerpt.
93	const offset, length = 3000, 1200
94	want := original[offset : offset+length]
95	got := make([]byte, length)
96	if _, err := r.Seek(offset, io.SeekStart); err != nil {
97		log.Fatalf("Seek: %v", err)
98	}
99	if _, err := io.ReadFull(r, got); err != nil {
100		log.Fatalf("ReadFull: %v", err)
101	}
102	if !bytes.Equal(got, want) {
103		log.Fatal("round trip did not preserve excerpt")
104	} else {
105		fmt.Printf("Excerpt    preserved  (%d bytes).\n", len(got))
106	}
107
108	// Output:
109	// Whole file preserved (11357 bytes).
110	// Excerpt    preserved  (1200 bytes).
111}
112