mirror of
https://gitea.com/gitea/gitea-mirror.git
synced 2026-03-20 03:40:27 +00:00
Fix dbfs error handling (#36844)
Add tests for opening non-existing files.
This commit is contained in:
@@ -75,7 +75,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er
|
||||
}
|
||||
|
||||
func (f *file) Read(p []byte) (n int, err error) {
|
||||
if f.metaID == 0 || !f.allowRead {
|
||||
if !f.allowRead {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func (f *file) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (f *file) Write(p []byte) (n int, err error) {
|
||||
if f.metaID == 0 || !f.allowWrite {
|
||||
if !f.allowWrite {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -184,10 +184,6 @@ func (f *file) Close() error {
|
||||
}
|
||||
|
||||
func (f *file) Stat() (os.FileInfo, error) {
|
||||
if f.metaID == 0 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -232,15 +228,17 @@ func (f *file) open(flag int) (err error) {
|
||||
if f.metaID != 0 {
|
||||
return os.ErrExist
|
||||
}
|
||||
} else {
|
||||
// create a new file if none exists.
|
||||
if f.metaID == 0 {
|
||||
if err = f.createEmpty(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// create a new file if not exists.
|
||||
if f.metaID == 0 {
|
||||
if err = f.createEmpty(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.metaID == 0 {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
if flag&os.O_TRUNC != 0 {
|
||||
if err = f.truncate(); err != nil {
|
||||
return err
|
||||
@@ -252,7 +250,7 @@ func (f *file) open(flag int) (err error) {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} // end if: allowWrite
|
||||
|
||||
// read only mode
|
||||
if f.metaID == 0 {
|
||||
@@ -322,9 +320,6 @@ func (f *file) delete() error {
|
||||
}
|
||||
|
||||
func (f *file) size() (int64, error) {
|
||||
if f.metaID == 0 {
|
||||
return 0, os.ErrNotExist
|
||||
}
|
||||
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -339,7 +334,7 @@ func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) {
|
||||
} else if ok {
|
||||
return &fileMeta, nil
|
||||
}
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
func buildPath(path string) string {
|
||||
|
||||
@@ -40,6 +40,9 @@ The DBFS solution:
|
||||
* In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size.
|
||||
* Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek.
|
||||
The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much.
|
||||
|
||||
Limitations of the DBFS solution:
|
||||
* Not fully POSIX-compliant, some behaviors may be different from the real filesystem, especially for concurrent read/write
|
||||
*/
|
||||
|
||||
type dbfsMeta struct {
|
||||
|
||||
@@ -9,19 +9,14 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func changeDefaultFileBlockSize(n int64) (restore func()) {
|
||||
old := defaultFileBlockSize
|
||||
defaultFileBlockSize = n
|
||||
return func() {
|
||||
defaultFileBlockSize = old
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbfsBasic(t *testing.T) {
|
||||
defer changeDefaultFileBlockSize(4)()
|
||||
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
|
||||
|
||||
// test basic write/read
|
||||
f, err := OpenFile(t.Context(), "test.txt", os.O_RDWR|os.O_CREATE)
|
||||
@@ -122,10 +117,55 @@ func TestDbfsBasic(t *testing.T) {
|
||||
stat, err = f.Stat()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, stat.Size())
|
||||
|
||||
t.Run("NonExisting", func(t *testing.T) {
|
||||
f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
assert.Nil(t, f)
|
||||
|
||||
f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
assert.Nil(t, f)
|
||||
|
||||
f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
assert.Nil(t, f)
|
||||
})
|
||||
|
||||
t.Run("Existing", func(t *testing.T) {
|
||||
assertFileContent := func(f File, expected string) {
|
||||
_, err := f.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
buf, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, string(buf))
|
||||
}
|
||||
|
||||
f, err := OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE)
|
||||
require.NoError(t, err)
|
||||
_, _ = f.Write([]byte("test"))
|
||||
assertFileContent(f, "test")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND)
|
||||
require.NoError(t, err)
|
||||
_, _ = f.Write([]byte("\nnew"))
|
||||
assertFileContent(f, "test\nnew")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_TRUNC)
|
||||
require.NoError(t, err)
|
||||
assertFileContent(f, "")
|
||||
assert.NoError(t, f.Close())
|
||||
|
||||
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_EXCL)
|
||||
assert.ErrorIs(t, err, os.ErrExist)
|
||||
assert.Nil(t, f)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDbfsReadWrite(t *testing.T) {
|
||||
defer changeDefaultFileBlockSize(4)()
|
||||
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
|
||||
|
||||
f1, err := OpenFile(t.Context(), "test.log", os.O_RDWR|os.O_CREATE)
|
||||
assert.NoError(t, err)
|
||||
@@ -157,30 +197,32 @@ func TestDbfsReadWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDbfsSeekWrite(t *testing.T) {
|
||||
defer changeDefaultFileBlockSize(4)()
|
||||
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
|
||||
|
||||
f, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
|
||||
assert.NoError(t, err)
|
||||
defer f.Close()
|
||||
// write something
|
||||
fw, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
|
||||
require.NoError(t, err)
|
||||
defer fw.Close()
|
||||
|
||||
n, err := f.Write([]byte("111"))
|
||||
n, err := fw.Write([]byte("111"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(int64(n), io.SeekStart)
|
||||
_, err = fw.Seek(int64(n), io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Write([]byte("222"))
|
||||
_, err = fw.Write([]byte("222"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(int64(n), io.SeekStart)
|
||||
_, err = fw.Seek(int64(n), io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Write([]byte("333"))
|
||||
_, err = fw.Write([]byte("333"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// then read it
|
||||
fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY)
|
||||
assert.NoError(t, err)
|
||||
defer f.Close()
|
||||
require.NoError(t, err)
|
||||
defer fr.Close()
|
||||
|
||||
buf, err := io.ReadAll(fr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user