This is an automated email from the ASF dual-hosted git repository.
hanahmily pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
The following commit(s) were added to refs/heads/main by this push:
new 0b55f26e Implement local file system (#341)
0b55f26e is described below
commit 0b55f26ee2ca4281e0f1373f35f944449d2ad81c
Author: HHoflittlefish777 <[email protected]>
AuthorDate: Mon Oct 30 08:27:26 2023 +0800
Implement local file system (#341)
* implement local file system
* delete DIr interface
---------
Co-authored-by: Gao Hongtao <[email protected]>
Co-authored-by: 吴晟 Wu Sheng <[email protected]>
---
CHANGES.md | 1 +
docs/concept/persistence-storage.md | 121 +++------------
pkg/fs/error.go | 44 ++++++
pkg/fs/file_system.go | 58 +++-----
pkg/fs/local_file_system.go | 285 ++++++++++++++++++++++++++++++++++++
pkg/fs/local_file_system_test.go | 149 +++++++++++++++++++
6 files changed, 523 insertions(+), 135 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 9e740579..7c48f319 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,7 @@ Release Notes.
### Features
- Support etcd client authentication.
+- Implement Local file system.
## 0.5.0
diff --git a/docs/concept/persistence-storage.md
b/docs/concept/persistence-storage.md
index 1b389e91..190fa42d 100644
--- a/docs/concept/persistence-storage.md
+++ b/docs/concept/persistence-storage.md
@@ -27,64 +27,6 @@ If the user sets io_uring for use, the read and write
requests will first be pla
The most common IO mode is Synchronous IO, but it has a relatively low
throughput. BanyanDB provides a nonblocking mode that is compatible with lower
Linux versions.
# Operation
-## Directory
-### Create
-Create the specified directory and return the file descriptor, the error will
happen if the directory already exists.
-The following is the pseudocode that calls the API in the go style.、
-
-param:
-
-name: The name of the directory.
-
-permisson: Permission you want to set. BanyanDB provides three modes: Read,
Write, ReadAndWrite. you can use it as Mode.Read.
-
-`CreateDirectory(name String, permission Mode) (error)`
-
-### Open
-Open the directory and return an error if the file descriptor does not exist.
-The following is the pseudocode that calls the API in the go style.
-
-param:
-
-name: The name of the directory.
-
-return: Directory pointer, you can use it for various operations.
-
-`OpenDirectory(name String) (*Dir, error)`
-
-### Delete
-Delete the directory and all files and return an error if the directory does
not exist or the directory not reading or writing.
-The following is the pseudocode that calls the API in the go style.
-
-`Dir.DeleteDirectory() (error)`
-
-### Rename
-Rename the directory and return an error if the directory already exists.
-The following is the pseudocode that calls the API in the go style.
-
-param:
-
-name: The name of the directory.
-
-`Dir.RenameDirectory(newName String) (error)`
-
-### Read
-Get all lists of files or children's directories in the directory and an error
if the directory does not exist.
-The following is the pseudocode that calls the API in the go style.
-
-return: List of files belonging to the directory.
-
-`Dir.ReadDirectory() (FileList, error)`
-
-### Permission
-When creating a file, the default owner is the user who created the directory.
The owner can specify read and write permissions of the directory. If not
specified, the default is read and write permissions, which include permissions
for all files in the directory.
-The following is the pseudocode that calls the API in the go style.
-
-param:
-
-permisson: Permission you want to set. BanyanDB provides three mode: Read,
Write, ReadAndWrite. you can use it as Mode.Read.
-
-`Dir.SetDirectoryPermission(permission Mode) (error)`
## File
### Create
@@ -97,19 +39,9 @@ name: The name of the file.
permisson: Permission you want to set. BanyanDB provides three mode: Read,
Write, ReadAndWrite. you can use it as Mode.Read.
-`CreateFile(name String, permission Mode) (error)`
-
-### Open
-Open the file and return an error if the file descriptor does not exist.
-The following is the pseudocode that calls the API in the go style.
+return: The file instance, can be used for various file operations.
-param:
-
-name: The name of the file.
-
-return: File pointer, you can use it for various operations.
-
-`OpenFile(name String) (*File, error)`
+`CreateFile(name String, permission Mode) (File, error)`
### Write
BanyanDB provides two methods for writing files.
@@ -123,7 +55,9 @@ param:
buffer: The data append to the file.
-`File.AppendWriteFile(buffer []byte) (error)`
+Actual length of written data.
+
+`File.Write(buffer []byte) (int, error)`
For vector append mode:
@@ -131,7 +65,9 @@ param:
iov: The data in consecutive buffers.
-`File.AppendWritevFile(iov *[][]byte) (error)`
+return: Actual length of written data.
+
+`File.Writev(iov *[][]byte) (int, error)`
For flush mode:
@@ -141,16 +77,16 @@ buffer: The data append to the file.
permisson: Permission you want to set. BanyanDB provides three mode: Read,
Write, ReadAndWrite. you can use it as Mode.Read.
-return: File pointer, you can use it for various operations.
+return: Actual length of flushed data.
-`FlushWriteFile(buffer []byte, permission Mode) (*File, error)`
+`Write(buffer []byte, permission Mode) (int, error)`
### Delete
BanyanDB provides the deleting operation, which can delete a file at once. it
will return an error if the directory does not exist or the file not reading or
writing.
The following is the pseudocode that calls the API in the go style.
-`File.DeleteFile() (error)`
+`DeleteFile(name string) (error)`
### Read
For reading operation, two read methods are provided:
@@ -167,7 +103,9 @@ offset: Read begin location of the file.
buffer: The read length is the same as the buffer length.
-`File.ReadFile(offset int, buffer []byte) (error)`
+return: Actual length of reading data.
+
+`File.Read(offset int64, buffer []byte) (int, error)`
For vector reading:
@@ -175,29 +113,19 @@ param:
iov: Discontinuous buffers in memory.
-`File.ReadvFile(iov *[][]byte) (error)`
+return: Actual length of reading data.
+
+`File.Readv(iov *[][]byte) (int, error)`
For stream reading:
param:
-offset: Read begin location of the file.
-
buffer: Every read length in the stream is the same as the buffer length.
return: A Iterator, the size of each iteration is the length of the buffer.
-`File.StreamReadFile(offset int, buffer []byte) (*iter, error)`
-
-### Rename
-Rename the file and return an error if the directory exists in this directory.
-The following is the pseudocode that calls the API in the go style.
-
-param:
-
-newName: The new name of the file.
-
-`File.RenameFile(newName String) (error)`
+`File.StreamRead(buffer []byte) (*iter, error)`
### Get size
Get the file written data's size and return an error if the file does not
exist. The unit of file size is Byte.
@@ -205,14 +133,9 @@ The following is the pseudocode that calls the API in the
go style.
return: the file written data's size.
-`File.GetFileSize() (int, error)`
-
-### Permission
-When creating a file, the default owner is the user who created the file. The
owner can specify the read and write permissions of the file. If not specified,
the default is read and write permissions.
-The following is the pseudocode that calls the API in the go style.
+`File.Size() (int, error)`
-param:
-
-permisson: Permission you want to set. BanyanDB provides three mode: Read,
Write, ReadAndWrite. you can use it as Mode.Read.
+### Close
+Close File.The following is the pseudocode that calls the API in the go style.
-`File.SetFilePermission(permission Mode) (error)`
+`File.Close() error`
diff --git a/pkg/fs/error.go b/pkg/fs/error.go
new file mode 100644
index 00000000..a54c513a
--- /dev/null
+++ b/pkg/fs/error.go
@@ -0,0 +1,44 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Package fs (file system) is an independent component to operate file and
directory.
+package fs
+
+import "fmt"
+
+const (
+ isExistError = 0
+ isNotExistError = 1
+ permissionError = 2
+ openError = 3
+ deleteError = 4
+ writeError = 5
+ readError = 6
+ flushError = 7
+ closeError = 8
+ otherError = 9
+)
+
+// FileSystemError implements the Error interface.
+type FileSystemError struct {
+ Message string
+ Code int
+}
+
+func (err *FileSystemError) Error() string {
+ return fmt.Sprintf("File system return error: %s, error code: %d",
err.Message, err.Code)
+}
diff --git a/pkg/fs/file_system.go b/pkg/fs/file_system.go
index 8003cf8a..23f91d83 100644
--- a/pkg/fs/file_system.go
+++ b/pkg/fs/file_system.go
@@ -19,59 +19,45 @@
package fs
import (
- "container/list"
+ "bufio"
)
+const moduleName string = "filesystem"
+
// Mode contains permission of file and directory.
-type Mode struct{}
+type Mode uint64
// Iter is used for streaming read.
-type Iter struct{}
-
-// Dir operation interface.
-type Dir interface {
- // Delete the directory.
- DeleteDirectory() error
- // Rename the directory.
- RenameDirectory(newName string) error
- // Get all lists of files or children's directories in the directory.
- ReadDirectory() (list.List, error)
- // Set directory permission.
- SetDirectoryPermission(permission Mode) error
+type Iter struct {
+ fileName string
+ reader *bufio.Reader
+ buffer []byte
}
// File operation interface.
type File interface {
// Append mode, which adds new data to the end of a file.
- AppendWriteFile(buffer []byte) error
+ Write(buffer []byte) (int, error)
// Vector Append mode, which supports appending consecutive buffers to
the end of the file.
- AppendWritevFile(iov *[][]byte) error
- // Delete the file.
- DeleteFile() error
+ Writev(iov *[][]byte) (int, error)
// Reading a specified location of file.
- ReadFile(offset int, buffer []byte) error
+ Read(offset int64, buffer []byte) (int, error)
// Reading contiguous regions of a file and dispersing them into
discontinuous buffers.
- ReadvFile(iov *[][]byte) error
- // Read the entire file using streaming read
- StreamReadFile(offset int, buffer []byte) (*Iter, error)
- // Rename the file.
- RenameFile(newName string) error
+ Readv(offset int64, iov *[][]byte) (int, error)
+ // Read the entire file using streaming read.
+ StreamRead(buffer []byte) (*Iter, error)
// Get the file written data's size and return an error if the file
does not exist. The unit of file size is Byte.
- GetFileSize() (int, error)
- // Set directory permission.
- SetFilePermission(permission Mode) error
+ Size() (int64, error)
+ // Close File.
+ Close() error
}
// FileSystem operation interface.
type FileSystem interface {
- // Create the directory by specified name and mode.
- CreateDirectory(name string, permission Mode) error
- // Open the directory by specified name.
- OpenDirectory(name string) (*Dir, error)
- // Create the file by specified name and mode.
- CreateFile(name string, permission Mode) error
- // Open the file by specified name.
- OpenFile(name string) (*File, error)
+ // Create and open the file by specified name and mode.
+ CreateFile(name string, permission Mode) (File, error)
// Flush mode, which flushes all data to one file.
- FlushWriteFile(buffer []byte, permission Mode) (*File, error)
+ Write(buffer []byte, name string, permission Mode) (int, error)
+ // Delete the file.
+ DeleteFile(name string) error
}
diff --git a/pkg/fs/local_file_system.go b/pkg/fs/local_file_system.go
new file mode 100644
index 00000000..3a5b89c3
--- /dev/null
+++ b/pkg/fs/local_file_system.go
@@ -0,0 +1,285 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Package fs (file system) is an independent component to operate file and
directory.
+package fs
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/apache/skywalking-banyandb/pkg/logger"
+)
+
+// LocalFileSystem implements the File System interface.
+type LocalFileSystem struct {
+ logger *logger.Logger
+}
+
+// LocalFile implements the File interface.
+type LocalFile struct {
+ file *os.File
+}
+
+// NewLocalFileSystem is used to create the Local File system.
+func NewLocalFileSystem() FileSystem {
+ return &LocalFileSystem{
+ logger: logger.GetLogger(moduleName),
+ }
+}
+
+func readErrorHandle(operation string, err error, name string, size int) (int,
error) {
+ switch {
+ case errors.Is(err, io.EOF):
+ return size, err
+ case os.IsNotExist(err):
+ return size, &FileSystemError{
+ Code: isNotExistError,
+ Message: fmt.Sprintf("%s failed, file is not exist,
file name: %s, error message: %s", operation, name, err),
+ }
+ case os.IsPermission(err):
+ return size, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("%s failed, there is not enough
permission, file name: %s, error message: %s", operation, name, err),
+ }
+ default:
+ return size, &FileSystemError{
+ Code: readError,
+ Message: fmt.Sprintf("%s failed, read file error, file
name: %s, read file size: %d, error message: %s", operation, name, size, err),
+ }
+ }
+}
+
+// CreateFile is used to create and open the file by specified name and mode.
+func (fs *LocalFileSystem) CreateFile(name string, permission Mode) (File,
error) {
+ file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
os.FileMode(permission))
+ switch {
+ case err == nil:
+ return &LocalFile{
+ file: file,
+ }, nil
+ case os.IsExist(err):
+ return nil, &FileSystemError{
+ Code: isExistError,
+ Message: fmt.Sprintf("File is exist, file name:
%s,error message: %s", name, err),
+ }
+ case os.IsPermission(err):
+ return nil, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough permission,
file name: %s, permission: %d,error message: %s", name, permission, err),
+ }
+ default:
+ return nil, &FileSystemError{
+ Code: otherError,
+ Message: fmt.Sprintf("Create file return error, file
name: %s,error message: %s", name, err),
+ }
+ }
+}
+
+// Write flushes all data to one file.
+func (fs *LocalFileSystem) Write(buffer []byte, name string, permission Mode)
(int, error) {
+ file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
os.FileMode(permission))
+ if err != nil {
+ switch {
+ case os.IsExist(err):
+ return 0, &FileSystemError{
+ Code: isExistError,
+ Message: fmt.Sprintf("File is exist, file name:
%s,error message: %s", name, err),
+ }
+ case os.IsPermission(err):
+ return 0, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough
permission, file name: %s, permission: %d,error message: %s", name, permission,
err),
+ }
+ default:
+ return 0, &FileSystemError{
+ Code: otherError,
+ Message: fmt.Sprintf("Create file return error,
file name: %s,error message: %s", name, err),
+ }
+ }
+ }
+ defer file.Close()
+
+ size, err := file.Write(buffer)
+ if err != nil {
+ return size, &FileSystemError{
+ Code: flushError,
+ Message: fmt.Sprintf("Flush file return error, file
name: %s,error message: %s", name, err),
+ }
+ }
+
+ return size, nil
+}
+
+// DeleteFile is used to delete the file.
+func (fs *LocalFileSystem) DeleteFile(name string) error {
+ err := os.Remove(name)
+ switch {
+ case err == nil:
+ return nil
+ case os.IsNotExist(err):
+ return &FileSystemError{
+ Code: isNotExistError,
+ Message: fmt.Sprintf("File is not exist, file name: %s,
error message: %s", name, err),
+ }
+ case os.IsPermission(err):
+ return &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough permission,
file name: %s, error message: %s", name, err),
+ }
+ default:
+ return &FileSystemError{
+ Code: otherError,
+ Message: fmt.Sprintf("Delete file error, file name: %s,
error message: %s", name, err),
+ }
+ }
+}
+
+// Write adds new data to the end of a file.
+func (file *LocalFile) Write(buffer []byte) (int, error) {
+ size, err := file.file.Write(buffer)
+ switch {
+ case err == nil:
+ return size, nil
+ case os.IsNotExist(err):
+ return size, &FileSystemError{
+ Code: isNotExistError,
+ Message: fmt.Sprintf("File is not exist, file name: %s,
error message: %s", file.file.Name(), err),
+ }
+ case os.IsPermission(err):
+ return size, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough permission,
file name: %s, error message: %s", file.file.Name(), err),
+ }
+ default:
+ // include io.ErrShortWrite
+ return size, &FileSystemError{
+ Code: writeError,
+ Message: fmt.Sprintf("Write file error, file name: %s,
error message: %s", file.file.Name(), err),
+ }
+ }
+}
+
+// Writev supports appending consecutive buffers to the end of the file.
+// TODO: Optimizing under Linux.
+func (file *LocalFile) Writev(iov *[][]byte) (int, error) {
+ var size int
+ for _, buffer := range *iov {
+ wsize, err := file.file.Write(buffer)
+ switch {
+ case err == nil:
+ size += wsize
+ case os.IsNotExist(err):
+ return size, &FileSystemError{
+ Code: isNotExistError,
+ Message: fmt.Sprintf("File is not exist, file
name: %s, error message: %s", file.file.Name(), err),
+ }
+ case os.IsPermission(err):
+ return size, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough
permission, file name: %s, error message: %s", file.file.Name(), err),
+ }
+ default:
+ // include io.ErrShortWrite
+ return size, &FileSystemError{
+ Code: writeError,
+ Message: fmt.Sprintf("Write file error, file
name: %s, error message: %s", file.file.Name(), err),
+ }
+ }
+ }
+ return size, nil
+}
+
+// Read is used to read a specified location of file.
+func (file *LocalFile) Read(offset int64, buffer []byte) (int, error) {
+ rsize, err := file.file.ReadAt(buffer, offset)
+ if err != nil {
+ return readErrorHandle("Read operation", err, file.file.Name(),
rsize)
+ }
+
+ return rsize, nil
+}
+
+// Readv is used to read contiguous regions of a file and disperse them into
discontinuous buffers.
+// TODO: Optimizing under Linux.
+func (file *LocalFile) Readv(offset int64, iov *[][]byte) (int, error) {
+ var size int
+ for _, buffer := range *iov {
+ rsize, err := file.file.ReadAt(buffer, offset)
+ if err != nil {
+ return readErrorHandle("Readv operation", err,
file.file.Name(), rsize)
+ }
+ size += rsize
+ offset += int64(rsize)
+ }
+
+ return size, nil
+}
+
+// StreamRead is used to read the entire file using streaming read.
+func (file *LocalFile) StreamRead(buffer []byte) (*Iter, error) {
+ reader := bufio.NewReader(file.file)
+ return &Iter{reader: reader, buffer: buffer, fileName:
file.file.Name()}, nil
+}
+
+// Size is used to get the file written data's size and return an error if the
file does not exist. The unit of file size is Byte.
+func (file *LocalFile) Size() (int64, error) {
+ fileInfo, err := os.Stat(file.file.Name())
+ if err != nil {
+ if os.IsNotExist(err) {
+ return -1, &FileSystemError{
+ Code: isNotExistError,
+ Message: fmt.Sprintf("File is not exist, file
name: %s, error message: %s", file.file.Name(), err),
+ }
+ } else if os.IsPermission(err) {
+ return -1, &FileSystemError{
+ Code: permissionError,
+ Message: fmt.Sprintf("There is not enough
permission, file name: %s, error message: %s", file.file.Name(), err),
+ }
+ } else {
+ return -1, &FileSystemError{
+ Code: otherError,
+ Message: fmt.Sprintf("Get file size error, file
name: %s, error message: %s", file.file.Name(), err),
+ }
+ }
+ }
+ return fileInfo.Size(), nil
+}
+
+// Close is used to close File.
+func (file *LocalFile) Close() error {
+ err := file.file.Close()
+ if err != nil {
+ return &FileSystemError{
+ Code: closeError,
+ Message: fmt.Sprintf("Close File error, directory name:
%s, error message: %s", file.file.Name(), err),
+ }
+ }
+ return nil
+}
+
+// Next is used to obtain the next data of stream.
+func (iter *Iter) Next() (int, error) {
+ rsize, err := iter.reader.Read(iter.buffer)
+ if err != nil {
+ return readErrorHandle("ReadStream operation", err,
iter.fileName, rsize)
+ }
+ return rsize, nil
+}
diff --git a/pkg/fs/local_file_system_test.go b/pkg/fs/local_file_system_test.go
new file mode 100644
index 00000000..2340f1d5
--- /dev/null
+++ b/pkg/fs/local_file_system_test.go
@@ -0,0 +1,149 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Package fs (file system) is an independent component to operate file and
directory.
+package fs
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "os"
+
+ "github.com/onsi/ginkgo/v2"
+ "github.com/onsi/gomega"
+)
+
+var _ = ginkgo.Describe("Loacl File System", func() {
+ const (
+ data string = "BanyanDB"
+ dirName string = "tmpDir"
+ fileName string = "tmpDir/temFile"
+ flushFileName string = "tmpDir/tempFlushFile"
+ )
+
+ var (
+ fs FileSystem
+ file File
+ )
+
+ ginkgo.Context("Local File", func() {
+ ginkgo.BeforeEach(func() {
+ fs = NewLocalFileSystem()
+ err := os.MkdirAll(dirName, 0o777)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ file, err = fs.CreateFile(fileName, 0o777)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ _, err = os.Stat(fileName)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ })
+
+ ginkgo.AfterEach(func() {
+ err := os.RemoveAll(dirName)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ })
+
+ ginkgo.It("Flush File Operation", func() {
+ size, err := fs.Write([]byte(data), flushFileName,
0o777)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == len(data)).To(gomega.BeTrue())
+ err = os.Remove(flushFileName)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ })
+
+ ginkgo.It("Create File Test", func() {
+ _, err := os.Stat(fileName)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ })
+
+ ginkgo.It("Write And Read File Test", func() {
+ size, err := file.Write([]byte(data))
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == len(data)).To(gomega.BeTrue())
+
+ buffer := make([]byte, len(data))
+ size, err = file.Read(0, buffer)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == len(data)).To(gomega.BeTrue())
+ gomega.Expect(bytes.Equal(buffer,
[]byte(data))).To(gomega.BeTrue())
+ })
+
+ ginkgo.It("Writev And Readv File Test", func() {
+ var iov [][]byte
+ for i := 0; i < 2; i++ {
+ iov = append(iov, []byte(data))
+ }
+ size, err := file.Writev(&iov)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == 2*len(data)).To(gomega.BeTrue())
+
+ var riov [][]byte
+ for i := 0; i < 2; i++ {
+ riov = append(riov, make([]byte, len(data)))
+ }
+ size, err = file.Readv(0, &riov)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == 2*len(data)).To(gomega.BeTrue())
+ for _, buffer := range riov {
+ gomega.Expect(bytes.Equal(buffer,
[]byte(data))).To(gomega.BeTrue())
+ }
+ })
+
+ ginkgo.It("Stream Read Test", func() {
+ size, err := file.Write([]byte(data))
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == len(data)).To(gomega.BeTrue())
+
+ buffer := make([]byte, len(data))
+ iter, err := file.StreamRead(buffer)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ for {
+ size, err := iter.Next()
+ if err == nil {
+ gomega.Expect(size ==
len(data)).To(gomega.BeTrue())
+ } else {
+ gomega.Expect(errors.Is(err,
io.EOF)).To(gomega.BeTrue())
+ break
+ }
+ }
+ })
+
+ ginkgo.It("Size Test", func() {
+ size, err := file.Write([]byte(data))
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(size == len(data)).To(gomega.BeTrue())
+ n, err := file.Size()
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ gomega.Expect(n == int64(len(data))).To(gomega.BeTrue())
+ })
+
+ ginkgo.It("Close Test", func() {
+ err := file.Close()
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ buffer := make([]byte, len(data))
+ _, err = file.Read(0, buffer)
+ gomega.Expect(err).To(gomega.HaveOccurred())
+ })
+
+ ginkgo.It("Delete Test", func() {
+ err := fs.DeleteFile(fileName)
+ gomega.Expect(err).ToNot(gomega.HaveOccurred())
+ _, err = os.Stat(fileName)
+ gomega.Expect(err).To(gomega.HaveOccurred())
+ })
+ })
+})