From 1df3e1eaab29598667bc3608506b5b2bec59915f Mon Sep 17 00:00:00 2001 From: oleiade Date: Thu, 23 May 2024 11:38:13 +0200 Subject: [PATCH] Expose the underlying behavior through a ReadSeekStater interface --- js/modules/k6/experimental/csv/module.go | 2 +- js/modules/k6/experimental/fs/file.go | 5 +++- js/modules/k6/experimental/fs/module.go | 31 ++++++++++++++++---- js/modules/k6/experimental/fs/module_test.go | 16 +++++----- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/js/modules/k6/experimental/csv/module.go b/js/modules/k6/experimental/csv/module.go index e4521f93a78..c873f70ffd3 100644 --- a/js/modules/k6/experimental/csv/module.go +++ b/js/modules/k6/experimental/csv/module.go @@ -99,7 +99,7 @@ func (mi *ModuleInstance) NewParser(call goja.ConstructorCall) *goja.Object { } // Instantiate and configure csv reader - r := csv.NewReader(&file.Impl) + r := csv.NewReader(file.ReadSeekStater) r.ReuseRecord = true // evaluate if this is needed, and if it leads to unforeseen issues r.Comma = options.Delimiter // default delimiter, should be modifiable by the user diff --git a/js/modules/k6/experimental/fs/file.go b/js/modules/k6/experimental/fs/file.go index 8e3b686f8d4..f0170d38bcb 100644 --- a/js/modules/k6/experimental/fs/file.go +++ b/js/modules/k6/experimental/fs/file.go @@ -24,11 +24,14 @@ type file struct { } // Stat returns a FileInfo describing the named file. -func (f *file) stat() *FileInfo { +func (f *file) Stat() *FileInfo { filename := filepath.Base(f.path) return &FileInfo{Name: filename, Size: f.size()} } +// Ensure that `file` implements the Stater interface. +var _ Stater = (*file)(nil) + // FileInfo holds information about a file. type FileInfo struct { // Name holds the base name of the file. diff --git a/js/modules/k6/experimental/fs/module.go b/js/modules/k6/experimental/fs/module.go index c78fdce3c9b..994c3e8083d 100644 --- a/js/modules/k6/experimental/fs/module.go +++ b/js/modules/k6/experimental/fs/module.go @@ -7,6 +7,7 @@ package fs import ( "errors" "fmt" + "io" "reflect" "github.com/grafana/sobek" @@ -136,7 +137,7 @@ func (mi *ModuleInstance) openImpl(path string) (*File, error) { file := &File{ Path: path, - Impl: file{ + ReadSeekStater: &file{ path: path, data: data, }, @@ -147,6 +148,26 @@ func (mi *ModuleInstance) openImpl(path string) (*File, error) { return file, nil } +// Stater is an interface that provides information about a file. +// +// Although in the context of this module we have a single implementation +// of this interface, it is defined to allow exposing the `file`'s behavior +// to other module through the `ReadSeekStater` interface without having to +// leak our internal abstraction. +type Stater interface { + // Stat returns a FileInfo describing the named file. + Stat() *FileInfo +} + +// ReadSeekStater is an interface that combines the io.ReadSeeker and Stater +// interfaces and ensure that structs implementing it have the necessary +// methods to interact with files. +type ReadSeekStater interface { + io.Reader + io.Seeker + Stater +} + // 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 @@ -162,7 +183,7 @@ type File struct { // implementation details, but keep it public so that we can access it // from other modules that would want to leverage its implementation of // io.Reader and io.Seeker. - Impl file `js:"-"` + ReadSeekStater ReadSeekStater `js:"-"` // vu holds a reference to the VU this file is associated with. // @@ -182,7 +203,7 @@ func (f *File) Stat() *sobek.Promise { promise, resolve, _ := promises.New(f.vu) go func() { - resolve(f.Impl.stat()) + resolve(f.ReadSeekStater.Stat()) }() return promise @@ -227,7 +248,7 @@ func (f *File) Read(into sobek.Value) *sobek.Promise { // occurs on the main thread, during the promise's resolution. callback := f.vu.RegisterCallback() go func() { - n, readErr := f.Impl.Read(buffer) + n, readErr := f.ReadSeekStater.Read(buffer) callback(func() error { _ = copy(intoBytes[0:n], buffer) @@ -290,7 +311,7 @@ func (f *File) Seek(offset sobek.Value, whence sobek.Value) *sobek.Promise { callback := f.vu.RegisterCallback() go func() { - newOffset, err := f.Impl.Seek(intOffset, seekMode) + newOffset, err := f.ReadSeekStater.Seek(intOffset, seekMode) callback(func() error { if err != nil { reject(err) diff --git a/js/modules/k6/experimental/fs/module_test.go b/js/modules/k6/experimental/fs/module_test.go index bba76a726ed..e753afe3f14 100644 --- a/js/modules/k6/experimental/fs/module_test.go +++ b/js/modules/k6/experimental/fs/module_test.go @@ -95,7 +95,7 @@ func TestOpen(t *testing.T) { _, err = runtime.RunOnEventLoop(wrapInAsyncLambda(` try { - const file = await fs.open('bonjour.txt') + const file = await fs.open('bonjour.txt') throw 'unexpected promise resolution with result: ' + file; } catch (err) { if (err.name !== 'ForbiddenError') { @@ -199,7 +199,7 @@ func TestOpen(t *testing.T) { func TestFile(t *testing.T) { t.Parallel() - t.Run("stat method should succeed", func(t *testing.T) { + t.Run("Stat method should succeed", func(t *testing.T) { t.Parallel() runtime, err := newConfiguredRuntime(t) @@ -213,7 +213,7 @@ func TestFile(t *testing.T) { _, err = runtime.RunOnEventLoop(wrapInAsyncLambda(fmt.Sprintf(` const file = await fs.open(%q) - const info = await file.stat() + const info = await file.Stat() if (info.name !== 'bonjour.txt') { throw 'unexpected file name ' + info.name + '; expected \'bonjour.txt\''; @@ -338,7 +338,7 @@ func TestFile(t *testing.T) { runtime.VU.InitEnvField.FileSystems["file"] = fs _, err = runtime.RunOnEventLoop(wrapInAsyncLambda(fmt.Sprintf(` - const file = await fs.open(%q); + const file = await fs.open(%q); let bytesRead; // No argument should fail with TypeError. @@ -406,11 +406,11 @@ func TestFile(t *testing.T) { runtime.VU.InitEnvField.FileSystems["file"] = fs _, err = runtime.RunOnEventLoop(wrapInAsyncLambda(fmt.Sprintf(` - // file size is 3 + // file size is 3 const file = await fs.open(%q); // Create a buffer of size fileSize + 1 - let buffer = new Uint8Array(4); + let buffer = new Uint8Array(4); let n = await file.read(buffer) if (n !== 3) { throw 'expected read to return 10, got ' + n + ' instead'; @@ -555,7 +555,7 @@ func TestFile(t *testing.T) { // Invalid type offset should fail with TypeError. try { newOffset = await file.seek('abc') - throw "file.seek('abc') promise unexpectedly resolved with result: " + newOffset + throw "file.seek('abc') promise unexpectedly resolved with result: " + newOffset } catch (err) { if (err.name !== 'TypeError') { throw "file.seek('1') rejected with unexpected error: " + err @@ -585,7 +585,7 @@ func TestFile(t *testing.T) { // Invalid whence should fail with TypeError. try { newOffset = await file.seek(1, -1) - throw "file.seek(1, -1) promise unexpectedly resolved with result: " + newOffset + throw "file.seek(1, -1) promise unexpectedly resolved with result: " + newOffset } catch (err) { if (err.name !== 'TypeError') { throw "file.seek(1, -1) rejected with unexpected error: " + err