add vendoring with go dep

This commit is contained in:
Adrian Todorov
2017-10-25 20:52:40 +00:00
parent 704f4d20d1
commit a59409f16b
1627 changed files with 489673 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
package statement
import (
"time"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// ExecStatement run outside scripts. This functionality is not built out
// TODO: Wire up!
type ExecStatement struct {
StatementID string
Script string
runtime time.Duration
}
// SetID statisfies the Statement Interface
func (i *ExecStatement) SetID(s string) {
i.StatementID = s
}
// Run statisfies the Statement Interface
func (i *ExecStatement) Run(s *stressClient.StressTest) {
runtime := time.Now()
i.runtime = time.Since(runtime)
}
// Report statisfies the Statement Interface
func (i *ExecStatement) Report(s *stressClient.StressTest) string {
return ""
}

View File

@@ -0,0 +1,41 @@
package statement
import (
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestExecSetID(t *testing.T) {
e := newTestExec()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestExecRun(t *testing.T) {
e := newTestExec()
s, _, _ := stressClient.NewTestStressTest()
e.Run(s)
if e == nil {
t.Fail()
}
}
func TestExecReport(t *testing.T) {
e := newTestExec()
s, _, _ := stressClient.NewTestStressTest()
rep := e.Report(s)
if rep != "" {
t.Fail()
}
}
func newTestExec() *ExecStatement {
return &ExecStatement{
StatementID: "fooID",
Script: "fooscript.txt",
}
}

View File

@@ -0,0 +1,176 @@
package statement
import (
crypto "crypto/rand"
"fmt"
"math/rand"
)
// ################
// # Function #
// ################
// Function is a struct that holds information for generating values in templated points
type Function struct {
Type string
Fn string
Argument int
Count int
}
// NewStringer creates a new Stringer
func (f *Function) NewStringer(series int) Stringer {
var fn Stringer
switch f.Type {
case "int":
fn = NewIntFunc(f.Fn, f.Argument)
case "float":
fn = NewFloatFunc(f.Fn, f.Argument)
case "str":
fn = NewStrFunc(f.Fn, f.Argument)
default:
fn = func() string { return "STRINGER ERROR" }
}
if int(f.Count) != 0 {
return cycle(f.Count, fn)
}
return nTimes(series, fn)
}
// ################
// # Stringers #
// ################
// Stringers is a collection of Stringer
type Stringers []Stringer
// Eval returns an array of all the Stringer functions evaluated once
func (s Stringers) Eval(time func() int64) []interface{} {
arr := make([]interface{}, len(s)+1)
for i, st := range s {
arr[i] = st()
}
arr[len(s)] = time()
return arr
}
// Stringer is a function that returns a string
type Stringer func() string
func randStr(n int) func() string {
return func() string {
b := make([]byte, n/2)
_, _ = crypto.Read(b)
return fmt.Sprintf("%x", b)
}
}
// NewStrFunc reates a new striger to create strings for templated writes
func NewStrFunc(fn string, arg int) Stringer {
switch fn {
case "rand":
return randStr(arg)
default:
return func() string { return "STR ERROR" }
}
}
func randFloat(n int) func() string {
return func() string {
return fmt.Sprintf("%v", rand.Intn(n))
}
}
func incFloat(n int) func() string {
i := n
return func() string {
s := fmt.Sprintf("%v", i)
i++
return s
}
}
// NewFloatFunc reates a new striger to create float values for templated writes
func NewFloatFunc(fn string, arg int) Stringer {
switch fn {
case "rand":
return randFloat(arg)
case "inc":
return incFloat(arg)
default:
return func() string { return "FLOAT ERROR" }
}
}
func randInt(n int) Stringer {
return func() string {
return fmt.Sprintf("%vi", rand.Intn(n))
}
}
func incInt(n int) Stringer {
i := n
return func() string {
s := fmt.Sprintf("%vi", i)
i++
return s
}
}
// NewIntFunc reates a new striger to create int values for templated writes
func NewIntFunc(fn string, arg int) Stringer {
switch fn {
case "rand":
return randInt(arg)
case "inc":
return incInt(arg)
default:
return func() string { return "INT ERROR" }
}
}
// nTimes will return the previous return value of a function
// n-many times before calling the function again
func nTimes(n int, fn Stringer) Stringer {
i := 0
t := fn()
return func() string {
i++
if i > n {
t = fn()
i = 1
}
return t
}
}
// cycle will cycle through a list of values before repeating them
func cycle(n int, fn Stringer) Stringer {
if n == 0 {
return fn
}
i := 0
cache := make([]string, n)
t := fn()
cache[i] = t
return func() string {
i++
if i < n {
cache[i] = fn()
}
t = cache[(i-1)%n]
return t
}
}

View File

@@ -0,0 +1,143 @@
package statement
import (
"testing"
)
func TestNewStrRandStringer(t *testing.T) {
function := newStrRandFunction()
strRandStringer := function.NewStringer(10)
s := strRandStringer()
if len(s) != function.Argument {
t.Errorf("Expected: %v\nGot: %v\n", function.Argument, len(s))
}
}
func TestNewIntIncStringer(t *testing.T) {
function := newIntIncFunction()
intIncStringer := function.NewStringer(10)
s := intIncStringer()
if s != "0i" {
t.Errorf("Expected: 0i\nGot: %v\n", s)
}
}
func TestNewIntRandStringer(t *testing.T) {
function := newIntRandFunction()
intRandStringer := function.NewStringer(10)
s := intRandStringer()
if parseInt(s[:len(s)-1]) > function.Argument {
t.Errorf("Expected value below: %v\nGot value: %v\n", function.Argument, s)
}
}
func TestNewFloatIncStringer(t *testing.T) {
function := newFloatIncFunction()
floatIncStringer := function.NewStringer(10)
s := floatIncStringer()
if parseFloat(s) != function.Argument {
t.Errorf("Expected value: %v\nGot: %v\n", function.Argument, s)
}
}
func TestNewFloatRandStringer(t *testing.T) {
function := newFloatRandFunction()
floatRandStringer := function.NewStringer(10)
s := floatRandStringer()
if parseFloat(s) > function.Argument {
t.Errorf("Expected value below: %v\nGot value: %v\n", function.Argument, s)
}
}
func TestStringersEval(t *testing.T) {
// Make the *Function(s)
strRandFunction := newStrRandFunction()
intIncFunction := newIntIncFunction()
intRandFunction := newIntRandFunction()
floatIncFunction := newFloatIncFunction()
floatRandFunction := newFloatRandFunction()
// Make the *Stringer(s)
strRandStringer := strRandFunction.NewStringer(10)
intIncStringer := intIncFunction.NewStringer(10)
intRandStringer := intRandFunction.NewStringer(10)
floatIncStringer := floatIncFunction.NewStringer(10)
floatRandStringer := floatRandFunction.NewStringer(10)
// Make the *Stringers
stringers := Stringers([]Stringer{strRandStringer, intIncStringer, intRandStringer, floatIncStringer, floatRandStringer})
// Spoff the Time function
// Call *Stringers.Eval
values := stringers.Eval(spoofTime)
// Check the strRandFunction
if len(values[0].(string)) != strRandFunction.Argument {
t.Errorf("Expected: %v\nGot: %v\n", strRandFunction.Argument, len(values[0].(string)))
}
// Check the intIncFunction
if values[1].(string) != "0i" {
t.Errorf("Expected: 0i\nGot: %v\n", values[1].(string))
}
// Check the intRandFunction
s := values[2].(string)
if parseInt(s[:len(s)-1]) > intRandFunction.Argument {
t.Errorf("Expected value below: %v\nGot value: %v\n", intRandFunction.Argument, s)
}
// Check the floatIncFunction
if parseFloat(values[3].(string)) != floatIncFunction.Argument {
t.Errorf("Expected value: %v\nGot: %v\n", floatIncFunction.Argument, values[3])
}
// Check the floatRandFunction
if parseFloat(values[4].(string)) > floatRandFunction.Argument {
t.Errorf("Expected value below: %v\nGot value: %v\n", floatRandFunction.Argument, values[4])
}
// Check the spoofTime func
if values[5] != 8 {
}
}
func spoofTime() int64 {
return int64(8)
}
func newStrRandFunction() *Function {
return &Function{
Type: "str",
Fn: "rand",
Argument: 8,
Count: 1000,
}
}
func newIntIncFunction() *Function {
return &Function{
Type: "int",
Fn: "inc",
Argument: 0,
Count: 0,
}
}
func newIntRandFunction() *Function {
return &Function{
Type: "int",
Fn: "rand",
Argument: 100,
Count: 1000,
}
}
func newFloatIncFunction() *Function {
return &Function{
Type: "float",
Fn: "inc",
Argument: 0,
Count: 1000,
}
}
func newFloatRandFunction() *Function {
return &Function{
Type: "float",
Fn: "rand",
Argument: 100,
Count: 1000,
}
}

View File

@@ -0,0 +1,40 @@
package statement
import (
"fmt"
"time"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// GoStatement is a Statement Implementation to allow other statements to be run concurrently
type GoStatement struct {
Statement
StatementID string
}
// SetID statisfies the Statement Interface
func (i *GoStatement) SetID(s string) {
i.StatementID = s
}
// Run statisfies the Statement Interface
func (i *GoStatement) Run(s *stressClient.StressTest) {
// TODO: remove
switch i.Statement.(type) {
case *QueryStatement:
time.Sleep(1 * time.Second)
}
s.Add(1)
go func() {
i.Statement.Run(s)
s.Done()
}()
}
// Report statisfies the Statement Interface
func (i *GoStatement) Report(s *stressClient.StressTest) string {
return fmt.Sprintf("Go %v", i.Statement.Report(s))
}

View File

@@ -0,0 +1,41 @@
package statement
import (
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestGoSetID(t *testing.T) {
e := newTestGo()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestGoRun(t *testing.T) {
e := newTestGo()
s, _, _ := stressClient.NewTestStressTest()
e.Run(s)
if e == nil {
t.Fail()
}
}
func TestGoReport(t *testing.T) {
e := newTestGo()
s, _, _ := stressClient.NewTestStressTest()
report := e.Report(s)
if report != "Go " {
t.Errorf("Expected: %v\nGot: %v\n", "Go ", report)
}
}
func newTestGo() *GoStatement {
return &GoStatement{
Statement: newTestExec(),
StatementID: "fooID",
}
}

View File

@@ -0,0 +1,69 @@
package statement
import (
"log"
"time"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// InfluxqlStatement is a Statement Implementation that allows statements that parse in InfluxQL to be passed directly to the target instance
type InfluxqlStatement struct {
StatementID string
Query string
Tracer *stressClient.Tracer
}
func (i *InfluxqlStatement) tags() map[string]string {
tags := make(map[string]string)
return tags
}
// SetID statisfies the Statement Interface
func (i *InfluxqlStatement) SetID(s string) {
i.StatementID = s
}
// Run statisfies the Statement Interface
func (i *InfluxqlStatement) Run(s *stressClient.StressTest) {
// Set the tracer
i.Tracer = stressClient.NewTracer(i.tags())
// Make the Package
p := stressClient.NewPackage(stressClient.Query, []byte(i.Query), i.StatementID, i.Tracer)
// Increment the tracer
i.Tracer.Add(1)
// Send the Package
s.SendPackage(p)
// Wait for all operations to finish
i.Tracer.Wait()
}
// Report statisfies the Statement Interface
// No test coverage, fix
func (i *InfluxqlStatement) Report(s *stressClient.StressTest) (out string) {
allData := s.GetStatementResults(i.StatementID, "query")
iqlr := &influxQlReport{
statement: i.Query,
columns: allData[0].Series[0].Columns,
values: allData[0].Series[0].Values,
}
iqlr.responseTime = time.Duration(responseTimes(iqlr.columns, iqlr.values)[0].Value)
switch countSuccesses(iqlr.columns, iqlr.values) {
case 0:
iqlr.success = false
case 1:
iqlr.success = true
default:
log.Fatal("Error fetching response for InfluxQL statement")
}
return iqlr.String()
}

View File

@@ -0,0 +1,44 @@
package statement
import (
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestInfluxQlSetID(t *testing.T) {
e := newTestInfluxQl()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestInfluxQlRun(t *testing.T) {
e := newTestInfluxQl()
s, packageCh, _ := stressClient.NewTestStressTest()
go func() {
for pkg := range packageCh {
if pkg.T != stressClient.Query {
t.Errorf("Expected package to be Query\nGot: %v", pkg.T)
}
if string(pkg.Body) != e.Query {
t.Errorf("Expected query: %v\nGot: %v", e.Query, string(pkg.Body))
}
if pkg.StatementID != e.StatementID {
t.Errorf("Expected statementID: %v\nGot: %v", e.StatementID, pkg.StatementID)
}
pkg.Tracer.Done()
}
}()
e.Run(s)
}
func newTestInfluxQl() *InfluxqlStatement {
return &InfluxqlStatement{
Query: "CREATE DATABASE foo",
Tracer: stressClient.NewTracer(make(map[string]string)),
StatementID: "fooID",
}
}

View File

@@ -0,0 +1,214 @@
package statement
import (
"bytes"
"fmt"
"log"
"strconv"
"strings"
"sync"
"time"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// InsertStatement is a Statement Implementation that creates points to be written to the target InfluxDB instance
type InsertStatement struct {
TestID string
StatementID string
// Statement Name
Name string
// Template string for points. Filled by the output of stringers
TemplateString string
// TagCount is used to find the number of series in the dataset
TagCount int
// The Tracer prevents InsertStatement.Run() from returning early
Tracer *stressClient.Tracer
// Timestamp is #points to write and percision
Timestamp *Timestamp
// Templates turn into stringers
Templates Templates
stringers Stringers
// Number of series in this insert Statement
series int
// Returns the proper time for the next point
time func() int64
// Concurrency utiliities
sync.WaitGroup
sync.Mutex
// Timer for runtime and pps calculation
runtime time.Duration
}
func (i *InsertStatement) tags() map[string]string {
tags := map[string]string{
"number_fields": i.numFields(),
"number_series": fmtInt(i.series),
"number_points_write": fmtInt(i.Timestamp.Count),
}
return tags
}
// SetID statisfies the Statement Interface
func (i *InsertStatement) SetID(s string) {
i.StatementID = s
}
// SetVars sets up the environment for InsertStatement to call it's Run function
func (i *InsertStatement) SetVars(s *stressClient.StressTest) chan<- string {
// Set the #series at 1 to start
i.series = 1
// Num series is the product of the cardinality of the tags
for _, tmpl := range i.Templates[0:i.TagCount] {
i.series *= tmpl.numSeries()
}
// make stringers from the templates
i.stringers = i.Templates.Init(i.series)
// Set the time function, keeps track of 'time' of the points being created
i.time = i.Timestamp.Time(s.StartDate, i.series, s.Precision)
// Set a commune on the StressTest
s.Lock()
comCh := s.SetCommune(i.Name)
s.Unlock()
// Set the tracer
i.Tracer = stressClient.NewTracer(i.tags())
return comCh
}
// Run statisfies the Statement Interface
func (i *InsertStatement) Run(s *stressClient.StressTest) {
// Set variables on the InsertStatement and make the comCh
comCh := i.SetVars(s)
// TODO: Refactor to eleminate the ctr
// Start the counter
ctr := 0
// Create the first bytes buffer
buf := bytes.NewBuffer([]byte{})
runtime := time.Now()
for k := 0; k < i.Timestamp.Count; k++ {
// Increment the counter. ctr == k + 1?
ctr++
// Make the point from the template string and the stringers
point := fmt.Sprintf(i.TemplateString, i.stringers.Eval(i.time)...)
// Add the string to the buffer
buf.WriteString(point)
// Add a newline char to seperate the points
buf.WriteString("\n")
// If len(batch) == batchSize then send it
if ctr%s.BatchSize == 0 && ctr != 0 {
b := buf.Bytes()
// Trimming the trailing newline character
b = b[0 : len(b)-1]
// Create the package
p := stressClient.NewPackage(stressClient.Write, b, i.StatementID, i.Tracer)
// Use Tracer to wait for all operations to finish
i.Tracer.Add(1)
// Send the package
s.SendPackage(p)
// Reset the bytes Buffer
temp := bytes.NewBuffer([]byte{})
buf = temp
}
// TODO: Racy
// Has to do with InsertStatement and QueryStatement communication
if len(comCh) < cap(comCh) {
select {
case comCh <- point:
break
default:
break
}
}
}
// If There are additional points remaining in the buffer send them before exiting
if buf.Len() != 0 {
b := buf.Bytes()
// Trimming the trailing newline character
b = b[0 : len(b)-1]
// Create the package
p := stressClient.NewPackage(stressClient.Write, b, i.StatementID, i.Tracer)
// Use Tracer to wait for all operations to finish
i.Tracer.Add(1)
// Send the package
s.SendPackage(p)
}
// Wait for all tracers to decrement
i.Tracer.Wait()
// Stop the timer
i.runtime = time.Since(runtime)
}
// Report statisfies the Statement Interface
func (i *InsertStatement) Report(s *stressClient.StressTest) string {
// Pull data via StressTest client
allData := s.GetStatementResults(i.StatementID, "write")
if allData == nil || allData[0].Series == nil {
log.Fatalf("No data returned for write report\n Statement Name: %v\n Statement ID: %v\n", i.Name, i.StatementID)
}
ir := &insertReport{
name: i.Name,
columns: allData[0].Series[0].Columns,
values: allData[0].Series[0].Values,
}
responseTimes := responseTimes(ir.columns, ir.values)
ir.percentile = percentile(responseTimes)
ir.avgResponseTime = avgDuration(responseTimes)
ir.stdDevResponseTime = stddevDuration(responseTimes)
ir.pointsPerSecond = int(float64(i.Timestamp.Count) / i.runtime.Seconds())
ir.numRetries = countRetries(ir.columns, ir.values)
ir.successfulWrites = countSuccesses(ir.columns, ir.values)
ir.avgRequestBytes = numberBytes(ir.columns, ir.values)
return ir.String()
}
func (i *InsertStatement) numFields() string {
pt := strings.Split(i.TemplateString, " ")
fields := strings.Split(pt[1], ",")
return fmtInt(len(fields))
}
func fmtInt(i int) string {
return strconv.FormatInt(int64(i), 10)
}

View File

@@ -0,0 +1,50 @@
package statement
import (
"strings"
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestInsertSetID(t *testing.T) {
e := newTestInsert()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestInsertRun(t *testing.T) {
i := newTestInsert()
s, packageCh, _ := stressClient.NewTestStressTest()
// Listen to the other side of the directiveCh
go func() {
for pkg := range packageCh {
countPoints := i.Timestamp.Count
batchSize := s.BatchSize
got := len(strings.Split(string(pkg.Body), "\n"))
switch got {
case countPoints % batchSize:
case batchSize:
default:
t.Errorf("countPoints: %v\nbatchSize: %v\ngot: %v\n", countPoints, batchSize, got)
}
pkg.Tracer.Done()
}
}()
i.Run(s)
}
func newTestInsert() *InsertStatement {
return &InsertStatement{
TestID: "foo_test",
StatementID: "foo_ID",
Name: "foo_name",
TemplateString: "cpu,%v %v %v",
Timestamp: newTestTimestamp(),
Templates: newTestTemplates(),
TagCount: 1,
}
}

View File

@@ -0,0 +1,161 @@
package statement
import (
"fmt"
"log"
"time"
"github.com/influxdata/influxdb/models"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// QueryStatement is a Statement Implementation to run queries on the target InfluxDB instance
type QueryStatement struct {
StatementID string
Name string
// TemplateString is a query template that can be filled in by Args
TemplateString string
Args []string
// Number of queries to run
Count int
// Tracer for tracking returns
Tracer *stressClient.Tracer
// track time for all queries
runtime time.Duration
}
// This function adds tags to the recording points
func (i *QueryStatement) tags() map[string]string {
tags := make(map[string]string)
return tags
}
// SetID statisfies the Statement Interface
func (i *QueryStatement) SetID(s string) {
i.StatementID = s
}
// Run statisfies the Statement Interface
func (i *QueryStatement) Run(s *stressClient.StressTest) {
i.Tracer = stressClient.NewTracer(i.tags())
vals := make(map[string]interface{})
var point models.Point
runtime := time.Now()
for j := 0; j < i.Count; j++ {
// If the query is a simple query, send it.
if len(i.Args) == 0 {
b := []byte(i.TemplateString)
// Make the package
p := stressClient.NewPackage(stressClient.Query, b, i.StatementID, i.Tracer)
// Increment the tracer
i.Tracer.Add(1)
// Send the package
s.SendPackage(p)
} else {
// Otherwise cherry pick field values from the commune?
// TODO: Currently the program lock up here if s.GetPoint
// cannot return a value, which can happen.
// See insert.go
s.Lock()
point = s.GetPoint(i.Name, s.Precision)
s.Unlock()
setMapValues(vals, point)
// Set the template string with args from the commune
b := []byte(fmt.Sprintf(i.TemplateString, setArgs(vals, i.Args)...))
// Make the package
p := stressClient.NewPackage(stressClient.Query, b, i.StatementID, i.Tracer)
// Increment the tracer
i.Tracer.Add(1)
// Send the package
s.SendPackage(p)
}
}
// Wait for all operations to finish
i.Tracer.Wait()
// Stop time timer
i.runtime = time.Since(runtime)
}
// Report statisfies the Statement Interface
func (i *QueryStatement) Report(s *stressClient.StressTest) string {
// Pull data via StressTest client
allData := s.GetStatementResults(i.StatementID, "query")
if len(allData) == 0 || allData[0].Series == nil {
log.Fatalf("No data returned for query report\n Statement Name: %v\n Statement ID: %v\n", i.Name, i.StatementID)
}
qr := &queryReport{
name: i.Name,
columns: allData[0].Series[0].Columns,
values: allData[0].Series[0].Values,
}
responseTimes := responseTimes(qr.columns, qr.values)
qr.percentile = percentile(responseTimes)
qr.avgResponseTime = avgDuration(responseTimes)
qr.stdDevResponseTime = stddevDuration(responseTimes)
qr.successfulReads = countSuccesses(qr.columns, qr.values)
qr.responseBytes = numberBytes(qr.columns, qr.values)
return qr.String()
}
func getRandomTagPair(m models.Tags) string {
for k, v := range m {
return fmt.Sprintf("%v='%v'", k, v)
}
return ""
}
func getRandomFieldKey(m map[string]interface{}) string {
for k := range m {
return fmt.Sprintf("%v", k)
}
return ""
}
func setMapValues(m map[string]interface{}, p models.Point) {
fields, err := p.Fields()
if err != nil {
panic(err)
}
m["%f"] = getRandomFieldKey(fields)
m["%m"] = string(p.Name())
m["%t"] = getRandomTagPair(p.Tags())
m["%a"] = p.UnixNano()
}
func setArgs(m map[string]interface{}, args []string) []interface{} {
values := make([]interface{}, len(args))
for i, arg := range args {
values[i] = m[arg]
}
return values
}

View File

@@ -0,0 +1,42 @@
package statement
import (
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestQuerySetID(t *testing.T) {
e := newTestQuery()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestQueryRun(t *testing.T) {
i := newTestQuery()
s, packageCh, _ := stressClient.NewTestStressTest()
// Listen to the other side of the directiveCh
go func() {
for pkg := range packageCh {
if i.TemplateString != string(pkg.Body) {
t.Fail()
}
pkg.Tracer.Done()
}
}()
i.Run(s)
}
func newTestQuery() *QueryStatement {
return &QueryStatement{
StatementID: "foo_ID",
Name: "foo_name",
TemplateString: "SELECT count(value) FROM cpu",
Args: []string{},
Count: 5,
Tracer: stressClient.NewTracer(map[string]string{}),
}
}

View File

@@ -0,0 +1,237 @@
package statement
import (
"encoding/json"
"fmt"
"log"
"math"
"sort"
"time"
influx "github.com/influxdata/influxdb/client/v2"
)
// TODO: Refactor this file to utilize a common interface
// This will make adding new reports easier in the future
// Runs performance numbers for insert statements
type insertReport struct {
name string
numRetries int
pointsPerSecond int
successfulWrites int
avgRequestBytes int
avgResponseTime time.Duration
stdDevResponseTime time.Duration
percentile time.Duration
columns []string
values [][]interface{}
}
// Returns the version of the report that is output to STDOUT
func (ir *insertReport) String() string {
tmplString := `Write Statement: %v
Points/Sec: %v
Resp Time Average: %v
Resp Time Standard Deviation: %v
95th Percentile Write Response: %v
Average Request Bytes: %v
Successful Write Reqs: %v
Retries: %v`
return fmt.Sprintf(tmplString,
ir.name,
ir.pointsPerSecond,
ir.avgResponseTime,
ir.stdDevResponseTime,
ir.percentile,
ir.avgRequestBytes,
ir.successfulWrites,
ir.numRetries)
}
// Returns a point representation of the report to be written to the ResultsDB
func (ir *insertReport) Point() *influx.Point {
measurement := "testDefault"
tags := map[string]string{}
fields := map[string]interface{}{"field": "blank"}
point, err := influx.NewPoint(measurement, tags, fields, time.Now())
if err != nil {
log.Fatalf("Error creating insertReport point\n measurement: %v\n tags: %v\n fields: %v\n error: %v\n", measurement, tags, fields, err)
}
return point
}
// Runs performance numbers for query statements
type queryReport struct {
name string
successfulReads int
responseBytes int
stddevResponseBytes int
avgResponseTime time.Duration
stdDevResponseTime time.Duration
percentile time.Duration
columns []string
values [][]interface{}
}
// Returns the version of the report that is output to STDOUT
func (qr *queryReport) String() string {
tmplString := `Query Statement: %v
Resp Time Average: %v
Resp Time Standard Deviation: %v
95th Percentile Read Response: %v
Query Resp Bytes Average: %v bytes
Successful Queries: %v`
return fmt.Sprintf(tmplString,
qr.name,
qr.avgResponseTime,
qr.stdDevResponseTime,
qr.percentile,
qr.responseBytes,
qr.successfulReads)
}
// Returns a point representation of the report to be written to the ResultsDB
func (qr *queryReport) Point() *influx.Point {
measurement := "testDefault"
tags := map[string]string{}
fields := map[string]interface{}{"field": "blank"}
point, err := influx.NewPoint(measurement, tags, fields, time.Now())
if err != nil {
log.Fatalf("Error creating queryReport point\n measurement: %v\n tags: %v\n fields: %v\n error: %v\n", measurement, tags, fields, err)
}
return point
}
// Runs performance numbers for InfluxQL statements
type influxQlReport struct {
statement string
responseTime time.Duration
success bool
columns []string
values [][]interface{}
}
// Returns the version of the report that is output to STDOUT
func (iqlr *influxQlReport) String() string {
// Fancy format success
var success string
switch iqlr.success {
case true:
success = "[√]"
case false:
success = "[X]"
}
return fmt.Sprintf("%v '%v' -> %v", success, iqlr.statement, iqlr.responseTime)
}
// Returns a point representation of the report to be written to the ResultsDB
func (iqlr *influxQlReport) Point() *influx.Point {
measurement := "testDefault"
tags := map[string]string{}
fields := map[string]interface{}{"field": "blank"}
point, err := influx.NewPoint(measurement, tags, fields, time.Now())
if err != nil {
log.Fatalf("Error creating influxQL point\n measurement: %v\n tags: %v\n fields: %v\n error: %v\n", measurement, tags, fields, err)
}
return point
}
// Given a field or tag name this function returns the index where the values are found
func getColumnIndex(col string, columns []string) int {
index := -1
for i, column := range columns {
if column == col {
index = i
}
}
return index
}
// Given a full set of results pulls the average num_bytes
func numberBytes(columns []string, values [][]interface{}) int {
out := 0
index := getColumnIndex("num_bytes", columns)
for _, val := range values {
reqBytes, err := val[index].(json.Number).Int64()
if err != nil {
log.Fatalf("Error coercing json.Number to Int64\n json.Number:%v\n error: %v\n", val[index], err)
}
out += int(reqBytes)
}
return out / len(values)
}
// Counts the number of 200(query) or 204(write) responses and returns them
func countSuccesses(columns []string, values [][]interface{}) (out int) {
index := getColumnIndex("status_code", columns)
for _, val := range values {
status, err := val[index].(json.Number).Int64()
if err != nil {
log.Fatalf("Error coercing json.Number to Int64\n json.Number:%v\n error: %v\n", val[index], err)
}
if status == 204 || status == 200 {
out++
}
}
return out
}
// Counts number of 500 status codes
func countRetries(columns []string, values [][]interface{}) (out int) {
index := getColumnIndex("status_code", columns)
for _, val := range values {
status, err := val[index].(json.Number).Int64()
if err != nil {
log.Fatalf("Error coercing json.Number to Int64\n json.Number:%v\n error: %v\n", val[index], err)
}
if status == 500 {
out++
}
}
return out
}
// Pulls out the response_time_ns values and formats them into ResponseTimes for reporting
func responseTimes(columns []string, values [][]interface{}) (rs ResponseTimes) {
rs = make([]ResponseTime, 0)
index := getColumnIndex("response_time_ns", columns)
for _, val := range values {
respTime, err := val[index].(json.Number).Int64()
if err != nil {
log.Fatalf("Error coercing json.Number to Int64\n json.Number:%v\n error: %v\n", val[index], err)
}
rs = append(rs, NewResponseTime(int(respTime)))
}
return rs
}
// Returns the 95th perecntile response time
func percentile(rs ResponseTimes) time.Duration {
sort.Sort(rs)
return time.Duration(rs[(len(rs) * 19 / 20)].Value)
}
// Returns the average response time
func avgDuration(rs ResponseTimes) (out time.Duration) {
for _, t := range rs {
out += time.Duration(t.Value)
}
return out / time.Duration(len(rs))
}
// Returns the standard deviation of a sample of response times
func stddevDuration(rs ResponseTimes) (out time.Duration) {
avg := avgDuration(rs)
for _, t := range rs {
out += (avg - time.Duration(t.Value)) * (avg - time.Duration(t.Value))
}
return time.Duration(int64(math.Sqrt(float64(out) / float64(len(rs)))))
}

View File

@@ -0,0 +1,210 @@
package statement
import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"
)
func TestInsertReportString(t *testing.T) {
ir := newTestInsertReport()
tmplString := `Write Statement: %v
Points/Sec: %v
Resp Time Average: %v
Resp Time Standard Deviation: %v
95th Percentile Write Response: %v
Average Request Bytes: %v
Successful Write Reqs: %v
Retries: %v`
expected := fmt.Sprintf(tmplString,
ir.name,
ir.pointsPerSecond,
ir.avgResponseTime,
ir.stdDevResponseTime,
ir.percentile,
ir.avgRequestBytes,
ir.successfulWrites,
ir.numRetries)
got := ir.String()
if expected != got {
t.Fail()
}
}
func TestInsertReportPoint(t *testing.T) {
ir := newTestInsertReport()
expected := "testDefault"
got := strings.Split(ir.Point().String(), " ")[0]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestQueryReportString(t *testing.T) {
qr := newTestQueryReport()
tmplString := `Query Statement: %v
Resp Time Average: %v
Resp Time Standard Deviation: %v
95th Percentile Read Response: %v
Query Resp Bytes Average: %v bytes
Successful Queries: %v`
expected := fmt.Sprintf(tmplString,
qr.name,
qr.avgResponseTime,
qr.stdDevResponseTime,
qr.percentile,
qr.responseBytes,
qr.successfulReads)
got := qr.String()
if expected != got {
t.Fail()
}
}
func TestQueryReportPoint(t *testing.T) {
qr := newTestQueryReport()
expected := "testDefault"
got := strings.Split(qr.Point().String(), " ")[0]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestInfluxQLReportString(t *testing.T) {
iqlr := newTestInfluxQLReport()
expected := fmt.Sprintf("[X] '%v' -> %v", iqlr.statement, iqlr.responseTime)
got := iqlr.String()
if expected != got {
t.Fail()
}
}
func TestInfluxQLReportPoint(t *testing.T) {
iqlr := newTestInfluxQLReport()
expected := "testDefault"
got := strings.Split(iqlr.Point().String(), " ")[0]
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func newTestInsertReport() *insertReport {
return &insertReport{
name: "foo_name",
numRetries: 0,
pointsPerSecond: 500000,
successfulWrites: 20000,
avgRequestBytes: 18932,
avgResponseTime: time.Duration(int64(20000)),
stdDevResponseTime: time.Duration(int64(20000)),
percentile: time.Duration(int64(20000)),
}
}
func newTestQueryReport() *queryReport {
return &queryReport{
name: "foo_name",
successfulReads: 2000,
responseBytes: 39049,
stddevResponseBytes: 9091284,
avgResponseTime: 139082,
stdDevResponseTime: 29487,
percentile: 8273491,
}
}
func newTestInfluxQLReport() *influxQlReport {
return &influxQlReport{
statement: "foo_name",
responseTime: time.Duration(int64(20000)),
success: false,
}
}
func TestGetColumnIndex(t *testing.T) {
col := "thing"
columns := []string{"thing"}
expected := 0
got := getColumnIndex(col, columns)
if expected != got {
t.Fail()
}
}
func TestNumberBytes(t *testing.T) {
columns := []string{"num_bytes"}
values := [][]interface{}{[]interface{}{json.Number("1")}}
expected := 1
got := numberBytes(columns, values)
if expected != got {
t.Fail()
}
}
func TestCountSuccesses(t *testing.T) {
columns := []string{"status_code"}
values := [][]interface{}{[]interface{}{json.Number("200")}}
expected := 1
got := countSuccesses(columns, values)
if expected != got {
t.Fail()
}
}
func TestCountRetries(t *testing.T) {
columns := []string{"status_code"}
values := [][]interface{}{[]interface{}{json.Number("500")}}
expected := 1
got := countRetries(columns, values)
if expected != got {
t.Fail()
}
}
func TestResponseTimes(t *testing.T) {
columns := []string{"response_time_ns"}
values := [][]interface{}{[]interface{}{json.Number("380")}}
expected := ResponseTimes([]ResponseTime{NewResponseTime(380)})
got := responseTimes(columns, values)
if expected[0].Value != got[0].Value {
t.Fail()
}
}
func TestPercentile(t *testing.T) {
rs := createTestResponseTimes()
expected := time.Duration(21)
got := percentile(rs)
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestAvgDuration(t *testing.T) {
rs := createTestResponseTimes()
expected := time.Duration(11)
got := avgDuration(rs)
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestStddevDuration(t *testing.T) {
rs := createTestResponseTimes()
expected := time.Duration(6)
got := stddevDuration(rs)
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func createTestResponseTimes() ResponseTimes {
rstms := []int{1, 2, 3, 4, 5, 6, 7, 13, 14, 15, 16, 17, 18, 19, 8, 9, 10, 11, 12, 20, 21, 22}
rs := []ResponseTime{}
for _, rst := range rstms {
rs = append(rs, NewResponseTime(rst))
}
return rs
}

View File

@@ -0,0 +1,40 @@
package statement
import (
"time"
)
// ResponseTime is a struct that contains `Value`
// `Time` pairing.
type ResponseTime struct {
Value int
Time time.Time
}
// NewResponseTime returns a new response time
// with value `v` and time `time.Now()`.
func NewResponseTime(v int) ResponseTime {
r := ResponseTime{Value: v, Time: time.Now()}
return r
}
// ResponseTimes is a slice of response times
type ResponseTimes []ResponseTime
// Implements the `Len` method for the
// sort.Interface type
func (rs ResponseTimes) Len() int {
return len(rs)
}
// Implements the `Less` method for the
// sort.Interface type
func (rs ResponseTimes) Less(i, j int) bool {
return rs[i].Value < rs[j].Value
}
// Implements the `Swap` method for the
// sort.Interface type
func (rs ResponseTimes) Swap(i, j int) {
rs[i], rs[j] = rs[j], rs[i]
}

View File

@@ -0,0 +1,45 @@
package statement
import (
"testing"
)
func TestNewResponseTime(t *testing.T) {
value := 100000
rs := NewResponseTime(value)
if rs.Value != value {
t.Errorf("expected: %v\ngot: %v\n", value, rs.Value)
}
}
func newResponseTimes() ResponseTimes {
return []ResponseTime{
NewResponseTime(100),
NewResponseTime(10),
}
}
func TestResponseTimeLen(t *testing.T) {
rs := newResponseTimes()
if rs.Len() != 2 {
t.Fail()
}
}
func TestResponseTimeLess(t *testing.T) {
rs := newResponseTimes()
less := rs.Less(1, 0)
if !less {
t.Fail()
}
}
func TestResponseTimeSwap(t *testing.T) {
rs := newResponseTimes()
rs0 := rs[0]
rs1 := rs[1]
rs.Swap(0, 1)
if rs0 != rs[1] || rs1 != rs[0] {
t.Fail()
}
}

View File

@@ -0,0 +1,59 @@
package statement
import (
"fmt"
"strings"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// SetStatement set state variables for the test
type SetStatement struct {
Var string
Value string
StatementID string
Tracer *stressClient.Tracer
}
// SetID statisfies the Statement Interface
func (i *SetStatement) SetID(s string) {
i.StatementID = s
}
// Run statisfies the Statement Interface
func (i *SetStatement) Run(s *stressClient.StressTest) {
i.Tracer = stressClient.NewTracer(make(map[string]string))
d := stressClient.NewDirective(strings.ToLower(i.Var), strings.ToLower(i.Value), i.Tracer)
switch d.Property {
// Needs to be set on both StressTest and stressClient
// Set the write percison for points generated
case "precision":
s.Precision = d.Value
i.Tracer.Add(1)
s.SendDirective(d)
// Lives on StressTest
// Set the date for the first point entered into the database
case "startdate":
s.Lock()
s.StartDate = d.Value
s.Unlock()
// Lives on StressTest
// Set the BatchSize for writes
case "batchsize":
s.Lock()
s.BatchSize = parseInt(d.Value)
s.Unlock()
// All other variables live on stressClient
default:
i.Tracer.Add(1)
s.SendDirective(d)
}
i.Tracer.Wait()
}
// Report statisfies the Statement Interface
func (i *SetStatement) Report(s *stressClient.StressTest) string {
return fmt.Sprintf("SET %v = '%v'", i.Var, i.Value)
}

View File

@@ -0,0 +1,92 @@
package statement
import (
"fmt"
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestSetSetID(t *testing.T) {
e := newTestSet("database", "foo")
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\nGot: %v\n", newID, e.StatementID)
}
}
func TestSetRun(t *testing.T) {
properties := []string{
"precision",
"startdate",
"batchsize",
"resultsaddress",
"testname",
"addresses",
"writeinterval",
"queryinterval",
"database",
"writeconcurrency",
"queryconcurrency",
}
for _, prop := range properties {
testSetRunUtl(t, prop, "1")
}
}
func testSetRunUtl(t *testing.T, property string, value string) {
i := newTestSet(property, value)
s, _, directiveCh := stressClient.NewTestStressTest()
// Listen to the other side of the directiveCh
go func() {
for d := range directiveCh {
if i.Var != d.Property {
t.Errorf("wrong property sent to stressClient\n expected: %v\n got: %v\n", i.Var, d.Property)
}
if i.Value != d.Value {
t.Errorf("wrong value sent to stressClient\n expected: %v\n got: %v\n", i.Value, d.Value)
}
d.Tracer.Done()
}
}()
// Run the statement
i.Run(s)
// Check the result
switch i.Var {
case "precision":
if i.Value != s.Precision {
t.Errorf("Failed to set %v\n", i.Var)
}
case "startdate":
if i.Value != s.StartDate {
t.Errorf("Failed to set %v\n", i.Var)
}
case "batchsize":
if parseInt(i.Value) != s.BatchSize {
t.Errorf("Failed to set %v\n", i.Var)
}
// TODO: Actually test this
case "resultsaddress":
default:
}
}
func TestSetReport(t *testing.T) {
set := newTestSet("this", "that")
s, _, _ := stressClient.NewTestStressTest()
rpt := set.Report(s)
expected := fmt.Sprintf("SET %v = '%v'", set.Var, set.Value)
if rpt != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, rpt)
}
}
func newTestSet(toSet, value string) *SetStatement {
return &SetStatement{
Var: toSet,
Value: value,
Tracer: stressClient.NewTracer(make(map[string]string)),
StatementID: "fooID",
}
}

View File

@@ -0,0 +1,32 @@
package statement
import (
"log"
"strconv"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// Statement is the common interface to shape the testing environment and prepare database requests
// The parser turns the 'statements' in the config file into Statements
type Statement interface {
Run(s *stressClient.StressTest)
Report(s *stressClient.StressTest) string
SetID(s string)
}
func parseInt(s string) int {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
log.Fatalf("Error parsing integer:\n String: %v\n Error: %v\n", s, err)
}
return int(i)
}
func parseFloat(s string) int {
i, err := strconv.ParseFloat(s, 64)
if err != nil {
log.Fatalf("Error parsing integer:\n String: %v\n Error: %v\n", s, err)
}
return int(i)
}

View File

@@ -0,0 +1,47 @@
package statement
// A Template contains all information to fill in templated variables in inset and query statements
type Template struct {
Tags []string
Function *Function
}
// Templates are a collection of Template
type Templates []*Template
// Init makes Stringers out of the Templates for quick point creation
func (t Templates) Init(seriesCount int) Stringers {
arr := make([]Stringer, len(t))
for i, tmp := range t {
if len(tmp.Tags) == 0 {
arr[i] = tmp.Function.NewStringer(seriesCount)
continue
}
arr[i] = tmp.NewTagFunc()
}
return arr
}
// Calculates the number of series implied by a template
func (t *Template) numSeries() int {
// If !t.Tags then tag cardinality is t.Function.Count
if len(t.Tags) == 0 {
return t.Function.Count
}
// Else tag cardinality is len(t.Tags)
return len(t.Tags)
}
// NewTagFunc returns a Stringer that loops through the given tags
func (t *Template) NewTagFunc() Stringer {
if len(t.Tags) == 0 {
return func() string { return "EMPTY TAGS" }
}
i := 0
return func() string {
s := t.Tags[i]
i = (i + 1) % len(t.Tags)
return s
}
}

View File

@@ -0,0 +1,72 @@
package statement
import (
"testing"
)
func TestNewTagFunc(t *testing.T) {
wtags := newTestTagsTemplate()
wfunc := newTestFunctionTemplate()
expected := wtags.Tags[0]
got := wtags.NewTagFunc()()
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = "EMPTY TAGS"
got = wfunc.NewTagFunc()()
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestNumSeries(t *testing.T) {
wtags := newTestTagsTemplate()
wfunc := newTestFunctionTemplate()
expected := len(wtags.Tags)
got := wtags.numSeries()
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = wfunc.Function.Count
got = wfunc.numSeries()
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func TestTemplatesInit(t *testing.T) {
tmpls := newTestTemplates()
s := tmpls.Init(5)
vals := s.Eval(spoofTime)
expected := tmpls[0].Tags[0]
got := vals[0]
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
expected = "0i"
got = vals[1]
if got != expected {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func newTestTemplates() Templates {
return []*Template{
newTestTagsTemplate(),
newTestFunctionTemplate(),
}
}
func newTestTagsTemplate() *Template {
return &Template{
Tags: []string{"thing", "other_thing"},
}
}
func newTestFunctionTemplate() *Template {
return &Template{
Function: newIntIncFunction(),
}
}

View File

@@ -0,0 +1,51 @@
package statement
import (
"log"
"time"
)
// A Timestamp contains all informaiton needed to generate timestamps for points created by InsertStatements
type Timestamp struct {
Count int
Duration time.Duration
Jitter bool
}
// Time returns the next timestamp needed by the InsertStatement
func (t *Timestamp) Time(startDate string, series int, precision string) func() int64 {
var start time.Time
var err error
if startDate == "now" {
start = time.Now()
} else {
start, err = time.Parse("2006-01-02", startDate)
}
if err != nil {
log.Fatalf("Error parsing start time from StartDate\n string: %v\n error: %v\n", startDate, err)
}
return nextTime(start, t.Duration, series, precision)
}
func nextTime(ti time.Time, step time.Duration, series int, precision string) func() int64 {
t := ti
count := 0
return func() int64 {
count++
if count > series {
t = t.Add(step)
count = 1
}
var timestamp int64
if precision == "s" {
timestamp = t.Unix()
} else {
timestamp = t.UnixNano()
}
return timestamp
}
}

View File

@@ -0,0 +1,31 @@
package statement
import (
"testing"
"time"
)
func TestTimestampTime(t *testing.T) {
tstp := newTestTimestamp()
function := tstp.Time("2016-01-01", 100, "s")
expected := int64(1451606400)
got := function()
if expected != got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
function = tstp.Time("now", 100, "ns")
expected = time.Now().UnixNano()
got = function()
if expected < got {
t.Errorf("expected: %v\ngot: %v\n", expected, got)
}
}
func newTestTimestamp() *Timestamp {
duration, _ := time.ParseDuration("10s")
return &Timestamp{
Count: 5001,
Duration: duration,
Jitter: false,
}
}

View File

@@ -0,0 +1,32 @@
package statement
import (
"fmt"
"time"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
// WaitStatement is a Statement Implementation to prevent the test from returning to early when running GoStatements
type WaitStatement struct {
StatementID string
runtime time.Duration
}
// SetID statisfies the Statement Interface
func (w *WaitStatement) SetID(s string) {
w.StatementID = s
}
// Run statisfies the Statement Interface
func (w *WaitStatement) Run(s *stressClient.StressTest) {
runtime := time.Now()
s.Wait()
w.runtime = time.Since(runtime)
}
// Report statisfies the Statement Interface
func (w *WaitStatement) Report(s *stressClient.StressTest) string {
return fmt.Sprintf("WAIT -> %v", w.runtime)
}

View File

@@ -0,0 +1,41 @@
package statement
import (
"strings"
"testing"
"github.com/influxdata/influxdb/stress/v2/stress_client"
)
func TestWaitSetID(t *testing.T) {
e := newTestWait()
newID := "oaijnifo"
e.SetID(newID)
if e.StatementID != newID {
t.Errorf("Expected: %v\ngott: %v\n", newID, e.StatementID)
}
}
func TestWaitRun(t *testing.T) {
e := newTestWait()
s, _, _ := stressClient.NewTestStressTest()
e.Run(s)
if e == nil {
t.Fail()
}
}
func TestWaitReport(t *testing.T) {
e := newTestWait()
s, _, _ := stressClient.NewTestStressTest()
rpt := e.Report(s)
if !strings.Contains(rpt, "WAIT") {
t.Fail()
}
}
func newTestWait() *WaitStatement {
return &WaitStatement{
StatementID: "fooID",
}
}