Skip to content

Commit

Permalink
Add distribution strategy
Browse files Browse the repository at this point in the history
* Move back alert to mogwais
* Use distribution as the default strategy
* Update REAME to document strategies
  • Loading branch information
fzaninotto committed Jan 7, 2014
1 parent 2ab7aaf commit ab22491
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 26 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ To add just the mogwais you want, use the `mogwai()` and `allMogwais()` method t

`gremlins.js` currently provides a few gremlins and mogwais:

* [alertGremlin](src/species/alert.js) prevents calls to alert() from blocking the test
* [clickerGremlin](src/species/clicker.js) clicks anywhere on the visible area of the document
* [formFillerGremlin](src/species/formFiller.js) fills forms by entering data, selecting options, clicking checkboxes, etc
* [scrollerGremlin](src/species/scroller.js) scrolls the viewport to reveal another part of the document
* [typerGremlin](src/species/typer.js) types keys on the keyboard
* [alertMogwai](src/mogwais/alert.js) prevents calls to alert() from blocking the test
* [fpsMogwai](src/mogwais/fps.js) logs the number of frames per seconds (FPS) of the browser
* [gizmoMogwai](src/mogwais/gizmo.js) can stop the gremlins when they go too far

Expand Down Expand Up @@ -181,7 +181,7 @@ If you want the attack to be repeatable, you need to seed the random number gene
horde.seed(1234);
```

### Executing Code Before or After the attack
### Executing Code Before or After The Attack

Before starting the attack, you may want to execute custom code. This is especially useful to:

Expand Down Expand Up @@ -215,6 +215,21 @@ horde.before(function waitFiveSeconds(done) {

### Setting Up a Strategy

By default, gremlins will attack in random order, in a uniform distribution, separated by a delay of 10ms. This attack strategy is called the [distribution](src/strategies/distribution.js) strategy. You can customize it using the `horde.strategy()` method:

```js
horde.strategy(gremlins.strategies.distribution()
.delay(50) // wait 50 ms between each action
.distribution([0.3, 0.3, 0.3, 0.1]) // the first three gremlins have more chances to be executed than the last
)
```

You can also use another strategy. A strategy is just a callback expecting three parameters: an array of gremlins, a parameter object (the one passed to `unleash()`), and a final callback. Two other strategies are bundled ([allTogether](src/strategies/allTogether.js) and [bySpecies](src/strategies/bySpecies.js)), and it should be fairly easy to implement a custom strategy for more sophisticated attack scenarios.

### Stopping The Attack

The horde can stop the attack in case of emmergency using the `horde.stop()` method. Gizmo uses this method to prevent further damages to the application after 10 errors, and you can use it, too, if you don't want the attack to continue.

### Customizing The Logger

By default, gremlins.js logs all gremlin actions and mogwai observations in the console. If you prefer using an alternative logging method (for instance, storing gremlins activity in LocalStorage and sending it in Ajax once every 10 seconds), just provide a logger object with 4 methods (log, info, warn, and error) to the `logger()` method:
Expand All @@ -233,6 +248,10 @@ horde.logger(customLogger);

## Contributing

Your feedback about the usage of gremlins.js in your specific context is valuable, don't hesitate to open GitHub Issues for any problem or question you may have.

All contributions are welcome. New gremlins, new mogwais, new strategies, should all be tested against the two examples bundled in the application. Don't forget to rebuild the minified version of the library using `make`.

## License

gremlins.js is licensed under the [MIT Licence](LICENSE), courtesy of [marmelab](http://marmelab.com).
9 changes: 8 additions & 1 deletion examples/TodoMVC/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ <h1>todos</h1>
var e = $.Event( "keypress", { which: 13 } );
$(document.activeElement).trigger(e);
})
.gremlin(gremlins.species.scroller())
.mogwai(gremlins.mogwais.gizmo())
.mogwai(gremlins.mogwais.fps())
.strategy(gremlins.strategies.allTogether()
.strategy(gremlins.strategies.distribution()
.delay(50)
.distribution([
0.3, // clicker
0.3, // formFiller
0.3, // todoCreator
0.1, // scroller
])
)
.before(function() {
console.profile('gremlins');
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ require(['gremlins'], function(gremlins) {
.gremlin(gremlins.species.formFiller())
.gremlin(gremlins.species.clicker().clickTypes(['click']))
.gremlin(gremlins.species.scroller())
.mogwai(gremlins.species.alert())
.gremlin(function() {
alert('here');
})
.after(function() {
this.log('finished!');
})
.mogwai(gremlins.mogwais.alert())
.mogwai(gremlins.mogwais.fps())
.mogwai(gremlins.mogwais.gizmo().maxErrors(2))
.unleash();
Expand Down
2 changes: 1 addition & 1 deletion gremlins.min.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ define(function(require) {

var gremlins = {
species: {
alert: require('./species/alert'),
clicker: require('./species/clicker'),
formFiller: require('./species/formFiller'),
scroller: require('./species/scroller'),
typer: require('./species/typer')
},
mogwais: {
alert: require('./mogwais/alert'),
fps: require('./mogwais/fps'),
gizmo: require('./mogwais/gizmo')
},
strategies: {
allTogether: require('./strategies/allTogether'),
bySpecies: require('./strategies/bySpecies')
allTogether: require('./strategies/allTogether'),
bySpecies: require('./strategies/bySpecies'),
distribution: require('./strategies/distribution')
}
};

Expand Down Expand Up @@ -439,7 +440,7 @@ define(function(require) {
this.allMogwais();
}
if (this._strategies.length === 0) {
this.strategy(gremlins.strategies.allTogether());
this.strategy(gremlins.strategies.distribution());
}

var gremlinsAndMogwais = [].concat(this._gremlins, this._mogwais);
Expand Down
36 changes: 19 additions & 17 deletions src/species/alert.js → src/mogwais/alert.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
/**
* The alert gremlin answers calls to alert()
* The alert mogwai answers calls to alert()
*
* The alert gremlin overrides window.alert, window.confirm, and window.prompt
* The alert mogwai overrides window.alert, window.confirm, and window.prompt
* to avoid stopping the stress test with blocking JavaScript calls. Instead
* of displaying a dialog, these methods are simply replaced by a write in the
* logger.
*
* var alertGremlin = gremlins.species.alert();
* horde.gremlin(alertGremlin);
* var alertMogwai = gremlins.mogwais.alert();
* horde.mogwai(alertMogwai);
*
* The alert mogwai can be customized as follows:
*
* alertGremlin.watchEvents(['alert', 'confirm', 'prompt']); // select the events to catch
* alertGremlin.confirmResponse(function() { // what a call to confirm() should return });
* alertGremlin.promptResponse(function() { // what a call to prompt() should return });
* alertGremlin.logger(loggerObject); // inject a logger
* alertMogwai.watchEvents(['alert', 'confirm', 'prompt']); // select the events to catch
* alertMogwai.confirmResponse(function() { // what a call to confirm() should return });
* alertMogwai.promptResponse(function() { // what a call to prompt() should return });
* alertMogwai.logger(loggerObject); // inject a logger
* alertMogwai.randomizer(randomizerObject); // inject a randomizer
*
* Example usage:
*
* horde.gremlin(gremlins.species.alert()
* horde.mogwai(gremlins.mogwais.alert()
* .watchEvents(['prompt'])
* .promptResponse(function() { return 'I typed garbage'; })
* );
Expand Down Expand Up @@ -63,35 +64,36 @@ define(function(require) {
/**
* @mixes config
*/
function alertGremlin() {
function alertMogwai() {
console.log('here');
if (config.watchEvents.indexOf('alert') !== -1) {
window.alert = function (msg) {
config.logger.warn('gremlin', 'alert ', msg, 'alert');
config.logger.warn('mogwai ', 'alert ', msg, 'alert');
};
}
if (config.watchEvents.indexOf('confirm') !== -1) {
window.confirm = function (msg) {
config.confirmResponse();
config.logger.warn('gremlin', 'alert ', msg, 'confirm');
config.logger.warn('mogwai ', 'alert ', msg, 'confirm');
};
}
if (config.watchEvents.indexOf('prompt') !== -1) {
window.prompt = function (msg) {
config.promptResponse();
config.logger.warn('gremlin', 'alert ', msg, 'prompt');
config.logger.warn('mogwai ', 'alert ', msg, 'prompt');
};
}
}

alertGremlin.cleanUp = function() {
alertMogwai.cleanUp = function() {
window.alert = alert;
window.confirm = confirm;
window.prompt = prompt;
return alertGremlin;
return alertMogwai;
};

configurable(alertGremlin, config);
configurable(alertMogwai, config);

return alertGremlin;
return alertMogwai;
};
});
1 change: 1 addition & 0 deletions src/species/clicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* clickerGremlin.canClick(function(element) { return true }); // to limit where the gremlin can click
* clickerGremlin.maxNbTries(5); // How many times the gremlin must look for a clickable element before quitting
* clickerGremlin.logger(loggerObject); // inject a logger
* clickerGremlin.randomizer(randomizerObject); // inject a randomizer
*
* Example usage:
*
Expand Down
117 changes: 117 additions & 0 deletions src/strategies/distribution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Execute all Gremlins randomly following a distribution, separated by 10ms delay, for 100 times
*
* This is the default attack strategy, so selecting no strategy is equivalent to
*
* var distributionStrategy = gremlins.strategies.distribution();
* horde.strategy(distributionStrategy);
*
* By default, this strategy uses a uniform distribution, i.e. all gremlins
* have an equal chance to be selected for the next action.
*
* The distribution strategy can be customized as follows:
*
* distributionStrategy.distribution([0.25, 0.25, 0.25, 0.25]); // chances for each gremlin to be selected ; total must equal 1
* distributionStrategy.delay(10); // delay in milliseconds between each wave
* distributionStrategy.nb(100); // number of waves to execute (can be overridden in params)
* distributionStrategy.randomizer(randomizerObject); // inject a randomizer
*
* Example usage:
* horde.strategy(gremlins.strategies.distribution()
* .delay(50)
* .distribution([
* 0.3, // first gremlin
* 0.3, // second gremlin
* 0.3, // third gremlin
* 0.1, // fourth gremlin
* ])
* )
*/
define(function(require) {
"use strict";

var executeInSeries = require('../utils/executeInSeries');
var configurable = require('../utils/configurable');
var Chance = require('../vendor/chance');

return function() {

/**
* @mixin
*/
var config = {
distribution: [], // percentage of each gremlin species ; the sum of all values should equal to 1
delay: 10, // delay in milliseconds between each wave
nb: 100, // number of waves to execute (can be overridden in params)
randomizer: new Chance()
};

var stopped;
var doneCallback;

/**
* @mixes config
*/
function distributionStrategy(gremlins, params, done) {
var nb = params && params.nb ? params.nb : config.nb,
gremlins = gremlins.slice(0), // clone the array to avoid modifying the original
distribution = config.distribution.length === 0 ? getUniformDistribution(gremlins) : config.distribution,
horde = this;

if (nb === 0) return done();

stopped = false;
doneCallback = done; // done can also be called by stop()

function executeNext(gremlin, i, callback) {
if (stopped) return;
if (i >= nb) return callDone();
executeInSeries([gremlin], [], horde, function() {
setTimeout(function() {
executeNext(pickGremlin(gremlins, distribution), ++i, callback);
}, config.delay);
});
}

executeNext(pickGremlin(gremlins, distribution), 0, executeNext);
}

function getUniformDistribution(gremlins) {
var len = gremlins.length;
if (len === 0) return [];
var distribution = [];
var value = 1 / len;
for (var i = 0; i < len ; i++) {
distribution.push(value);
}
return distribution;
}

function pickGremlin(gremlins, distribution) {
var chance = 0;
var random = config.randomizer.floating({ min: 0, max: 1 });
for (var i = 0, count = gremlins.length; i < count; i++) {
chance += distribution[i];
if (random <= chance) return gremlins[i];
}
// no gremlin - probably error in the distribution
return function() {};
}

distributionStrategy.stop = function() {
stopped = true;
setTimeout(callDone, 4);
};

function callDone() {
if (typeof doneCallback === 'function') {
doneCallback();
}
doneCallback = null;
}

configurable(distributionStrategy, config);

return distributionStrategy;
};
});

0 comments on commit ab22491

Please sign in to comment.