github.com/rajeshell/goja_nodejs@v0.1.0/require/module_test.go (about) 1 package require 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "testing" 13 14 js "github.com/rajeshell/goja" 15 ) 16 17 func mapFileSystemSourceLoader(files map[string]string) SourceLoader { 18 return func(path string) ([]byte, error) { 19 s, ok := files[path] 20 if !ok { 21 return nil, ModuleFileDoesNotExistError 22 } 23 return []byte(s), nil 24 } 25 } 26 27 func TestRequireNativeModule(t *testing.T) { 28 const SCRIPT = ` 29 var m = require("test/m"); 30 m.test(); 31 ` 32 33 vm := js.New() 34 35 registry := new(Registry) 36 registry.Enable(vm) 37 38 RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) { 39 o := module.Get("exports").(*js.Object) 40 o.Set("test", func(call js.FunctionCall) js.Value { 41 return runtime.ToValue("passed") 42 }) 43 }) 44 45 v, err := vm.RunString(SCRIPT) 46 if err != nil { 47 t.Fatal(err) 48 } 49 50 if !v.StrictEquals(vm.ToValue("passed")) { 51 t.Fatalf("Unexpected result: %v", v) 52 } 53 } 54 55 func TestRegisterCoreModule(t *testing.T) { 56 vm := js.New() 57 58 registry := new(Registry) 59 registry.Enable(vm) 60 61 RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) { 62 o := module.Get("exports").(*js.Object) 63 o.Set("test", func(call js.FunctionCall) js.Value { 64 return runtime.ToValue("passed") 65 }) 66 }) 67 68 RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) { 69 o := module.Get("exports").(*js.Object) 70 o.Set("test", func(call js.FunctionCall) js.Value { 71 return runtime.ToValue("passed1") 72 }) 73 }) 74 75 RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) { 76 o := module.Get("exports").(*js.Object) 77 o.Set("test", func(call js.FunctionCall) js.Value { 78 return runtime.ToValue("test1 passed") 79 }) 80 }) 81 82 registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) { 83 84 }) 85 86 _, err := vm.RunString(` 87 const m1 = require("coremod"); 88 const m2 = require("node:coremod"); 89 if (m1 !== m2) { 90 throw new Error("Modules are not equal"); 91 } 92 if (m1.test() !== "passed") { 93 throw new Error("m1.test() has failed"); 94 } 95 96 const m3 = require("node:coremod1"); 97 const m4 = require("coremod1"); 98 if (m3 !== m4) { 99 throw new Error("Modules are not equal (1)"); 100 } 101 if (m3.test() !== "passed1") { 102 throw new Error("m3.test() has failed"); 103 } 104 105 try { 106 require("node:bob"); 107 } catch (e) { 108 if (!e.message.includes("No such built-in module")) { 109 throw e; 110 } 111 } 112 require("bob"); 113 114 try { 115 require("test1"); 116 throw new Error("Expected exception"); 117 } catch (e) { 118 if (!e.message.includes("Invalid module")) { 119 throw e; 120 } 121 } 122 123 if (require("node:test1").test() !== "test1 passed") { 124 throw new Error("test1.test() has failed"); 125 } 126 `) 127 128 if err != nil { 129 t.Fatal(err) 130 } 131 } 132 133 func TestRequireRegistryNativeModule(t *testing.T) { 134 const SCRIPT = ` 135 var log = require("test/log"); 136 log.print('passed'); 137 ` 138 139 logWithOutput := func(w io.Writer, prefix string) ModuleLoader { 140 return func(vm *js.Runtime, module *js.Object) { 141 o := module.Get("exports").(*js.Object) 142 o.Set("print", func(call js.FunctionCall) js.Value { 143 fmt.Fprint(w, prefix, call.Argument(0).String()) 144 return js.Undefined() 145 }) 146 } 147 } 148 149 vm1 := js.New() 150 buf1 := &bytes.Buffer{} 151 152 registry1 := new(Registry) 153 registry1.Enable(vm1) 154 155 registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 ")) 156 157 vm2 := js.New() 158 buf2 := &bytes.Buffer{} 159 160 registry2 := new(Registry) 161 registry2.Enable(vm2) 162 163 registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 ")) 164 165 _, err := vm1.RunString(SCRIPT) 166 if err != nil { 167 t.Fatal(err) 168 } 169 170 s := buf1.String() 171 if s != "vm1 passed" { 172 t.Fatalf("vm1: Unexpected result: %q", s) 173 } 174 175 _, err = vm2.RunString(SCRIPT) 176 if err != nil { 177 t.Fatal(err) 178 } 179 180 s = buf2.String() 181 if s != "vm2 passed" { 182 t.Fatalf("vm2: Unexpected result: %q", s) 183 } 184 } 185 186 func TestRequire(t *testing.T) { 187 absPath, err := filepath.Abs("./testdata/m.js") 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 isWindows := runtime.GOOS == "windows" 193 194 tests := []struct { 195 path string 196 ok bool 197 }{ 198 { 199 "./testdata/m.js", 200 true, 201 }, 202 { 203 "../require/testdata/m.js", 204 true, 205 }, 206 { 207 absPath, 208 true, 209 }, 210 { 211 `.\testdata\m.js`, 212 isWindows, 213 }, 214 { 215 `..\require\testdata\m.js`, 216 isWindows, 217 }, 218 } 219 220 const SCRIPT = ` 221 var m = require(testPath); 222 m.test(); 223 ` 224 225 for _, test := range tests { 226 t.Run(test.path, func(t *testing.T) { 227 vm := js.New() 228 vm.Set("testPath", test.path) 229 230 registry := new(Registry) 231 registry.Enable(vm) 232 233 v, err := vm.RunString(SCRIPT) 234 235 ok := err == nil 236 237 if ok != test.ok { 238 t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err) 239 } 240 241 if !ok { 242 return 243 } 244 245 if !v.StrictEquals(vm.ToValue("passed")) { 246 t.Fatalf("Unexpected result: %v", v) 247 } 248 }) 249 } 250 } 251 252 func TestSourceLoader(t *testing.T) { 253 const SCRIPT = ` 254 var m = require("m.js"); 255 m.test(); 256 ` 257 258 const MODULE = ` 259 function test() { 260 return "passed1"; 261 } 262 263 exports.test = test; 264 ` 265 266 vm := js.New() 267 268 registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) { 269 if name == "m.js" { 270 return []byte(MODULE), nil 271 } 272 return nil, errors.New("Module does not exist") 273 })) 274 registry.Enable(vm) 275 276 v, err := vm.RunString(SCRIPT) 277 if err != nil { 278 t.Fatal(err) 279 } 280 281 if !v.StrictEquals(vm.ToValue("passed1")) { 282 t.Fatalf("Unexpected result: %v", v) 283 } 284 } 285 286 func TestStrictModule(t *testing.T) { 287 const SCRIPT = ` 288 var m = require("m.js"); 289 m.test(); 290 ` 291 292 const MODULE = ` 293 "use strict"; 294 295 function test() { 296 var a = "passed1"; 297 eval("var a = 'not passed'"); 298 return a; 299 } 300 301 exports.test = test; 302 ` 303 304 vm := js.New() 305 306 registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) { 307 if name == "m.js" { 308 return []byte(MODULE), nil 309 } 310 return nil, errors.New("Module does not exist") 311 })) 312 registry.Enable(vm) 313 314 v, err := vm.RunString(SCRIPT) 315 if err != nil { 316 t.Fatal(err) 317 } 318 319 if !v.StrictEquals(vm.ToValue("passed1")) { 320 t.Fatalf("Unexpected result: %v", v) 321 } 322 } 323 324 func TestResolve(t *testing.T) { 325 testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) { 326 vm := js.New() 327 r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs))) 328 r.Enable(vm) 329 t.Logf("Require(%s)", fpath) 330 ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath)) 331 if err != nil { 332 return nil, nil, err 333 } 334 return vm, ret, nil 335 } 336 337 globalFolders := []string{ 338 "/usr/lib/node_modules", 339 "/home/src/.node_modules", 340 } 341 342 fs := map[string]string{ 343 "/home/src/app/app.js": `exports.name = "app"`, 344 "/home/src/app2/app2.json": `{"name": "app2"}`, 345 "/home/src/app3/index.js": `exports.name = "app3"`, 346 "/home/src/app4/index.json": `{"name": "app4"}`, 347 "/home/src/app5/package.json": `{"main": "app5.js"}`, 348 "/home/src/app5/app5.js": `exports.name = "app5"`, 349 "/home/src/app6/package.json": `{"main": "."}`, 350 "/home/src/app6/index.js": `exports.name = "app6"`, 351 "/home/src/app7/package.json": `{"main": "./a/b/c/file.js"}`, 352 "/home/src/app7/a/b/c/file.js": `exports.name = "app7"`, 353 "/usr/lib/node_modules/app8": `exports.name = "app8"`, 354 "/home/src/app9/app9.js": `exports.name = require('./a/file.js').name`, 355 "/home/src/app9/a/file.js": `exports.name = require('./b/file.js').name`, 356 "/home/src/app9/a/b/file.js": `exports.name = require('./c/file.js').name`, 357 "/home/src/app9/a/b/c/file.js": `exports.name = "app9"`, 358 "/home/src/.node_modules/app10": `exports.name = "app10"`, 359 "/home/src/app11/app11.js": `exports.name = require('d/file.js').name`, 360 "/home/src/app11/a/b/c/app11.js": `exports.name = require('d/file.js').name`, 361 "/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`, 362 "/app12.js": `exports.name = require('a/file.js').name`, 363 "/node_modules/a/file.js": `exports.name = "app12"`, 364 "/app13/app13.js": `exports.name = require('b/file.js').name`, 365 "/node_modules/b/file.js": `exports.name = "app13"`, 366 "node_modules/app14/index.js": `exports.name = "app14"`, 367 "../node_modules/app15/index.js": `exports.name = "app15"`, 368 } 369 370 for i, tc := range []struct { 371 src string 372 path string 373 ok bool 374 field string 375 value string 376 }{ 377 {"/home/src", "./app/app", true, "name", "app"}, 378 {"/home/src", "./app/app.js", true, "name", "app"}, 379 {"/home/src", "./app/bad.js", false, "", ""}, 380 {"/home/src", "./app2/app2", true, "name", "app2"}, 381 {"/home/src", "./app2/app2.json", true, "name", "app2"}, 382 {"/home/src", "./app/bad.json", false, "", ""}, 383 {"/home/src", "./app3", true, "name", "app3"}, 384 {"/home/src", "./appbad", false, "", ""}, 385 {"/home/src", "./app4", true, "name", "app4"}, 386 {"/home/src", "./appbad", false, "", ""}, 387 {"/home/src", "./app5", true, "name", "app5"}, 388 {"/home/src", "./app6", true, "name", "app6"}, 389 {"/home/src", "./app7", true, "name", "app7"}, 390 {"/home/src", "app8", true, "name", "app8"}, 391 {"/home/src", "./app9/app9", true, "name", "app9"}, 392 {"/home/src", "app10", true, "name", "app10"}, 393 {"/home/src", "./app11/app11.js", true, "name", "app11"}, 394 {"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"}, 395 {"/", "./app12", true, "name", "app12"}, 396 {"/", "./app13/app13", true, "name", "app13"}, 397 {".", "app14", true, "name", "app14"}, 398 {"..", "nonexistent", false, "", ""}, 399 } { 400 vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs) 401 if err != nil { 402 if tc.ok { 403 t.Errorf("%d: require() failed: %v", i, err) 404 } 405 continue 406 } else { 407 if !tc.ok { 408 t.Errorf("%d: expected to fail, but did not", i) 409 continue 410 } 411 } 412 f := mod.ToObject(vm).Get(tc.field) 413 if f == nil { 414 t.Errorf("%v: field %q not found", i, tc.field) 415 continue 416 } 417 value := f.String() 418 if value != tc.value { 419 t.Errorf("%v: got %q expected %q", i, value, tc.value) 420 } 421 } 422 } 423 424 func TestRequireCycle(t *testing.T) { 425 vm := js.New() 426 r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{ 427 "a.js": `var b = require('./b.js'); exports.done = true;`, 428 "b.js": `var a = require('./a.js'); exports.done = true;`, 429 }))) 430 r.Enable(vm) 431 res, err := vm.RunString(` 432 var a = require('./a.js'); 433 var b = require('./b.js'); 434 a.done && b.done; 435 `) 436 if err != nil { 437 t.Fatal(err) 438 } 439 if v := res.Export(); v != true { 440 t.Fatalf("Unexpected result: %v", v) 441 } 442 } 443 444 func TestErrorPropagation(t *testing.T) { 445 vm := js.New() 446 r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{ 447 "m.js": `throw 'test passed';`, 448 }))) 449 rr := r.Enable(vm) 450 _, err := rr.Require("./m") 451 if err == nil { 452 t.Fatal("Expected an error") 453 } 454 if ex, ok := err.(*js.Exception); ok { 455 if !ex.Value().StrictEquals(vm.ToValue("test passed")) { 456 t.Fatalf("Unexpected Exception: %v", ex) 457 } 458 } else { 459 t.Fatal(err) 460 } 461 } 462 463 func TestSourceMapLoader(t *testing.T) { 464 vm := js.New() 465 r := NewRegistry(WithLoader(func(p string) ([]byte, error) { 466 switch p { 467 case "dir/m.js": 468 return []byte(`throw 'test passed'; 469 //# sourceMappingURL=m.js.map`), nil 470 case "dir/m.js.map": 471 return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"} 472 `), nil 473 } 474 return nil, ModuleFileDoesNotExistError 475 })) 476 477 rr := r.Enable(vm) 478 _, err := rr.Require("./dir/m") 479 if err == nil { 480 t.Fatal("Expected an error") 481 } 482 if ex, ok := err.(*js.Exception); ok { 483 if !ex.Value().StrictEquals(vm.ToValue("test passed")) { 484 t.Fatalf("Unexpected Exception: %v", ex) 485 } 486 } else { 487 t.Fatal(err) 488 } 489 } 490 491 func testsetup() (string, func(), error) { 492 name, err := os.MkdirTemp("", "goja-nodejs-require-test") 493 if err != nil { 494 return "", nil, err 495 } 496 return name, func() { 497 os.RemoveAll(name) 498 }, nil 499 } 500 501 func TestDefaultModuleLoader(t *testing.T) { 502 workdir, teardown, err := testsetup() 503 if err != nil { 504 t.Fatal(err) 505 } 506 defer teardown() 507 508 err = os.Chdir(workdir) 509 if err != nil { 510 t.Fatal(err) 511 } 512 err = os.Mkdir("module", 0755) 513 if err != nil { 514 t.Fatal(err) 515 } 516 err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644) 517 if err != nil { 518 t.Fatal(err) 519 } 520 vm := js.New() 521 r := NewRegistry() 522 rr := r.Enable(vm) 523 _, err = rr.Require("./module") 524 if err == nil { 525 t.Fatal("Expected an error") 526 } 527 if ex, ok := err.(*js.Exception); ok { 528 if !ex.Value().StrictEquals(vm.ToValue("test passed")) { 529 t.Fatalf("Unexpected Exception: %v", ex) 530 } 531 } else { 532 t.Fatal(err) 533 } 534 }