// Copyright 2021 The gVisor 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
//
//     http://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.

package buffer

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"slices"
	"strings"
	"testing"

	"gvisor.dev/gvisor/pkg/rand"
	"gvisor.dev/gvisor/pkg/state"
	"gvisor.dev/gvisor/pkg/tcpip/checksum"
)

func BenchmarkReadAt(b *testing.B) {
	b.ReportAllocs()
	var buf Buffer
	buf.Append(NewView(100))
	defer buf.Release()

	bytes := make([]byte, 10)
	for i := 0; i < b.N; i++ {
		buf.ReadAt(bytes, 0)
	}
}

func BenchmarkWriteRead(b *testing.B) {
	b.ReportAllocs()
	var buf Buffer
	defer buf.Release()
	sz := 1000
	rbuf := bytes.NewBuffer(make([]byte, sz))
	for i := 0; i < b.N; i++ {
		buf.Append(NewView(sz))
		rbuf.Reset()
		buf.ReadToWriter(rbuf, int64(sz))
	}
}

func fillAppend(b *Buffer, data []byte) {
	b.Append(NewViewWithData(data))
}

func fillAppendEnd(b *Buffer, data []byte) {
	b.GrowTo(baseChunkSize-1, false)
	b.Append(NewViewWithData(data))
	b.TrimFront(baseChunkSize - 1)
}

func fillWriteFromReader(b *Buffer, data []byte) {
	buf := bytes.NewBuffer(data)
	b.WriteFromReader(buf, int64(len(data)))
}

func fillWriteFromReaderEnd(b *Buffer, data []byte) {
	b.GrowTo(baseChunkSize-1, false)
	buf := bytes.NewBuffer(data)
	b.WriteFromReader(buf, int64(len(data)))
	b.TrimFront(baseChunkSize - 1)
}

var fillFuncs = map[string]func(*Buffer, []byte){
	"append":             fillAppend,
	"appendEnd":          fillAppendEnd,
	"writeFromReader":    fillWriteFromReader,
	"writeFromReaderEnd": fillWriteFromReaderEnd,
}

func testReadAt(t *testing.T, b *Buffer, offset int64, n int, wantStr string, wantErr error) {
	t.Helper()
	d := make([]byte, n)
	n, err := b.ReadAt(d, offset)
	if n != len(wantStr) {
		t.Errorf("got %d, want %d", n, len(wantStr))
	}
	if err != wantErr {
		t.Errorf("got err %v, want %v", err, wantErr)
	}
	if !bytes.Equal(d[:n], []byte(wantStr)) {
		t.Errorf("got %q, want %q", string(d[:n]), wantStr)
	}
}

func TestBuffer(t *testing.T) {
	testCases := []struct {
		name   string
		input  string
		output string
		op     func(*testing.T, *Buffer)
	}{
		// Preconditions.
		{
			name:   "truncate-check",
			input:  "hello",
			output: "hello", // Not touched.
			op: func(t *testing.T, b *Buffer) {
				defer func() {
					if r := recover(); r == nil {
						t.Errorf("Truncate(-1) did not panic")
					}
				}()
				b.Truncate(-1)
			},
		},
		{
			name:   "growto-check",
			input:  "hello",
			output: "hello", // Not touched.
			op: func(t *testing.T, b *Buffer) {
				defer func() {
					if r := recover(); r == nil {
						t.Errorf("GrowTo(-1) did not panic")
					}
				}()
				b.GrowTo(-1, false)
			},
		},
		{
			name:   "advance-check",
			input:  "hello",
			output: "", // Consumed.
			op: func(t *testing.T, b *Buffer) {
				defer func() {
					if r := recover(); r == nil {
						t.Errorf("advanceRead(Size()+1) did not panic")
					}
				}()
				b.advanceRead(b.Size() + 1)
			},
		},

		// Prepend.
		{
			name:   "prepend",
			input:  "world",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				b.Prepend(NewViewWithData([]byte("hello ")))
			},
		},
		{
			name:   "prepend-backfill-full",
			input:  "hello world",
			output: "jello world",
			op: func(t *testing.T, b *Buffer) {
				b.TrimFront(1)
				b.Prepend(NewViewWithData([]byte("j")))
			},
		},
		{
			name:   "prepend-backfill-under",
			input:  "hello world",
			output: "hola world",
			op: func(t *testing.T, b *Buffer) {
				b.TrimFront(5)
				b.Prepend(NewViewWithData([]byte("hola")))
			},
		},
		{
			name:   "prepend-backfill-over",
			input:  "hello world",
			output: "smello world",
			op: func(t *testing.T, b *Buffer) {
				b.TrimFront(1)
				b.Prepend(NewViewWithData([]byte("sm")))
			},
		},
		{
			name:   "prepend-fill",
			input:  strings.Repeat("1", baseChunkSize-1),
			output: "0" + strings.Repeat("1", baseChunkSize-1),
			op: func(t *testing.T, b *Buffer) {
				b.Prepend(NewViewWithData([]byte("0")))
			},
		},
		{
			name:   "prepend-overflow",
			input:  strings.Repeat("1", baseChunkSize),
			output: "0" + strings.Repeat("1", baseChunkSize),
			op: func(t *testing.T, b *Buffer) {
				b.Prepend(NewViewWithData([]byte("0")))
			},
		},
		{
			name:   "prepend-multiple-buffers",
			input:  strings.Repeat("1", baseChunkSize-1),
			output: strings.Repeat("0", baseChunkSize*3) + strings.Repeat("1", baseChunkSize-1),
			op: func(t *testing.T, b *Buffer) {
				b.Prepend(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
				b.Prepend(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
				b.Prepend(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
			},
		},

		// Append and write.
		{
			name:   "append",
			input:  "hello",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(" world")))
			},
		},
		{
			name:   "append-fill",
			input:  strings.Repeat("1", baseChunkSize-1),
			output: strings.Repeat("1", baseChunkSize-1) + "0",
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte("0")))
			},
		},
		{
			name:   "append-overflow",
			input:  strings.Repeat("1", baseChunkSize),
			output: strings.Repeat("1", baseChunkSize) + "0",
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte("0")))
			},
		},
		{
			name:   "append-multiple-views",
			input:  strings.Repeat("1", baseChunkSize-1),
			output: strings.Repeat("1", baseChunkSize-1) + strings.Repeat("0", baseChunkSize*3),
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
				b.Append(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
				b.Append(NewViewWithData([]byte(strings.Repeat("0", baseChunkSize))))
			},
		},

		// AppendOwned.
		{
			name:   "append-owned",
			input:  "hello",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				v := NewViewWithData([]byte("Xworld"))
				// Appending to a buffer that has extra references means this will
				// degrade into an "appendOwned" for the chunk being added.
				c := b.Clone()
				defer c.Release()
				b.Append(v)
				v.chunk.data[0] = ' '
			},
		},

		// Truncate.
		{
			name:   "truncate",
			input:  "hello world",
			output: "hello",
			op: func(t *testing.T, b *Buffer) {
				b.Truncate(5)
			},
		},
		{
			name:   "truncate-noop",
			input:  "hello world",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				b.Truncate(b.Size() + 1)
			},
		},
		{
			name:   "truncate-multiple-buffers",
			input:  strings.Repeat("1", baseChunkSize),
			output: strings.Repeat("1", baseChunkSize*2-1),
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(strings.Repeat("1", baseChunkSize))))
				b.Truncate(baseChunkSize*2 - 1)
			},
		},
		{
			name:   "truncate-multiple-buffers-to-one",
			input:  strings.Repeat("1", baseChunkSize),
			output: "11111",
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(strings.Repeat("1", baseChunkSize))))
				b.Truncate(5)
			},
		},

		// TrimFront.
		{
			name:   "trim",
			input:  "hello world",
			output: "world",
			op: func(t *testing.T, b *Buffer) {
				b.TrimFront(6)
			},
		},
		{
			name:   "trim-too-large",
			input:  "hello world",
			output: "",
			op: func(t *testing.T, b *Buffer) {
				b.TrimFront(b.Size() + 1)
			},
		},
		{
			name:   "trim-multiple-buffers",
			input:  strings.Repeat("1", baseChunkSize),
			output: strings.Repeat("1", baseChunkSize*2-1),
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(strings.Repeat("1", baseChunkSize))))
				b.TrimFront(1)
			},
		},
		{
			name:   "trim-multiple-buffers-to-one-buffer",
			input:  strings.Repeat("1", baseChunkSize),
			output: "1",
			op: func(t *testing.T, b *Buffer) {
				b.Append(NewViewWithData([]byte(strings.Repeat("1", baseChunkSize))))
				b.TrimFront(baseChunkSize*2 - 1)
			},
		},

		// GrowTo.
		{
			name:   "growto",
			input:  "hello world",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				b.GrowTo(1, true)
			},
		},
		{
			name:   "growto-from-zero",
			output: strings.Repeat("\x00", 1024),
			op: func(t *testing.T, b *Buffer) {
				b.GrowTo(1024, true)
			},
		},
		{
			name:   "growto-from-non-zero",
			input:  strings.Repeat("1", baseChunkSize),
			output: strings.Repeat("1", baseChunkSize) + strings.Repeat("\x00", baseChunkSize),
			op: func(t *testing.T, b *Buffer) {
				b.GrowTo(baseChunkSize*2, true)
			},
		},

		// Clone.
		{
			name:   "clone",
			input:  "hello",
			output: "hello",
			op: func(t *testing.T, b *Buffer) {
				other := b.Clone()
				bs := other.Flatten()
				want := []byte("hello")
				if !bytes.Equal(bs, want) {
					t.Errorf("expected %v, got %v", want, bs)
				}
			},
		},
		{
			name:   "copy-large",
			input:  strings.Repeat("1", baseChunkSize+1),
			output: strings.Repeat("1", baseChunkSize+1),
			op: func(t *testing.T, b *Buffer) {
				other := b.Clone()
				bs := other.Flatten()
				want := []byte(strings.Repeat("1", baseChunkSize+1))
				if !bytes.Equal(bs, want) {
					t.Errorf("expected %v, got %v", want, bs)
				}
			},
		},

		// Merge.
		{
			name:   "merge",
			input:  "hello",
			output: "hello world",
			op: func(t *testing.T, b *Buffer) {
				var other Buffer
				other.Append(NewViewWithData([]byte(" world")))
				b.Merge(&other)
				if sz := other.Size(); sz != 0 {
					t.Errorf("expected 0, got %d", sz)
				}
			},
		},
		{
			name:   "merge-large",
			input:  strings.Repeat("1", baseChunkSize+1),
			output: strings.Repeat("1", baseChunkSize+1) + strings.Repeat("0", baseChunkSize+1),
			op: func(t *testing.T, b *Buffer) {
				var other Buffer
				other.Append(NewViewWithData(([]byte(strings.Repeat("0", baseChunkSize+1)))))
				b.Merge(&other)
				if sz := other.Size(); sz != 0 {
					t.Errorf("expected 0, got %d", sz)
				}
			},
		},

		// ReadAt.
		{
			name:   "readat",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 0, 6, "hello", io.EOF) },
		},
		{
			name:   "readat-long",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 0, 8, "hello", io.EOF) },
		},
		{
			name:   "readat-short",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 0, 3, "hel", nil) },
		},
		{
			name:   "readat-offset",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 2, 3, "llo", io.EOF) },
		},
		{
			name:   "readat-long-offset",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 2, 8, "llo", io.EOF) },
		},
		{
			name:   "readat-short-offset",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, 2, 2, "ll", nil) },
		},
		{
			name:   "readat-skip-all",
			input:  "hello",
			output: "hello",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, baseChunkSize+1, 1, "", io.EOF) },
		},
		{
			name:   "readat-second-view",
			input:  strings.Repeat("0", baseChunkSize+1) + "12",
			output: strings.Repeat("0", baseChunkSize+1) + "12",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, baseChunkSize+1, 1, "1", nil) },
		},
		{
			name:   "readat-second-buffer-end",
			input:  strings.Repeat("0", baseChunkSize+1) + "12",
			output: strings.Repeat("0", baseChunkSize+1) + "12",
			op:     func(t *testing.T, b *Buffer) { testReadAt(t, b, baseChunkSize+1, 2, "12", io.EOF) },
		},
	}

	for _, tc := range testCases {
		for fillName, fn := range fillFuncs {
			t.Run(fillName+"/"+tc.name, func(t *testing.T) {
				// Construct & fill the view.
				var buf Buffer
				fn(&buf, []byte(tc.input))

				// Run the operation.
				if tc.op != nil {
					tc.op(t, &buf)
				}

				// Flatten and validate.
				out := buf.Flatten()
				if !bytes.Equal([]byte(tc.output), out) {
					t.Errorf("expected %q, got %q", tc.output, string(out))
				}

				// Ensure the size is correct.
				if len(out) != int(buf.Size()) {
					t.Errorf("size is wrong: expected %d, got %d", len(out), buf.Size())
				}

				// Calculate contents via apply.
				var appliedOut []byte
				buf.Apply(func(v *View) {
					appliedOut = append(appliedOut, v.AsSlice()...)
				})
				if len(appliedOut) != len(out) {
					t.Errorf("expected %d, got %d", len(out), len(appliedOut))
				}
				if !bytes.Equal(appliedOut, out) {
					t.Errorf("expected %v, got %v", out, appliedOut)
				}

				// Calculate contents via ReadToWriter.
				var b bytes.Buffer
				n, err := buf.ReadToWriter(&b, int64(len(out)))
				if n != int64(len(out)) {
					t.Errorf("expected %d, got %d", len(out), n)
				}
				if err != nil {
					t.Errorf("expected nil, got %v", err)
				}
				if !bytes.Equal(b.Bytes(), out) {
					t.Errorf("expected %v, got %v", out, b.Bytes())
				}
			})
		}
	}
}

func TestBufferPullUp(t *testing.T) {
	for _, tc := range []struct {
		desc   string
		inputs []string
		offset int
		length int
		output string
		failed bool
		// lengths is the lengths of each buffer node after the pull up.
		lengths []int
	}{
		{
			desc: "whole empty view",
		},
		{
			desc:    "zero pull",
			inputs:  []string{"hello", " world"},
			lengths: []int{5, 6},
		},
		{
			desc:    "whole view",
			inputs:  []string{"hello", " world"},
			offset:  0,
			length:  11,
			output:  "hello world",
			lengths: []int{11},
		},
		{
			desc:    "middle to end aligned",
			inputs:  []string{"0123", "45678", "9abcd"},
			offset:  4,
			length:  10,
			output:  "456789abcd",
			lengths: []int{4, 10},
		},
		{
			desc:    "middle to end unaligned",
			inputs:  []string{"0123", "45678", "9abcd"},
			offset:  6,
			length:  8,
			output:  "6789abcd",
			lengths: []int{4, 10},
		},
		{
			desc:    "middle aligned",
			inputs:  []string{"0123", "45678", "9abcd", "efgh"},
			offset:  6,
			length:  5,
			output:  "6789a",
			lengths: []int{4, 10, 4},
		},

		// Failed cases.
		{
			desc:   "empty view - length too long",
			offset: 0,
			length: 1,
			failed: true,
		},
		{
			desc:   "empty view - offset too large",
			offset: 1,
			length: 1,
			failed: true,
		},
		{
			desc:    "length too long",
			inputs:  []string{"0123", "45678", "9abcd"},
			offset:  4,
			length:  100,
			failed:  true,
			lengths: []int{4, 5, 5},
		},
		{
			desc:    "offset too large",
			inputs:  []string{"0123", "45678", "9abcd"},
			offset:  100,
			length:  1,
			failed:  true,
			lengths: []int{4, 5, 5},
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			var b Buffer
			defer b.Release()
			for _, s := range tc.inputs {
				v := NewViewWithData([]byte(s))
				b.appendOwned(v)
			}

			got, gotOk := b.PullUp(tc.offset, tc.length)
			want, wantOk := []byte(tc.output), !tc.failed
			if gotOk == wantOk && got.Size() == 0 && len(want) == 0 {
				return
			}
			if gotOk != wantOk || !bytes.Equal(got.AsSlice(), want) {
				t.Errorf("v.PullUp(%d, %d) = %q, %t; %q, %t", tc.offset, tc.length, got.AsSlice(), gotOk, want, wantOk)
			}

			var gotLengths []int
			for v := b.data.Front(); v != nil; v = v.Next() {
				gotLengths = append(gotLengths, v.Size())
			}
			if !slices.Equal(gotLengths, tc.lengths) {
				t.Errorf("lengths = %v; want %v", gotLengths, tc.lengths)
			}
		})
	}
}

func TestReadFromLargeWriter(t *testing.T) {
	writeSize := int64(1 << 20)
	largeWriter := bytes.NewBuffer(make([]byte, writeSize))
	b := Buffer{}
	// Expect this write to be buffered into several MaxChunkSize sized views.
	n, err := b.WriteFromReader(largeWriter, writeSize)
	if err != nil {
		t.Fatalf("b.WriteFromReader() failed: want err=nil, got %v", err)
	}
	if n != writeSize {
		t.Errorf("got b.WriteFromReader()=%d, want %d", n, writeSize)
	}
	nChunks := int(writeSize / MaxChunkSize)
	if b.data.Len() != nChunks {
		t.Errorf("b.WriteFromReader() failed, got b.data.Len()=%d, want %d", b.data.Len(), nChunks)
	}
}

func TestRead(t *testing.T) {
	readStrings := []string{"abcdef", "123456", "ghijkl"}
	totalSize := len(readStrings) * len(readStrings[0])
	for readSz := 0; readSz < totalSize+1; readSz++ {
		b := Buffer{}
		for _, s := range readStrings {
			v := NewViewWithData([]byte(s))
			b.appendOwned(v)
		}
		orig := b.Clone()
		orig.Truncate(int64(readSz))
		p := make([]byte, readSz)
		_, err := b.read(p)
		if err != nil {
			t.Fatalf("Read([]byte(%d)) failed: %v", readSz, err)
		}
		if !bytes.Equal(p, orig.Flatten()) {
			t.Errorf("Read([]byte(%d)) failed, want p=%v, got %v", readSz, orig.Flatten(), p)
		}
		if int(b.Size()) != totalSize-readSz {
			t.Errorf("Read([]byte(%d)) failed, want b.Size()=%v, got %v", readSz, totalSize-readSz, b.Size())
		}
	}
}

func TestReadByte(t *testing.T) {
	readString := "abcdef123456ghijkl"
	b := Buffer{}
	nViews := 3
	for i := 0; i < nViews; i++ {
		vLen := len(readString) / nViews
		v := NewViewWithData([]byte(readString[i*vLen : (i+1)*vLen]))
		b.appendOwned(v)
	}
	for i := 0; i < len(readString); i++ {
		orig := readString[i]
		bt, err := b.readByte()
		if err != nil {
			t.Fatalf("readByte() failed: %v", err)
		}
		if bt != orig {
			t.Errorf("readByte() failed, want %v, got %v", orig, bt)
		}
		if int(b.Size()) != len(readString[i+1:]) {
			t.Errorf("readByte() failed, want b.Size()=%v, got %v", len(readString[i+1:]), b.Size())
		}
	}
}

func TestPullUpModifiedViews(t *testing.T) {
	var b Buffer
	defer b.Release()
	for _, s := range []string{"abcdef", "123456", "ghijkl"} {
		v := NewViewWithData([]byte(s))
		v.TrimFront(3)
		b.appendOwned(v)
	}

	v, ok := b.PullUp(3, 3)
	if !ok {
		t.Errorf("PullUp failed: want ok=true, got ok=false")
	}
	want := []byte("456")
	if !bytes.Equal(v.AsSlice(), want) {
		t.Errorf("PullUp failed: want %v, got %v", want, v.AsSlice())
	}
}

func TestBufferClone(t *testing.T) {
	const (
		originalSize  = 90
		bytesToDelete = 30
	)
	b := MakeWithData(bytes.Repeat([]byte{originalSize}, originalSize))
	clonedB := b.Clone()
	b.TrimFront(bytesToDelete)

	if got, want := int(b.Size()), originalSize-bytesToDelete; got != want {
		t.Errorf("original buffer was not changed: size expected = %d, got = %d", want, got)
	}
	if got := clonedB.Size(); got != originalSize {
		t.Errorf("cloned buffer should not be modified: expected size = %d, got = %d", originalSize, got)
	}
}

func TestBufferSubApply(t *testing.T) {
	var b Buffer
	defer b.Release()
	b.appendOwned(NewViewWithData([]byte("0123")))
	b.appendOwned(NewViewWithData([]byte("45678")))
	b.appendOwned(NewViewWithData([]byte("9abcd")))
	data := []byte("0123456789abcd")

	for i := 0; i <= len(data); i++ {
		for j := i; j <= len(data); j++ {
			t.Run(fmt.Sprintf("SubApply(%d,%d)", i, j), func(t *testing.T) {
				var got []byte
				b.SubApply(i, j-i, func(v *View) {
					got = append(got, v.AsSlice()...)
				})
				if want := data[i:j]; !bytes.Equal(got, want) {
					t.Errorf("got = %q; want %q", got, want)
				}
			})
		}
	}
}

func doSaveAndLoad(t *testing.T, toSave, toLoad *Buffer) {
	t.Helper()
	var buf bytes.Buffer
	ctx := context.Background()
	if _, err := state.Save(ctx, &buf, toSave); err != nil {
		t.Fatal("state.Save:", err)
	}
	if _, err := state.Load(ctx, bytes.NewReader(buf.Bytes()), toLoad); err != nil {
		t.Fatal("state.Load:", err)
	}
}

func TestSaveRestoreBufferEmpty(t *testing.T) {
	var toSave Buffer
	var b Buffer
	doSaveAndLoad(t, &toSave, &b)

	if got := b.Flatten(); len(got) != 0 {
		t.Errorf("v.Flatten() = %x, want []", got)
	}
}

func TestSaveRestoreBuffer(t *testing.T) {
	// Create data that fits  slots.
	data := bytes.Join([][]byte{
		bytes.Repeat([]byte{1, 2}, baseChunkSize),
	}, nil)

	var toSave Buffer
	toSave.appendOwned(NewViewWithData(data))

	var b Buffer
	doSaveAndLoad(t, &toSave, &b)

	// Next available slot at index 3; 0-2 slot are used.
	if got := b.Flatten(); !bytes.Equal(got, data) {
		t.Errorf("v.Flatten() = %x, want %x", got, data)
	}
}

func TestRangeIntersect(t *testing.T) {
	for _, tc := range []struct {
		desc       string
		x, y, want Range
	}{
		{
			desc: "empty intersects empty",
		},
		{
			desc: "empty intersection",
			x:    Range{end: 10},
			y:    Range{begin: 10, end: 20},
		},
		{
			desc: "some intersection",
			x:    Range{begin: 5, end: 20},
			y:    Range{end: 10},
			want: Range{begin: 5, end: 10},
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			if got := tc.x.Intersect(tc.y); got != tc.want {
				t.Errorf("(%#v).Intersect(%#v) = %#v; want %#v", tc.x, tc.y, got, tc.want)
			}
			if got := tc.y.Intersect(tc.x); got != tc.want {
				t.Errorf("(%#v).Intersect(%#v) = %#v; want %#v", tc.y, tc.x, got, tc.want)
			}
		})
	}
}

func TestRangeOffset(t *testing.T) {
	for _, tc := range []struct {
		input  Range
		offset int
		output Range
	}{
		{
			input:  Range{},
			offset: 0,
			output: Range{},
		},
		{
			input:  Range{},
			offset: -1,
			output: Range{begin: -1, end: -1},
		},
		{
			input:  Range{begin: 10, end: 20},
			offset: -1,
			output: Range{begin: 9, end: 19},
		},
		{
			input:  Range{begin: 10, end: 20},
			offset: 2,
			output: Range{begin: 12, end: 22},
		},
	} {
		if got := tc.input.Offset(tc.offset); got != tc.output {
			t.Errorf("(%#v).Offset(%d) = %#v, want %#v", tc.input, tc.offset, got, tc.output)
		}
	}
}

func TestRangeLen(t *testing.T) {
	for _, tc := range []struct {
		r    Range
		want int
	}{
		{r: Range{}, want: 0},
		{r: Range{begin: 1, end: 1}, want: 0},
		{r: Range{begin: -1, end: -1}, want: 0},
		{r: Range{end: 10}, want: 10},
		{r: Range{begin: 5, end: 10}, want: 5},
	} {
		if got := tc.r.Len(); got != tc.want {
			t.Errorf("(%#v).Len() = %d, want %d", tc.r, got, tc.want)
		}
	}
}

func TestChecksum(t *testing.T) {
	data := make([]byte, 100)
	rand.Read(data)

	b := MakeWithData(data[:30])
	b.appendOwned(NewViewWithData(data[30:70]))
	b.appendOwned(NewViewWithData(data[70:]))

	for offset := 0; offset < 100; offset++ {
		var cs checksum.Checksumer
		cs.Add(data[offset:])
		dataChecksum := cs.Checksum()
		bufChecksum := b.Checksum(offset)

		if dataChecksum != bufChecksum {
			t.Errorf("(%#v).Checksum(%d) = %d, want %d", b, offset, bufChecksum, dataChecksum)
		}
	}
}
