hulken
======

Hulken is a stress testing tool for everything speaking HTTP. Hulken supports multiple urls, GETs and POSTs, static and dynamic payloads, multiple agents and more. Hulken is highly configurable but defaults to some reasonable settings. Hulken works both as a library and a stand-alone command line tool. *Hulken is swedish for The Hulk*.

## .. smash and nothing else
Hulken is in no way a proper load test tool like LoadRunner, JMeter and such (e.g it does not try to simulate realistic user behaviours), but that's usually not what I need. I need something quick and lightweight to run early and often that helps me catch the biggest boo boo's before they see the light of day. If you have the same need I hope that Hulken can be useful to you too.

## Quick Examples
### as a library:

`npm install hulken --save`


```
var hulken = require('hulken');

var myArrayOfRequests = [{
    method: 'get',
    path: '/',
    expectedTextToExist: 'Welcome to the landing page'
}];

var hulken_options = {  
  targetUrl: 'http://localhost:8888',  
  requestsArray: JSON.stringify(myArrayOfRequests),
};  

hulken.run(function(stats){  
  console.log('error ... perhaps i should look closer at the stats');  
  },function(stats){  
    console.log('success! ... auto tweet my stats to the world!');  
    },hulken_options);

```
*(you can override a lot of default settings, [see documentation](#settings))*

instead of passing in the requests in requestsArray you can also set the  **requestsFilePath** (the value is the path) that points to a json file like this:
```
[{
  "method":"get",
  "path":"/index",
  "expectedTextToExist":"Start"
 },
 {
    "method":"get",
    "path":"/about",
    "expectedTextToExist":"About us"
 },
 {
    "method": "post",
    "path": "/",
    "expectedTextToExist": "thank you for your POST",
    "payload": {
      "foo": "bar"
     }
}]
```
*(you can also send dynamic payloads, [see documentation ](#dynamicPayloads)  )*  
**checkout ./tests/integrations.js for more examples!**

### as a command line tool

`npm install hulken -g`

Create a 'options.json' file with the following content.
the **requestsFilePath** points to the same hulkenRequests.json as in the example above (as a library). If you wish you can also pass in the list of requests inline (as a valid json string, escaping your double quotes) with **requestsArray**.
```
{
  "targetUrl" : "http://yourapp.com",
  "requestsFilePath": "./path/to/my/hulkenRequests.json"
}
```
*(you can have hulken generate an example options for you, [see documentation](#commandLineTricks))*


and then you can use the **hulken** command to run a stress test:
```
hulken options.json
```
**option files makes it easy to reuse requests files when targeting different environments (dev, test, staging etc..)**

## Documentation
[1.) Settings you can override through options ](#settings)  
[2.) Dynamic payloads ](#dynamicPayloads)  
[3.) The stats ](#theStats)  
[4.) Command line tricks ](#commandLineTricks)  
[5.) Automatically generate requests files with Informants ](#informants)  
[6.) Smash responsibly - It is your foot!  ](#smashResponsibly)  
[7.) Release notes ](#releaseNotes)  
[8.) Tests ](#tests)  
[9.) License ](#license)

<a name="settings"></a>
### Settings

When you use hulken as a library you override these settings in the options object you pass in. When you use hulken as a command line tool you override these settings in the options file.

>**setting name** (default value) | explanation  

* **targetUrl** ("http://localhost") | url to application under test  
* **numberOfHulkenAgents** (1) | number of agents sending requests
* **timesToRunEachRequest** (1) | number of times to execute each request per agent  
* **requestsFilePath** ("./hulkenRequests.json") | path to requestsFile (including file name)  
* **requestsArray** (undefined) | an array of hulken requests as a json string (see Quick Examples - as a library). Overrides requestsFilePath if both are present.
* **tokensSkippingRequest** ([':']) | requests with urls containing one or more of these chars gets ignored  
* **requestsToSkip** (['/logout', 'signoff']) | requests with urls matching one of the provided urls gets ignored
* **loginRequired** (false) | is a login required to execute the requests?
* **username** ("") | mandatory if loginRequired is true ... the username
* **password** ("") | mandatory if loginRequired is true ... the password
* **usernamePostName** ("username") | the username post name
* **passwordPostName** ("password") | the password post name
* **loginUrl** ("/login") | the url to post to when logging in
* **loginResponseExpectedText** ("") | a text which hulken searches for in the response to the login post (if non is provided hulken will consider http 200 OK good enough)
* **happyTimeLimit** (10) | max test suite duration (in seconds). If the whole test takes longer than this value hulken gets angry.
* **slowRequestsTimeLimit** (3) | response times (in seconds) over this value are considered slow
* **angryOnFailedRequest** (false) | Hulken gets angry (calls error callback) if a single request fails
* **chatty** (true) | set to false to make Hulken less chatty
* **happyMessage** ("HULKEN PLEASED WITH RESULT, NO ONE NEEDS TO GET HURT TODAY!") | pass in what you want Hulken to say when Hulken is happy with a result
* **angryMessage** (".... BAD RESULT... HULKEN ANGRY!") | pass in what you want Hulken to say when Hulken is angry with a result
* **minWaitTime** (1000) | minimum wait time in milliseconds (every request waits for a random time before executing)
* **maxWaitTime** (6000) | maximum wait time in milliseconds (every request waits for a random time before executing)
* **returnAllRequests** (false) | If true the stats object will contain all executed requests (stats.allRequests)
* **headers** ({}) | set HTTP headers in a simple object: {
  "key1": "value1",
  "key2": "value2",
  "accept-encoding": "Overriden"
  }. These headers will be set for every request in the test.
* **requestValueLists** ({}) | insert value lists in a simple object: {
    usernames: ['john', 'jessica','admin'],
    cities: ['Stockholm', 'London', 'Berlin', 'New York']
  }  
* **printLoadDistribution** (false) | prints how the load was randomly distributed (see minWaitTime and maxWaitTime) during the test with timestamps:  *hh:mm:ss number of requests sent*  
example:  
12:10:37 18  
12:10:38 28  
12:10:39 17  *(at 12:10:39 17 requests were sent)*

<a name="dynamicPayloads"></a>
### Dynamic payloads


POSTs require a payload.

Besides hard coded values you can let hulken generate random post values consisting of letters, numbers or letters and numbers. The syntax is as follows:

`::random <valuetype> <numberOfChars>`

available value types are **numbers** [0-9], **letters** [A-Za-z] and **lettersandnumbers** [A-Za-z0-9].

For example, the request below will send a POST to the url */random* with a payload consisting of 3 generated property values. *random* will be a 10 characters long string of letters and numbers. *random2* will be a 15 characters long string of numbers. *random3* will be a 20 characters long string of letters.
```
{
  "method": "post",
  "path": "/random",
  "expectedTextToExist": "thank you for the random value",
  "payload": {
    "random": "::random lettersandnumbers 10",
    "random2": "::random numbers 15",
    "random3": "::random letters 20"
  }
}
```

You can also pass in **value lists** with your options file/object and have hulken pick randomly from these when executing the requests.

The syntax in your request is as follows:  
`::randomList <nameOfList>`

in options pass in:
```
requestValueLists : {
  usernames: ['john', 'jessica','admin'],
  cities: ['Stockholm', 'London', 'Berlin', 'New York']
}
```
and then in your requests file have a post that looks like this:
```
{
  "method": "post",
  "path": "/RandomList",
  "expectedTextToExist": "you have sent /RandomList a POST request",
  "payload": {
    "username": "::randomList usernames",
    "city" : "::randomList cities"
  }
}
```

<a name="theStats"></a>
### The stats

When you use hulken as a library you get callbacks with stats. This is what this stats object looks like. This object can also contain **allRequests** see [Settings](#settings)
```
{ numberOfHulkenAgents: 50,
  numberOfConcurrentRequests: 150,
  numberOfUniqueRequests: 3,
  totalSecondsElapsed: 6.001,
  avgReqResponseTime: 0.001806666666666668,
  reqsPerSecond: 24.995834027662056,
  randomRequestWaitTime: '1-6 seconds',
  slowRequests: [],
  failedRequests: [] }
```

<a name="commandLineTricks"></a>
### Command line tricks


Hulken can generate an example options file for you, all you have to do is provide the target url.
```
hulken make_options http://localhost:8080
```


<a name="informants"></a>
### Automatically generate requests files with Informants

an hulken_informant offers a quick and simple way to create a stress test suite by inspecting your application routes and auto generating the requests file for you!

[hulken_informant_express3 (works with express3)](https://www.npmjs.org/package/hulken_informant_express3)  
[hulken_informant_hapi (works with hapi.js 7)](https://www.npmjs.org/package/hulken_informant_hapi)  
[hulken_informant_hapi8 (works with hapi.js 8)](https://www.npmjs.com/package/hulken_informant_hapi8)  
**missing your framework?**  
feel free to create a *hulken_informant_x* and send me the link

<a name="smashResponsibly"></a>
### Smash responsibly!

Hulken knows no limits! Be it number of agents, times to execute each request or the length of a randomly generated post value. **IT IS YOUR FOOT! =)** Seriously though - how could hulken enforce any reasonable limits? What is reasonable depends on the application under test and the machine executing the test.

<a name="releaseNotes"></a>
### Release notes
**1.2.3**  
* add property expectedStatusCode: (int value) to a requeast to override the default 
expected status code 200.  
Example: ``expectedStatusCode: 201``

**1.2.2**  
* only npm audit fix  

**1.2.1**
* only improved the readme

**1.2.0**

* set **printLoadDistribution: true** and hulken will print how the load was randomly distributed during the test with timestamps: hh:mm:ss number of requests sent

**1.1.0**
* passing in an array of requests inline in the options file or object as requestsArray (json string) overrides requestsFilePath and the need for an external requests.json. (see updated Quick Examples - as a library)

**1.0.4**
* fix for a bug caused by non-string payload value in the requests file (thanks to [mhoyer](https://github.com/mhoyer))
* minor fixes

**1.0.3**
* only cleaned up some bad markdown in README.md

**1.0.2**
* corrected old link in make_options output
* added new informant for hapi.js 8 to ReadMe

**1.0.1**
* minor fixes

**1.0.0** (non breaking changes only)
* hulken can now pick post payloads randomly from value lists, [see documentation](#dynamicPayloads)
* cleaned up README
* added some tests

**0.10.2**
* removed unnecessary dependency on async

**0.10.1**
* minor fixes

**0.10.0**
* hulken can now generate random post values. (instead of {"foo" : "bar"} you do {"foo" : "::random letters 10"}. read more about it [here](#moreOnPosts).
* minor refactoring (mostly improving integration tests)


**0.9.0**
* set HTTP headers for every request in the test by passing in the option ```headers : {'key1' : 'value1', 'key2' : 'value2'}```
* minor code refactoring

**0.8.0**
* pass in option ```returnAllRequests: true``` to include all executed requests (path and response time) in the stats object

**0.7.4**
* minor fixes

**0.7.3**
* fixed a bug that came with the latest refactoring

**0.7.1**
* code refactoring and minor fixes

**0.7.0**
* Hulken can now generate an example options file when executed from the command line.
 ```hulken make_options http://localhost:8080``` will create
an example options file with http://localhost:8080 as the targetUrl.


<a name="tests"></a>
### Tests

`npm test`

<a name="license"></a>
### License

Released under the MIT license. Copyright (c) 2014 Johan Hellgren.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
