blob: 8e5ca72449c4f67954b6aa712e110aa5df927105 [file] [log] [blame]
// Copyright 2016 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 direntry
import (
"bytes"
"errors"
"testing"
"time"
"unsafe"
"thinfs/fs"
)
func TestDirentrySize(t *testing.T) {
short := &shortDirentry{}
size := unsafe.Sizeof(*short)
if size != DirentrySize {
t.Fatalf("Unexpected short direntry size: %d (expected %d)", size, DirentrySize)
}
align := unsafe.Alignof(*short)
if align != 1 {
t.Fatalf("Unexpected short direntry alignment: %d (expected %d)", align, 1)
}
long := &longDirentry{}
size = unsafe.Sizeof(*long)
if size != DirentrySize {
t.Fatalf("Unexpected long direntry size: %d (expected %d)", size, DirentrySize)
}
align = unsafe.Alignof(*long)
if align != 1 {
t.Fatalf("Unexpected long direntry alignment: %d (expected %d)", align, 1)
}
}
func checkEntry(t *testing.T, d *Dirent, goldName string, goldCluster uint32, goldAttr fs.FileType, goldSize uint32) {
if d.Cluster != goldCluster {
t.Fatalf("Expected cluster %d, saw %d", goldCluster, d.Cluster)
} else if d.GetType() != goldAttr {
t.Fatalf("Expected type %d, saw %d", goldAttr, d.GetType())
} else if d.GetName() != goldName {
t.Fatalf("Expected name %s, saw %s", goldName, d.GetName())
} else if d.Size != goldSize {
t.Fatalf("Expected size %d, saw %d", goldSize, d.Size)
}
if d.IsFree() {
t.Fatal("By default, direntries should not be free")
} else if d.IsLastFree() {
t.Fatal("By default, direntries should not be last free")
}
}
func TestUpdateWriteTime(t *testing.T) {
goldName := "FILE.TXT"
goldCluster := uint32(1243)
goldAttr := fs.FileTypeRegularFile
goldSize := uint32(5)
d := New(goldName, goldCluster, goldAttr)
d.Size = goldSize
checkEntry(t, d, goldName, goldCluster, goldAttr, goldSize)
checkTime := func(newTime, goldTime time.Time) {
// Set the time
d.WriteTime = newTime
// Serialize and deserialize to simulate writing to disk
callback := func(i int) ([]byte, error) {
panic("Callback should not be necessary")
}
buf, err := d.Serialize(callback)
if err != nil {
t.Fatal(err)
}
// Load dirent
callback = func(i int) ([]byte, error) {
return buf, nil
}
d, _, err = LoadDirent(callback, 0)
if err != nil {
t.Fatal(err)
}
// FAT timestamps use two second granularity. Compare the "on disk" time with the actual time
// we intended to write.
diskTime := d.WriteTime
if diskTime != goldTime {
t.Fatalf("Invalid disk time (actual %s), (expected %s)", diskTime, goldTime)
}
}
// Test a normal time
newTime := time.Now()
goldTime := newTime.Truncate(time.Second * 2)
checkTime(newTime, goldTime)
// Test a time in the distant future
newTime = time.Date(2108, 1, 1, 1, 1, 0, 0, time.Local)
goldTime = time.Date(2107, 12, 31, 23, 59, 58, 0, time.Local)
checkTime(newTime, goldTime)
// Test a time in the extremely distant future
newTime = time.Date(2200, 1, 1, 1, 1, 0, 0, time.Local)
goldTime = time.Date(2107, 12, 31, 23, 59, 58, 0, time.Local)
checkTime(newTime, goldTime)
}
func appendEmptyFile(directory []byte, name string) []byte {
d := New(name, 0, fs.FileTypeRegularFile)
callback := func(i int) ([]byte, error) {
if i >= len(directory)/DirentrySize {
// Pretend we reached the end of the directory
return LastFreeDirent(), nil
}
return directory[i*DirentrySize : (i+1)*DirentrySize], nil
}
buf, err := d.Serialize(callback)
if err != nil {
panic("Could not append empty file")
}
return append(directory, buf...)
}
func appendFree(directory []byte, lastFree bool) []byte {
if lastFree {
return append(directory, LastFreeDirent()...)
}
return append(directory, FreeDirent()...)
}
func checkedSerialize(t *testing.T, directory []byte, d *Dirent, goldSize int) []byte {
callback := func(i int) ([]byte, error) {
return directory[i*DirentrySize : (i+1)*DirentrySize], nil
}
buf, err := d.Serialize(callback)
if err != nil {
t.Fatal(err)
} else if len(buf) != goldSize {
t.Fatalf("Expected serialized buffer to have length %d, but it had length %d", goldSize, len(buf))
}
return buf
}
func checkedLookup(t *testing.T, directory []byte, name string, goldIndex int) *Dirent {
callback := func(i int) ([]byte, error) {
return directory[i*DirentrySize : (i+1)*DirentrySize], nil
}
d, foundIndex, err := LookupDirent(callback, name)
if err != nil {
t.Fatal(err)
} else if foundIndex != goldIndex {
t.Fatalf("Found a direntry at an unexpected index %d (expected %d)", foundIndex, goldIndex)
}
return d
}
func TestSerializeAndLoadShort(t *testing.T) {
goldName := "FILENAME.TXT"
goldCluster := uint32(1)
goldAttr := fs.FileTypeRegularFile
goldSize := uint32(5)
d := New(goldName, goldCluster, goldAttr)
d.Size = goldSize
checkEntry(t, d, goldName, goldCluster, goldAttr, goldSize)
var directory []byte
directory = appendFree(directory /* lastFree = */, true)
buf := checkedSerialize(t, directory, d, DirentrySize)
directory = make([]byte, 0)
directory = append(directory, buf...)
directory = appendFree(directory /* lastFree = */, true)
foundDirent := checkedLookup(t, directory, goldName, 0)
checkEntry(t, foundDirent, goldName, goldCluster, goldAttr, goldSize)
}
func TestSerializeAndLoadShortAtOffset(t *testing.T) {
goldName := "FILE.FOO"
goldCluster := uint32(123)
goldAttr := fs.FileTypeRegularFile
goldSize := uint32(0)
d := New(goldName, goldCluster, goldAttr)
d.Size = goldSize
checkEntry(t, d, goldName, goldCluster, goldAttr, goldSize)
var directory []byte
directory = appendEmptyFile(directory, "A.TXT")
directory = appendEmptyFile(directory, "B.TXT")
directory = appendEmptyFile(directory, "The quick brown.fox")
directory = appendFree(directory /* lastFree = */, false)
directory = appendFree(directory /* lastFree = */, true)
buf := checkedSerialize(t, directory, d, DirentrySize)
directory = make([]byte, 0)
directory = appendEmptyFile(directory, "A.TXT") // Index 0
directory = appendEmptyFile(directory, "B.TXT") // Index 1
directory = appendEmptyFile(directory, "The quick brown.fox") // Index 2, 3, 4
directory = append(directory, buf...) // Index 5
directory = appendFree(directory /* lastFree = */, true)
foundDirent := checkedLookup(t, directory, goldName, 5)
checkEntry(t, foundDirent, goldName, goldCluster, goldAttr, goldSize)
}
func TestSerializeAndLoadLong(t *testing.T) {
goldName := "The quick brown.fox"
goldCluster := uint32(2)
goldAttr := fs.FileTypeRegularFile
goldSize := uint32(5)
d := New(goldName, goldCluster, goldAttr)
d.Size = goldSize
checkEntry(t, d, goldName, goldCluster, goldAttr, goldSize)
var directory []byte
directory = appendFree(directory /* lastFree = */, true)
buf := checkedSerialize(t, directory, d, DirentrySize*3) // 2 for LFN, 1 for short name
directory = make([]byte, 0)
directory = append(directory, buf...)
directory = appendFree(directory /* lastFree = */, true)
foundDirent := checkedLookup(t, directory, goldName, 0)
checkEntry(t, foundDirent, goldName, goldCluster, goldAttr, goldSize)
}
func TestSerializeAndLoadGenerationNumber(t *testing.T) {
// Serialize names (creating generation numbers) and write them to a directory
var directory []byte
foo1 := ".foobar"
goldFoo1Short := []byte("FOOBAR~1 ")
directory = appendEmptyFile(directory, foo1) // Index 0, 1. Generation number: 1
directory = appendFree(directory /* lastFree = */, true)
// Load the direntry.
entry := checkedLookup(t, directory, ".foobar", 0)
if !bytes.Equal(entry.nameDOS, goldFoo1Short) {
t.Fatal("Uxpected short name: ", entry.nameDOS)
}
// Re-serialize the direntry. Show that the generation number is unchanged.
checkedSerialize(t, directory, entry, DirentrySize*2)
if !bytes.Equal(entry.nameDOS, goldFoo1Short) {
t.Fatal("Uxpected short name: ", entry.nameDOS)
}
}
func TestSerializeAndLoadLongAtOffset(t *testing.T) {
goldName := ".FOOBAR"
goldCluster := uint32(0x00112233)
goldAttr := fs.FileTypeRegularFile
goldSize := uint32(0)
d := New(goldName, goldCluster, goldAttr)
d.Size = goldSize
checkEntry(t, d, goldName, goldCluster, goldAttr, goldSize)
var directory []byte
directory = appendEmptyFile(directory, ".foobar") // Index 0, 1
directory = appendEmptyFile(directory, ".Foobar") // Index 2, 3
directory = appendEmptyFile(directory, ".FOobar") // Index 4, 5
directory = appendFree(directory /* lastFree = */, true)
buf := checkedSerialize(t, directory, d, DirentrySize*2)
directory = make([]byte, 0)
directory = appendEmptyFile(directory, ".foobar") // Index 0, 1
directory = appendEmptyFile(directory, ".Foobar") // Index 2, 3
directory = appendEmptyFile(directory, ".FOobar") // Index 4, 5
directory = appendFree(directory /* lastFree = */, false) // Index 6
directory = append(directory, buf...) // Index 7, 8
directory = appendFree(directory /* lastFree = */, true)
// We should be able to distinguish between the different variations of ".foobar".
checkedLookup(t, directory, ".foobar", 0)
checkedLookup(t, directory, ".Foobar", 2)
checkedLookup(t, directory, ".FOobar", 4)
foundDirent := checkedLookup(t, directory, goldName, 7)
checkEntry(t, foundDirent, goldName, goldCluster, goldAttr, goldSize)
}
func TestLoadMissingOffset(t *testing.T) {
var directory []byte
directory = appendEmptyFile(directory, ".foobar") // Index 0, 1
directory = appendEmptyFile(directory, ".Foobar") // Index 2, 3
directory = appendEmptyFile(directory, ".FOobar") // Index 4, 5
directory = appendFree(directory /* lastFree = */, true)
// We should be able to distinguish between the different variations of ".foobar".
checkedLookup(t, directory, ".foobar", 0)
checkedLookup(t, directory, ".Foobar", 2)
checkedLookup(t, directory, ".FOobar", 4)
foundDirent := checkedLookup(t, directory, ".FooBar", 0)
if foundDirent != nil {
t.Fatal("Expected that dirent would not be found")
}
}
func TestSerializeError(t *testing.T) {
// Serialize with a completely broken callback
d := New("This long filename will require a generation number (and callback)", 0, fs.FileTypeRegularFile)
goldErr := errors.New("This is the first callback error")
callback := func(i int) ([]byte, error) {
return nil, goldErr
}
_, err := d.Serialize(callback)
if err != goldErr {
t.Fatalf("Expected error %s, saw %s", goldErr, err)
}
}
func TestLookupCallbackError(t *testing.T) {
goldErr := errors.New("This is a callback error")
callback := func(i int) ([]byte, error) {
return nil, goldErr
}
_, _, err := LookupDirent(callback, "foobar")
if err != goldErr {
t.Fatalf("Expected error %s, saw %s", goldErr, err)
}
}
func TestLoadLongDirentError(t *testing.T) {
var directory []byte
directory = appendEmptyFile(directory, "The quick brown.fox") // Index 0, 1, 2
directory = appendFree(directory /* lastFree = */, true)
callback := func(i int) ([]byte, error) {
return directory[i*DirentrySize : (i+1)*DirentrySize], nil
}
// Try reading a long direntry from an invalid starting location.
_, _, err := LoadDirent(callback, 1)
if err != errLongDirentry {
t.Fatalf("Expected err %s, saw %s", errLongDirentry, err)
}
// Try reading a long direntry when the corresponding short direntry is free.
copy(directory[DirentrySize*2:DirentrySize*3], FreeDirent())
_, _, err = LoadDirent(callback, 0)
if err != errLongDirentry {
t.Fatalf("Expected err %s, saw %s", errLongDirentry, err)
}
}