Add tests for generic attribute changes

This commit is contained in:
Aneesh Nireshwalia 2024-02-22 17:55:50 -07:00
parent d4be734c73
commit 4bbd25a37f
No known key found for this signature in database
GPG Key ID: 6F5A52831C046F44
4 changed files with 770 additions and 36 deletions

View File

@ -0,0 +1,210 @@
//go:build windows
// +build windows
package restic
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/test"
"golang.org/x/sys/windows"
)
func TestRestoreCreationTime(t *testing.T) {
t.Parallel()
path := t.TempDir()
fi, err := os.Lstat(path)
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
creationTimeAttribute := getCreationTime(fi, path)
test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path))
//Using the temp dir creation time as the test creation time for the test file and folder
runGenericAttributesTest(t, path, TypeCreationTime, WindowsAttributes{CreationTime: creationTimeAttribute}, false)
}
func TestRestoreFileAttributes(t *testing.T) {
t.Parallel()
genericAttributeName := TypeFileAttributes
tempDir := t.TempDir()
normal := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
hidden := uint32(syscall.FILE_ATTRIBUTE_HIDDEN)
system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM)
archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE)
encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED)
fileAttributes := []WindowsAttributes{
//normal
{FileAttributes: &normal},
//hidden
{FileAttributes: &hidden},
//system
{FileAttributes: &system},
//archive
{FileAttributes: &archive},
//encrypted
{FileAttributes: &encrypted},
}
for i, fileAttr := range fileAttributes {
genericAttrs, err := WindowsAttrsToGenericAttributes(fileAttr)
test.OK(t, err)
expectedNodes := []Node{
{
Name: fmt.Sprintf("testfile%d", i),
Type: "file",
Mode: 0655,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: genericAttrs,
},
}
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, fileAttr, false)
}
normal = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY)
hidden = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | syscall.FILE_ATTRIBUTE_HIDDEN)
system = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_SYSTEM)
archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE)
encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED)
folderAttributes := []WindowsAttributes{
//normal
{FileAttributes: &normal},
//hidden
{FileAttributes: &hidden},
//system
{FileAttributes: &system},
//archive
{FileAttributes: &archive},
//encrypted
{FileAttributes: &encrypted},
}
for i, folderAttr := range folderAttributes {
genericAttrs, err := WindowsAttrsToGenericAttributes(folderAttr)
test.OK(t, err)
expectedNodes := []Node{
{
Name: fmt.Sprintf("testdirectory%d", i),
Type: "dir",
Mode: 0755,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: genericAttrs,
},
}
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, folderAttr, false)
}
}
func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
genericAttributes, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
test.OK(t, err)
expectedNodes := []Node{
{
Name: "testfile",
Type: "file",
Mode: 0644,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: genericAttributes,
},
{
Name: "testdirectory",
Type: "dir",
Mode: 0755,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: genericAttributes,
},
}
runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, genericAttributeExpected, warningExpected)
}
func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []Node, tempDir string, genericAttr GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) {
for _, testNode := range expectedNodes {
testPath, node := restoreAndGetNode(t, tempDir, testNode, warningExpected)
rawMessage := node.GenericAttributes[genericAttr]
genericAttrsExpected, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
test.OK(t, err)
rawMessageExpected := genericAttrsExpected[genericAttr]
test.Equals(t, rawMessageExpected, rawMessage, "Generic attribute: %s got from NodeFromFileInfo not equal for path: %s", string(genericAttr), testPath)
}
}
func restoreAndGetNode(t *testing.T, tempDir string, testNode Node, warningExpected bool) (string, *Node) {
testPath := filepath.Join(tempDir, "001", testNode.Name)
err := os.MkdirAll(filepath.Dir(testPath), testNode.Mode)
test.OK(t, errors.Wrapf(err, "Failed to create parent directories for: %s", testPath))
if testNode.Type == "file" {
testFile, err := os.Create(testPath)
test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
testFile.Close()
} else if testNode.Type == "dir" {
err := os.Mkdir(testPath, testNode.Mode)
test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath))
}
err = testNode.RestoreMetadata(testPath, func(msg string) {
if warningExpected {
test.Assert(t, warningExpected, "Warning triggered as expected: %s", msg)
} else {
// If warning is not expected, this code should not get triggered.
test.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", testPath, msg))
}
})
test.OK(t, errors.Wrapf(err, "Failed to restore metadata for: %s", testPath))
fi, err := os.Lstat(testPath)
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", testPath))
nodeFromFileInfo, err := NodeFromFileInfo(testPath, fi)
test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
return testPath, nodeFromFileInfo
}
const TypeSomeNewAttribute GenericAttributeType = "MockAttributes.SomeNewAttribute"
func TestNewGenericAttributeType(t *testing.T) {
t.Parallel()
newGenericAttribute := map[GenericAttributeType]json.RawMessage{}
newGenericAttribute[TypeSomeNewAttribute] = []byte("any value")
tempDir := t.TempDir()
expectedNodes := []Node{
{
Name: "testfile",
Type: "file",
Mode: 0644,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: newGenericAttribute,
},
{
Name: "testdirectory",
Type: "dir",
Mode: 0755,
ModTime: parseTime("2005-05-14 21:07:03.111"),
AccessTime: parseTime("2005-05-14 21:07:04.222"),
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
GenericAttributes: newGenericAttribute,
},
}
for _, testNode := range expectedNodes {
testPath, node := restoreAndGetNode(t, tempDir, testNode, true)
_, ua, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
test.OK(t, err)
// Since this GenericAttribute is unknown to this version of the software, it will not get set on the file.
test.Assert(t, len(ua) == 0, "Unkown attributes: %s found for path: %s", ua, testPath)
}
}

View File

@ -3,6 +3,7 @@ package restorer
import (
"bytes"
"context"
"encoding/json"
"io"
"math"
"os"
@ -27,17 +28,27 @@ type Snapshot struct {
}
type File struct {
Data string
Links uint64
Inode uint64
Mode os.FileMode
ModTime time.Time
Data string
Links uint64
Inode uint64
Mode os.FileMode
ModTime time.Time
attributes *FileAttributes
}
type Dir struct {
Nodes map[string]Node
Mode os.FileMode
ModTime time.Time
Nodes map[string]Node
Mode os.FileMode
ModTime time.Time
attributes *FileAttributes
}
type FileAttributes struct {
ReadOnly bool
Hidden bool
System bool
Archive bool
Encrypted bool
}
func saveFile(t testing.TB, repo restic.BlobSaver, node File) restic.ID {
@ -52,7 +63,7 @@ func saveFile(t testing.TB, repo restic.BlobSaver, node File) restic.ID {
return id
}
func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode uint64) restic.ID {
func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode uint64, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) restic.ID {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -78,20 +89,21 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
mode = 0644
}
err := tree.Insert(&restic.Node{
Type: "file",
Mode: mode,
ModTime: node.ModTime,
Name: name,
UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()),
Content: fc,
Size: uint64(len(n.(File).Data)),
Inode: fi,
Links: lc,
Type: "file",
Mode: mode,
ModTime: node.ModTime,
Name: name,
UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()),
Content: fc,
Size: uint64(len(n.(File).Data)),
Inode: fi,
Links: lc,
GenericAttributes: getGenericAttributes(node.attributes, false),
})
rtest.OK(t, err)
case Dir:
id := saveDir(t, repo, node.Nodes, inode)
id := saveDir(t, repo, node.Nodes, inode, getGenericAttributes)
mode := node.Mode
if mode == 0 {
@ -99,13 +111,14 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
}
err := tree.Insert(&restic.Node{
Type: "dir",
Mode: mode,
ModTime: node.ModTime,
Name: name,
UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()),
Subtree: &id,
Type: "dir",
Mode: mode,
ModTime: node.ModTime,
Name: name,
UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()),
Subtree: &id,
GenericAttributes: getGenericAttributes(node.attributes, false),
})
rtest.OK(t, err)
default:
@ -121,13 +134,13 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
return id
}
func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot) (*restic.Snapshot, restic.ID) {
func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot, getGenericAttributes func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage)) (*restic.Snapshot, restic.ID) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg, wgCtx := errgroup.WithContext(ctx)
repo.StartPackUploader(wgCtx, wg)
treeID := saveDir(t, repo, snapshot.Nodes, 1000)
treeID := saveDir(t, repo, snapshot.Nodes, 1000, getGenericAttributes)
err := repo.Flush(ctx)
if err != nil {
t.Fatal(err)
@ -147,6 +160,11 @@ func saveSnapshot(t testing.TB, repo restic.Repository, snapshot Snapshot) (*res
return sn, id
}
var noopGetGenericAttributes = func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage) {
// No-op
return nil
}
func TestRestorer(t *testing.T) {
var tests = []struct {
Snapshot
@ -322,7 +340,7 @@ func TestRestorer(t *testing.T) {
for _, test := range tests {
t.Run("", func(t *testing.T) {
repo := repository.TestRepository(t)
sn, id := saveSnapshot(t, repo, test.Snapshot)
sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
t.Logf("snapshot saved as %v", id.Str())
res := NewRestorer(repo, sn, false, nil)
@ -439,7 +457,7 @@ func TestRestorerRelative(t *testing.T) {
t.Run("", func(t *testing.T) {
repo := repository.TestRepository(t)
sn, id := saveSnapshot(t, repo, test.Snapshot)
sn, id := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
t.Logf("snapshot saved as %v", id.Str())
res := NewRestorer(repo, sn, false, nil)
@ -669,7 +687,7 @@ func TestRestorerTraverseTree(t *testing.T) {
for _, test := range tests {
t.Run("", func(t *testing.T) {
repo := repository.TestRepository(t)
sn, _ := saveSnapshot(t, repo, test.Snapshot)
sn, _ := saveSnapshot(t, repo, test.Snapshot, noopGetGenericAttributes)
res := NewRestorer(repo, sn, false, nil)
@ -745,7 +763,7 @@ func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
},
},
},
})
}, noopGetGenericAttributes)
res := NewRestorer(repo, sn, false, nil)
@ -800,7 +818,7 @@ func TestVerifyCancel(t *testing.T) {
}
repo := repository.TestRepository(t)
sn, _ := saveSnapshot(t, repo, snapshot)
sn, _ := saveSnapshot(t, repo, snapshot, noopGetGenericAttributes)
res := NewRestorer(repo, sn, false, nil)

View File

@ -29,7 +29,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) {
},
},
},
})
}, noopGetGenericAttributes)
res := NewRestorer(repo, sn, false, nil)
@ -95,7 +95,7 @@ func TestRestorerProgressBar(t *testing.T) {
},
"file2": File{Links: 1, Inode: 2, Data: "example"},
},
})
}, noopGetGenericAttributes)
mock := &printerMock{}
progress := restoreui.NewProgress(mock, 0)

View File

@ -4,11 +4,20 @@
package restorer
import (
"context"
"encoding/json"
"math"
"os"
"path"
"syscall"
"testing"
"time"
"unsafe"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/test"
rtest "github.com/restic/restic/internal/test"
"golang.org/x/sys/windows"
)
@ -33,3 +42,500 @@ func getBlockCount(t *testing.T, filename string) int64 {
return int64(math.Ceil(float64(result) / 512))
}
type DataStreamInfo struct {
name string
data string
}
type NodeInfo struct {
DataStreamInfo
parentDir string
attributes FileAttributes
Exists bool
IsDirectory bool
}
func TestFileAttributeCombination(t *testing.T) {
testFileAttributeCombination(t, false)
}
func TestEmptyFileAttributeCombination(t *testing.T) {
testFileAttributeCombination(t, true)
}
func testFileAttributeCombination(t *testing.T, isEmpty bool) {
t.Parallel()
//Generate combination of 5 attributes.
attributeCombinations := generateCombinations(5, []bool{})
fileName := "TestFile.txt"
// Iterate through each attribute combination
for _, attr1 := range attributeCombinations {
//Set up the required file information
fileInfo := NodeInfo{
DataStreamInfo: getDataStreamInfo(isEmpty, fileName),
parentDir: "dir",
attributes: getFileAttributes(attr1),
Exists: false,
}
//Get the current test name
testName := getCombinationTestName(fileInfo, fileName, fileInfo.attributes)
//Run test
t.Run(testName, func(t *testing.T) {
mainFilePath := runAttributeTests(t, fileInfo, fileInfo.attributes)
verifyFileRestores(isEmpty, mainFilePath, t, fileInfo)
})
}
}
func generateCombinations(n int, prefix []bool) [][]bool {
if n == 0 {
// Return a slice containing the current permutation
return [][]bool{append([]bool{}, prefix...)}
}
// Generate combinations with True
prefixTrue := append(prefix, true)
permsTrue := generateCombinations(n-1, prefixTrue)
// Generate combinations with False
prefixFalse := append(prefix, false)
permsFalse := generateCombinations(n-1, prefixFalse)
// Combine combinations with True and False
return append(permsTrue, permsFalse...)
}
func getDataStreamInfo(isEmpty bool, fileName string) DataStreamInfo {
var dataStreamInfo DataStreamInfo
if isEmpty {
dataStreamInfo = DataStreamInfo{
name: fileName,
}
} else {
dataStreamInfo = DataStreamInfo{
name: fileName,
data: "Main file data stream.",
}
}
return dataStreamInfo
}
func getFileAttributes(values []bool) FileAttributes {
return FileAttributes{
ReadOnly: values[0],
Hidden: values[1],
System: values[2],
Archive: values[3],
Encrypted: values[4],
}
}
func getCombinationTestName(fi NodeInfo, fileName string, overwriteAttr FileAttributes) string {
if fi.attributes.ReadOnly {
fileName += "-ReadOnly"
}
if fi.attributes.Hidden {
fileName += "-Hidden"
}
if fi.attributes.System {
fileName += "-System"
}
if fi.attributes.Archive {
fileName += "-Archive"
}
if fi.attributes.Encrypted {
fileName += "-Encrypted"
}
if fi.Exists {
fileName += "-Overwrite"
if overwriteAttr.ReadOnly {
fileName += "-R"
}
if overwriteAttr.Hidden {
fileName += "-H"
}
if overwriteAttr.System {
fileName += "-S"
}
if overwriteAttr.Archive {
fileName += "-A"
}
if overwriteAttr.Encrypted {
fileName += "-E"
}
}
return fileName
}
func runAttributeTests(t *testing.T, fileInfo NodeInfo, existingFileAttr FileAttributes) string {
testDir := t.TempDir()
res, _ := setupWithFileAttributes(t, fileInfo, testDir, existingFileAttr)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := res.RestoreTo(ctx, testDir)
rtest.OK(t, err)
mainFilePath := path.Join(testDir, fileInfo.parentDir, fileInfo.name)
//Verify restore
verifyFileAttributes(t, mainFilePath, fileInfo.attributes)
return mainFilePath
}
func setupWithFileAttributes(t *testing.T, nodeInfo NodeInfo, testDir string, existingFileAttr FileAttributes) (*Restorer, []int) {
t.Helper()
if nodeInfo.Exists {
if !nodeInfo.IsDirectory {
err := os.MkdirAll(path.Join(testDir, nodeInfo.parentDir), os.ModeDir)
rtest.OK(t, err)
filepath := path.Join(testDir, nodeInfo.parentDir, nodeInfo.name)
if existingFileAttr.Encrypted {
err := createEncryptedFileWriteData(filepath, nodeInfo)
rtest.OK(t, err)
} else {
// Write the data to the file
file, err := os.OpenFile(path.Clean(filepath), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
rtest.OK(t, err)
_, err = file.Write([]byte(nodeInfo.data))
rtest.OK(t, err)
err = file.Close()
rtest.OK(t, err)
}
} else {
err := os.MkdirAll(path.Join(testDir, nodeInfo.parentDir, nodeInfo.name), os.ModeDir)
rtest.OK(t, err)
}
pathPointer, err := syscall.UTF16PtrFromString(path.Join(testDir, nodeInfo.parentDir, nodeInfo.name))
rtest.OK(t, err)
syscall.SetFileAttributes(pathPointer, getAttributeValue(&existingFileAttr))
}
index := 0
order := []int{}
streams := []DataStreamInfo{}
if !nodeInfo.IsDirectory {
order = append(order, index)
index++
streams = append(streams, nodeInfo.DataStreamInfo)
}
return setup(t, getNodes(nodeInfo.parentDir, nodeInfo.name, order, streams, nodeInfo.IsDirectory, &nodeInfo.attributes)), order
}
func createEncryptedFileWriteData(filepath string, fileInfo NodeInfo) (err error) {
var ptr *uint16
if ptr, err = windows.UTF16PtrFromString(filepath); err != nil {
return err
}
var handle windows.Handle
//Create the file with encrypted flag
if handle, err = windows.CreateFile(ptr, uint32(windows.GENERIC_READ|windows.GENERIC_WRITE), uint32(windows.FILE_SHARE_READ), nil, uint32(windows.CREATE_ALWAYS), windows.FILE_ATTRIBUTE_ENCRYPTED, 0); err != nil {
return err
}
//Write data to file
if _, err = windows.Write(handle, []byte(fileInfo.data)); err != nil {
return err
}
//Close handle
return windows.CloseHandle(handle)
}
func setup(t *testing.T, nodesMap map[string]Node) *Restorer {
repo := repository.TestRepository(t)
getFileAttributes := func(attr *FileAttributes, isDir bool) (genericAttributes map[restic.GenericAttributeType]json.RawMessage) {
if attr == nil {
return
}
fileattr := getAttributeValue(attr)
if isDir {
//If the node is a directory add FILE_ATTRIBUTE_DIRECTORY to attributes
fileattr |= windows.FILE_ATTRIBUTE_DIRECTORY
}
attrs, err := restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{FileAttributes: &fileattr})
test.OK(t, err)
return attrs
}
sn, _ := saveSnapshot(t, repo, Snapshot{
Nodes: nodesMap,
}, getFileAttributes)
res := NewRestorer(repo, sn, false, nil)
return res
}
func getAttributeValue(attr *FileAttributes) uint32 {
var fileattr uint32
if attr.ReadOnly {
fileattr |= windows.FILE_ATTRIBUTE_READONLY
}
if attr.Hidden {
fileattr |= windows.FILE_ATTRIBUTE_HIDDEN
}
if attr.Encrypted {
fileattr |= windows.FILE_ATTRIBUTE_ENCRYPTED
}
if attr.Archive {
fileattr |= windows.FILE_ATTRIBUTE_ARCHIVE
}
if attr.System {
fileattr |= windows.FILE_ATTRIBUTE_SYSTEM
}
return fileattr
}
func getNodes(dir string, mainNodeName string, order []int, streams []DataStreamInfo, isDirectory bool, attributes *FileAttributes) map[string]Node {
var mode os.FileMode
if isDirectory {
mode = os.FileMode(2147484159)
} else {
if attributes != nil && attributes.ReadOnly {
mode = os.FileMode(0o444)
} else {
mode = os.FileMode(0o666)
}
}
getFileNodes := func() map[string]Node {
nodes := map[string]Node{}
if isDirectory {
//Add a directory node at the same level as the other streams
nodes[mainNodeName] = Dir{
ModTime: time.Now(),
attributes: attributes,
Mode: mode,
}
}
if len(streams) > 0 {
for _, index := range order {
stream := streams[index]
var attr *FileAttributes = nil
if mainNodeName == stream.name {
attr = attributes
} else if attributes != nil && attributes.Encrypted {
//Set encrypted attribute
attr = &FileAttributes{Encrypted: true}
}
nodes[stream.name] = File{
ModTime: time.Now(),
Data: stream.data,
Mode: mode,
attributes: attr,
}
}
}
return nodes
}
return map[string]Node{
dir: Dir{
Mode: normalizeFileMode(0750 | mode),
ModTime: time.Now(),
Nodes: getFileNodes(),
},
}
}
func verifyFileAttributes(t *testing.T, mainFilePath string, attr FileAttributes) {
ptr, err := windows.UTF16PtrFromString(mainFilePath)
rtest.OK(t, err)
//Get file attributes using syscall
fileAttributes, err := syscall.GetFileAttributes(ptr)
rtest.OK(t, err)
//Test positive and negative scenarios
if attr.ReadOnly {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_READONLY != 0, "Expected read only attibute.")
} else {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_READONLY == 0, "Unexpected read only attibute.")
}
if attr.Hidden {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_HIDDEN != 0, "Expected hidden attibute.")
} else {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_HIDDEN == 0, "Unexpected hidden attibute.")
}
if attr.System {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_SYSTEM != 0, "Expected system attibute.")
} else {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_SYSTEM == 0, "Unexpected system attibute.")
}
if attr.Archive {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ARCHIVE != 0, "Expected archive attibute.")
} else {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ARCHIVE == 0, "Unexpected archive attibute.")
}
if attr.Encrypted {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ENCRYPTED != 0, "Expected encrypted attibute.")
} else {
rtest.Assert(t, fileAttributes&windows.FILE_ATTRIBUTE_ENCRYPTED == 0, "Unexpected encrypted attibute.")
}
}
func verifyFileRestores(isEmpty bool, mainFilePath string, t *testing.T, fileInfo NodeInfo) {
if isEmpty {
_, err1 := os.Stat(mainFilePath)
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The file "+fileInfo.name+" does not exist")
} else {
verifyMainFileRestore(t, mainFilePath, fileInfo)
}
}
func verifyMainFileRestore(t *testing.T, mainFilePath string, fileInfo NodeInfo) {
fi, err1 := os.Stat(mainFilePath)
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The file "+fileInfo.name+" does not exist")
size := fi.Size()
rtest.Assert(t, size > 0, "The file "+fileInfo.name+" exists but is empty")
content, err := os.ReadFile(mainFilePath)
rtest.OK(t, err)
rtest.Assert(t, string(content) == fileInfo.data, "The file "+fileInfo.name+" exists but the content is not overwritten")
}
func TestDirAttributeCombination(t *testing.T) {
t.Parallel()
attributeCombinations := generateCombinations(4, []bool{})
dirName := "TestDir"
// Iterate through each attribute combination
for _, attr1 := range attributeCombinations {
//Set up the required directory information
dirInfo := NodeInfo{
DataStreamInfo: DataStreamInfo{
name: dirName,
},
parentDir: "dir",
attributes: getDirFileAttributes(attr1),
Exists: false,
IsDirectory: true,
}
//Get the current test name
testName := getCombinationTestName(dirInfo, dirName, dirInfo.attributes)
//Run test
t.Run(testName, func(t *testing.T) {
mainDirPath := runAttributeTests(t, dirInfo, dirInfo.attributes)
//Check directory exists
_, err1 := os.Stat(mainDirPath)
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The directory "+dirInfo.name+" does not exist")
})
}
}
func getDirFileAttributes(values []bool) FileAttributes {
return FileAttributes{
// readonly not valid for directories
Hidden: values[0],
System: values[1],
Archive: values[2],
Encrypted: values[3],
}
}
func TestFileAttributeCombinationsOverwrite(t *testing.T) {
testFileAttributeCombinationsOverwrite(t, false)
}
func TestEmptyFileAttributeCombinationsOverwrite(t *testing.T) {
testFileAttributeCombinationsOverwrite(t, true)
}
func testFileAttributeCombinationsOverwrite(t *testing.T, isEmpty bool) {
t.Parallel()
//Get attribute combinations
attributeCombinations := generateCombinations(5, []bool{})
//Get overwrite file attribute combinations
overwriteCombinations := generateCombinations(5, []bool{})
fileName := "TestOverwriteFile"
//Iterate through each attribute combination
for _, attr1 := range attributeCombinations {
fileInfo := NodeInfo{
DataStreamInfo: getDataStreamInfo(isEmpty, fileName),
parentDir: "dir",
attributes: getFileAttributes(attr1),
Exists: true,
}
overwriteFileAttributes := []FileAttributes{}
for _, overwrite := range overwriteCombinations {
overwriteFileAttributes = append(overwriteFileAttributes, getFileAttributes(overwrite))
}
//Iterate through each overwrite attribute combination
for _, overwriteFileAttr := range overwriteFileAttributes {
//Get the test name
testName := getCombinationTestName(fileInfo, fileName, overwriteFileAttr)
//Run test
t.Run(testName, func(t *testing.T) {
mainFilePath := runAttributeTests(t, fileInfo, overwriteFileAttr)
verifyFileRestores(isEmpty, mainFilePath, t, fileInfo)
})
}
}
}
func TestDirAttributeCombinationsOverwrite(t *testing.T) {
t.Parallel()
//Get attribute combinations
attributeCombinations := generateCombinations(4, []bool{})
//Get overwrite dir attribute combinations
overwriteCombinations := generateCombinations(4, []bool{})
dirName := "TestOverwriteDir"
//Iterate through each attribute combination
for _, attr1 := range attributeCombinations {
dirInfo := NodeInfo{
DataStreamInfo: DataStreamInfo{
name: dirName,
},
parentDir: "dir",
attributes: getDirFileAttributes(attr1),
Exists: true,
IsDirectory: true,
}
overwriteDirFileAttributes := []FileAttributes{}
for _, overwrite := range overwriteCombinations {
overwriteDirFileAttributes = append(overwriteDirFileAttributes, getDirFileAttributes(overwrite))
}
//Iterate through each overwrite attribute combinations
for _, overwriteDirAttr := range overwriteDirFileAttributes {
//Get the test name
testName := getCombinationTestName(dirInfo, dirName, overwriteDirAttr)
//Run test
t.Run(testName, func(t *testing.T) {
mainDirPath := runAttributeTests(t, dirInfo, dirInfo.attributes)
//Check directory exists
_, err1 := os.Stat(mainDirPath)
rtest.Assert(t, !errors.Is(err1, os.ErrNotExist), "The directory "+dirInfo.name+" does not exist")
})
}
}
}