Skip to content

Commit

Permalink
[fs] Add file.read and file.seek methods to the fs module (2/3) (
Browse files Browse the repository at this point in the history
  • Loading branch information
oleiade committed Nov 7, 2023
1 parent 9fdc7b2 commit 376f238
Show file tree
Hide file tree
Showing 7 changed files with 915 additions and 16 deletions.
38 changes: 35 additions & 3 deletions examples/experimental/fs/fs.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
import { open } from "k6/experimental/fs";
import { open, SeekMode } from "k6/experimental/fs";

export const options = {
vus: 100,
iterations: 1000,
};

// As k6 does not support asynchronous code in the init context, yet, we need to
// use a top-level async function to be able to use the `await` keyword.
// k6 doesn't support async in the init context. We use a top-level async function for `await`.
//
// Each Virtual User gets its own `file` copy.
// So, operations like `seek` or `read` won't impact other VUs.
let file;
(async function () {
file = await open("bonjour.txt");
})();

export default async function () {
// About information about the file
const fileinfo = await file.stat();
if (fileinfo.name != "bonjour.txt") {
throw new Error("Unexpected file name");
}

const buffer = new Uint8Array(4);

let totalBytesRead = 0;
while (true) {
// Read into the buffer
const bytesRead = await file.read(buffer);
if (bytesRead == null) {
// EOF
break;
}

// Do something useful with the content of the buffer

totalBytesRead += bytesRead;

// If bytesRead is less than the buffer size, we've read the whole file
if (bytesRead < buffer.byteLength) {
break;
}
}

// Check that we read the expected number of bytes
if (totalBytesRead != fileinfo.size) {
throw new Error("Unexpected number of bytes read");
}

// Seek back to the beginning of the file
await file.seek(0, SeekMode.Start);
}
3 changes: 3 additions & 0 deletions js/modules/k6/experimental/fs/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (

// TypeError is emitted when an incorrect type has been used.
TypeError

// EOFError is emitted when the end of a file has been reached.
EOFError
)

// fsError represents a custom error object emitted by the fs module.
Expand Down
12 changes: 8 additions & 4 deletions js/modules/k6/experimental/fs/errors_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions js/modules/k6/experimental/fs/file.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package fs

import (
"io"
"path/filepath"
"sync/atomic"

"go.k6.io/k6/lib"
)

// file is an abstraction for interacting with files.
Expand All @@ -10,6 +14,13 @@ type file struct {

// data holds a pointer to the file's data
data []byte

// offset holds the current offset in the file
//
// TODO: using an atomic here does not guarantee ordering of reads and seeks, and leaves
// the behavior not strictly defined. This is something we might want to address in the future, and
// is tracked as part of #3433.
offset atomic.Int64
}

// Stat returns a FileInfo describing the named file.
Expand All @@ -26,3 +37,106 @@ type FileInfo struct {
// Size holds the size of the file in bytes.
Size int `json:"size"`
}

// Read reads up to len(into) bytes into the provided byte slice.
//
// It returns the number of bytes read (0 <= n <= len(into)) and any error
// encountered.
//
// If the end of the file has been reached, it returns EOFError.
func (f *file) Read(into []byte) (n int, err error) {
currentOffset := f.offset.Load()
fileSize := f.size()

// Check if we have reached the end of the file
if currentOffset == fileSize {
return 0, newFsError(EOFError, "EOF")
}

// Calculate the effective new offset
targetOffset := currentOffset + int64(len(into))
newOffset := lib.Min(targetOffset, fileSize)

// Read the data into the provided slice, and update
// the offset accordingly
n = copy(into, f.data[currentOffset:newOffset])
f.offset.Store(newOffset)

// If we've reached or surpassed the end, set the error to EOF
if targetOffset > fileSize {
err = newFsError(EOFError, "EOF")
}

return n, err
}

// Ensure that `file` implements the io.Reader interface.
var _ io.Reader = (*file)(nil)

// Seek sets the offset for the next operation on the file, under the mode given by `whence`.
//
// `offset` indicates the number of bytes to move the offset. Based on
// the `whence` parameter, the offset is set relative to the start,
// current offset or end of the file.
//
// When using SeekModeStart, the offset must be positive.
// Negative offsets are allowed when using `SeekModeCurrent` or `SeekModeEnd`.
func (f *file) Seek(offset int64, whence SeekMode) (int64, error) {
startingOffset := f.offset.Load()

newOffset := startingOffset
switch whence {
case SeekModeStart:
if offset < 0 {
return 0, newFsError(TypeError, "offset cannot be negative when using SeekModeStart")
}

newOffset = offset
case SeekModeCurrent:
newOffset += offset
case SeekModeEnd:
if offset > 0 {
return 0, newFsError(TypeError, "offset cannot be positive when using SeekModeEnd")
}

newOffset = f.size() + offset
default:
return 0, newFsError(TypeError, "invalid seek mode")
}

if newOffset < 0 {
return 0, newFsError(TypeError, "seeking before start of file")
}

if newOffset > f.size() {
return 0, newFsError(TypeError, "seeking beyond end of file")
}

// Update the file instance's offset to the new selected position
f.offset.Store(newOffset)

return newOffset, nil
}

var _ io.Seeker = (*file)(nil)

// SeekMode is used to specify the seek mode when seeking in a file.
type SeekMode = int

const (
// SeekModeStart sets the offset relative to the start of the file.
SeekModeStart SeekMode = 0

// SeekModeCurrent seeks relative to the current offset.
SeekModeCurrent = 1

// SeekModeEnd seeks relative to the end of the file.
//
// When using this mode the seek operation will move backwards from
// the end of the file.
SeekModeEnd = 2
)

func (f *file) size() int64 {
return int64(len(f.data))
}
Loading

0 comments on commit 376f238

Please sign in to comment.