Skip to content

Commit

Permalink
Add an fs.openSync temporary helper
Browse files Browse the repository at this point in the history
  • Loading branch information
oleiade committed Jul 17, 2023
1 parent d93cc6d commit a228a8a
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 1 deletion.
38 changes: 37 additions & 1 deletion js/modules/k6/experimental/fs/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func (rm *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
func (mi *ModuleInstance) Exports() modules.Exports {
return modules.Exports{
Named: map[string]any{
"open": mi.Open,
"open": mi.Open,
"openSync": mi.OpenSync,
},
}
}
Expand Down Expand Up @@ -90,6 +91,41 @@ func (mi *ModuleInstance) Open(path goja.Value) *goja.Promise {
return promise
}

// OpenSync opens a file and returns a [File] instance.
//
// This method is synchronous and should only be used in the init context.
//
// This method is intended as a temporary workaround until we have proper
// support for asynchronous functions execution in the k6 init context.
//
// TODO @oleiade: remove this method once we have proper support for
// asynchronous functions execution in the k6 init context.
func (mi *ModuleInstance) OpenSync(path goja.Value) (goja.Value, error) {
rt := mi.vu.Runtime()

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

if common.IsNullish(path) {
common.Throw(rt, newFsError(TypeError, "open() failed; reason: path cannot be null or undefined"))
}

// Obtain the underlying path string from the JS value.
pathStr := path.String()
if pathStr == "" {
common.Throw(rt, newFsError(TypeError, "open() failed; reason: path cannot be empty"))
}

file, err := mi.openImpl(pathStr)
if err != nil {
common.Throw(rt, err)
}

return rt.ToValue(file), nil
}

func (mi *ModuleInstance) openImpl(path string) (*File, error) {
initEnv := mi.vu.InitEnv()

Expand Down
153 changes: 153 additions & 0 deletions js/modules/k6/experimental/fs/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,159 @@ func TestOpen(t *testing.T) {
})
}

func TestOpenSync(t *testing.T) {
t.Parallel()

t.Run("opening existing file synchronously should succeed", func(t *testing.T) {
t.Parallel()

tests := []struct {
name string
openPath string
wantPath string
}{
{
name: "open absolute path",
openPath: fsext.FilePathSeparator + "bonjour.txt",
wantPath: fsext.FilePathSeparator + "bonjour.txt",
},
{
name: "open relative path",
openPath: filepath.Join(".", fsext.FilePathSeparator, "bonjour.txt"),
wantPath: fsext.FilePathSeparator + "bonjour.txt",
},
{
name: "open path with ..",
openPath: fsext.FilePathSeparator + "dir" + fsext.FilePathSeparator + ".." + fsext.FilePathSeparator + "bonjour.txt",
wantPath: fsext.FilePathSeparator + "bonjour.txt",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

runtime, err := newConfiguredRuntime(t)
require.NoError(t, err)

fs := newTestFs(t, func(fs afero.Fs) error {
fileErr := afero.WriteFile(fs, tt.wantPath, []byte("Bonjour, le monde"), 0o644)
if fileErr != nil {
return fileErr
}

return fs.Mkdir(fsext.FilePathSeparator+"dir", 0o644)
})
runtime.VU.InitEnvField.FileSystems["file"] = fs
runtime.VU.InitEnvField.CWD = &url.URL{Scheme: "file", Path: fsext.FilePathSeparator}

_, err = runtime.VU.RuntimeField.RunString(fmt.Sprintf(`
try {
const file = fs.openSync(%q)
if (file.path !== %q) {
throw 'unexpected file path ' + file.path + '; expected %q';
}
} catch (err) {
throw "unexpected error: " + err
}
`, tt.openPath, tt.wantPath, tt.wantPath))

assert.NoError(t, err)
})
}
})

t.Run("opening file synchronously in VU context should fail", func(t *testing.T) {
t.Parallel()

runtime, err := newConfiguredRuntime(t)
require.NoError(t, err)

runtime.MoveToVUContext(&lib.State{
Tags: lib.NewVUStateTags(metrics.NewRegistry().RootTagSet().With("tag-vu", "mytag")),
})

_, err = runtime.VU.RuntimeField.RunString(`
fs.openSync('bonjour.txt')
`)

assert.Error(t, err)
assert.Contains(t, err.Error(), "ForbiddenError")
})

t.Run("calling open without providing a path should fail", func(t *testing.T) {
t.Parallel()

tests := []struct {
name string
openPath string
}{
{
name: "open empty path should fail",
openPath: "",
},
{
name: "open null path should fail",
openPath: "null",
},
{
name: "open undefined path should fail",
openPath: "undefined",
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

runtime, err := newConfiguredRuntime(t)
require.NoError(t, err)

_, gotErr := runtime.VU.RuntimeField.RunString(fmt.Sprintf(`fs.openSync(%q)`, tt.openPath))

assert.Error(t, gotErr)
})
}
})

t.Run("opening directory synchronously should fail", func(t *testing.T) {
t.Parallel()

runtime, err := newConfiguredRuntime(t)
require.NoError(t, err)

testDirPath := fsext.FilePathSeparator + "dir"
fs := newTestFs(t, func(fs afero.Fs) error {
return fs.Mkdir(testDirPath, 0o644)
})

runtime.VU.InitEnvField.FileSystems["file"] = fs

_, err = runtime.VU.RuntimeField.RunString(fmt.Sprintf(`fs.openSync(%q)`, testDirPath))

assert.Error(t, err)
assert.Contains(t, err.Error(), "InvalidResourceError")
})

t.Run("opening non existing file should fail", func(t *testing.T) {
t.Parallel()

runtime, err := newConfiguredRuntime(t)
require.NoError(t, err)

_, err = runtime.VU.RuntimeField.RunString(`fs.openSync('doesnotexist.txt')`)

assert.Error(t, err)
assert.Contains(t, err.Error(), "NotFoundError")
})
}

func TestFile(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit a228a8a

Please sign in to comment.