Skip to content

Commit be1cb06

Browse files
Fixing env watcher for godebug
1 parent 2b01410 commit be1cb06

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

compiler/natives/src/internal/godebug/godebug.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package godebug
55

66
import (
77
"sync"
8+
9+
"github.com/gopherjs/gopherjs/js"
810
)
911

1012
type Setting struct {
@@ -37,6 +39,57 @@ func (s *Setting) Value() string {
3739
return *s.value.Load()
3840
}
3941

42+
const godebugEnvKey = `GODEBUG`
43+
44+
var injectWatcher sync.Once
45+
var godebugUpdate func(def, env string)
46+
47+
// setUpdate is provided by package runtime.
48+
// It calls update(def, env), where def is the default GODEBUG setting
49+
// and env is the current value of the $GODEBUG environment variable.
50+
// After that first call, the runtime calls update(def, env)
51+
// again each time the environment variable changes
52+
// (due to use of os.Setenv, for example).
53+
func setUpdate(update func(def, env string)) {
54+
injectWatcher.Do(func() {
55+
js.Global.Call(`$injectGoDebugEnvWatcher`, godebugNotify)
56+
})
57+
godebugUpdate = update
58+
godebugEnv := getEnvString(godebugEnvKey)
59+
godebugNotify(godebugEnvKey, godebugEnv)
60+
}
61+
62+
func getEnvString(key string) string {
63+
process := js.Global.Get(`process`)
64+
if process == js.Undefined {
65+
return ``
66+
}
67+
68+
env := process.Get(`env`)
69+
if env == js.Undefined {
70+
return ``
71+
}
72+
73+
value := env.Get(key)
74+
if value == js.Undefined {
75+
return ``
76+
}
77+
78+
return value.String()
79+
}
80+
81+
// godebugNotify is the function injected into process.env
82+
// and called anytime an environment variable is set.
83+
func godebugNotify(key, value string) {
84+
update := godebugUpdate
85+
if update == nil || key != godebugEnvKey {
86+
return
87+
}
88+
89+
godebugDefault := ``
90+
update(godebugDefault, value)
91+
}
92+
4093
func update(def, env string) {
4194
updateMu.Lock()
4295
defer updateMu.Unlock()

compiler/prelude/prelude.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,13 @@ var $sliceData = (slice, typ) => {
576576
}
577577
return $indexPtr(slice.$array, slice.$offset, typ.elem);
578578
};
579+
580+
var $injectGoDebugEnvWatcher = (onEnvChange) => {
581+
process.env = new Proxy(process.env, {
582+
set(target, key, value) {
583+
onEnvChange(key, value);
584+
target[key] = value;
585+
return true;
586+
}
587+
});
588+
};

tests/runtime_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package tests
55
import (
66
"fmt"
77
"runtime"
8+
"strconv"
9+
"strings"
810
"testing"
11+
_ "unsafe"
912

1013
"github.com/google/go-cmp/cmp"
1114
"github.com/gopherjs/gopherjs/js"
@@ -160,3 +163,42 @@ func TestCallers(t *testing.T) {
160163
panic("panic")
161164
})
162165
}
166+
167+
// Need this to tunnel into `internal/godebug` and run a test
168+
// without causing a dependency cycle with the `testing` package.
169+
//
170+
//go:linkname godebug_setUpdate internal/godebug.setUpdate
171+
func godebug_setUpdate(update func(string, string))
172+
173+
func Test_GoDebugInjection(t *testing.T) {
174+
buf := []string{}
175+
update := func(def, env string) {
176+
if def != `` {
177+
t.Errorf(`Expected the default value to be empty but got %q`, def)
178+
}
179+
buf = append(buf, strconv.Quote(env))
180+
}
181+
check := func(want string) {
182+
if got := strings.Join(buf, `, `); got != want {
183+
t.Errorf(`Unexpected result: got: %q, want: %q`, got, want)
184+
}
185+
buf = buf[:0]
186+
}
187+
188+
// Call it multiple times to ensure that the watcher is only injected once.
189+
// Each one of these calls should emit an update first, then when GODEBUG is set.
190+
godebug_setUpdate(update)
191+
godebug_setUpdate(update)
192+
check(`"", ""`) // two empty strings for initial update calls.
193+
194+
t.Setenv(`GODEBUG`, `gopherJSTest=ben`)
195+
check(`"gopherJSTest=ben"`) // must only be once for update for new value.
196+
197+
godebug_setUpdate(update)
198+
check(`"gopherJSTest=ben"`) // must only be once for initial update with already set value.
199+
200+
t.Setenv(`GODEBUG`, `gopherJSTest=tom`)
201+
t.Setenv(`GODEBUG`, `gopherJSTest=sam`)
202+
t.Setenv(`NOT_GODEBUG`, `gopherJSTest=bob`)
203+
check(`"gopherJSTest=tom", "gopherJSTest=sam"`)
204+
}

0 commit comments

Comments
 (0)