diff --git a/README.md b/README.md index f7f24890..8381bc59 100644 --- a/README.md +++ b/README.md @@ -604,7 +604,7 @@ If Ddosify can't receive the response for a request, that step is marked as Fail | `not` | ( param `bool` ) | returns converse of given param | | `range` | ( param `int`, low `int`,high `int` ) | returns param is in range of [low,high): low is included, high is not included. | | `json_path` | ( json_path `string`) | extracts from response body using given json path | -| `xml_path` | ( xpath `string` ) | extracts from response body using given xml path | +| `xpath` | ( xpath `string` ) | extracts from response body using given xml path | | `regexp` | ( param `any`, regexp `string`, matchNo `int` ) | extracts from given value in the first parameter using given regular expression | ### Operators diff --git a/config/json.go b/config/json.go index 0d80fd61..9af0a03d 100644 --- a/config/json.go +++ b/config/json.go @@ -189,30 +189,9 @@ func (j *JsonReader) Init(jsonByte []byte) (err error) { } func (j *JsonReader) CreateHammer() (h types.Hammer, err error) { - // Read Data - var readData map[string]types.CsvData - if len(j.Data) > 0 { - readData = make(map[string]types.CsvData, len(j.Data)) - } - for k, conf := range j.Data { - var rows []map[string]interface{} - rows, err = readCsv(conf) - if err != nil { - return - } - var csvData types.CsvData - csvData.Rows = rows - - if conf.Order == "random" { - csvData.Random = true - } - readData[k] = csvData - } - // Scenario s := types.Scenario{ Envs: j.Envs, - Data: readData, } var si types.ScenarioStep for _, step := range j.Steps { @@ -264,6 +243,26 @@ func (j *JsonReader) CreateHammer() (h types.Hammer, err error) { samplingRate = types.DefaultSamplingCount } + testDataConf := make(map[string]types.CsvConf) + for key, val := range j.Data { + vars := make(map[string]types.Tag) + for k, v := range val.Vars { + vars[k] = types.Tag{ + Tag: v.Tag, + Type: v.Type, + } + } + testDataConf[key] = types.CsvConf{ + Path: val.Path, + Delimiter: val.Delimiter, + SkipFirstLine: val.SkipFirstLine, + Vars: vars, + SkipEmptyLine: val.SkipEmptyLine, + AllowQuota: val.AllowQuota, + Order: val.Order, + } + } + // Hammer h = types.Hammer{ IterationCount: *j.IterCount, @@ -275,6 +274,7 @@ func (j *JsonReader) CreateHammer() (h types.Hammer, err error) { ReportDestination: j.Output, Debug: j.Debug, SamplingRate: samplingRate, + TestDataConf: testDataConf, } return } diff --git a/config/json_test.go b/config/json_test.go index 5408cbc1..f30e5d58 100644 --- a/config/json_test.go +++ b/config/json_test.go @@ -56,6 +56,7 @@ func TestCreateHammerDefaultValues(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -111,6 +112,7 @@ func TestCreateHammer(t *testing.T) { Addr: addr, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -166,6 +168,7 @@ func TestCreateHammerWithIterationCountInsteadOfReqCount(t *testing.T) { Addr: addr, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -224,6 +227,7 @@ func TestCreateHammerWithIterationCountOverridesReqCount(t *testing.T) { Addr: addr, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -259,6 +263,7 @@ func TestCreateHammerManualLoad(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -294,6 +299,7 @@ func TestCreateHammerManualLoadOverrideOthers(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -440,43 +446,6 @@ func TestCreateHammerCaptureEnvs(t *testing.T) { } } -func TestCreateHammerDataCsv(t *testing.T) { - t.Parallel() - jsonReader, _ := NewConfigReader(readConfigFile("config_testdata/config_data_csv.json"), ConfigTypeJson) - - expectedRandom := true - - h, err := jsonReader.CreateHammer() - if err != nil { - t.Errorf("TestCreateHammerDataCsv error occurred: %v", err) - } - - csvData := h.Scenario.Data["info"] - - if !reflect.DeepEqual(csvData.Random, expectedRandom) { - t.Errorf("TestCreateHammerDataCsv got: %t expected: %t", csvData.Random, expectedRandom) - } - - expectedRow := map[string]interface{}{ - "name": "Kenan", - "city": "Tokat", - "team": "Galatasaray", - "payload": map[string]interface{}{ - "data": map[string]interface{}{ - "profile": map[string]interface{}{ - "name": "Kenan", - }, - }, - }, - "age": 25, - } - - if !reflect.DeepEqual(expectedRow, csvData.Rows[0]) { - t.Errorf("TestCreateHammerDataCsv got: %#v expected: %#v", csvData.Rows[0], expectedRow) - } - -} - func TestCreateHammerInvalidTarget(t *testing.T) { t.Parallel() jsonReader, _ := NewConfigReader(readConfigFile("config_testdata/config_invalid_target.json"), ConfigTypeJson) @@ -552,6 +521,7 @@ func TestCreateHammerTLSWithOnlyCertPath(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -597,6 +567,7 @@ func TestCreateHammerTLSWithOnlyKeyPath(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() @@ -633,6 +604,7 @@ func TestCreateHammerTLSWithWithEmptyPath(t *testing.T) { Strategy: proxy.ProxyTypeSingle, }, SamplingRate: types.DefaultSamplingCount, + TestDataConf: make(map[string]types.CsvConf), } h, err := jsonReader.CreateHammer() diff --git a/config_examples/assertion/expected_body.json b/config_examples/assertion/expected_body.json new file mode 100644 index 00000000..aa4e2dc4 --- /dev/null +++ b/config_examples/assertion/expected_body.json @@ -0,0 +1,28 @@ +[ + "AED", + "ARS", + "AUD", + "BGN", + "BHD", + "BRL", + "CAD", + "CHF", + "CNY", + "DKK", + "DZD", + "EUR", + "FKP", + "INR", + "JEP", + "JPY", + "KES", + "KWD", + "KZT", + "MXN", + "NZD", + "RUB", + "SEK", + "SGD", + "TRY", + "USD" +] \ No newline at end of file diff --git a/core/engine.go b/core/engine.go index 82f6b2e6..4061fce8 100644 --- a/core/engine.go +++ b/core/engine.go @@ -30,6 +30,7 @@ import ( "go.ddosify.com/ddosify/core/proxy" "go.ddosify.com/ddosify/core/report" "go.ddosify.com/ddosify/core/scenario" + "go.ddosify.com/ddosify/core/scenario/testdata" "go.ddosify.com/ddosify/core/types" ) @@ -95,6 +96,13 @@ func (e *engine) Init() (err error) { return } + // read test data + readData, err := readTestData(e.hammer.TestDataConf) + if err != nil { + return err + } + e.hammer.Scenario.Data = readData + if err = e.scenarioService.Init(e.ctx, e.hammer.Scenario, e.proxyService.GetAll(), e.hammer.Debug); err != nil { return } @@ -337,3 +345,28 @@ func reverse(s interface{}) { swap(i, j) } } + +var readTestData = func(testDataConf map[string]types.CsvConf) (map[string]types.CsvData, error) { + // Read Data + var readData map[string]types.CsvData + if len(testDataConf) > 0 { + readData = make(map[string]types.CsvData, len(testDataConf)) + } + for k, conf := range testDataConf { + var rows []map[string]interface{} + var err error + rows, err = testdata.ReadCsv(conf) + if err != nil { + return nil, err + } + var csvData types.CsvData + csvData.Rows = rows + + if conf.Order == "random" { + csvData.Random = true + } + readData[k] = csvData + } + + return readData, nil +} diff --git a/core/engine_test.go b/core/engine_test.go index f17033df..634d7643 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -1484,6 +1484,11 @@ func TestLoadRandomInfoFromData(t *testing.T) { t.Errorf("TestLoadRandomInfoFromData error occurred %v", err) } + originalReadTestData := readTestData + readTestData = func(testDataConf map[string]types.CsvConf) (map[string]types.CsvData, error) { + return map[string]types.CsvData{"info": csvData}, nil + } + err = e.Init() if err != nil { t.Errorf("TestLoadRandomInfoFromData error occurred %v", err) @@ -1491,6 +1496,7 @@ func TestLoadRandomInfoFromData(t *testing.T) { e.Start() + readTestData = originalReadTestData if !requestCalled { t.Errorf("TestLoadRandomInfoFromData test server has not been called, url path injection failed") } @@ -1500,6 +1506,52 @@ func TestLoadRandomInfoFromData(t *testing.T) { } } +func TestDataCsv(t *testing.T) { + readConfigFile := func(path string) []byte { + f, _ := os.Open(path) + + byteValue, _ := ioutil.ReadAll(f) + return byteValue + } + + jsonReader, _ := config.NewConfigReader(readConfigFile("../config/config_testdata/config_data_csv.json"), config.ConfigTypeJson) + + expectedRandom := true + + h, _ := jsonReader.CreateHammer() + + data, err := readTestData(h.TestDataConf) + + if err != nil { + t.Errorf("TestDataCsv error occurred: %v", err) + } + + csvData := data["info"] + + if !reflect.DeepEqual(csvData.Random, expectedRandom) { + t.Errorf("TestCreateHammerDataCsv got: %t expected: %t", csvData.Random, expectedRandom) + } + + expectedRow := map[string]interface{}{ + "name": "Kenan", + "city": "Tokat", + "team": "Galatasaray", + "payload": map[string]interface{}{ + "data": map[string]interface{}{ + "profile": map[string]interface{}{ + "name": "Kenan", + }, + }, + }, + "age": 25, + } + + if !reflect.DeepEqual(expectedRow, csvData.Rows[0]) { + t.Errorf("TestCreateHammerDataCsv got: %#v expected: %#v", csvData.Rows[0], expectedRow) + } + +} + // The test creates a web server with Certificate auth, // then it spawns an Engine and verifies that the auth was successfully passsed. func TestTLSMutualAuth(t *testing.T) { diff --git a/config/csv.go b/core/scenario/testdata/csv.go similarity index 94% rename from config/csv.go rename to core/scenario/testdata/csv.go index cb03c26d..9b9d4105 100644 --- a/config/csv.go +++ b/core/scenario/testdata/csv.go @@ -1,4 +1,4 @@ -package config +package testdata import ( "encoding/csv" @@ -9,16 +9,18 @@ import ( "net/url" "os" "strconv" + + "go.ddosify.com/ddosify/core/types" ) -func validateConf(conf CsvConf) error { +func validateConf(conf types.CsvConf) error { if !(conf.Order == "random" || conf.Order == "sequential") { return fmt.Errorf("unsupported order %s, should be random|sequential", conf.Order) } return nil } -func readCsv(conf CsvConf) ([]map[string]interface{}, error) { +func ReadCsv(conf types.CsvConf) ([]map[string]interface{}, error) { err := validateConf(conf) if err != nil { return nil, err diff --git a/config/csv_test.go b/core/scenario/testdata/csv_test.go similarity index 88% rename from config/csv_test.go rename to core/scenario/testdata/csv_test.go index 84855735..87026074 100644 --- a/config/csv_test.go +++ b/core/scenario/testdata/csv_test.go @@ -1,19 +1,21 @@ -package config +package testdata import ( "fmt" "reflect" "strings" "testing" + + "go.ddosify.com/ddosify/core/types" ) func TestValidateCsvConf(t *testing.T) { t.Parallel() - conf := CsvConf{ + conf := types.CsvConf{ Path: "", Delimiter: "", SkipFirstLine: false, - Vars: map[string]Tag{}, + Vars: map[string]types.Tag{}, SkipEmptyLine: false, AllowQuota: false, Order: "", @@ -29,11 +31,11 @@ func TestValidateCsvConf(t *testing.T) { func TestReadCsv(t *testing.T) { t.Parallel() - conf := CsvConf{ - Path: "config_testdata/test.csv", + conf := types.CsvConf{ + Path: "../../../config/config_testdata/test.csv", Delimiter: ";", SkipFirstLine: true, - Vars: map[string]Tag{ + Vars: map[string]types.Tag{ "0": {Tag: "name", Type: "string"}, "3": {Tag: "payload", Type: "json"}, "4": {Tag: "age", Type: "int"}, @@ -45,7 +47,7 @@ func TestReadCsv(t *testing.T) { Order: "sequential", } - rows, err := readCsv(conf) + rows, err := ReadCsv(conf) if err != nil { t.Errorf("TestReadCsv %v", err) @@ -95,15 +97,15 @@ func TestReadCsv(t *testing.T) { } var table = []struct { - conf CsvConf + conf types.CsvConf latency float64 }{ { - conf: CsvConf{ + conf: types.CsvConf{ Path: "config_testdata/test.csv", Delimiter: ";", SkipFirstLine: true, - Vars: map[string]Tag{ + Vars: map[string]types.Tag{ "0": {Tag: "name", Type: "string"}, "3": {Tag: "payload", Type: "json"}, "4": {Tag: "age", Type: "int"}, @@ -122,7 +124,7 @@ func TestBenchmarkCsvRead(t *testing.T) { res := testing.Benchmark(func(b *testing.B) { for i := 0; i < b.N; i++ { - readCsv(v.conf) + ReadCsv(v.conf) } }) diff --git a/core/types/hammer.go b/core/types/hammer.go index d0c9537e..239ef05f 100644 --- a/core/types/hammer.go +++ b/core/types/hammer.go @@ -53,6 +53,21 @@ type TimeRunCount []struct { Count int } +type Tag struct { + Tag string `json:"tag"` + Type string `json:"type"` +} + +type CsvConf struct { + Path string `json:"path"` + Delimiter string `json:"delimiter"` + SkipFirstLine bool `json:"skip_first_line"` + Vars map[string]Tag `json:"vars"` // "0":"name", "1":"city","2":"team" + SkipEmptyLine bool `json:"skip_empty_line"` + AllowQuota bool `json:"allow_quota"` + Order string `json:"order"` +} + // Hammer is like a lighter for the engine. // It includes attack metadata and all necessary data to initialize the internal services in the engine. type Hammer struct { @@ -85,6 +100,9 @@ type Hammer struct { // Sampling rate SamplingRate int + + // Test Data Config + TestDataConf map[string]CsvConf } // Validate validates attack metadata and executes the validation methods of the services. diff --git a/main_benchmark_test.go b/main_benchmark_test.go index 5e7b8905..ecf0bfb0 100644 --- a/main_benchmark_test.go +++ b/main_benchmark_test.go @@ -94,6 +94,7 @@ func BenchmarkEngines(t *testing.B) { index := os.Getenv("index") if index == "" { // parent + success := true for i, _ := range table { // open a new process for each test config // start a child env := fmt.Sprintf("index=%d", i) @@ -107,10 +108,17 @@ func BenchmarkEngines(t *testing.B) { panic(err.Error()) } - proc.Wait() + pState, err := proc.Wait() if err != nil { panic(err.Error()) } + + if !pState.Success() { + success = false + } + } + if !success { + t.Fail() } } else { i, _ := strconv.Atoi(index) @@ -158,7 +166,7 @@ func BenchmarkEngines(t *testing.B) { defer trace.Stop() } - t.Run(fmt.Sprintf("config_%s", conf.path), func(t *testing.B) { + success := t.Run(fmt.Sprintf("config_%s", conf.path), func(t *testing.B) { var memPercents []float32 var cpuStats []*cpu.TimesStat @@ -206,6 +214,10 @@ func BenchmarkEngines(t *testing.B) { }) + if success { + os.Exit(0) + } + os.Exit(1) } }