Skip to content

Commit

Permalink
Add tests and apply review suggestions 07-07-2023
Browse files Browse the repository at this point in the history
  • Loading branch information
oleiade committed Jul 7, 2023
1 parent a27c795 commit 960d212
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 63 deletions.
14 changes: 10 additions & 4 deletions examples/experimental/fs/open.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { open, File } from "k6/experimental/fs";
import { open } 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.
Expand All @@ -8,7 +13,8 @@ let file;
})();

export default async function () {
const stats = await file.stat();
console.log("the file name is: " + stats.name);
console.log("the file size is: " + stats.size);
const fileinfo = await file.stat();
if (fileinfo.name != "bonjour.txt") {
throw new Error("Unexpected file name");
}
}
17 changes: 10 additions & 7 deletions js/modules/k6/experimental/fs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import (
// file is an abstraction for interacting with files.
type file struct {
// name holds the name of the file, as presented to [Open].
Name string `json:"name"`
name string

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

// Stat returns a FileInfo describing the named file.
func (f *file) Stat() *fileInfo {
basepath := path.Base(f.Name)
return &fileInfo{Name: basepath, Size: len(*f.data)}
func (f *file) Stat() *FileInfo {
basepath := path.Base(f.name)
return &FileInfo{Name: basepath, Size: len(f.data)}
}

// FileInfo describes a file and is returned by the [file.Stat] method.
type fileInfo struct {
// FileInfo holds information about a file.
//
// It is a wrapper around the [fileInfo] struct, which is meant to be directly
// exposed to the JS runtime.
type FileInfo struct {
// Name holds the base name of the file.
Name string `json:"name"`

Expand Down
109 changes: 65 additions & 44 deletions js/modules/k6/experimental/fs/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"

"github.com/dop251/goja"
"go.k6.io/k6/js/common"
"go.k6.io/k6/js/modules"
"go.k6.io/k6/lib/fsext"
)
Expand Down Expand Up @@ -37,9 +38,7 @@ var (
// New returns a pointer to a new [RootModule] instance.
func New() *RootModule {
return &RootModule{
fileRegistry: fileRegistry{
files: make(map[string]*[]byte),
},
fileRegistry: *newFileRegistry(),
}
}

Expand All @@ -55,73 +54,102 @@ func (mi *ModuleInstance) Exports() modules.Exports {
return modules.Exports{
Named: map[string]any{
"open": mi.Open,
"File": file{},
"FileInfo": fileInfo{},
"File": File{},
"FileInfo": FileInfo{},
},
}
}

// Open opens a file and returns a promise that will resolve to a [File] instance
func (mi *ModuleInstance) Open(path goja.Value) *goja.Promise {
rt := mi.vu.Runtime()

promise, resolve, reject := makeHandledPromise(mi.vu)

// Files can only be opened in the init context.
if mi.vu.State() != nil {
reject(NewError(ForbiddenError, "open() failed; reason: opening a file in the VU context is forbidden"))
return promise
}

if common.IsNullish(path) || path == nil {
reject(NewError(TypeError, "open() failed; reason: path cannot be null or undefined"))
return promise
}

// Obtain the underlying path string from the JS value.
var pathStr string
if err := rt.ExportTo(path, &pathStr); err != nil {
reject(err)
return promise
}

initEnv := mi.vu.InitEnv()
pathStr = initEnv.GetAbsFilePath(pathStr)

fs := initEnv.FileSystems["file"]
if isDir, err := fsext.IsDir(fs, pathStr); err != nil {
reject(err)
return promise
} else if isDir {
reject(fmt.Errorf("open() failed; reason: %q is a directory", pathStr))
if pathStr == "" {
reject(NewError(TypeError, "open() failed; reason: path cannot be empty"))
return promise
}

go func() {
fs, ok := mi.vu.InitEnv().FileSystems["file"]
if !ok {
reject("open() failed; reason: no file configured")
return
}

data, err := mi.fileRegistry.open(pathStr, fs)
fileValue, err := mi.open(pathStr)
if err != nil {
reject(err)
return
}

f := File{
file: &file{
Name: pathStr,
data: data,
},
vu: mi.vu,
registry: mi.fileRegistry,
}

rt.ToValue(f).ToObject(rt)
resolve(f)
resolve(fileValue)
}()

return promise
}

func (mi *ModuleInstance) open(path string) (goja.Value, error) {
initEnv := mi.vu.InitEnv()
path = initEnv.GetAbsFilePath(path)

fs, ok := initEnv.FileSystems["file"]
if !ok {
panic("open() failed; reason: unable to access the filesystem")
}

if exists, err := fsext.Exists(fs, path); err != nil {
return nil, fmt.Errorf("open() failed, unable to verify if %q exists; reason: %w", path, err)
} else if !exists {
return nil, NewError(NotFoundError, fmt.Sprintf("no such file or directory %q", path))
}

if isDir, err := fsext.IsDir(fs, path); err != nil {
return nil, fmt.Errorf("open() failed, unable to verify if %q is a directory; reason: %w", path, err)
} else if isDir {
return nil, NewError(InvalidResourceError, fmt.Sprintf("cannot open %q: opening a directory is not supported", path))
}

data, err := mi.fileRegistry.open(path, fs)
if err != nil {
return nil, err
}

file := File{
Name: path,
file: file{
name: path,
data: data,
},
vu: mi.vu,
registry: mi.fileRegistry,
}

return mi.vu.Runtime().ToValue(file), nil
}

// File represents a file and exposes methods to interact with it.
//
// It is a wrapper around the [file] struct, which is meant to be directly
// exposed to the JS runtime.
type File struct {
// fileImpl contains the actual implementation of the file.
*file
// Name holds the name of the file, as presented to [Open].
Name string `json:"path"`

// fileImpl contains the actual implementation of the FileImpl.
file

// vu holds a reference to the VU this file is associated with.
//
Expand All @@ -141,16 +169,9 @@ func (f *File) Stat() *goja.Promise {
promise, resolve, _ := makeHandledPromise(f.vu)

go func() {
resolve(f.Stat())
fmt.Printf("Address of the file data: %p\n", f.file.data)
resolve(f.file.Stat())
}()

return promise
}

// FileInfo holds information about a file.
//
// It is a wrapper around the [fileInfo] struct, which is meant to be directly
// exposed to the JS runtime.
type FileInfo struct {
*fileInfo
}
Loading

0 comments on commit 960d212

Please sign in to comment.