Skip to content

Commit

Permalink
Initialize global event system in GlobalState, defer Exit emission ea…
Browse files Browse the repository at this point in the history
…rlier

This ensures that the Exit event is sent even in the case of an early
error, such as a script exception.

Resolves #3112 (comment)
  • Loading branch information
Ivan Mirić committed Jun 22, 2023
1 parent 846f12f commit d5897fa
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 56 deletions.
28 changes: 14 additions & 14 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,8 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) (err error) {
// from sub-contexts while also attaching a reason for the abort.
runCtx, runAbort := execution.NewTestRunContext(lingerCtx, logger)

test, err := loadAndConfigureTest(c.gs, cmd, args, getConfig)
if err != nil {
return err
}
if test.keyLogger != nil {
defer func() {
if klErr := test.keyLogger.Close(); klErr != nil {
logger.WithError(klErr).Warn("Error while closing the SSLKEYLOGFILE")
}
}()
}

emitEvent := func(evt *event.Event) func() {
waitDone := test.preInitState.Events.Emit(evt)
waitDone := c.gs.Events.Emit(evt)
return func() {
waitCtx, waitCancel := context.WithTimeout(globalCtx, waitEventDoneTimeout)
defer waitCancel()
Expand All @@ -101,9 +89,21 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) (err error) {
Type: event.Exit,
Data: &event.ExitData{Error: err},
})()
test.preInitState.Events.UnsubscribeAll()
c.gs.Events.UnsubscribeAll()
}()

test, err := loadAndConfigureTest(c.gs, cmd, args, getConfig)
if err != nil {
return err
}
if test.keyLogger != nil {
defer func() {
if klErr := test.keyLogger.Close(); klErr != nil {
logger.WithError(klErr).Warn("Error while closing the SSLKEYLOGFILE")
}
}()
}

// Write the full consolidated *and derived* options back to the Runner.
conf := test.derivedConfig
testRunState, err := test.buildTestRunState(conf.Options)
Expand Down
3 changes: 3 additions & 0 deletions cmd/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"

"go.k6.io/k6/event"
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/ui/console"
)
Expand Down Expand Up @@ -39,6 +40,7 @@ type GlobalState struct {
BinaryName string
CmdArgs []string
Env map[string]string
Events *event.System

DefaultFlags, Flags GlobalFlags

Expand Down Expand Up @@ -110,6 +112,7 @@ func NewGlobalState(ctx context.Context) *GlobalState {
BinaryName: filepath.Base(binary),
CmdArgs: os.Args,
Env: env,
Events: event.NewEventSystem(100, logger),
DefaultFlags: defaultFlags,
Flags: getFlags(defaultFlags, env),
OutMutex: outMutex,
Expand Down
3 changes: 1 addition & 2 deletions cmd/test_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"go.k6.io/k6/cmd/state"
"go.k6.io/k6/errext"
"go.k6.io/k6/errext/exitcodes"
"go.k6.io/k6/event"
"go.k6.io/k6/js"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/fsext"
Expand Down Expand Up @@ -71,7 +70,7 @@ func loadTest(gs *state.GlobalState, cmd *cobra.Command, args []string) (*loaded
RuntimeOptions: runtimeOptions,
Registry: registry,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry),
Events: event.NewEventSystem(100, gs.Logger),
Events: gs.Events,
LookupEnv: func(key string) (string, bool) {
val, ok := gs.Env[key]
return val, ok
Expand Down
131 changes: 91 additions & 40 deletions cmd/tests/cmd_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,55 +2062,106 @@ func TestEventSystemOK(t *testing.T) {
assert.Equal(t, expLog, log)
}

// Tests that the exit event is emitted with the test exit error.
func TestEventSystemAborted(t *testing.T) {
// Check emitted events in the case of a script error.
func TestEventSystemError(t *testing.T) {
t.Parallel()

ts := NewGlobalTestState(t)
testCases := []struct {
name, script string
expLog []string
expExitCode exitcodes.ExitCode
}{
{
name: "abort",
script: `
import { test } from 'k6/execution';
moduleName := fmt.Sprintf("k6/x/testevents-%d", atomic.AddUint64(&uniqueModuleNumber, 1))
mod := events.New(event.GlobalEvents, nil)
modules.Register(moduleName, mod)
export let options = {
vus: 1,
iterations: 5,
}
export default function () {
test.abort('oops!');
}
`, expLog: []string{
"got event Init with data '<nil>'",
"got event TestStart with data '<nil>'",
"got event IterStart with data '{Iteration:0 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:test aborted: oops! at file:///-:11:16(6)}'",
"got event TestEnd with data '<nil>'",
"got event Exit with data '&{Error:test aborted: oops! at file:///-:11:16(6)}'",
"test aborted: oops! at file:///-:11:16(6)",
},
expExitCode: exitcodes.ScriptAborted,
},
{
name: "init",
script: "undefinedVar",
expLog: []string{
"got event Exit with data '&{Error:could not initialize '-': could not load JS test " +
"'file:///-': ReferenceError: undefinedVar is not defined\n\tat file:///-:2:0(12)\n}'",
"ReferenceError: undefinedVar is not defined\n\tat file:///-:2:0(12)\n",
},
expExitCode: exitcodes.ScriptException,
},
{
name: "throw",
script: `
export let options = {
vus: 1,
iterations: 2,
}
export default function () {
throw new Error('oops!');
}
`, expLog: []string{
"got event Init with data '<nil>'",
"got event TestStart with data '<nil>'",
"got event IterStart with data '{Iteration:0 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:0 VUID:1 ScenarioName:default Error:Error: oops!\n\tat file:///-:9:11(3)\n}'",
"Error: oops!\n\tat file:///-:9:11(3)\n",
"got event IterStart with data '{Iteration:1 VUID:1 ScenarioName:default Error:<nil>}'",
"got event IterEnd with data '{Iteration:1 VUID:1 ScenarioName:default Error:Error: oops!\n\tat file:///-:9:11(3)\n}'",
"Error: oops!\n\tat file:///-:9:11(3)\n",
"got event TestEnd with data '<nil>'",
"got event Exit with data '&{Error:<nil>}'",
},
expExitCode: 0,
},
}

ts.CmdArgs = []string{"k6", "--quiet", "run", "-"}
ts.ExpectedExitCode = int(exitcodes.ScriptAborted)
ts.Stdin = bytes.NewBuffer([]byte(fmt.Sprintf(`
import events from '%s';
import { test } from 'k6/execution';
import { sleep } from 'k6';
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ts := NewGlobalTestState(t)

export let options = {
vus: 5,
duration: '5s',
}
moduleName := fmt.Sprintf("k6/x/testevents-%d", atomic.AddUint64(&uniqueModuleNumber, 1))
mod := events.New(event.GlobalEvents, event.VUEvents)
modules.Register(moduleName, mod)

export default function () {
sleep(1);
test.abort('oops!');
}
`, moduleName)))
ts.CmdArgs = []string{"k6", "--quiet", "run", "-"}
ts.ExpectedExitCode = int(tc.expExitCode)
ts.Stdin = bytes.NewBuffer([]byte(fmt.Sprintf("import events from '%s';\n%s", moduleName, tc.script)))

cmd.ExecuteWithGlobalState(ts.GlobalState)
cmd.ExecuteWithGlobalState(ts.GlobalState)

doneCh := make(chan struct{})
go func() {
mod.WG.Wait()
close(doneCh)
}()
doneCh := make(chan struct{})
go func() {
mod.WG.Wait()
close(doneCh)
}()

select {
case <-doneCh:
case <-time.After(time.Second):
t.Fatal("timed out")
}
select {
case <-doneCh:
case <-time.After(time.Second):
t.Fatal("timed out")
}

expLog := []string{
`got event Init with data '<nil>'`,
`got event TestStart with data '<nil>'`,
`got event TestEnd with data '<nil>'`,
`got event Exit with data '&{Error:test aborted: oops! at file:///-:13:14(12)}'`,
`test aborted: oops! at file:///-:13:14(12)`,
log := ts.LoggerHook.Lines()
assert.Equal(t, tc.expLog, log)
})
}
log := ts.LoggerHook.Lines()
assert.Equal(t, expLog, log)
}
2 changes: 2 additions & 0 deletions cmd/tests/test_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.k6.io/k6/cmd/state"
"go.k6.io/k6/event"
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/ui/console"
Expand Down Expand Up @@ -90,6 +91,7 @@ func NewGlobalTestState(tb testing.TB) *GlobalTestState {
BinaryName: "k6",
CmdArgs: []string{},
Env: map[string]string{"K6_NO_USAGE_REPORT": "true"},
Events: event.NewEventSystem(100, logger),
DefaultFlags: defaultFlags,
Flags: defaultFlags,
OutMutex: outMutex,
Expand Down

0 comments on commit d5897fa

Please sign in to comment.