blob: 43783e35aef1a3785c377262b1bbc02e03562577 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package far
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
func TestWrite(t *testing.T) {
files := []string{"a", "b", "dir/c"}
d := create(t, files)
defer os.Remove(d)
inputs := map[string]string{}
for _, path := range files {
inputs[path] = filepath.Join(d, path)
}
w := bytes.NewBuffer(nil)
if err := Write(w, inputs); err != nil {
t.Fatal(err)
}
far := w.Bytes()
if !bytes.Equal(far[0:8], []byte(Magic)) {
t.Errorf("got %x, want %x", far[0:8], []byte(Magic))
}
if !bytes.Equal(far, exampleArchive()) {
t.Errorf("archives didn't match, got:\n%s", hex.Dump(far))
}
}
func TestLengths(t *testing.T) {
if want := 16; IndexLen != want {
t.Errorf("IndexLen: got %d, want %d", IndexLen, want)
}
if want := 24; IndexEntryLen != want {
t.Errorf("IndexEntryLen: got %d, want %d", IndexEntryLen, want)
}
if want := 32; DirectoryEntryLen != want {
t.Errorf("DirectoryEntryLen: got %d, want %d", DirectoryEntryLen, want)
}
}
// create makes a temporary directory and populates it with the files in
// the given slice. the files will contain their name as content. The path of
// the created directory is returned.
func create(t *testing.T, files []string) string {
d, err := ioutil.TempDir("", t.Name())
if err != nil {
t.Fatal(err)
}
for _, path := range files {
absPath := filepath.Join(d, path)
if err := os.MkdirAll(filepath.Dir(absPath), os.ModePerm); err != nil {
t.Fatal(err)
}
f, err := os.Create(absPath)
if err != nil {
t.Fatal(err)
}
if _, err := fmt.Fprintf(f, "%s\n", path); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
}
return d
}
func TestReader(t *testing.T) {
_, err := NewReader(bytes.NewReader(exampleArchive()))
if err != nil {
t.Fatal(err)
}
corruptors := []func([]byte){
// corrupt magic
func(b []byte) { b[0] = 0 },
// corrupt index length
func(b []byte) { binary.LittleEndian.PutUint64(b[8:], 1) },
// corrupt dirindex type
func(b []byte) { b[IndexLen] = 255 },
// TODO(raggi): corrupt index entry offset
// TODO(raggi): remove index entries
}
for i, corrupt := range corruptors {
far := exampleArchive()
corrupt(far)
_, err := NewReader(bytes.NewReader(far))
if _, ok := err.(ErrInvalidArchive); !ok {
t.Errorf("corrupt archive %d, got unexpected error %v", i, err)
}
}
}
func TestListFiles(t *testing.T) {
far := exampleArchive()
r, err := NewReader(bytes.NewReader(far))
if err != nil {
t.Fatal(err)
}
files := r.List()
want := []string{"a", "b", "dir/c"}
if len(files) != len(want) {
t.Fatalf("listfiles: got %v, want %v", files, want)
}
sort.Strings(files)
for i, want := range want {
if got := files[i]; got != want {
t.Errorf("listfiles: got %q, want %q at %d", got, want, i)
}
}
}
func TestReaderOpen(t *testing.T) {
far := exampleArchive()
r, err := NewReader(bytes.NewReader(far))
if err != nil {
t.Fatal(err)
}
if got, want := len(r.dirEntries), 3; got != want {
t.Errorf("got %v, want %v", got, want)
}
for _, f := range []string{"a", "b", "dir/c"} {
ra, err := r.Open(f)
if err != nil {
t.Fatal(err)
}
// buffer past the far content padding to check clamping of the readat range
want := make([]byte, 10*1024)
copy(want, []byte(f))
want[len(f)] = '\n'
got := make([]byte, 10*1024)
n, err := ra.ReadAt(got, 0)
if err != nil {
t.Fatal(err)
}
if want := len(f) + 1; n != want {
t.Errorf("got %d, want %d", n, want)
}
if !bytes.Equal(got, want) {
t.Errorf("got %x, want %x", got, want)
}
}
ra, err := r.Open("a")
if err != nil {
t.Fatal(err)
}
// ensure that negative offsets are rejected
n, err := ra.ReadAt(make([]byte, 10), -10)
if err != io.EOF || n != 0 {
t.Errorf("got %d %v, want %d, %v", n, err, 0, io.EOF)
}
// ensure that offsets beyond length are rejected
n, err = ra.ReadAt(make([]byte, 10), 10)
if err != io.EOF || n != 0 {
t.Errorf("got %d %v, want %d, %v", n, err, 0, io.EOF)
}
}
func TestReaderReadFile(t *testing.T) {
far := exampleArchive()
r, err := NewReader(bytes.NewReader(far))
if err != nil {
t.Fatal(err)
}
if got, want := len(r.dirEntries), 3; got != want {
t.Errorf("got %v, want %v", got, want)
}
for _, f := range []string{"a", "b", "dir/c"} {
got, err := r.ReadFile(f)
if err != nil {
t.Fatal(err)
}
// buffer past the far content padding to check clamping of the readat range
want := []byte(f + "\n")
if !bytes.Equal(got, want) {
t.Errorf("got %x, want %x", got, want)
}
}
ra, err := r.Open("a")
if err != nil {
t.Fatal(err)
}
// ensure that negative offsets are rejected
n, err := ra.ReadAt(make([]byte, 10), -10)
if err != io.EOF || n != 0 {
t.Errorf("got %d %v, want %d, %v", n, err, 0, io.EOF)
}
// ensure that offsets beyond length are rejected
n, err = ra.ReadAt(make([]byte, 10), 10)
if err != io.EOF || n != 0 {
t.Errorf("got %d %v, want %d, %v", n, err, 0, io.EOF)
}
}
func TestReaderGetSize(t *testing.T) {
far := exampleArchive()
r, err := NewReader(bytes.NewReader(far))
if err != nil {
t.Fatal(err)
}
if got, want := r.GetSize("a"), uint64(2); got != want {
t.Errorf("GetSize() = %d, want %d", got, want)
}
}
func TestReadEmpty(t *testing.T) {
r, err := NewReader(bytes.NewReader(emptyArchive()))
if err != nil {
t.Fatal(err)
}
if len(r.List()) != 0 {
t.Error("empty archive should contain no files")
}
}
func TestIsFAR(t *testing.T) {
type args struct {
r io.Reader
}
tests := []struct {
name string
args args
want bool
}{
{
name: "valid magic",
args: args{strings.NewReader(Magic)},
want: true,
},
{
name: "empty",
args: args{strings.NewReader("")},
want: false,
},
{
name: "not magic",
args: args{strings.NewReader("ohai")},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsFAR(tt.args.r); got != tt.want {
t.Errorf("IsFAR() = %v, want %v", got, tt.want)
}
})
}
}
// exampleArchive produces an archive similar to far(1) output
func exampleArchive() []byte {
b := make([]byte, 16384)
copy(b, []byte{
0xc8, 0xbf, 0x0b, 0x48, 0xad, 0xab, 0xc5, 0x11, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x49, 0x52, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x49, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x53,
0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x61, 0x62, 0x64, 0x69, 0x72, 0x2f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
})
copy(b[4096:], []byte("a\n"))
copy(b[8192:], []byte("b\n"))
copy(b[12288:], []byte("dir/c\n"))
return b
}
func emptyArchive() []byte {
return []byte{0xc8, 0xbf, 0xb, 0x48, 0xad, 0xab, 0xc5, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
}