diff --git a/cmd/exfat_print_boot_sector_header/main.go b/cmd/exfat_print_boot_sector_header/main.go index dca5c8d..a8b6d44 100644 --- a/cmd/exfat_print_boot_sector_header/main.go +++ b/cmd/exfat_print_boot_sector_header/main.go @@ -43,5 +43,5 @@ func main() { err = er.Parse() log.PanicIf(err) - er.ActiveBootRegion().Dump() + er.ActiveBootSectorHeader().Dump() } diff --git a/navigator_entry_types.go b/navigator_entry_types.go index 7437e53..4f7e92b 100644 --- a/navigator_entry_types.go +++ b/navigator_entry_types.go @@ -507,7 +507,7 @@ type ExfatVolumeGuidDirectoryEntry struct { // String returns a descriptive string. func (vgde ExfatVolumeGuidDirectoryEntry) String() string { - return fmt.Sprintf("VolumeGuidDirectoryEntry", vgde.SecondaryCountRaw, vgde.SetChecksum, vgde.GeneralPrimaryFlags, vgde.VolumeGuid) + return fmt.Sprintf("VolumeGuidDirectoryEntry", vgde.SecondaryCountRaw, vgde.SetChecksum, vgde.GeneralPrimaryFlags, vgde.VolumeGuid[:4]) } // SecondaryCount returns the count of associated secondary-records. diff --git a/navigator_entry_types_test.go b/navigator_entry_types_test.go new file mode 100644 index 0000000..7b9566a --- /dev/null +++ b/navigator_entry_types_test.go @@ -0,0 +1,111 @@ +package exfat + +import ( + "testing" +) + +func TestEntryType_Dump(t *testing.T) { + EntryType(0xab).Dump() +} + +func TestEntryType_String(t *testing.T) { + s := EntryType(0xab).String() + if s != "EntryType" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatFileDirectoryEntry_Dump(t *testing.T) { + fdf := ExfatFileDirectoryEntry{} + fdf.Dump() +} + +func TestExfatStreamExtensionDirectoryEntry_Dump(t *testing.T) { + sede := ExfatStreamExtensionDirectoryEntry{} + sede.Dump() +} + +func TestDirectoryEntryParserKey_String(t *testing.T) { + depk := DirectoryEntryParserKey{} + s := depk.String() + if s != "DirectoryEntryParserKey" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestFileAttributes_String(t *testing.T) { + s := FileAttributes(0x1234).String() + if s != "FileAttributes" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatVolumeGuidDirectoryEntry_String(t *testing.T) { + vgde := ExfatVolumeGuidDirectoryEntry{} + s := vgde.String() + if s != "VolumeGuidDirectoryEntry" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatVolumeGuidDirectoryEntry_SecondaryCount(t *testing.T) { + vgde := ExfatVolumeGuidDirectoryEntry{ + SecondaryCountRaw: 99, + } + + if vgde.SecondaryCount() != 99 { + t.Fatalf("SecondaryCount not correct.") + } +} + +func TestExfatVolumeGuidDirectoryEntry_TypeName(t *testing.T) { + vgde := ExfatVolumeGuidDirectoryEntry{} + if vgde.TypeName() != "VolumeGuid" { + t.Fatalf("TypeName not correct.") + } +} + +func TestExfatTexFATDirectoryEntry_String(t *testing.T) { + tfde := ExfatTexFATDirectoryEntry{} + s := tfde.String() + if s != "TexFATDirectoryEntry<>" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatTexFATDirectoryEntry_TypeName(t *testing.T) { + tfde := ExfatTexFATDirectoryEntry{} + if tfde.TypeName() != "TexFAT" { + t.Fatalf("TypeName not correct.") + } +} + +func TestExfatVendorExtensionDirectoryEntry_String(t *testing.T) { + vede := ExfatVendorExtensionDirectoryEntry{} + s := vede.String() + if s != "VendorExtensionDirectoryEntry" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatVendorExtensionDirectoryEntry_TypeName(t *testing.T) { + vede := ExfatVendorExtensionDirectoryEntry{} + if vede.TypeName() != "VendorExtension" { + t.Fatalf("TypeName not correct.") + } +} + +func TestExfatVendorAllocationDirectoryEntry_String(t *testing.T) { + vade := ExfatVendorAllocationDirectoryEntry{} + s := vade.String() + if s != "VendorAllocationDirectoryEntry" { + t.Fatalf("String not correct: [%s]", s) + } +} + +func TestExfatVendorAllocationDirectoryEntry_TypeName(t *testing.T) { + vade := ExfatVendorAllocationDirectoryEntry{} + if vade.TypeName() != "VendorAllocation" { + t.Fatalf("TypeName not correct.") + } +} diff --git a/navigator_test.go b/navigator_test.go index ddf255e..1ba4e61 100644 --- a/navigator_test.go +++ b/navigator_test.go @@ -297,7 +297,7 @@ func TestDirectoryEntryIndex_GetFile(t *testing.T) { } } -func TestDirectoryEntryIndex_FindIndexedFile(t *testing.T) { +func TestDirectoryEntryIndex_FindIndexedFile__Hit(t *testing.T) { f, er := getTestFileAndParser() defer f.Close() @@ -326,7 +326,27 @@ func TestDirectoryEntryIndex_FindIndexedFile(t *testing.T) { } } -func TestDirectoryEntryIndex_FindIndexedFileFileDirectoryEntry(t *testing.T) { +func TestDirectoryEntryIndex_FindIndexedFile__Miss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + _, found := index.FindIndexedFile("invalid-file") + if found != false { + t.Fatalf("Expected invalid-file to not be found.") + } +} + +func TestDirectoryEntryIndex_FindIndexedFileFileDirectoryEntry__Hit(t *testing.T) { f, er := getTestFileAndParser() defer f.Close() @@ -351,7 +371,27 @@ func TestDirectoryEntryIndex_FindIndexedFileFileDirectoryEntry(t *testing.T) { } } -func TestDirectoryEntryIndex_FindIndexedFileStreamExtensionDirectoryEntry(t *testing.T) { +func TestDirectoryEntryIndex_FindIndexedFileFileDirectoryEntry__Miss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + fdf := index.FindIndexedFileFileDirectoryEntry("invalid-file") + if fdf != nil { + t.Fatalf("Expected file miss.") + } +} + +func TestDirectoryEntryIndex_FindIndexedFileStreamExtensionDirectoryEntry__Hit(t *testing.T) { f, er := getTestFileAndParser() defer f.Close() @@ -370,3 +410,91 @@ func TestDirectoryEntryIndex_FindIndexedFileStreamExtensionDirectoryEntry(t *tes t.Fatalf("Stream-extension entry-type not found: (%d)", sede.FirstCluster) } } + +func TestDirectoryEntryIndex_FindIndexedFileDirectoryEntry__Hit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + de := index.FindIndexedFileDirectoryEntry("2-delahaye-type-165-cabriolet-dsc_8025.jpg", "File", 0) + if de == nil { + t.Fatalf("Could not find entry.") + } + + fdf := de.(*ExfatFileDirectoryEntry) + + fdfExpected := index.FindIndexedFileFileDirectoryEntry("2-delahaye-type-165-cabriolet-dsc_8025.jpg") + + if fdf != fdfExpected { + t.Fatalf("Entry not found.") + } +} + +func TestDirectoryEntryIndex_FindIndexedFileDirectoryEntry__MissOnFile(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + de := index.FindIndexedFileDirectoryEntry("invalid-file", "File", 0) + if de != nil { + t.Fatalf("Expected lookup miss.") + } +} + +func TestDirectoryEntryIndex_FindIndexedFileDirectoryEntry__MissOnType(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + de := index.FindIndexedFileDirectoryEntry("2-delahaye-type-165-cabriolet-dsc_8025.jpg", "InvalidType", 0) + if de != nil { + t.Fatalf("Expected lookup miss.") + } +} + +func TestDirectoryEntryIndex_FindIndexedFileDirectoryEntry__MissOnIndex(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + firstClusterNumber := er.FirstClusterOfRootDirectory() + en := NewExfatNavigator(er, firstClusterNumber) + + index, _, _, err := en.IndexDirectoryEntries() + log.PanicIf(err) + + de := index.FindIndexedFileDirectoryEntry("2-delahaye-type-165-cabriolet-dsc_8025.jpg", "FileName", 4) + if de != nil { + t.Fatalf("Expected lookup miss.") + } +} diff --git a/structures.go b/structures.go index f0dc37f..ac17376 100644 --- a/structures.go +++ b/structures.go @@ -498,7 +498,7 @@ func (er *ExfatReader) readExtendedBootSector(sectorSize uint32) (extendedBootCo log.PanicIf(err) if extendedBootSignature != requiredExtendedBootSignature { - panic(fmt.Errorf("extended boot-signature not correct: %x", extendedBootSignature)) + log.Panicf("extended boot-signature not correct: %x", extendedBootSignature) } return extendedBootCode, nil @@ -880,9 +880,9 @@ func (er *ExfatReader) SectorsPerCluster() uint32 { return er.bootRegion.bsh.SectorsPerCluster() } -// ActiveBootRegion returns the active boot-sector struct (whether main or +// ActiveBootSectorHeader returns the active boot-sector struct (whether main or // backup). -func (er *ExfatReader) ActiveBootRegion() BootSectorHeader { +func (er *ExfatReader) ActiveBootSectorHeader() BootSectorHeader { // TODO(dustin): !! Add test. diff --git a/structures_test.go b/structures_test.go index 9d7f904..1769688 100644 --- a/structures_test.go +++ b/structures_test.go @@ -167,6 +167,27 @@ func TestExfatReader_parseFats(t *testing.T) { // TODO(dustin): Add additional validation on FAT structures. } +func TestExfatReader_parseFats__NotLoaded(t *testing.T) { + defer func() { + errRaw := recover() + if errRaw == nil { + t.Fatalf("Expected error when BSH not yet loaded.") + } + + err := errRaw.(error) + if err.Error() != "boot-sectors not loaded yet" { + t.Fatalf("Expected not-loaded error.") + } + }() + + f, er := getTestFileAndParser() + + defer f.Close() + + _, err := er.parseFats() + log.PanicIf(err) +} + func TestExfatReader_Parse(t *testing.T) { f, er := getTestFileAndParser() @@ -175,3 +196,85 @@ func TestExfatReader_Parse(t *testing.T) { err := er.Parse() log.PanicIf(err) } + +func TestExfatReader_getCurrentSector(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + sector, offset := er.getCurrentSector() + if sector == 0 || offset != 0 { + t.Fatalf("Current sector not correct: (%d)", sector) + } +} + +func TestExfatReader_printCurrentSector(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + er.printCurrentSector() +} + +func TestExfatReader_assertAlignedToSector__ok(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + er.assertAlignedToSector() +} + +func TestExfatReader_assertAlignedToSector__fail(t *testing.T) { + defer func() { + err := recover() + if err == nil { + t.Fatalf("Expected failure when misaligned.") + } + }() + + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + _, err = f.Seek(1, os.SEEK_CUR) + log.PanicIf(err) + + er.assertAlignedToSector() +} + +func TestExfatReader_ActiveBootSectorHeader(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + if er.ActiveBootSectorHeader() != er.bootRegion.bsh { + t.Fatalf("ActiveBootSectorHeader not correct.") + } +} + +func TestMappedCluster_IsBad__true(t *testing.T) { + if MappedCluster(0).IsBad() != false { + t.Fatalf("Expected MC to not be bad.") + } +} + +func TestMappedCluster_IsBad__false(t *testing.T) { + if MappedCluster(0xfffffff7).IsBad() != true { + t.Fatalf("Expected MC to be bad.") + } +} diff --git a/tree.go b/tree.go index a6be923..d1ffb54 100644 --- a/tree.go +++ b/tree.go @@ -30,9 +30,6 @@ type TreeNode struct { // NewTreeNode returns a new instance of TreeNode. func NewTreeNode(name string, isDirectory bool, ide IndexedDirectoryEntry, fde *ExfatFileDirectoryEntry, sede *ExfatStreamExtensionDirectoryEntry) (tn *TreeNode) { - - // TODO(dustin): !! Add tests. - childrenList := make(sort.StringSlice, 0) childrenMap := make(map[string]*TreeNode) @@ -56,18 +53,12 @@ func NewTreeNode(name string, isDirectory bool, ide IndexedDirectoryEntry, fde * // Name returns the name of the current file or directory (without any of its // parents' path information). func (tn *TreeNode) Name() string { - - // TODO(dustin): !! Add tests. - return tn.name } // IndexedDirectoryEntry returns the underlying, low-level directory-entry // information that were retrieved for this directory. func (tn *TreeNode) IndexedDirectoryEntry() IndexedDirectoryEntry { - - // TODO(dustin): !! Add tests. - return tn.ide } @@ -75,9 +66,6 @@ func (tn *TreeNode) IndexedDirectoryEntry() IndexedDirectoryEntry { // part of the IDE but this is important and is nicer to have directly // available). func (tn *TreeNode) FileDirectoryEntry() *ExfatFileDirectoryEntry { - - // TODO(dustin): !! Add tests. - return tn.fde } @@ -85,52 +73,34 @@ func (tn *TreeNode) FileDirectoryEntry() *ExfatFileDirectoryEntry { // actually a part of the IDE but this is important and is nicer to have // directly available). func (tn *TreeNode) StreamDirectoryEntry() *ExfatStreamExtensionDirectoryEntry { - - // TODO(dustin): !! Add tests. - return tn.sede } // IsDirectory indicates whether the node is a directory or not. func (tn *TreeNode) IsDirectory() bool { - - // TODO(dustin): !! Add tests. - return tn.isDirectory } // ChildFolders lists any child-folders. Only applies to directory nodes. func (tn *TreeNode) ChildFolders() []string { - - // TODO(dustin): !! Add tests. - return tn.childrenFolders } // ChildFiles lists any child files. Only applies to directory nodes. func (tn *TreeNode) ChildFiles() []string { - - // TODO(dustin): !! Add tests. - return tn.childrenFiles } // GetChild a particular child node. func (tn *TreeNode) GetChild(filename string) *TreeNode { - - // TODO(dustin): !! Add tests. - return tn.childrenMap[filename] } // Lookup finds the given relative path within our children. func (tn *TreeNode) Lookup(pathParts []string) (lastPathParts []string, lastNode *TreeNode, found *TreeNode) { - - // TODO(dustin): !! Add tests. - if len(pathParts) == 0 { // We've reached and found the last part. - return pathParts, tn, tn + return nil, nil, tn } childNode := tn.childrenMap[pathParts[0]] @@ -145,9 +115,6 @@ func (tn *TreeNode) Lookup(pathParts []string) (lastPathParts []string, lastNode // AddChild registers a new child to this node. It's stored in sorted order. func (tn *TreeNode) AddChild(name string, isDirectory bool, fde *ExfatFileDirectoryEntry, sede *ExfatStreamExtensionDirectoryEntry, ide IndexedDirectoryEntry) *TreeNode { - - // TODO(dustin): !! Add tests. - childNode := NewTreeNode(name, isDirectory, ide, fde, sede) // The adds are driven through a process based on a map, so the order will @@ -210,8 +177,6 @@ func (tree *Tree) loadDirectory(clusterNumber uint32, node *TreeNode) (err error } }() - // TODO(dustin): !! Add tests. - en := NewExfatNavigator(tree.er, clusterNumber) index, _, _, err := en.IndexDirectoryEntries() @@ -250,8 +215,6 @@ func (tree *Tree) Load() (err error) { } }() - // TODO(dustin): !! Add tests. - clusterNumber := tree.er.FirstClusterOfRootDirectory() err = tree.loadDirectory(clusterNumber, tree.rootNode) @@ -273,12 +236,17 @@ func (tree *Tree) Lookup(pathParts []string) (node *TreeNode, err error) { } }() + startNode := tree.rootNode + for { - lastPathParts, lastNode, foundNode := tree.rootNode.Lookup(pathParts) + lastPathParts, lastNode, foundNode := startNode.Lookup(pathParts) if foundNode != nil { - // Shouldn't be possible. - if len(lastPathParts) != 0 { - log.Panicf("it looks like we found the node but the path-parts were not exhausted") + // The node was found. Make sure that it's fully constituted before + // returning. + + if foundNode.isDirectory == true && foundNode.loaded == false { + err := tree.loadDirectory(foundNode.sede.FirstCluster, foundNode) + log.PanicIf(err) } return foundNode, nil @@ -292,6 +260,9 @@ func (tree *Tree) Lookup(pathParts []string) (node *TreeNode, err error) { err := tree.loadDirectory(lastNode.sede.FirstCluster, lastNode) log.PanicIf(err) + + startNode = lastNode + pathParts = lastPathParts } } @@ -312,8 +283,6 @@ func (tree *Tree) Visit(cb TreeVisitorFunc) (err error) { } }() - // TODO(dustin): !! Add tests. - pathParts := make([]string, 0) err = tree.visit(pathParts, tree.rootNode, cb) @@ -334,13 +303,9 @@ func (tree *Tree) visit(pathParts []string, node *TreeNode, cb TreeVisitorFunc) } }() - // TODO(dustin): !! Add tests. - err = cb(pathParts, node) log.PanicIf(err) - files := make([]*TreeNode, 0) - for _, childFolderName := range node.childrenFolders { childNode := node.childrenMap[childFolderName] @@ -348,18 +313,14 @@ func (tree *Tree) visit(pathParts []string, node *TreeNode, cb TreeVisitorFunc) copy(childPathParts, pathParts) childPathParts[len(childPathParts)-1] = childNode.name - if childNode.isDirectory == true { - // Finish loading node. - if childNode.loaded == false { - err := tree.loadDirectory(childNode.sede.FirstCluster, childNode) - log.PanicIf(err) - } - - err := tree.visit(childPathParts, childNode, cb) + // Finish loading node. + if childNode.loaded == false { + err := tree.loadDirectory(childNode.sede.FirstCluster, childNode) log.PanicIf(err) - } else { - files = append(files, childNode) } + + err := tree.visit(childPathParts, childNode, cb) + log.PanicIf(err) } // Do the files all at once, at the bottom. diff --git a/tree_test.go b/tree_test.go index ca86338..7791ef1 100644 --- a/tree_test.go +++ b/tree_test.go @@ -89,7 +89,7 @@ func TestTree_List(t *testing.T) { } } -func TestTree_Lookup__Hit(t *testing.T) { +func TestTree_Lookup__File__Hit(t *testing.T) { f, er := getTestFileAndParser() defer f.Close() @@ -105,12 +105,16 @@ func TestTree_Lookup__Hit(t *testing.T) { node, err := tree.Lookup([]string{"testdirectory2", "ff7b94be-cec2-11e9-b7b1-6b2e61bd775c"}) log.PanicIf(err) + if node == nil { + t.Fatalf("Did not find the node.") + } + if node.Name() != "ff7b94be-cec2-11e9-b7b1-6b2e61bd775c" { t.Fatalf("Found node not correct (hit).") } } -func TestTree_Lookup__Miss(t *testing.T) { +func TestTree_Lookup__File__Miss(t *testing.T) { f, er := getTestFileAndParser() defer f.Close() @@ -123,10 +127,621 @@ func TestTree_Lookup__Miss(t *testing.T) { err = tree.Load() log.PanicIf(err) - node, err := tree.Lookup([]string{"invalid", "path"}) + node, err := tree.Lookup([]string{"testdirectory2", "invalid_file"}) log.PanicIf(err) if node != nil { t.Fatalf("Found node not correct (miss).") } } + +func TestTree_Lookup__Folder__Hit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory2"}) + log.PanicIf(err) + + if node == nil { + t.Fatalf("Did not find the node.") + } + + if node.Name() != "testdirectory2" { + t.Fatalf("Found node not correct (hit).") + } +} + +func TestTree_Lookup__Folder__Miss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory2", "invalid_path", "invalid_file"}) + log.PanicIf(err) + + if node != nil { + t.Fatalf("Expected to not find any nodes.") + } +} + +func TestTree_Lookup__Root__Hit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{}) + log.PanicIf(err) + + if node != tree.rootNode { + t.Fatalf("Expected root node to be returned.") + } +} + +func TestTree_Lookup__Root__EntryMiss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"invalid_file"}) + log.PanicIf(err) + + if node != nil { + t.Fatalf("Expected no node to be found.") + } +} + +func TestTree_IndexedDirectoryEntry(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"2-delahaye-type-165-cabriolet-dsc_8025.jpg"}) + log.PanicIf(err) + + ide := node.IndexedDirectoryEntry() + if reflect.DeepEqual(ide, node.ide) != true { + t.Fatalf("IndexedDirectoryEntry did not return IDE.") + } +} + +func TestTree_loadDirectory(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + // Load our directory. + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + err = tree.loadDirectory(node.sede.FirstCluster, node) + log.PanicIf(err) + + // Do the test. + + rootNode, err := tree.Lookup([]string{}) + log.PanicIf(err) + + _, _, foundNode := rootNode.Lookup([]string{"testdirectory", "300daec8-cec3-11e9-bfa2-0f240e41d1d8"}) + log.PanicIf(err) + + if foundNode.Name() != "300daec8-cec3-11e9-bfa2-0f240e41d1d8" { + t.Fatalf("Found node not correct.") + } +} + +func TestNewTreeNode(t *testing.T) { + fde := new(ExfatFileDirectoryEntry) + sede := new(ExfatStreamExtensionDirectoryEntry) + + tn := NewTreeNode("some name", true, IndexedDirectoryEntry{}, fde, sede) + + if tn.name != "some name" { + t.Fatalf("name not set correctly.") + } else if tn.IsDirectory() != true { + t.Fatalf("IsDirectory not set correctly.") + } + + if tn.fde != fde { + t.Fatalf("ExfatFileDirectoryEntry not set correctly.") + } else if tn.sede != sede { + t.Fatalf("ExfatStreamExtensionDirectoryEntry not set correctly.") + } +} + +func TestTreeNode_AddChild(t *testing.T) { + rootNode := NewTreeNode("root", true, IndexedDirectoryEntry{}, nil, nil) + childNode := rootNode.AddChild("child name", false, nil, nil, IndexedDirectoryEntry{}) + + if reflect.DeepEqual(rootNode.ChildFiles(), []string{"child name"}) != true { + t.Fatalf("New child not registered in parent.") + } + + recoveredChild := rootNode.GetChild("child name") + if recoveredChild != childNode { + t.Fatalf("Recovered child node not correct.") + } + + if childNode.Name() != "child name" { + t.Fatalf("New child does not have the right name.") + } +} + +func TestTreeNode_Name(t *testing.T) { + tn := NewTreeNode("some name", true, IndexedDirectoryEntry{}, nil, nil) + + if tn.Name() != "some name" { + t.Fatalf("Name not correct.") + } +} + +func TestTreeNode_FileDirectoryEntry(t *testing.T) { + fde := new(ExfatFileDirectoryEntry) + + tn := NewTreeNode("some name", true, IndexedDirectoryEntry{}, fde, nil) + + if tn.FileDirectoryEntry() != fde { + t.Fatalf("FileDirectoryEntry not correct.") + } +} + +func TestTreeNode_StreamDirectoryEntry(t *testing.T) { + sede := new(ExfatStreamExtensionDirectoryEntry) + + tn := NewTreeNode("some name", true, IndexedDirectoryEntry{}, nil, sede) + + if tn.StreamDirectoryEntry() != sede { + t.Fatalf("StreamDirectoryEntry not correct.") + } +} + +func TestTreeNode_IsDirectory__true(t *testing.T) { + tn := NewTreeNode("some name", true, IndexedDirectoryEntry{}, nil, nil) + + if tn.IsDirectory() != true { + t.Fatalf("IsDirectory not correct.") + } +} + +func TestTreeNode_IsDirectory__false(t *testing.T) { + tn := NewTreeNode("some name", false, IndexedDirectoryEntry{}, nil, nil) + + if tn.IsDirectory() != false { + t.Fatalf("IsDirectory not correct.") + } +} + +func TestTreeNode_ChildFolders__Root(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + rootNode, err := tree.Lookup([]string{}) + log.PanicIf(err) + + expectedFolders := []string{ + "testdirectory", + "testdirectory2", + "testdirectory3", + } + + if reflect.DeepEqual(rootNode.ChildFolders(), expectedFolders) != true { + t.Fatalf("Child folders not correct: %v", rootNode.ChildFolders()) + } +} + +func TestTreeNode_ChildFolders__Subfolder(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + expectedFolders := []string{} + + if reflect.DeepEqual(node.ChildFolders(), expectedFolders) != true { + t.Fatalf("Child folders not correct: %v", node.ChildFolders()) + } +} + +func TestTreeNode_ChildFiles__Root(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + rootNode, err := tree.Lookup([]string{}) + log.PanicIf(err) + + expectedFiles := []string{ + "064cbfd4-cec3-11e9-926d-c362c80fab7b", + "2-delahaye-type-165-cabriolet-dsc_8025.jpg", + "79c6d31a-cca1-11e9-8325-9746d045e868", + "8fd71ab132c59bf33cd7890c0acebf12.jpg", + } + + if reflect.DeepEqual(rootNode.ChildFiles(), expectedFiles) != true { + t.Fatalf("Child files not correct: %v", rootNode.ChildFiles()) + } +} + +func TestTreeNode_ChildFiles__Subfolder(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + expectedFiles := []string{ + "300daec8-cec3-11e9-bfa2-0f240e41d1d8", + } + + if reflect.DeepEqual(node.ChildFiles(), expectedFiles) != true { + t.Fatalf("Child files not correct: %v", node.ChildFiles()) + } +} + +func TestTreeNode_GetChild(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + childNode := node.GetChild("300daec8-cec3-11e9-bfa2-0f240e41d1d8") + + if childNode != node.childrenMap["300daec8-cec3-11e9-bfa2-0f240e41d1d8"] { + t.Fatalf("Child not correct.") + } +} + +func TestTreeNode_Lookup__Folder__Hit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + _, _, foundNode := node.Lookup([]string{"300daec8-cec3-11e9-bfa2-0f240e41d1d8"}) + log.PanicIf(err) + + if foundNode.Name() != "300daec8-cec3-11e9-bfa2-0f240e41d1d8" { + t.Fatalf("Found node not correct.") + } +} + +func TestTreeNode_Lookup__Folder__Miss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + lastPathParts, lastNode, foundNode := node.Lookup([]string{"invalid_path", "invalid_file"}) + log.PanicIf(err) + + if foundNode != nil { + t.Fatalf("Expected no node to be returned for miss.") + } else if reflect.DeepEqual(lastPathParts, []string{"invalid_path", "invalid_file"}) != true { + t.Fatalf("Expected missing file to still be in the path-parts.") + } else if lastNode != node { + t.Fatalf("Last-node not correct.") + } +} + +func TestTreeNode_Lookup__File__Hit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + // Load our directory. + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + err = tree.loadDirectory(node.sede.FirstCluster, node) + log.PanicIf(err) + + // Do the test. + + rootNode, err := tree.Lookup([]string{}) + log.PanicIf(err) + + _, _, foundNode := rootNode.Lookup([]string{"testdirectory", "300daec8-cec3-11e9-bfa2-0f240e41d1d8"}) + log.PanicIf(err) + + if foundNode.Name() != "300daec8-cec3-11e9-bfa2-0f240e41d1d8" { + t.Fatalf("Found node not correct.") + } +} + +func TestTreeNode_Lookup__File__Miss(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + node, err := tree.Lookup([]string{"testdirectory"}) + log.PanicIf(err) + + lastPathParts, lastNode, foundNode := node.Lookup([]string{"invalid_file"}) + log.PanicIf(err) + + if foundNode != nil { + t.Fatalf("Expected no node to be returned for miss.") + } else if reflect.DeepEqual(lastPathParts, []string{"invalid_file"}) != true { + t.Fatalf("Expected missing file to still be in the path-parts.") + } else if lastNode != node { + t.Fatalf("Last-node not correct.") + } +} + +func TestTree_Load(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + rootNode, err := tree.Lookup([]string{}) + log.PanicIf(err) + + expectedFolders := []string{ + "testdirectory", + "testdirectory2", + "testdirectory3", + } + + if reflect.DeepEqual(rootNode.ChildFolders(), expectedFolders) != true { + t.Fatalf("Child folders not correct: %v", rootNode.ChildFolders()) + } + + expectedFiles := []string{ + "064cbfd4-cec3-11e9-926d-c362c80fab7b", + "2-delahaye-type-165-cabriolet-dsc_8025.jpg", + "79c6d31a-cca1-11e9-8325-9746d045e868", + "8fd71ab132c59bf33cd7890c0acebf12.jpg", + } + + if reflect.DeepEqual(rootNode.ChildFiles(), expectedFiles) != true { + t.Fatalf("Child files not correct: %v", rootNode.ChildFiles()) + } +} + +func TestTree_Visit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + collected := make([][]string, 0) + + cb := func(pathParts []string, node *TreeNode) (err error) { + collected = append(collected, pathParts) + return nil + } + + err = tree.Visit(cb) + log.PanicIf(err) + + expectedCollected := [][]string{ + []string{}, + []string{"testdirectory"}, + []string{"testdirectory", "300daec8-cec3-11e9-bfa2-0f240e41d1d8"}, + []string{"testdirectory2"}, + []string{"testdirectory2", "00c57ab0-cec3-11e9-b750-bbed8d2244c8"}, + []string{"testdirectory2", "ff7b94be-cec2-11e9-b7b1-6b2e61bd775c"}, + []string{"testdirectory2", "file1"}, + []string{"testdirectory2", "file2"}, + []string{"testdirectory3"}, + []string{"testdirectory3", "10422c86-cec3-11e9-953f-4f501efd2640"}, + []string{"064cbfd4-cec3-11e9-926d-c362c80fab7b"}, + []string{"2-delahaye-type-165-cabriolet-dsc_8025.jpg"}, + []string{"79c6d31a-cca1-11e9-8325-9746d045e868"}, + []string{"8fd71ab132c59bf33cd7890c0acebf12.jpg"}, + } + + if reflect.DeepEqual(collected, expectedCollected) != true { + for i, pathParts := range collected { + fmt.Printf("ACTUAL (%d): %v\n", i, pathParts) + } + + for i, pathParts := range expectedCollected { + fmt.Printf("EXPECTED (%d): %v\n", i, pathParts) + } + + t.Fatalf("Collected paths not correct.") + } +} + +func TestTree_visit(t *testing.T) { + f, er := getTestFileAndParser() + + defer f.Close() + + err := er.Parse() + log.PanicIf(err) + + tree := NewTree(er) + + err = tree.Load() + log.PanicIf(err) + + collected := make([][]string, 0) + + cb := func(pathParts []string, node *TreeNode) (err error) { + collected = append(collected, pathParts) + return nil + } + + pathParts := make([]string, 0) + + err = tree.visit(pathParts, tree.rootNode, cb) + log.PanicIf(err) + + expectedCollected := [][]string{ + []string{}, + []string{"testdirectory"}, + []string{"testdirectory", "300daec8-cec3-11e9-bfa2-0f240e41d1d8"}, + []string{"testdirectory2"}, + []string{"testdirectory2", "00c57ab0-cec3-11e9-b750-bbed8d2244c8"}, + []string{"testdirectory2", "ff7b94be-cec2-11e9-b7b1-6b2e61bd775c"}, + []string{"testdirectory2", "file1"}, + []string{"testdirectory2", "file2"}, + []string{"testdirectory3"}, + []string{"testdirectory3", "10422c86-cec3-11e9-953f-4f501efd2640"}, + []string{"064cbfd4-cec3-11e9-926d-c362c80fab7b"}, + []string{"2-delahaye-type-165-cabriolet-dsc_8025.jpg"}, + []string{"79c6d31a-cca1-11e9-8325-9746d045e868"}, + []string{"8fd71ab132c59bf33cd7890c0acebf12.jpg"}, + } + + if reflect.DeepEqual(collected, expectedCollected) != true { + for i, pathParts := range collected { + fmt.Printf("ACTUAL (%d): %v\n", i, pathParts) + } + + for i, pathParts := range expectedCollected { + fmt.Printf("EXPECTED (%d): %v\n", i, pathParts) + } + + t.Fatalf("Collected paths not correct.") + } +} diff --git a/utility_test.go b/utility_test.go new file mode 100644 index 0000000..b1f137c --- /dev/null +++ b/utility_test.go @@ -0,0 +1,14 @@ +package exfat + +import ( + "testing" +) + +func TestUnicodeFromAscii(t *testing.T) { + b := []byte{'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0} + s := UnicodeFromAscii(b, 3) + + if s != "abc" { + t.Fatalf("Ascii not decoded to Unicode correctly.") + } +}