Skip to content

Commit

Permalink
Refactoring most of the code to have a smarter usage of structs
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilibertDugas committed Jan 17, 2016
1 parent e6b270d commit 61b8bec
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 117 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ It needs the following fields:

Once the value is extracted, it's possible to use it in the further requests. Example below:

- *var*: "OAUTH_TOKEN"
- *field*: "headers"
- *regex*: "bearer (.*)?"
- **var**: "OAUTH_TOKEN"
- **field**: "headers"
- **regex**: "bearer (.*)?"

The variable $(OAUTH_TOKEN) can be injected in further requests afterwards
46 changes: 26 additions & 20 deletions http_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,77 +7,83 @@ import (
"net/http"
)

type GoltExecutor struct {
ThreadGroup GoltThreadGroup
Sender GoltSender
Logger *GoltLogger
SendingChannel chan []byte
}


func executeHttpRequests(threadGroup GoltThreadGroup, sender GoltSender) {
for i := 1; i <= threadGroup.Repetitions; i++ {
executeRequestsSequence(threadGroup.Requests, sender, threadGroup.Stage, i)
func (e *GoltExecutor) executeHttpRequests() {
for i := 1; i <= e.ThreadGroup.Repetitions; i++ {
e.executeRequestsSequence(e.ThreadGroup.Requests)
}
}

func executeRequestsSequence(httpRequests []GoltRequest, sender GoltSender, stage int, repetition int) {
func (e *GoltExecutor) executeRequestsSequence(httpRequests []GoltRequest) {
// TODO: By defining the map here, it's local to the thread, maybe we want something else
extractorMap := make(map[string]string)
extractionWasDone := false

for _, request := range httpRequests {
req := BuildRequest(extractionWasDone, request, extractorMap)
notifyWatcher()
notifyWatcher(e.SendingChannel)

start := time.Now()
resp, err := sender.Send(req)
resp, err := e.Sender.Send(req)
elapsed := time.Since(start)

if resp != nil {
defer resp.Body.Close()
}

// Log result
logResult(request, resp, err, stage, repetition, elapsed)
e.logResult(request, resp, err, elapsed)

// Handle all the regular expression extraction
extractionWasDone = handleExtraction(request, resp, extractorMap)
}
}

func notifyWatcher() {
sentRequest := []byte("sent")
channel <- sentRequest
}

// TODO: Too many parameters on this method, to refactor
func logResult(request GoltRequest, resp *http.Response, err error, stage int, repetition int, elapsed time.Duration) {
func (e *GoltExecutor) logResult(request GoltRequest, resp *http.Response, err error, elapsed time.Duration) {
var msg LogMessage
if err != nil {
errorMsg := fmt.Sprintf("%v", err)
msg = LogMessage{Stage: stage,
Repetition: repetition,
msg = LogMessage{
Url: request.URL,
ErrorMessage: errorMsg,
Status: 0,
Success: false,
Duration: elapsed}
} else {
isSuccess := isCallSuccessful(request.Assert, resp)
msg = LogMessage{Stage: stage,
Repetition: repetition,
msg = LogMessage{
Url: request.URL,
ErrorMessage: "N/A",
Status: resp.StatusCode,
Success: isSuccess,
Duration: elapsed}
}
Log(msg)
e.Logger.Log(msg)
}

func notifyWatcher(channel chan[] byte) {
sentRequest := []byte("sent")
channel <- sentRequest
}

func isCallSuccessful(assert GoltAssert, response *http.Response) bool {
var isCallSuccessful bool
isContentTypeSuccessful := true
isBodySuccessful := true
isStatusCodeSuccessful := assert.Status == response.StatusCode

if assert.Type != "" {
isContentTypeSuccessful = assert.Type == response.Header.Get("content-type")
}

isCallSuccessful = isStatusCodeSuccessful && isContentTypeSuccessful && isBodySuccessful
isCallSuccessful = isStatusCodeSuccessful && isContentTypeSuccessful
return isCallSuccessful
}

Expand Down
14 changes: 10 additions & 4 deletions http_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"testing"
"net/http"
"os"
"log"
)

const testKey1, testValue1 = "TEST_VALUE_1", "test-string-1"
Expand All @@ -13,7 +15,7 @@ var countedRequest = 0

var jsonHeaders = http.Header{"Content-Type":[]string{"application/json"}}
var htmlHeaders = http.Header{"Content-Type":[]string{"text/html"}}
var executorTestingTable = []struct{
var assertionTestingTable = []struct{
expectedSuccess bool
response *http.Response
} {
Expand All @@ -28,7 +30,7 @@ func TestIsCallSuccessful(t *testing.T) {
Type: "application/json",
Status: 200,
}
for _, entry := range executorTestingTable {
for _, entry := range assertionTestingTable {
if isCallSuccessful(assert, entry.response) != entry.expectedSuccess {
t.Error("The assertion was not validated properly")
}
Expand All @@ -37,8 +39,12 @@ func TestIsCallSuccessful(t *testing.T) {

func TestExecuteRequestsSequence(t *testing.T) {
requests := []GoltRequest{GoltRequest{}, GoltRequest{}}
sender := MockSender{}
executeRequestsSequence(requests, sender, 1, 1)
executor := GoltExecutor{
Sender: MockSender{},
Logger: &GoltLogger{Logger: log.New(os.Stdout, "", 0),},
SendingChannel: make(chan []byte, 1024),
}
executor.executeRequestsSequence(requests)
if countedRequest != 2 {
t.Error("Request sent should have been 2")
}
Expand Down
36 changes: 23 additions & 13 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,40 @@ import (
"time"
)

var logFile *os.File
var logger = log.New(os.Stdout, "", 0)
type GoltLogger struct{
LogFile *os.File
Logger *log.Logger
}

type LogMessage struct {
Url string
ErrorMessage string
Status int
Success bool
Duration time.Duration
}

func SetOutputFile(filename string) {
logFile, err := os.OpenFile(filename, os.O_TRUNC | os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
func (l *GoltLogger) SetOutputFile(filename string) {
var err error
l.LogFile, err = os.OpenFile(filename, os.O_TRUNC | os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
if err != nil {
fmt.Printf("error opening file: %v", err)
}
logger = log.New(logFile, "", 0)
logger.Println("Stage,Repetitions,Status Code,Success,Duration,Error Message")
l.Logger = log.New(l.LogFile, "", 0)
l.Logger.Println("url,statusCode,success,duration,errorMessage")
}

func Log(message LogMessage) {
func (l *GoltLogger) Log(message LogMessage) {
milliseconds := message.Duration.Nanoseconds() / int64(time.Millisecond)
msg := fmt.Sprintf("%d,%d,%d,%t,%d,%v",
message.Stage,
message.Repetition,
msg := fmt.Sprintf("%s,%d,%t,%d,%v",
message.Url,
message.Status,
message.Success,
milliseconds,
message.ErrorMessage)
logger.Printf("%s\n", msg)
l.Logger.Printf("%s\n", msg)
}

func Finish() {
logFile.Close()
func (l *GoltLogger) Finish() {
l.LogFile.Close()
}
16 changes: 3 additions & 13 deletions model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package main
import (
"time"
"net/http"
)

Expand All @@ -13,6 +12,7 @@ type Golts struct {
// from a configuration file.
type GoltThreadGroup struct {
Threads int
Timeout int
Repetitions int
Stage int
Type string
Expand All @@ -33,9 +33,8 @@ type GoltRequest struct {
// A GoltAssert contains the configuration of the assertions to be made for a
// GoltRequest.
type GoltAssert struct {
Timeout int
Status int
Type string
Status int
Type string
}

// A GoltExtractor contains the configuration to extract information of the
Expand All @@ -47,15 +46,6 @@ type GoltExtractor struct {
// TODO: Have the possibility to extract the value of a JSON field from the headers/body
}

type LogMessage struct {
Stage int
Repetition int
ErrorMessage string
Status int
Success bool
Duration time.Duration
}

type GoltSender interface {
Send(request *http.Request) (*http.Response, error)
}
66 changes: 41 additions & 25 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ import (
"sort"
"time"
"net/http"
"log"
"os"
)

const parallelGroup = "parallel"

var stageWaitGroup sync.WaitGroup
var threadWaitGroup sync.WaitGroup
var channel = make(chan []byte, 1024)
var httpClient *http.Client
var logger *GoltLogger
var watcher *GoltWatcher

type HttpSender struct {
Client *http.Client
}

func (http HttpSender) Send(request *http.Request) (*http.Response, error) {
return http.Client.Do(request)
func init() {
logger = &GoltLogger{
Logger: log.New(os.Stdout, "", 0),
}
watcher = &GoltWatcher{
Interval: 5.0,
WatchingChannel: channel,
}
}

func ExecuteGoltTest(goltTest Golts, logFile string) {
Expand All @@ -30,15 +34,15 @@ func ExecuteGoltTest(goltTest Golts, logFile string) {
}
sort.Ints(keys)

SetOutputFile(logFile)
go Watch(channel)
logger.SetOutputFile(logFile)
go watcher.Watch()
for _, k := range keys {
executeStage(m[k])
}

// Output final throughput
OutputAverageThroughput()
Finish()
watcher.OutputAverageThroughput()
logger.Finish()
}

func generateGoltMap(goltTest Golts) map[int][]GoltThreadGroup {
Expand All @@ -49,7 +53,6 @@ func generateGoltMap(goltTest Golts) map[int][]GoltThreadGroup {
m[element.Stage] = []GoltThreadGroup{element}
} else {
m[element.Stage] = append(array, element)

}
}
return m
Expand All @@ -58,7 +61,6 @@ func generateGoltMap(goltTest Golts) map[int][]GoltThreadGroup {
// FIXME: The two following functions are very repetitive. Find a way to clean it
func executeStage(stage []GoltThreadGroup) {
stageWaitGroup.Add(len(stage))

for _, item := range stage {
httpClient = generateHttpClient(item)
go executeThreadGroup(item)
Expand All @@ -69,9 +71,16 @@ func executeStage(stage []GoltThreadGroup) {
func executeThreadGroup(threadGroup GoltThreadGroup) {
threadWaitGroup.Add(threadGroup.Threads)

executor := GoltExecutor{
ThreadGroup: threadGroup,
Sender: HttpSender{httpClient},
Logger: logger,
SendingChannel: channel,
}

for i := 0; i < threadGroup.Threads; i++ {
go func() {
executeHttpRequests(threadGroup, HttpSender{httpClient})
executor.executeHttpRequests()
threadWaitGroup.Done()
}()
}
Expand All @@ -81,17 +90,24 @@ func executeThreadGroup(threadGroup GoltThreadGroup) {
}

func generateHttpClient(threadGroup GoltThreadGroup) *http.Client {
// TODO: Currently timeout is not supported with the new data model
/*var httpClient *http.Client
if item.Assert.Timeout > 0 {
var httpClient *http.Client
if threadGroup.Timeout > 0 {
httpClient = &http.Client{
Timeout: time.Duration(time.Millisecond * time.Duration(item.Assert.Timeout)),
Timeout: time.Duration(time.Millisecond * time.Duration(threadGroup.Timeout)),
}
} else {
httpClient = &http.Client{}
}*/
// Default timeout of 30 seconds for HTTP calls to avoid hung threads
return &http.Client{
Timeout: time.Duration(time.Second * 30),
// Default timeout of 30 seconds for HTTP calls to avoid hung threads
httpClient = &http.Client{
Timeout: time.Duration(time.Second * 30),
}
}
}
return httpClient
}

type HttpSender struct {
Client *http.Client
}

func (http HttpSender) Send(request *http.Request) (*http.Response, error) {
return http.Client.Do(request)
}
Loading

0 comments on commit 61b8bec

Please sign in to comment.