mirror of https://github.com/helm/helm
parent
647ffaf7d3
commit
7ca7525755
@ -0,0 +1,40 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/hapi/chart"
|
||||
//"k8s.io/helm/pkg/hapi/release"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
|
||||
// ReleaseName is the name of the release.
|
||||
ReleaseName string
|
||||
|
||||
// Revision is the release revision. This is a ULID or empty if no release
|
||||
// has been stored.
|
||||
Revision string
|
||||
|
||||
// Chart is the chart.
|
||||
Chart *chart.Metadata
|
||||
|
||||
// Values is the override values (not the default values)
|
||||
Values chartutil.Values
|
||||
|
||||
Notes string
|
||||
|
||||
// Manifests is the manifests that Kubernetes will install.
|
||||
// Assume this is filename, content for now
|
||||
//Manifests map[string][]byte
|
||||
Manifests []string
|
||||
|
||||
Hooks []string
|
||||
|
||||
// Release is the release object
|
||||
Release chartutil.ReleaseOptions
|
||||
|
||||
// Capabilities are passed by reference to avoid modifications bubbling up.
|
||||
Capabilities chartutil.Capabilities
|
||||
|
||||
Files chartutil.Files
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/* Package events provides an event system for Helm.
|
||||
*
|
||||
* This is not a general purpose event framework. It is a system for handling Helm
|
||||
* objects.
|
||||
*
|
||||
* An event Emitter is an object that binds events and event handlers. An EventHandler
|
||||
* is a function that can respond to an event. And a Context is the information
|
||||
* passed from the calling location into the event handler. All together, the
|
||||
* system works by creating an event emitter, registering event handlers, and
|
||||
* then calling the event handlers at the appropriate place and time, passing in
|
||||
* a context as you go.
|
||||
*
|
||||
* In this particular implementation, the Context is considered mutable. So an
|
||||
* event handler is allowed to change the contents of the context. It is up to
|
||||
* the calling code to decide whether to "accept" those changes back into the
|
||||
* calling context.
|
||||
*/
|
||||
package events
|
@ -0,0 +1,90 @@
|
||||
package events
|
||||
|
||||
const (
|
||||
EventChartLoad = "chart-load"
|
||||
EventPreRender = "pre-render"
|
||||
EventPostRender = "post-render"
|
||||
EventPreInstall = "pre-install"
|
||||
EventPostInstall = "post-install"
|
||||
)
|
||||
|
||||
// EventHandler is a function capabile of responding to an event.
|
||||
type EventHandler func(*Context) error
|
||||
|
||||
// registry is the storage for events and handlers.
|
||||
type registry map[string][]EventHandler
|
||||
|
||||
// Emitter provides a way to register, manage, and execute events.
|
||||
type Emitter struct {
|
||||
reg registry
|
||||
}
|
||||
|
||||
// New creates a new event Emitter with no registered events.
|
||||
func New() *Emitter {
|
||||
return &Emitter{
|
||||
reg: registry{},
|
||||
}
|
||||
}
|
||||
|
||||
// Emit executes the given event with the given context.
|
||||
//
|
||||
// The first time an error occurs, this will cease the event cycle and return the
|
||||
// error.
|
||||
func (e *Emitter) Emit(event string, ctx *Context) error {
|
||||
fns := e.reg[event]
|
||||
for _, fn := range fns {
|
||||
if err := fn(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// On binds an EventHandler to an event.
|
||||
// If an event already has EventHandlers, this will be appended to the end of the
|
||||
// list.
|
||||
func (e *Emitter) On(event string, fn EventHandler) {
|
||||
handlers, ok := e.reg[event]
|
||||
if !ok {
|
||||
e.reg[event] = []EventHandler{fn}
|
||||
return
|
||||
}
|
||||
e.reg[event] = append(handlers, fn)
|
||||
}
|
||||
|
||||
// Handlers returns all of the EventHandlers for the given event.
|
||||
//
|
||||
// If no handlers are registered for this event, then an empty array is returned.
|
||||
// This is not an error condition, because it is perfectly normal for an event to
|
||||
// have no registered handlers.
|
||||
func (e *Emitter) Handlers(event string) []EventHandler {
|
||||
handlers, ok := e.reg[event]
|
||||
if !ok {
|
||||
return []EventHandler{}
|
||||
}
|
||||
return handlers
|
||||
}
|
||||
|
||||
// SetHandlers sets all of the handlers for a particular event.
|
||||
//
|
||||
// This will overwrite the existing event handlers for this event. It is allowed
|
||||
// to set this to an empty list.
|
||||
func (e *Emitter) SetHandlers(event string, listeners []EventHandler) {
|
||||
e.reg[event] = listeners
|
||||
}
|
||||
|
||||
// Len returns the number of event handlers registered for the given event.
|
||||
func (e *Emitter) Len(event string) int {
|
||||
return len(e.reg[event])
|
||||
}
|
||||
|
||||
// Events returns the names of all of the events that have been registered.
|
||||
// Note that this does not ensure that these events have registered handlers.
|
||||
// See SetHandlers above.
|
||||
func (e *Emitter) Events() []string {
|
||||
h := make([]string, 0, len(e.reg))
|
||||
for name := range e.reg {
|
||||
h = append(h, name)
|
||||
}
|
||||
return h
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func stubFunc(*Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func stubErrFunc(*Context) error {
|
||||
return errors.New("nope")
|
||||
}
|
||||
|
||||
var _ EventHandler = stubFunc
|
||||
var _ EventHandler = stubErrFunc
|
||||
|
||||
var stubCtx = &Context{}
|
||||
|
||||
func TestEmitter(t *testing.T) {
|
||||
emitter := New()
|
||||
is := assert.New(t)
|
||||
|
||||
is.Len(emitter.Events(), 0, "expect no events")
|
||||
is.Equal(0, emitter.Len("foo"), "expect empty foo event")
|
||||
}
|
||||
|
||||
func TestEmitter_On(t *testing.T) {
|
||||
emitter := New()
|
||||
is := assert.New(t)
|
||||
|
||||
emitter.On("foo", stubFunc)
|
||||
is.Equal([]string{"foo"}, emitter.Events(), "expect foo event")
|
||||
is.Equal(1, emitter.Len("foo"), "expect one foo event handler")
|
||||
|
||||
emitter.On("foo", stubErrFunc)
|
||||
is.Equal(2, emitter.Len("foo"), "expect two foo event handlers")
|
||||
|
||||
emitter.On("bar", stubFunc)
|
||||
is.Equal([]string{"foo", "bar"}, emitter.Events(), "expect foo, bar events")
|
||||
is.Equal(1, emitter.Len("bar"), "expect one bar event handler")
|
||||
}
|
||||
|
||||
func TestEmitter_Handlers(t *testing.T) {
|
||||
emitter := New()
|
||||
is := assert.New(t)
|
||||
|
||||
is.Len(emitter.Handlers("foo"), 0, "expect no handlers")
|
||||
|
||||
emitter.On("foo", stubFunc)
|
||||
is.Len(emitter.Handlers("foo"), 1, "expect one handler")
|
||||
|
||||
emitter.On("foo", stubFunc)
|
||||
emitter.On("foo", stubErrFunc)
|
||||
|
||||
is.Len(emitter.Handlers("foo"), 3, "expect 3 handlers")
|
||||
|
||||
fn := emitter.Handlers("foo")[2]
|
||||
is.Error(fn(stubCtx), "expect last one to be the stubErrFunc")
|
||||
}
|
||||
|
||||
func TestEmitter_SetHandlers(t *testing.T) {
|
||||
emitter := New()
|
||||
is := assert.New(t)
|
||||
|
||||
emitter.On("foo", stubFunc)
|
||||
emitter.SetHandlers("foo", []EventHandler{stubErrFunc})
|
||||
is.Equal(emitter.Len("foo"), 1, "Expect one handler")
|
||||
|
||||
fn := emitter.Handlers("foo")[0]
|
||||
is.Error(fn(stubCtx), "expect only handler to be stubErrFunc")
|
||||
}
|
||||
|
||||
func TestEmitter_Emit(t *testing.T) {
|
||||
emitter := New()
|
||||
is := assert.New(t)
|
||||
|
||||
emitter.On("foo", stubFunc)
|
||||
is.Nil(emitter.Emit("foo", stubCtx), "Expect no error")
|
||||
|
||||
emitter.On("foo", stubErrFunc)
|
||||
is.Error(emitter.Emit("foo", stubCtx), "Expect error")
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// Package lua provides Lua event handling bindings
|
||||
//
|
||||
// This package takes the Helm Go event system and extends it on into Lua. The
|
||||
// library handles the bi-directional transformation of Lua and Go objects.
|
||||
//
|
||||
// A major design goal of this implementation is that it will be able to interoperate
|
||||
// with handlers registered directly in Go and (in the future) other languages
|
||||
// that are added to the events system. To this end, there are a number of "round
|
||||
// trips" from Lua to Go and back again that otherwise could have been optimized
|
||||
// out.
|
||||
package lua
|
@ -0,0 +1,218 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
"k8s.io/helm/pkg/events"
|
||||
|
||||
"layeh.com/gopher-luar"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Emitter is a Lua-specific wrapper around an event.Emitter
|
||||
//
|
||||
// This wrapper servces two purposes.
|
||||
//
|
||||
// First, it exposes the events API to Lua code. An 'events' module is created,
|
||||
// and methods are attached to it.
|
||||
//
|
||||
// Second, it transforms Lua events back into Go datastructures.
|
||||
//
|
||||
// When New() creates a new Emitter, it registers the events library within the
|
||||
// given VM. This means that you should not try to create multiple emitters for
|
||||
// a single Lua VM.
|
||||
//
|
||||
// Inside of the Lua VM, scripts can call `events.on`:
|
||||
//
|
||||
// -- This is Lua code
|
||||
// local events = require("events")
|
||||
// events.on("some_event", some_callback)
|
||||
//
|
||||
// When an event is triggered on the paremt events.Emitter, this Emitter will
|
||||
// cause that event to trigger within the Lua VM.
|
||||
//
|
||||
// Note that in this model, Go events, Lua events, and events from other systems
|
||||
// that implement this system can all interoperate with each other. However, the
|
||||
// only shared pieces of context are (appropriately) the events.Context object
|
||||
// and whatever Emitter methods are exposed.
|
||||
type Emitter struct {
|
||||
parent *events.Emitter
|
||||
vm *lua.LState
|
||||
}
|
||||
|
||||
// Bind links the vm and the events.Emitter via a lua.Emitter that is not returned.
|
||||
func Bind(vm *lua.LState, emitter *events.Emitter) {
|
||||
New(vm, emitter)
|
||||
}
|
||||
|
||||
// New creates a new *lua.Emitter
|
||||
//
|
||||
// It registers the 'events' module into the Lua engine as well. If you do
|
||||
// not want the events module registered (e.g. if you have done so already, or
|
||||
// are doing it manually), you may construct a raw Emitter.
|
||||
func New(vm *lua.LState, emitter *events.Emitter) *Emitter {
|
||||
lee := &Emitter{
|
||||
parent: emitter,
|
||||
vm: vm,
|
||||
}
|
||||
// Register handlers inside the VM
|
||||
lee.register()
|
||||
return lee
|
||||
}
|
||||
|
||||
// register creates a Lua module named "events" and registers the event handlers.
|
||||
func (l *Emitter) register() {
|
||||
|
||||
exports := map[string]lua.LGFunction{
|
||||
"on": l.On,
|
||||
}
|
||||
|
||||
l.vm.PreloadModule("events", func(vm *lua.LState) int {
|
||||
module := vm.SetFuncs(vm.NewTable(), exports)
|
||||
vm.Push(module)
|
||||
return 1
|
||||
})
|
||||
|
||||
// YAML module
|
||||
yaml := map[string]lua.LGFunction{
|
||||
"encode": func(vm *lua.LState) int {
|
||||
input := vm.CheckTable(1)
|
||||
data, err := yaml.Marshal(tableToMap(input))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vm.Push(lua.LString(string(data)))
|
||||
return 1
|
||||
},
|
||||
"decode": func(vm *lua.LState) int {
|
||||
data := vm.CheckString(1)
|
||||
dest := map[string]interface{}{}
|
||||
if err := yaml.Unmarshal([]byte(data), dest); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 0
|
||||
},
|
||||
}
|
||||
l.vm.PreloadModule("yaml", func(vm *lua.LState) int {
|
||||
module := vm.SetFuncs(vm.NewTable(), yaml)
|
||||
vm.Push(module)
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
// On is the main event handler registration function.
|
||||
//
|
||||
// The given LState must be a function call with (eventName, callbackFunction) as
|
||||
// the signature.
|
||||
func (l *Emitter) On(vm *lua.LState) int {
|
||||
eventName := vm.CheckString(1)
|
||||
fn := vm.CheckFunction(2)
|
||||
l.onWrapper(eventName, fn)
|
||||
return 1
|
||||
}
|
||||
|
||||
// onWrapper registers an event handler with the parent events.Emitter.
|
||||
//
|
||||
// A wrapper function is applied to match Lua's types to the expected Go types.
|
||||
// Likewise the events.Context object is exposed in Lua as a UserData object.
|
||||
func (l *Emitter) onWrapper(event string, fn *lua.LFunction) {
|
||||
l.parent.On(event, func(ctx *events.Context) error {
|
||||
println("called wrapper for", event)
|
||||
|
||||
lctx := luar.New(l.vm, ctx)
|
||||
|
||||
/*
|
||||
luaCtx := l.vm.NewUserData()
|
||||
luaCtx.Value = ctx
|
||||
l.vm.SetMetatable(luaCtx, l.vm.GetTypeMetatable("context"))
|
||||
*/
|
||||
|
||||
// I think we may need to trap a panic here.
|
||||
return l.vm.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, lctx)
|
||||
})
|
||||
}
|
||||
|
||||
func tableToMap(table *lua.LTable) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
table.ForEach(func(k, v lua.LValue) {
|
||||
key := ""
|
||||
switch k.Type() {
|
||||
case lua.LTString, lua.LTNumber:
|
||||
key = k.String()
|
||||
default:
|
||||
panic("cannot convert table key to string")
|
||||
}
|
||||
|
||||
switch raw := v.(type) {
|
||||
case lua.LString:
|
||||
res[key] = string(raw)
|
||||
case lua.LBool:
|
||||
res[key] = bool(raw)
|
||||
case lua.LNumber:
|
||||
res[key] = float64(raw)
|
||||
case *lua.LUserData:
|
||||
res[key] = raw.Value
|
||||
case *lua.LTable:
|
||||
// Test whether slice or map
|
||||
intkeys := true
|
||||
raw.ForEach(func(k, v lua.LValue) {
|
||||
if k.Type() != lua.LTNumber {
|
||||
intkeys = false
|
||||
}
|
||||
})
|
||||
if intkeys {
|
||||
res[key] = tableToSlice(raw)
|
||||
return
|
||||
}
|
||||
res[key] = tableToMap(raw)
|
||||
default:
|
||||
if raw == lua.LNil {
|
||||
res[key] = nil
|
||||
return
|
||||
}
|
||||
panic("unknown")
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func tableToSlice(numberTable *lua.LTable) []interface{} {
|
||||
slice := []interface{}{}
|
||||
numberTable.ForEach(func(k, v lua.LValue) {
|
||||
switch raw := v.(type) {
|
||||
case lua.LString:
|
||||
slice = append(slice, string(raw))
|
||||
case lua.LBool:
|
||||
slice = append(slice, bool(raw))
|
||||
case lua.LNumber:
|
||||
slice = append(slice, float64(raw))
|
||||
case *lua.LUserData:
|
||||
slice = append(slice, raw.Value)
|
||||
case *lua.LTable:
|
||||
// Test whether slice or map
|
||||
intkeys := true
|
||||
raw.ForEach(func(k, v lua.LValue) {
|
||||
if k.Type() != lua.LTNumber {
|
||||
intkeys = false
|
||||
}
|
||||
})
|
||||
if intkeys {
|
||||
slice = append(slice, tableToSlice(raw))
|
||||
return
|
||||
}
|
||||
slice = append(slice, tableToMap(raw))
|
||||
default:
|
||||
if raw == lua.LNil {
|
||||
slice = append(slice, nil)
|
||||
return
|
||||
}
|
||||
panic("unknown")
|
||||
}
|
||||
})
|
||||
return slice
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
"k8s.io/helm/pkg/events"
|
||||
)
|
||||
|
||||
func luaFunc(l *lua.LState) int {
|
||||
println("PING")
|
||||
return 0
|
||||
}
|
||||
|
||||
var mockCtx = &events.Context{}
|
||||
|
||||
func TestEmitter_onWrapper(t *testing.T) {
|
||||
emitter := events.New()
|
||||
vm := lua.NewState()
|
||||
defer vm.Close()
|
||||
|
||||
lee := &Emitter{
|
||||
parent: emitter,
|
||||
vm: vm,
|
||||
}
|
||||
|
||||
lee.onWrapper("ping", vm.NewFunction(luaFunc))
|
||||
|
||||
assert.Nil(t, emitter.Emit("ping", mockCtx), "Expect no errors")
|
||||
}
|
||||
|
||||
const onScript = `
|
||||
local events = require("events")
|
||||
|
||||
events.on("ping", function(c) return "pong" end)
|
||||
events.on("ping", function(c) return "zoinks" end)
|
||||
events.on("other thing", function(c) return "hi" end)
|
||||
`
|
||||
|
||||
func TestEmitter_On(t *testing.T) {
|
||||
emitter := events.New()
|
||||
vm := lua.NewState()
|
||||
defer vm.Close()
|
||||
|
||||
lee := New(vm, emitter)
|
||||
assert.Nil(t, vm.DoString(onScript), "load script")
|
||||
|
||||
// Emit an event in the Go events system, and expect it to propagate through
|
||||
// the Lua system
|
||||
lee.parent.Emit("ping", mockCtx)
|
||||
|
||||
// This is a trivial check. We just make sure that the top of the stack is
|
||||
// the return value from calling "ping" event
|
||||
result := vm.CheckString(1)
|
||||
assert.Equal(t, "pong", result, "expect ping event to return pong")
|
||||
println(vm.CheckString(2))
|
||||
|
||||
}
|
||||
|
||||
const luaTableScript = `
|
||||
example = {
|
||||
key = "value",
|
||||
number = 123,
|
||||
boolean = true,
|
||||
nada = nil,
|
||||
inner = {
|
||||
name = "Matt"
|
||||
},
|
||||
list = { "one", "two" },
|
||||
}
|
||||
`
|
||||
|
||||
func TestTableToMap(t *testing.T) {
|
||||
vm := lua.NewState()
|
||||
vm.DoString(luaTableScript)
|
||||
table := vm.GetGlobal("example")
|
||||
res := tableToMap(table.(*lua.LTable))
|
||||
|
||||
if res["key"] != "value" {
|
||||
t.Error("Expected key to be value")
|
||||
}
|
||||
|
||||
if res["number"] != float64(123) {
|
||||
t.Errorf("Expected number to be a float64 123, got %+v", res["number"])
|
||||
}
|
||||
|
||||
if res["boolean"] != true {
|
||||
t.Errorf("Expected bool true")
|
||||
}
|
||||
|
||||
if res["nada"] != nil {
|
||||
t.Error("Expected nada to be nil")
|
||||
}
|
||||
|
||||
if res["inner"].(map[string]interface{})["name"] != "Matt" {
|
||||
t.Error("Expected inner.name to be Matt")
|
||||
}
|
||||
|
||||
list := []string{"one", "two"}
|
||||
for i, val := range res["list"].([]interface{}) {
|
||||
if val != list[i] {
|
||||
t.Error("Expected list item to be", list[i])
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("res: %+v", res)
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
hapi "k8s.io/helm/pkg/hapi/chart"
|
||||
)
|
||||
|
||||
// LoadScripts is a depth-first script loader for Lua scripts
|
||||
//
|
||||
// It walks the chart and all dependencies, loading the ext/lua/chart.lua
|
||||
// script for each chart.
|
||||
//
|
||||
// If a script fails to load, loading immediately ceases and the error is returned.
|
||||
func LoadScripts(vm *lua.LState, chart *hapi.Chart) error {
|
||||
// We go depth first so that the top level chart gets the final word.
|
||||
// That is, the top level chart should be able to modify objects that the
|
||||
// child charts set.
|
||||
for _, child := range chart.Dependencies {
|
||||
LoadScripts(vm, child)
|
||||
}
|
||||
// For now, we only load a `chart.lua`, since that is how other Lua impls
|
||||
// do it (e.g. single entrypoint, not multi).
|
||||
for _, script := range chart.Ext {
|
||||
target := filepath.Join("ext", "lua", "chart.lua")
|
||||
if script.Name == target {
|
||||
if err := vm.DoString(string(script.Data)); err != nil {
|
||||
return fmt.Errorf("failed to execute Lua for %s: %s", chart.Metadata.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
hapi "k8s.io/helm/pkg/hapi/chart"
|
||||
)
|
||||
|
||||
func TestLoadScripts(t *testing.T) {
|
||||
chart := &hapi.Chart{
|
||||
Metadata: &hapi.Metadata{
|
||||
Name: "starboard",
|
||||
},
|
||||
Ext: []*hapi.File{
|
||||
{
|
||||
Name: filepath.Join("ext", "lua", "chart.lua"),
|
||||
Data: []byte(`hello="world"; name="Ishmael"`),
|
||||
},
|
||||
{
|
||||
Name: filepath.Join("ext", "lua", "decoy.lua"),
|
||||
Data: []byte(`hello="goodbye"`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
outterChart := &hapi.Chart{
|
||||
Metadata: &hapi.Metadata{
|
||||
Name: "port",
|
||||
},
|
||||
Ext: []*hapi.File{
|
||||
{
|
||||
Name: filepath.Join("ext", "lua", "chart.lua"),
|
||||
Data: []byte(`hello="Nantucket"; goodbye ="Spouter"`),
|
||||
},
|
||||
{
|
||||
Name: filepath.Join("ext", "lua", "decoy.lua"),
|
||||
Data: []byte(`hello="goodbye"`),
|
||||
},
|
||||
},
|
||||
Dependencies: []*hapi.Chart{chart},
|
||||
}
|
||||
|
||||
// Simple test on a single chart
|
||||
vm := lua.NewState()
|
||||
LoadScripts(vm, chart)
|
||||
|
||||
world := vm.GetGlobal("hello").String()
|
||||
assert.Equal(t, world, "world", `expected hello="world"`)
|
||||
|
||||
// Test on a nested chart
|
||||
vm = lua.NewState()
|
||||
LoadScripts(vm, outterChart)
|
||||
|
||||
// This should override the child chart's value
|
||||
result := vm.GetGlobal("hello").String()
|
||||
assert.Equal(t, result, "Nantucket", `expected hello="Nantucket"`)
|
||||
|
||||
// This should be unchanged
|
||||
result = vm.GetGlobal("goodbye").String()
|
||||
assert.Equal(t, result, "Spouter", `expected goodbye="Spouter"`)
|
||||
|
||||
// This should come from the child chart
|
||||
result = vm.GetGlobal("name").String()
|
||||
assert.Equal(t, result, "Ishmael", `expected name="Ishmael"`)
|
||||
}
|
Loading…
Reference in new issue