-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
require_impl.go
154 lines (138 loc) · 5.83 KB
/
require_impl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package modules
import (
"errors"
"net/url"
"strings"
"github.com/dop251/goja"
"go.k6.io/k6/loader"
)
// LegacyRequireImpl is a legacy implementation of `require()` that is not compatible with
// CommonJS as it loads modules relative to the currently required file,
// instead of relative to the file the `require()` is written in.
// See https://github.com/grafana/k6/issues/2674
type LegacyRequireImpl struct {
vu VU
modules *ModuleSystem
currentlyRequiredModule *url.URL
}
// NewLegacyRequireImpl creates a new LegacyRequireImpl
func NewLegacyRequireImpl(vu VU, ms *ModuleSystem, pwd url.URL) *LegacyRequireImpl {
return &LegacyRequireImpl{
vu: vu,
modules: ms,
currentlyRequiredModule: &pwd,
}
}
const issueLink = "https://github.com/grafana/k6/issues/3534"
// Require is the actual call that implements require
func (r *LegacyRequireImpl) Require(specifier string) (*goja.Object, error) {
// TODO remove this in the future when we address https://github.com/grafana/k6/issues/2674
// This is currently needed as each time require is called we need to record it's new pwd
// to be used if a require *or* open is used within the file as they are relative to the
// latest call to require.
//
// This is *not* the actual require behaviour defined in commonJS as it is actually always relative
// to the file it is in. This is unlikely to be an issue but this code is here to keep backwards
// compatibility *for now*.
//
// With native ESM this won't even be possible as `require` might not be called - instead an import
// might be used in which case we won't be able to be doing this hack. In that case we either will
// need some goja specific helper or to use stack traces as goja_nodejs does.
currentPWD := r.currentlyRequiredModule
if specifier != "k6" && !strings.HasPrefix(specifier, "k6/") {
defer func() {
r.currentlyRequiredModule = currentPWD
}()
// In theory we can give that downwards, but this makes the code more tightly coupled
// plus as explained above this will be removed in the future so the code reflects more
// closely what will be needed then
if !r.modules.resolver.locked {
r.warnUserOnPathResolutionDifferences(specifier)
}
fileURL, err := loader.Resolve(r.currentlyRequiredModule, specifier)
if err != nil {
return nil, err
}
r.currentlyRequiredModule = loader.Dir(fileURL)
}
if specifier == "" {
return nil, errors.New("require() can't be used with an empty specifier")
}
return r.modules.Require(currentPWD, specifier)
}
// CurrentlyRequiredModule returns the module that is currently being required.
// It is mostly used for old and somewhat buggy behaviour of the `open` call
func (r *LegacyRequireImpl) CurrentlyRequiredModule() url.URL {
return *r.currentlyRequiredModule
}
func (r *LegacyRequireImpl) warnUserOnPathResolutionDifferences(specifier string) {
normalizePathToURL := func(path string) (*url.URL, error) {
u, err := url.Parse(path)
if err != nil {
return nil, err
}
return loader.Dir(u), nil
}
// Warn users on their require depending on the none standard k6 behaviour.
rt := r.vu.Runtime()
logger := r.vu.InitEnv().Logger
correct, err := normalizePathToURL(getCurrentModuleScript(rt))
if err != nil {
logger.Warningf("Couldn't get the \"correct\" path to resolve specifier %q against: %q"+
"Please report to issue %s. "+
"This will not currently break your script but it might mean that in the future it won't work",
specifier, err, issueLink)
} else if r.currentlyRequiredModule.String() != correct.String() {
logger.Warningf("The \"wrong\" path (%q) and the path actually used by k6 (%q) to resolve %q are different. "+
"Please report to issue %s. "+
"This will not currently break your script but *WILL* in the future, please report this!!!",
correct, r.currentlyRequiredModule, specifier, issueLink)
}
k6behaviourString, err := getPreviousRequiringFile(rt)
if err != nil {
logger.Warningf("Couldn't get the \"wrong\" path to resolve specifier %q against: %q"+
"Please report to issue %s. "+
"This will not currently break your script but it might mean that in the future it won't work",
specifier, err, issueLink)
return
}
k6behaviour, err := normalizePathToURL(k6behaviourString)
if err != nil {
logger.Warningf("Couldn't get the \"wrong\" path to resolve specifier %q against: %q"+
"Please report to issue %s. "+
"This will not currently break your script but it might mean that in the future it won't work",
specifier, err, issueLink)
return
}
if r.currentlyRequiredModule.String() != k6behaviour.String() {
// this should always be equal, but check anyway to be certain we won't break something
logger.Warningf("The \"wrong\" path (%q) and the path actually used by k6 (%q) to resolve %q are different. "+
"Please report to issue %s. "+
"This will not currently break your script but it might mean that in the future it won't work",
k6behaviour, r.currentlyRequiredModule, specifier, issueLink)
}
}
func getCurrentModuleScript(rt *goja.Runtime) string {
var parent string
var buf [2]goja.StackFrame
frames := rt.CaptureCallStack(2, buf[:0])
parent = frames[1].SrcName()
return parent
}
func getPreviousRequiringFile(rt *goja.Runtime) (string, error) {
var buf [1000]goja.StackFrame
frames := rt.CaptureCallStack(1000, buf[:0])
for i, frame := range frames[1:] { // first one should be the current require
// TODO have this precalculated automatically
if frame.FuncName() == "go.k6.io/k6/js.(*requireImpl).require-fm" {
// we need to get the one *before* but as we skip the first one the index matches ;)
return frames[i].SrcName(), nil
}
}
// hopefully nobody is calling `require` with 1000 big stack :crossedfingers:
if len(frames) == 1000 {
return "", errors.New("stack too big")
}
// fallback
return frames[len(frames)-1].SrcName(), nil
}