535 lines
12 KiB
Go
535 lines
12 KiB
Go
|
package require
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"testing"
|
||
|
|
||
|
js "apigo.cc/ai/ai/goja"
|
||
|
)
|
||
|
|
||
|
func mapFileSystemSourceLoader(files map[string]string) SourceLoader {
|
||
|
return func(path string) ([]byte, error) {
|
||
|
s, ok := files[path]
|
||
|
if !ok {
|
||
|
return nil, ModuleFileDoesNotExistError
|
||
|
}
|
||
|
return []byte(s), nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRequireNativeModule(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var m = require("test/m");
|
||
|
m.test();
|
||
|
`
|
||
|
|
||
|
vm := js.New()
|
||
|
|
||
|
registry := new(Registry)
|
||
|
registry.Enable(vm)
|
||
|
|
||
|
RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) {
|
||
|
o := module.Get("exports").(*js.Object)
|
||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||
|
return runtime.ToValue("passed")
|
||
|
})
|
||
|
})
|
||
|
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(vm.ToValue("passed")) {
|
||
|
t.Fatalf("Unexpected result: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRegisterCoreModule(t *testing.T) {
|
||
|
vm := js.New()
|
||
|
|
||
|
registry := new(Registry)
|
||
|
registry.Enable(vm)
|
||
|
|
||
|
RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) {
|
||
|
o := module.Get("exports").(*js.Object)
|
||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||
|
return runtime.ToValue("passed")
|
||
|
})
|
||
|
})
|
||
|
|
||
|
RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) {
|
||
|
o := module.Get("exports").(*js.Object)
|
||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||
|
return runtime.ToValue("passed1")
|
||
|
})
|
||
|
})
|
||
|
|
||
|
RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) {
|
||
|
o := module.Get("exports").(*js.Object)
|
||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||
|
return runtime.ToValue("test1 passed")
|
||
|
})
|
||
|
})
|
||
|
|
||
|
registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) {
|
||
|
|
||
|
})
|
||
|
|
||
|
_, err := vm.RunString(`
|
||
|
const m1 = require("coremod");
|
||
|
const m2 = require("node:coremod");
|
||
|
if (m1 !== m2) {
|
||
|
throw new Error("Modules are not equal");
|
||
|
}
|
||
|
if (m1.test() !== "passed") {
|
||
|
throw new Error("m1.test() has failed");
|
||
|
}
|
||
|
|
||
|
const m3 = require("node:coremod1");
|
||
|
const m4 = require("coremod1");
|
||
|
if (m3 !== m4) {
|
||
|
throw new Error("Modules are not equal (1)");
|
||
|
}
|
||
|
if (m3.test() !== "passed1") {
|
||
|
throw new Error("m3.test() has failed");
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
require("node:bob");
|
||
|
} catch (e) {
|
||
|
if (!e.message.includes("No such built-in module")) {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
require("bob");
|
||
|
|
||
|
try {
|
||
|
require("test1");
|
||
|
throw new Error("Expected exception");
|
||
|
} catch (e) {
|
||
|
if (!e.message.includes("Invalid module")) {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (require("node:test1").test() !== "test1 passed") {
|
||
|
throw new Error("test1.test() has failed");
|
||
|
}
|
||
|
`)
|
||
|
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRequireRegistryNativeModule(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var log = require("test/log");
|
||
|
log.print('passed');
|
||
|
`
|
||
|
|
||
|
logWithOutput := func(w io.Writer, prefix string) ModuleLoader {
|
||
|
return func(vm *js.Runtime, module *js.Object) {
|
||
|
o := module.Get("exports").(*js.Object)
|
||
|
o.Set("print", func(call js.FunctionCall) js.Value {
|
||
|
fmt.Fprint(w, prefix, call.Argument(0).String())
|
||
|
return js.Undefined()
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vm1 := js.New()
|
||
|
buf1 := &bytes.Buffer{}
|
||
|
|
||
|
registry1 := new(Registry)
|
||
|
registry1.Enable(vm1)
|
||
|
|
||
|
registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 "))
|
||
|
|
||
|
vm2 := js.New()
|
||
|
buf2 := &bytes.Buffer{}
|
||
|
|
||
|
registry2 := new(Registry)
|
||
|
registry2.Enable(vm2)
|
||
|
|
||
|
registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 "))
|
||
|
|
||
|
_, err := vm1.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
s := buf1.String()
|
||
|
if s != "vm1 passed" {
|
||
|
t.Fatalf("vm1: Unexpected result: %q", s)
|
||
|
}
|
||
|
|
||
|
_, err = vm2.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
s = buf2.String()
|
||
|
if s != "vm2 passed" {
|
||
|
t.Fatalf("vm2: Unexpected result: %q", s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRequire(t *testing.T) {
|
||
|
absPath, err := filepath.Abs("./testdata/m.js")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
isWindows := runtime.GOOS == "windows"
|
||
|
|
||
|
tests := []struct {
|
||
|
path string
|
||
|
ok bool
|
||
|
}{
|
||
|
{
|
||
|
"./testdata/m.js",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
"../require/testdata/m.js",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
absPath,
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`.\testdata\m.js`,
|
||
|
isWindows,
|
||
|
},
|
||
|
{
|
||
|
`..\require\testdata\m.js`,
|
||
|
isWindows,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
const SCRIPT = `
|
||
|
var m = require(testPath);
|
||
|
m.test();
|
||
|
`
|
||
|
|
||
|
for _, test := range tests {
|
||
|
t.Run(test.path, func(t *testing.T) {
|
||
|
vm := js.New()
|
||
|
vm.Set("testPath", test.path)
|
||
|
|
||
|
registry := new(Registry)
|
||
|
registry.Enable(vm)
|
||
|
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
|
||
|
ok := err == nil
|
||
|
|
||
|
if ok != test.ok {
|
||
|
t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err)
|
||
|
}
|
||
|
|
||
|
if !ok {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(vm.ToValue("passed")) {
|
||
|
t.Fatalf("Unexpected result: %v", v)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSourceLoader(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var m = require("m.js");
|
||
|
m.test();
|
||
|
`
|
||
|
|
||
|
const MODULE = `
|
||
|
function test() {
|
||
|
return "passed1";
|
||
|
}
|
||
|
|
||
|
exports.test = test;
|
||
|
`
|
||
|
|
||
|
vm := js.New()
|
||
|
|
||
|
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||
|
if name == "m.js" {
|
||
|
return []byte(MODULE), nil
|
||
|
}
|
||
|
return nil, errors.New("Module does not exist")
|
||
|
}))
|
||
|
registry.Enable(vm)
|
||
|
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||
|
t.Fatalf("Unexpected result: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestStrictModule(t *testing.T) {
|
||
|
const SCRIPT = `
|
||
|
var m = require("m.js");
|
||
|
m.test();
|
||
|
`
|
||
|
|
||
|
const MODULE = `
|
||
|
"use strict";
|
||
|
|
||
|
function test() {
|
||
|
var a = "passed1";
|
||
|
eval("var a = 'not passed'");
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
exports.test = test;
|
||
|
`
|
||
|
|
||
|
vm := js.New()
|
||
|
|
||
|
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||
|
if name == "m.js" {
|
||
|
return []byte(MODULE), nil
|
||
|
}
|
||
|
return nil, errors.New("Module does not exist")
|
||
|
}))
|
||
|
registry.Enable(vm)
|
||
|
|
||
|
v, err := vm.RunString(SCRIPT)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||
|
t.Fatalf("Unexpected result: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestResolve(t *testing.T) {
|
||
|
testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) {
|
||
|
vm := js.New()
|
||
|
r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs)))
|
||
|
r.Enable(vm)
|
||
|
t.Logf("Require(%s)", fpath)
|
||
|
ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath))
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
return vm, ret, nil
|
||
|
}
|
||
|
|
||
|
globalFolders := []string{
|
||
|
"/usr/lib/node_modules",
|
||
|
"/home/src/.node_modules",
|
||
|
}
|
||
|
|
||
|
fs := map[string]string{
|
||
|
"/home/src/app/app.js": `exports.name = "app"`,
|
||
|
"/home/src/app2/app2.json": `{"name": "app2"}`,
|
||
|
"/home/src/app3/index.js": `exports.name = "app3"`,
|
||
|
"/home/src/app4/index.json": `{"name": "app4"}`,
|
||
|
"/home/src/app5/package.json": `{"main": "app5.js"}`,
|
||
|
"/home/src/app5/app5.js": `exports.name = "app5"`,
|
||
|
"/home/src/app6/package.json": `{"main": "."}`,
|
||
|
"/home/src/app6/index.js": `exports.name = "app6"`,
|
||
|
"/home/src/app7/package.json": `{"main": "./a/b/c/file.js"}`,
|
||
|
"/home/src/app7/a/b/c/file.js": `exports.name = "app7"`,
|
||
|
"/usr/lib/node_modules/app8": `exports.name = "app8"`,
|
||
|
"/home/src/app9/app9.js": `exports.name = require('./a/file.js').name`,
|
||
|
"/home/src/app9/a/file.js": `exports.name = require('./b/file.js').name`,
|
||
|
"/home/src/app9/a/b/file.js": `exports.name = require('./c/file.js').name`,
|
||
|
"/home/src/app9/a/b/c/file.js": `exports.name = "app9"`,
|
||
|
"/home/src/.node_modules/app10": `exports.name = "app10"`,
|
||
|
"/home/src/app11/app11.js": `exports.name = require('d/file.js').name`,
|
||
|
"/home/src/app11/a/b/c/app11.js": `exports.name = require('d/file.js').name`,
|
||
|
"/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`,
|
||
|
"/app12.js": `exports.name = require('a/file.js').name`,
|
||
|
"/node_modules/a/file.js": `exports.name = "app12"`,
|
||
|
"/app13/app13.js": `exports.name = require('b/file.js').name`,
|
||
|
"/node_modules/b/file.js": `exports.name = "app13"`,
|
||
|
"node_modules/app14/index.js": `exports.name = "app14"`,
|
||
|
"../node_modules/app15/index.js": `exports.name = "app15"`,
|
||
|
}
|
||
|
|
||
|
for i, tc := range []struct {
|
||
|
src string
|
||
|
path string
|
||
|
ok bool
|
||
|
field string
|
||
|
value string
|
||
|
}{
|
||
|
{"/home/src", "./app/app", true, "name", "app"},
|
||
|
{"/home/src", "./app/app.js", true, "name", "app"},
|
||
|
{"/home/src", "./app/bad.js", false, "", ""},
|
||
|
{"/home/src", "./app2/app2", true, "name", "app2"},
|
||
|
{"/home/src", "./app2/app2.json", true, "name", "app2"},
|
||
|
{"/home/src", "./app/bad.json", false, "", ""},
|
||
|
{"/home/src", "./app3", true, "name", "app3"},
|
||
|
{"/home/src", "./appbad", false, "", ""},
|
||
|
{"/home/src", "./app4", true, "name", "app4"},
|
||
|
{"/home/src", "./appbad", false, "", ""},
|
||
|
{"/home/src", "./app5", true, "name", "app5"},
|
||
|
{"/home/src", "./app6", true, "name", "app6"},
|
||
|
{"/home/src", "./app7", true, "name", "app7"},
|
||
|
{"/home/src", "app8", true, "name", "app8"},
|
||
|
{"/home/src", "./app9/app9", true, "name", "app9"},
|
||
|
{"/home/src", "app10", true, "name", "app10"},
|
||
|
{"/home/src", "./app11/app11.js", true, "name", "app11"},
|
||
|
{"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"},
|
||
|
{"/", "./app12", true, "name", "app12"},
|
||
|
{"/", "./app13/app13", true, "name", "app13"},
|
||
|
{".", "app14", true, "name", "app14"},
|
||
|
{"..", "nonexistent", false, "", ""},
|
||
|
} {
|
||
|
vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs)
|
||
|
if err != nil {
|
||
|
if tc.ok {
|
||
|
t.Errorf("%d: require() failed: %v", i, err)
|
||
|
}
|
||
|
continue
|
||
|
} else {
|
||
|
if !tc.ok {
|
||
|
t.Errorf("%d: expected to fail, but did not", i)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
f := mod.ToObject(vm).Get(tc.field)
|
||
|
if f == nil {
|
||
|
t.Errorf("%v: field %q not found", i, tc.field)
|
||
|
continue
|
||
|
}
|
||
|
value := f.String()
|
||
|
if value != tc.value {
|
||
|
t.Errorf("%v: got %q expected %q", i, value, tc.value)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestRequireCycle(t *testing.T) {
|
||
|
vm := js.New()
|
||
|
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||
|
"a.js": `var b = require('./b.js'); exports.done = true;`,
|
||
|
"b.js": `var a = require('./a.js'); exports.done = true;`,
|
||
|
})))
|
||
|
r.Enable(vm)
|
||
|
res, err := vm.RunString(`
|
||
|
var a = require('./a.js');
|
||
|
var b = require('./b.js');
|
||
|
a.done && b.done;
|
||
|
`)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if v := res.Export(); v != true {
|
||
|
t.Fatalf("Unexpected result: %v", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestErrorPropagation(t *testing.T) {
|
||
|
vm := js.New()
|
||
|
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||
|
"m.js": `throw 'test passed';`,
|
||
|
})))
|
||
|
rr := r.Enable(vm)
|
||
|
_, err := rr.Require("./m")
|
||
|
if err == nil {
|
||
|
t.Fatal("Expected an error")
|
||
|
}
|
||
|
if ex, ok := err.(*js.Exception); ok {
|
||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSourceMapLoader(t *testing.T) {
|
||
|
vm := js.New()
|
||
|
r := NewRegistry(WithLoader(func(p string) ([]byte, error) {
|
||
|
switch p {
|
||
|
case "dir/m.js":
|
||
|
return []byte(`throw 'test passed';
|
||
|
//# sourceMappingURL=m.js.map`), nil
|
||
|
case "dir/m.js.map":
|
||
|
return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"}
|
||
|
`), nil
|
||
|
}
|
||
|
return nil, ModuleFileDoesNotExistError
|
||
|
}))
|
||
|
|
||
|
rr := r.Enable(vm)
|
||
|
_, err := rr.Require("./dir/m")
|
||
|
if err == nil {
|
||
|
t.Fatal("Expected an error")
|
||
|
}
|
||
|
if ex, ok := err.(*js.Exception); ok {
|
||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func testsetup() (string, func(), error) {
|
||
|
name, err := os.MkdirTemp("", "goja-nodejs-require-test")
|
||
|
if err != nil {
|
||
|
return "", nil, err
|
||
|
}
|
||
|
return name, func() {
|
||
|
os.RemoveAll(name)
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func TestDefaultModuleLoader(t *testing.T) {
|
||
|
workdir, teardown, err := testsetup()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer teardown()
|
||
|
|
||
|
err = os.Chdir(workdir)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
err = os.Mkdir("module", 0755)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
vm := js.New()
|
||
|
r := NewRegistry()
|
||
|
rr := r.Enable(vm)
|
||
|
_, err = rr.Require("./module")
|
||
|
if err == nil {
|
||
|
t.Fatal("Expected an error")
|
||
|
}
|
||
|
if ex, ok := err.(*js.Exception); ok {
|
||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||
|
}
|
||
|
} else {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|