Preserve MemoryMedium file modes
Some checks failed
CI / test (push) Failing after 3s
CI / auto-fix (push) Failing after 0s
CI / auto-merge (push) Failing after 0s

This commit is contained in:
Virgil 2026-04-01 09:50:24 +00:00
parent cee004f426
commit 35b725d2b8
2 changed files with 50 additions and 9 deletions

43
io.go
View file

@ -192,6 +192,7 @@ func Copy(source Medium, sourcePath string, destination Medium, destinationPath
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
type MemoryMedium struct {
files map[string]string
modes map[string]fs.FileMode
dirs map[string]bool
modTimes map[string]time.Time
}
@ -203,6 +204,7 @@ var _ Medium = (*MemoryMedium)(nil)
func NewMemoryMedium() *MemoryMedium {
return &MemoryMedium{
files: make(map[string]string),
modes: make(map[string]fs.FileMode),
dirs: make(map[string]bool),
modTimes: make(map[string]time.Time),
}
@ -217,13 +219,14 @@ func (medium *MemoryMedium) Read(path string) (string, error) {
}
func (medium *MemoryMedium) Write(path, content string) error {
medium.files[path] = content
medium.modTimes[path] = time.Now()
return nil
return medium.WriteMode(path, content, 0644)
}
func (medium *MemoryMedium) WriteMode(path, content string, mode fs.FileMode) error {
return medium.Write(path, content)
medium.files[path] = content
medium.modes[path] = mode
medium.modTimes[path] = time.Now()
return nil
}
func (medium *MemoryMedium) EnsureDir(path string) error {
@ -239,6 +242,8 @@ func (medium *MemoryMedium) IsFile(path string) bool {
func (medium *MemoryMedium) Delete(path string) error {
if _, ok := medium.files[path]; ok {
delete(medium.files, path)
delete(medium.modes, path)
delete(medium.modTimes, path)
return nil
}
if _, ok := medium.dirs[path]; ok {
@ -266,6 +271,8 @@ func (medium *MemoryMedium) DeleteAll(path string) error {
found := false
if _, ok := medium.files[path]; ok {
delete(medium.files, path)
delete(medium.modes, path)
delete(medium.modTimes, path)
found = true
}
if _, ok := medium.dirs[path]; ok {
@ -279,6 +286,8 @@ func (medium *MemoryMedium) DeleteAll(path string) error {
for filePath := range medium.files {
if core.HasPrefix(filePath, prefix) {
delete(medium.files, filePath)
delete(medium.modes, filePath)
delete(medium.modTimes, filePath)
found = true
}
}
@ -299,6 +308,10 @@ func (medium *MemoryMedium) Rename(oldPath, newPath string) error {
if content, ok := medium.files[oldPath]; ok {
medium.files[newPath] = content
delete(medium.files, oldPath)
if mode, ok := medium.modes[oldPath]; ok {
medium.modes[newPath] = mode
delete(medium.modes, oldPath)
}
if modTime, ok := medium.modTimes[oldPath]; ok {
medium.modTimes[newPath] = modTime
delete(medium.modTimes, oldPath)
@ -358,6 +371,7 @@ func (medium *MemoryMedium) Open(path string) (fs.File, error) {
return &MemoryFile{
name: core.PathBase(path),
content: []byte(content),
mode: medium.fileMode(path),
}, nil
}
@ -365,6 +379,7 @@ func (medium *MemoryMedium) Create(path string) (goio.WriteCloser, error) {
return &MemoryWriteCloser{
medium: medium,
path: path,
mode: 0644,
}, nil
}
@ -374,6 +389,7 @@ func (medium *MemoryMedium) Append(path string) (goio.WriteCloser, error) {
medium: medium,
path: path,
data: []byte(content),
mode: medium.fileMode(path),
}, nil
}
@ -391,10 +407,11 @@ type MemoryFile struct {
name string
content []byte
offset int64
mode fs.FileMode
}
func (file *MemoryFile) Stat() (fs.FileInfo, error) {
return NewFileInfo(file.name, int64(len(file.content)), 0, time.Time{}, false), nil
return NewFileInfo(file.name, int64(len(file.content)), file.mode, time.Time{}, false), nil
}
func (file *MemoryFile) Read(buffer []byte) (int, error) {
@ -416,6 +433,7 @@ type MemoryWriteCloser struct {
medium *MemoryMedium
path string
data []byte
mode fs.FileMode
}
func (writeCloser *MemoryWriteCloser) Write(data []byte) (int, error) {
@ -425,10 +443,18 @@ func (writeCloser *MemoryWriteCloser) Write(data []byte) (int, error) {
func (writeCloser *MemoryWriteCloser) Close() error {
writeCloser.medium.files[writeCloser.path] = string(writeCloser.data)
writeCloser.medium.modes[writeCloser.path] = writeCloser.mode
writeCloser.medium.modTimes[writeCloser.path] = time.Now()
return nil
}
func (medium *MemoryMedium) fileMode(path string) fs.FileMode {
if mode, ok := medium.modes[path]; ok {
return mode
}
return 0644
}
func (medium *MemoryMedium) List(path string) ([]fs.DirEntry, error) {
if _, ok := medium.dirs[path]; !ok {
hasChildren := false
@ -485,11 +511,12 @@ func (medium *MemoryMedium) List(path string) ([]fs.DirEntry, error) {
}
if !seen[rest] {
seen[rest] = true
filePath := core.Concat(prefix, rest)
entries = append(entries, NewDirEntry(
rest,
false,
0644,
NewFileInfo(rest, int64(len(content)), 0644, time.Time{}, false),
medium.fileMode(filePath),
NewFileInfo(rest, int64(len(content)), medium.fileMode(filePath), time.Time{}, false),
))
}
}
@ -525,7 +552,7 @@ func (medium *MemoryMedium) Stat(path string) (fs.FileInfo, error) {
if !ok {
modTime = time.Now()
}
return NewFileInfo(core.PathBase(path), int64(len(content)), 0644, modTime, false), nil
return NewFileInfo(core.PathBase(path), int64(len(content)), medium.fileMode(path), modTime, false), nil
}
if _, ok := medium.dirs[path]; ok {
return NewFileInfo(core.PathBase(path), 0, fs.ModeDir|0755, time.Time{}, true), nil

View file

@ -78,6 +78,16 @@ func TestMemoryMedium_WriteMode_Good(t *testing.T) {
content, err := memoryMedium.Read("secure.txt")
require.NoError(t, err)
assert.Equal(t, "secret", content)
info, err := memoryMedium.Stat("secure.txt")
require.NoError(t, err)
assert.Equal(t, fs.FileMode(0600), info.Mode())
file, err := memoryMedium.Open("secure.txt")
require.NoError(t, err)
fileInfo, err := file.Stat()
require.NoError(t, err)
assert.Equal(t, fs.FileMode(0600), fileInfo.Mode())
}
func TestMemoryMedium_EnsureDir_Good(t *testing.T) {
@ -228,7 +238,7 @@ func TestMemoryMedium_StreamAndFSHelpers_Good(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "file.txt", info.Name())
assert.Equal(t, int64(5), info.Size())
assert.Equal(t, fs.FileMode(0), info.Mode())
assert.Equal(t, fs.FileMode(0644), info.Mode())
assert.True(t, info.ModTime().IsZero())
assert.False(t, info.IsDir())
assert.Nil(t, info.Sys())
@ -249,6 +259,7 @@ func TestMemoryMedium_StreamAndFSHelpers_Good(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "file.txt", entryInfo.Name())
assert.Equal(t, int64(5), entryInfo.Size())
assert.Equal(t, fs.FileMode(0644), entryInfo.Mode())
writer, err := memoryMedium.Create("created.txt")
require.NoError(t, err)
@ -276,6 +287,9 @@ func TestMemoryMedium_StreamAndFSHelpers_Good(t *testing.T) {
require.NoError(t, writeStream.Close())
assert.Equal(t, "stream output", memoryMedium.files["streamed.txt"])
statInfo, err := memoryMedium.Stat("streamed.txt")
require.NoError(t, err)
assert.Equal(t, fs.FileMode(0644), statInfo.Mode())
}
func TestIO_Read_Good(t *testing.T) {