merging in latest changes from master

This commit is contained in:
Spencer Alger 2014-02-13 15:33:21 -07:00
commit 86a4d92fe7
26 changed files with 12363 additions and 82 deletions

View file

@ -9,6 +9,7 @@ module.exports = function (grunt) {
src: __dirname + '/src',
app: __dirname + '/src/kibana',
unitTestDir: __dirname + '/test/unit',
testUtilsDir: __dirname + '/test/utils',
meta: {
banner: '/*! <%= package.name %> - v<%= package.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +

View file

@ -26,5 +26,6 @@
"lodash": "~2.4.1",
"d3": "~3.4.1",
"angular-route": "~1.2.12"
}
},
"devDependencies": {}
}

32
src/config.js Normal file
View file

@ -0,0 +1,32 @@
define(function () {
/** @scratch /configuration/config.js/1
* == Configuration
* config.js is where you will find the core Kibana configuration. This file contains parameter that
* must be set before kibana is run for the first time.
*/
/** @scratch /configuration/config.js/2
* === Parameters
*/
return {
/** @scratch /configuration/config.js/5
* ==== elasticsearch
*
* The URL to your elasticsearch server. You almost certainly don't
* want +http://localhost:9200+ here. Even if Kibana and Elasticsearch are on
* the same host. By default this will attempt to reach ES at the same host you have
* kibana installed on. You probably want to set it to the FQDN of your
* elasticsearch host
*/
elasticsearch: 'http://' + window.location.hostname + ':9200',
/** @scratch /configuration/config.js/5
* ==== kibana-int
*
* The default ES index to use for storing Kibana specific object
* such as stored dashboards
*/
kibanaIndex: 'kibana-int'
};
});

View file

@ -1,6 +1,7 @@
define(function (require) {
var DataSource = require('courier/data_source');
var Docs = require('courier/docs');
var EventEmitter = require('utils/event_emitter');
var inherits = require('utils/inherits');
var errors = require('courier/errors');
@ -14,9 +15,18 @@ define(function (require) {
};
}
function emitError(source, courier, error) {
if (EventEmitter.listenerCount(source, 'error')) {
source.emit('error', error);
} else {
courier.emit('error', error);
}
}
function mergeProp(state, filters, val, key) {
switch (key) {
case 'inherits':
case '_type':
// ignore
return;
case 'filter':
@ -45,7 +55,6 @@ define(function (require) {
// all of the filters from the source chain
var filters = [];
var collectProp = _.partial(mergeProp, state, filters);
// walk the chain and merge each property
@ -81,7 +90,7 @@ define(function (require) {
return state;
}
function fetch(client, sources, cb) {
function fetchSearchResults(courier, client, sources, cb) {
if (!client) {
this.emit('error', new Error('Courier does not have a client yet, unable to fetch queries.'));
return;
@ -90,6 +99,9 @@ define(function (require) {
var all = [];
var body = '';
_.each(sources, function (source) {
if (source.getType() !== 'search') {
return;
}
all.push(source);
var state = flattenDataSource(source);
@ -106,13 +118,58 @@ define(function (require) {
if (err) return cb(err);
_.each(resp.responses, function (resp, i) {
sources[i].emit('results', resp);
var source = sources[i];
if (resp.error) return emitError(source, courier, resp);
source.emit('results', resp);
});
cb(err, resp);
});
}
function fetchDocs(courier, client, sources, cb) {
if (!client) {
this.emit('error', new Error('Courier does not have a client yet, unable to fetch queries.'));
return;
}
var all = [];
var body = {
docs: []
};
_.each(sources, function (source) {
if (source.getType() !== 'get') {
return;
}
all.push(source);
var state = flattenDataSource(source);
body.docs.push({
index: state.index,
type: state.type,
id: state.id
});
});
return client.mget({ body: body }, function (err, resp) {
if (err) return cb(err);
_.each(resp.responses, function (resp, i) {
var source = sources[i];
if (resp.error) return emitError(source, courier, resp);
source.emit('results', resp);
});
cb(err, resp);
});
}
function saveUpdate(source, fields) {
}
/**
* Federated query service, supports data sources that inherit properties
* from one another and automatically emit results.
@ -127,10 +184,13 @@ define(function (require) {
};
var fetchTimer;
var activeRequest;
var courier = this;
var sources = {
search: [],
get: []
};
var sources = [];
function doFetch() {
function doSearch() {
if (!opts.client) {
this.emit('error', new Error('Courier does not have a client, pass it ' +
'in to the constructor or set it with the .client() method'));
@ -144,7 +204,7 @@ define(function (require) {
}
// we need to catch the original promise in order to keep it's abort method
activeRequest = fetch(opts.client, sources, function (err, resp) {
activeRequest = fetchSearchResults(courier, opts.client, sources.search, function (err, resp) {
activeRequest = null;
setFetchTimeout();
@ -157,61 +217,59 @@ define(function (require) {
function setFetchTimeout() {
clearTimeout(fetchTimer);
if (opts.fetchInterval) {
fetchTimer = setTimeout(doFetch, opts.fetchInterval);
fetchTimer = setTimeout(doSearch, opts.fetchInterval);
} else {
fetchTimer = null;
}
}
function stopFetching() {
function stopFetching(type) {
clearTimeout(fetchTimer);
}
function startFetchingSource(source) {
var existing = _.find(sources, { source: source });
if (existing) return false;
sources.push(source);
// start using a DataSource in fetches/updates
function openDataSource(source) {
var type = source.getType();
if (~sources[type].indexOf(source)) return false;
sources[type].push(source);
}
function stopFetchingSource(source) {
var i = sources.indexOf(source);
if (i !== -1) {
sources.slice(i, 1);
}
if (sources.length === 0) stopFetching();
// stop using a DataSource in fetches/updates
function closeDataSource(source) {
var type = source.getType();
var i = sources[type].indexOf(source);
if (i === -1) return;
sources[type].slice(i, 1);
// only search DataSources get fetched automatically
if (type === 'search' && sources.search.length === 0) stopFetching();
}
// is there a scheduled request?
function isStarted() {
// has the courier been started?
function isRunning() {
return !!fetchTimer;
}
// chainable public api
this.isStarted = chain(this, isStarted);
this.start = chain(this, doFetch);
this.startFetchingSource = chain(this, startFetchingSource);
this.start = chain(this, doSearch);
this.running = chain(this, isRunning);
this.stop = chain(this, stopFetching);
this.stopFetchingSource = chain(this, stopFetchingSource);
this.close = chain(this, function stopFetchingAllSources() {
_.each(sources, stopFetchingSource);
});
this.close = chain(this, function () { _(sources.search).each(closeDataSource); });
this.openDataSource = chain(this, openDataSource);
this.closeDataSource = chain(this, closeDataSource);
// setter
// setters
this.client = chain(this, function (client) {
opts.client = client;
});
// setter/getter
this.fetchInterval = function (val) {
opts.fetchInterval = val;
if (isStarted()) setFetchTimeout();
if (isRunning()) setFetchTimeout();
return this;
};
// factory
this.createSource = function (state) {
return new DataSource(this, state);
this.createSource = function (type, initialState) {
return new DataSource(this, type, initialState);
};
// apply the passed in config
@ -220,10 +278,10 @@ define(function (require) {
this[key](val);
}, this);
}
inherits(Courier, EventEmitter);
// private api, exposed for testing
Courier._flattenDataSource = flattenDataSource;
inherits(Courier, EventEmitter);
return Courier;
});

View file

@ -14,21 +14,30 @@ define(function (require) {
}
}
var optionNames = [
'index',
'type',
'query',
'filter',
'sort',
'highlight',
'aggs',
'from',
'size',
'source',
'inherits'
];
var apiMethods = {
search: [
'index',
'type',
'query',
'filter',
'sort',
'highlight',
'aggs',
'from',
'size',
'source',
'inherits'
],
get: [
'index',
'type',
'id',
'sourceInclude',
'sourceExclude'
]
};
function DataSource(courier, initialState) {
function DataSource(courier, type, initialState) {
var state;
if (initialState) {
@ -38,23 +47,36 @@ define(function (require) {
} else {
state = _.cloneDeep(initialState);
}
if (state._type) {
if (type && type !== state._type) {
throw new Error('Initial state is not of the type specified for this DataSource');
} else {
type = state._type;
}
}
} else {
state = {};
}
type = type || 'search';
if (!_.has(apiMethods, type)) {
throw new TypeError('Invalid DataSource type ' + type);
}
state._type = type;
var mapper = new Mapper();
var onNewListener = _.bind(function (name) {
// new newListener is emitted before it is added, count will be 0
if (name !== 'results' || listenerCount(this, 'results') !== 0) return;
courier.startFetchingSource(this);
courier.openDataSource(this);
this.removeListener('newListener', onNewListener);
this.on('removeListener', onRemoveListener);
}, this);
var onRemoveListener = _.bind(function () {
if (listenerCount(this, 'results') > 0) return;
courier.stopFetchingSource(this);
courier.closeDataSource(this);
this.removeListener('removeListener', onRemoveListener);
this.on('newListener', onNewListener);
}, this);
@ -82,12 +104,15 @@ define(function (require) {
return _.keys(mapping);
});
};
this.getType = function () {
return state._type;
};
this.extend = function () {
return courier.createSource().inherits(this);
return courier.createSource(type).inherits(this);
};
// get/set internal state values
optionNames.forEach(function (name) {
apiMethods[type].forEach(function (name) {
this[name] = function (val) {
state[name] = val;
if (name === 'index' && arguments[1]) {

135
src/courier/docs.js Normal file
View file

@ -0,0 +1,135 @@
define(function (require) {
var _ = require('lodash');
function Docs(courier) {
// docs that we have let loose, and want to track
var tracking = {};
var watchers = {};
function respId(getResp) {
return [
encodeURIComponent(getResp._index),
encodeURIComponent(getResp._type),
encodeURIComponent(getResp._id)
].join('/');
}
function change(id, updated) {
if (watchers[id]) {
var notify = function () {
var oldVal = tracking[id]._source;
tracking[id] = _.cloneDeep(update);
watchers[id].forEach(function (watcher) {
try {
watcher(updated, oldVal);
} catch (e) { console.error(e); }
});
};
if (updated) {
notify();
} else {
courier.get('client').get({
});
}
}
}
function track(resp) {
var id = respId(resp);
var tracker = _.pick(resp, '_id', '_type', '_index', '_source');
if (tracking[id] && equal(tracking[id]._source, resp)) return false;
change(id, resp);
}
/**
* add a function to be called when objects matching
* this resp are changed
* @param {object} resp - Response like object, should contain _id, _type, and _index keys
* @param {[type]} onChange - Function to be called when changes are noticed
*/
function watch(resp, onChange) {
var id = respId(resp);
if (!watchers[id]) watchers[id] = [];
watchers[id].push(onChange);
}
function get(args, cb, onChange) {
var client = courier.get('client');
client.get(args, function (err, getResp) {
if (err) return cb(err);
watch(getResp, onChange);
return cb(void 0, getResp);
});
}
function index(args, cb) {
var client = courier.get('client');
client.index(args, function (err, indexResp) {
if (err) return cb(err);
delete indexResp.created;
indexResp._source = args.body;
track(indexResp);
return cb(void 0, indexResp);
});
}
function update(args, cb) {
var client = courier.get('client');
client.update(args, function (err, updateResp) {
if (err) return cb(err);
return cb(void 0, updateResp);
});
}
this.watch = watch;
this.get = get;
this.index = index;
this.set = index;
this.update = update;
}
function equal(o1, o2) {
/* jshint eqeqeq:false, forin:false */
if (o1 === o2) return true;
if (o1 === null || o2 === null) return false;
if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
if (t1 == t2) {
if (t1 == 'object') {
if (_.isArray(o1)) {
if (!_.isArray(o2)) return false;
if ((length = o1.length) == o2.length) {
for (key = 0; key < length; key++) {
if (!equal(o1[key], o2[key])) return false;
}
return true;
}
} else if (_.isDate(o1)) {
return _.isDate(o2) && o1.getTime() == o2.getTime();
} else if (_.isRegExp(o1) && _.isRegExp(o2)) {
return o1.toString() == o2.toString();
} else {
if (_.isArray(o2)) return false;
keySet = {};
for (key in o1) {
if (_.isFunction(o1[key])) continue;
if (!equal(o1[key], o2[key])) return false;
keySet[key] = true;
}
for (key in o2) {
if (!keySet.hasOwnProperty(key) &&
o2[key] !== undefined &&
!_.isFunction(o2[key])) return false;
}
return true;
}
}
}
return false;
}
return Docs;
});

View file

@ -8,14 +8,16 @@ define(function (require) {
*/
function Mapper(index, type) {
this.indices = function () {
return new Promise(function (resolve, reject) {
});
};
this.getFields = function () {
};
this.getFieldType = function (field, type) {
return field, type;
};
}
return Mapper;

16
src/courier/scratch.js Normal file
View file

@ -0,0 +1,16 @@
var elasticsearch = require('elasticsearch');
var es = elasticsearch.Client();
es.msearch({
body: [
{
index: 'logstash-2014.02.1111'
},
{
query: { 'match_all': {} }
}
]
}, function (err, resp) {
console.log(resp);
es.close();
});

View file

@ -1,2 +1,3 @@
<courier-test type="apache" fields="extension,response,request"></courier-test>
<courier-test type="nginx" fields=""></courier-test>
<courier-test type="nginx" fields=""></courier-test>
<courier-doc-test index="" ></courier-doc-test>

View file

@ -3,8 +3,8 @@ define(function (require) {
angular.module('kibana/controllers')
.controller('Kibana', function (courier, $scope, $rootScope) {
$rootScope.dataSource = courier.createSource()
.index('logstash-2014.02.13', 'daily')
$rootScope.dataSource = courier.createSource('search')
.index('_all')
.size(5);
// this should be triggered from within the controlling application

View file

@ -77,6 +77,7 @@ define(function (require) {
require([
'services/courier',
'services/es',
'services/config',
'controllers/kibana'
], function () {

View file

@ -14,12 +14,12 @@
};
var bowerComponents = [
'd3',
['lodash', 'dist/lodash'],
'jquery',
'angular',
'angular-route',
['elasticsearch', 'elasticsearch.angular']
'd3',
['elasticsearch', 'elasticsearch.angular'],
'jquery',
['lodash', 'dist/lodash']
];
bowerComponents.forEach(function (name) {

View file

@ -0,0 +1,89 @@
define(function (require) {
var angular = require('angular');
var configFile = require('../../config');
var _ = require('lodash');
var module = angular.module('kibana/services');
module.service('config', function ($q, es, courier) {
var app = angular.module('kibana');
var config = {};
var watchers = {};
function watch(key, onChange) {
// probably a horrible idea
if (!watchers[key]) watchers[key] = [];
watchers[key].push(onChange);
}
function change(key, val) {
if (config[key] !== val) {
var oldVal = config[key];
config[key] = val;
if (watchers[key]) {
watchers[key].forEach(function (watcher) {
watcher(val, oldVal);
});
}
}
}
function getDoc() {
var defer = $q.promise();
courier.get({
index: config.kibanaIndex,
type: 'config',
id: app.constant('kbnVersion')
}, function fetchDoc(err, doc) {
_.assign(config, doc);
defer.resolve();
}, function onDocUpdate(doc) {
_.forOwn(doc, function (val, key) {
change(key, val);
});
});
return defer.promise;
}
return {
get: function (key) {
return config[key];
},
set: function (key, val) {
// sets a value in the config
// the es doc must be updated successfully for the update to reflect in the get api.
if (key === 'elasticsearch' || key === 'kibanaIndex') {
return $q.reject(new Error('These values must be updated in the config.js file.'));
}
var defer = $q.defer();
if (config[key] === val) {
defer.resolve();
return defer.promise;
}
var body = {};
body[key] = val;
courier.update({
index: config.kibanaIndex,
type: 'config',
id: app.constant('kbnVersion'),
body: body
}, function (err) {
if (err) return defer.reject(err);
change(key, val);
defer.resolve();
});
return defer.promise;
},
$watch: watch,
init: getDoc
};
});
});

View file

@ -8,6 +8,7 @@ module.exports = {
options: {
base: [
'<%= unitTestDir %>',
'<%= testUtilsDir %>',
'<%= src %>',
'<%= root %>/node_modules/mocha',
'<%= root %>/node_modules/expect.js'

19
tasks/config/jshint.js Normal file
View file

@ -0,0 +1,19 @@
module.exports = function(config) {
return {
// just lint the source dir
source: {
files: {
src: ['Gruntfile.js', '<%= root %>/src/**/*.js']
}
},
options: {
jshintrc: '<%= root %>/.jshintrc',
ignores: [
'node_modules/*',
'dist/*',
'sample/*',
'<%= root %>/src/bower_components/**/*'
]
}
};
};

4
tasks/default.js Normal file
View file

@ -0,0 +1,4 @@
// Lint and build CSS
module.exports = function(grunt) {
grunt.registerTask('default', ['jshint:source']);
};

View file

@ -22,6 +22,19 @@
<!-- tests -->
<script>
require.config({
paths: {
sinon: '../sinon'
},
shim: {
'sinon/sinon': {
deps: [
'sinon/sinon-timers-1.8.2'
],
exports: 'sinon'
}
}
})
require([
'/specs/courier.js',
'/specs/data_source.js'

View file

@ -1,6 +1,7 @@
define(function (require) {
var Courier = require('courier/courier');
var _ = require('lodash');
var sinon = require('sinon/sinon');
describe('Courier Module', function () {
@ -22,37 +23,51 @@ define(function (require) {
describe('sync API', function () {
var courier;
beforeEach(function () {
courier = new Courier();
});
afterEach(function () {
courier.close();
if (courier) {
courier.close();
}
});
describe('#fetchInterval', function () {
it('sets the interval in milliseconds that queries will be fetched', function () {
courier.fetchInterval(1000);
expect(courier.fetchInterval()).to.eql(1000);
});
it('sets the interval in milliseconds that queries will be fetched');
it('resets the timer if the courier has been started');
});
describe('#define', function () {
it('creates an empty (match all) DataSource object', function () {
var source = courier.define();
expect(source._state()).to.eql({});
describe('#createSource', function () {
it('creates an empty search DataSource object', function () {
courier = new Courier();
var source = courier.createSource();
expect(source._state()).to.eql({ _type: 'search' });
});
it('optionally accepts a type for the DataSource', function () {
var courier = new Courier();
expect(courier.createSource()._state()._type).to.eql('search');
expect(courier.createSource('search')._state()._type).to.eql('search');
expect(courier.createSource('get')._state()._type).to.eql('get');
expect(function () {
courier.createSource('invalid type');
}).to.throwError(TypeError);
});
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
courier = new Courier();
var savedState = JSON.stringify({
index: 'logstash-[YYYY-MM-DD]'
_type: 'get',
index: 'logstash-[YYYY-MM-DD]',
type: 'nginx',
id: '1'
});
var source = courier.define(savedState);
var source = courier.createSource('get', savedState);
expect(source + '').to.eql(savedState);
});
});
describe('#start', function () {
it('triggers a fetch and begins the fetch cycle');
it('triggers a fetch and begins the fetch cycle', function () {
courier = new Courier();
});
});
describe('#stop', function () {
@ -63,7 +78,7 @@ define(function (require) {
describe('source req tracking', function () {
it('updates the stored query when the data source is updated', function () {
var courier = new Courier();
var source = courier.define();
var source = courier.createSource('search');
source.on('results', _.noop);
source.index('the index name');
@ -77,7 +92,7 @@ define(function (require) {
it('merges the state of one data source with it\'s parents', function () {
var courier = new Courier();
var root = courier.define()
var root = courier.createSource('search')
.index('people')
.type('students')
.filter({
@ -86,7 +101,7 @@ define(function (require) {
}
});
var math = courier.define()
var math = courier.createSource('search')
.inherits(root)
.filter({
terms: {

View file

@ -1,5 +1,34 @@
define(function (require) {
var Courier = require('courier/courier');
var DataSource = require('courier/data_source');
describe('DataSource class', function () {
var courier = new Courier();
describe('::new', function () {
it('accepts and validates a type', function () {
var source = new DataSource(courier, 'get');
expect(source._state()._type).to.eql('get');
source = new DataSource(courier, 'search');
expect(source._state()._type).to.eql('search');
expect(function () {
source = new DataSource(courier, 'invalid Type');
}).to.throwError(TypeError);
});
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
var savedState = JSON.stringify({
_type: 'get',
index: 'logstash-[YYYY-MM-DD]',
type: 'nginx',
id: '1'
});
var source = new DataSource(courier, 'get', savedState);
expect(source + '').to.eql(savedState);
});
});
describe('events', function () {
describe('results', function () {
it('emits when a new result is available');

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
/**
* Sinon.JS 1.8.2, 2014/02/13
*
* @author Christian Johansen (christian@cjohansen.no)
* @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
*
* (The BSD License)
*
* Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, setTimeout, setInterval, clearTimeout, clearInterval, Date*/
/**
* Helps IE run the fake timers. By defining global functions, IE allows
* them to be overwritten at a later point. If these are not defined like
* this, overwriting them will result in anything from an exception to browser
* crash.
*
* If you don't require fake timers to work in IE, don't include this file.
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
function setTimeout() {}
function clearTimeout() {}
function setImmediate() {}
function clearImmediate() {}
function setInterval() {}
function clearInterval() {}
function Date() {}
// Reassign the original functions. Now their writable attribute
// should be true. Hackish, I know, but it works.
setTimeout = sinon.timers.setTimeout;
clearTimeout = sinon.timers.clearTimeout;
setImmediate = sinon.timers.setImmediate;
clearImmediate = sinon.timers.clearImmediate;
setInterval = sinon.timers.setInterval;
clearInterval = sinon.timers.clearInterval;
Date = sinon.timers.Date;
/*global sinon*/
/**
* Helps IE run the fake XMLHttpRequest. By defining global functions, IE allows
* them to be overwritten at a later point. If these are not defined like
* this, overwriting them will result in anything from an exception to browser
* crash.
*
* If you don't require fake XHR to work in IE, don't include this file.
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
function XMLHttpRequest() {}
// Reassign the original function. Now its writable attribute
// should be true. Hackish, I know, but it works.
XMLHttpRequest = sinon.xhr.XMLHttpRequest || undefined;

View file

@ -0,0 +1,86 @@
/**
* Sinon.JS 1.8.2, 2014/02/13
*
* @author Christian Johansen (christian@cjohansen.no)
* @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
*
* (The BSD License)
*
* Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, setTimeout, setInterval, clearTimeout, clearInterval, Date*/
/**
* Helps IE run the fake timers. By defining global functions, IE allows
* them to be overwritten at a later point. If these are not defined like
* this, overwriting them will result in anything from an exception to browser
* crash.
*
* If you don't require fake timers to work in IE, don't include this file.
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
function setTimeout() {}
function clearTimeout() {}
function setImmediate() {}
function clearImmediate() {}
function setInterval() {}
function clearInterval() {}
function Date() {}
// Reassign the original functions. Now their writable attribute
// should be true. Hackish, I know, but it works.
setTimeout = sinon.timers.setTimeout;
clearTimeout = sinon.timers.clearTimeout;
setImmediate = sinon.timers.setImmediate;
clearImmediate = sinon.timers.clearImmediate;
setInterval = sinon.timers.setInterval;
clearInterval = sinon.timers.clearInterval;
Date = sinon.timers.Date;
/*global sinon*/
/**
* Helps IE run the fake XMLHttpRequest. By defining global functions, IE allows
* them to be overwritten at a later point. If these are not defined like
* this, overwriting them will result in anything from an exception to browser
* crash.
*
* If you don't require fake XHR to work in IE, don't include this file.
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
function XMLHttpRequest() {}
// Reassign the original function. Now its writable attribute
// should be true. Hackish, I know, but it works.
XMLHttpRequest = sinon.xhr.XMLHttpRequest || undefined;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,419 @@
/**
* Sinon.JS 1.8.2, 2014/02/13
*
* @author Christian Johansen (christian@cjohansen.no)
* @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
*
* (The BSD License)
*
* Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
/*global module, require, window*/
/**
* Fake timer API
* setTimeout
* setInterval
* clearTimeout
* clearInterval
* tick
* reset
* Date
*
* Inspired by jsUnitMockTimeOut from JsUnit
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
if (typeof sinon == "undefined") {
var sinon = {};
}
(function (global) {
var id = 1;
function addTimer(args, recurring) {
if (args.length === 0) {
throw new Error("Function requires at least 1 parameter");
}
if (typeof args[0] === "undefined") {
throw new Error("Callback must be provided to timer calls");
}
var toId = id++;
var delay = args[1] || 0;
if (!this.timeouts) {
this.timeouts = {};
}
this.timeouts[toId] = {
id: toId,
func: args[0],
callAt: this.now + delay,
invokeArgs: Array.prototype.slice.call(args, 2)
};
if (recurring === true) {
this.timeouts[toId].interval = delay;
}
return toId;
}
function parseTime(str) {
if (!str) {
return 0;
}
var strings = str.split(":");
var l = strings.length, i = l;
var ms = 0, parsed;
if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
throw new Error("tick only understands numbers and 'h:m:s'");
}
while (i--) {
parsed = parseInt(strings[i], 10);
if (parsed >= 60) {
throw new Error("Invalid time " + str);
}
ms += parsed * Math.pow(60, (l - i - 1));
}
return ms * 1000;
}
function createObject(object) {
var newObject;
if (Object.create) {
newObject = Object.create(object);
} else {
var F = function () {};
F.prototype = object;
newObject = new F();
}
newObject.Date.clock = newObject;
return newObject;
}
sinon.clock = {
now: 0,
create: function create(now) {
var clock = createObject(this);
if (typeof now == "number") {
clock.now = now;
}
if (!!now && typeof now == "object") {
throw new TypeError("now should be milliseconds since UNIX epoch");
}
return clock;
},
setTimeout: function setTimeout(callback, timeout) {
return addTimer.call(this, arguments, false);
},
clearTimeout: function clearTimeout(timerId) {
if (!this.timeouts) {
this.timeouts = [];
}
if (timerId in this.timeouts) {
delete this.timeouts[timerId];
}
},
setInterval: function setInterval(callback, timeout) {
return addTimer.call(this, arguments, true);
},
clearInterval: function clearInterval(timerId) {
this.clearTimeout(timerId);
},
setImmediate: function setImmediate(callback) {
var passThruArgs = Array.prototype.slice.call(arguments, 1);
return addTimer.call(this, [callback, 0].concat(passThruArgs), false);
},
clearImmediate: function clearImmediate(timerId) {
this.clearTimeout(timerId);
},
tick: function tick(ms) {
ms = typeof ms == "number" ? ms : parseTime(ms);
var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;
var timer = this.firstTimerInRange(tickFrom, tickTo);
var firstException;
while (timer && tickFrom <= tickTo) {
if (this.timeouts[timer.id]) {
tickFrom = this.now = timer.callAt;
try {
this.callTimer(timer);
} catch (e) {
firstException = firstException || e;
}
}
timer = this.firstTimerInRange(previous, tickTo);
previous = tickFrom;
}
this.now = tickTo;
if (firstException) {
throw firstException;
}
return this.now;
},
firstTimerInRange: function (from, to) {
var timer, smallest = null, originalTimer;
for (var id in this.timeouts) {
if (this.timeouts.hasOwnProperty(id)) {
if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {
continue;
}
if (smallest === null || this.timeouts[id].callAt < smallest) {
originalTimer = this.timeouts[id];
smallest = this.timeouts[id].callAt;
timer = {
func: this.timeouts[id].func,
callAt: this.timeouts[id].callAt,
interval: this.timeouts[id].interval,
id: this.timeouts[id].id,
invokeArgs: this.timeouts[id].invokeArgs
};
}
}
}
return timer || null;
},
callTimer: function (timer) {
if (typeof timer.interval == "number") {
this.timeouts[timer.id].callAt += timer.interval;
} else {
delete this.timeouts[timer.id];
}
try {
if (typeof timer.func == "function") {
timer.func.apply(null, timer.invokeArgs);
} else {
eval(timer.func);
}
} catch (e) {
var exception = e;
}
if (!this.timeouts[timer.id]) {
if (exception) {
throw exception;
}
return;
}
if (exception) {
throw exception;
}
},
reset: function reset() {
this.timeouts = {};
},
Date: (function () {
var NativeDate = Date;
function ClockDate(year, month, date, hour, minute, second, ms) {
// Defensive and verbose to avoid potential harm in passing
// explicit undefined when user does not pass argument
switch (arguments.length) {
case 0:
return new NativeDate(ClockDate.clock.now);
case 1:
return new NativeDate(year);
case 2:
return new NativeDate(year, month);
case 3:
return new NativeDate(year, month, date);
case 4:
return new NativeDate(year, month, date, hour);
case 5:
return new NativeDate(year, month, date, hour, minute);
case 6:
return new NativeDate(year, month, date, hour, minute, second);
default:
return new NativeDate(year, month, date, hour, minute, second, ms);
}
}
return mirrorDateProperties(ClockDate, NativeDate);
}())
};
function mirrorDateProperties(target, source) {
if (source.now) {
target.now = function now() {
return target.clock.now;
};
} else {
delete target.now;
}
if (source.toSource) {
target.toSource = function toSource() {
return source.toSource();
};
} else {
delete target.toSource;
}
target.toString = function toString() {
return source.toString();
};
target.prototype = source.prototype;
target.parse = source.parse;
target.UTC = source.UTC;
target.prototype.toUTCString = source.prototype.toUTCString;
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
return target;
}
var methods = ["Date", "setTimeout", "setInterval",
"clearTimeout", "clearInterval"];
if (typeof global.setImmediate !== "undefined") {
methods.push("setImmediate");
}
if (typeof global.clearImmediate !== "undefined") {
methods.push("clearImmediate");
}
function restore() {
var method;
for (var i = 0, l = this.methods.length; i < l; i++) {
method = this.methods[i];
if (global[method].hadOwnProperty) {
global[method] = this["_" + method];
} else {
try {
delete global[method];
} catch (e) {}
}
}
// Prevent multiple executions which will completely remove these props
this.methods = [];
}
function stubGlobal(method, clock) {
clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method);
clock["_" + method] = global[method];
if (method == "Date") {
var date = mirrorDateProperties(clock[method], global[method]);
global[method] = date;
} else {
global[method] = function () {
return clock[method].apply(clock, arguments);
};
for (var prop in clock[method]) {
if (clock[method].hasOwnProperty(prop)) {
global[method][prop] = clock[method][prop];
}
}
}
global[method].clock = clock;
}
sinon.useFakeTimers = function useFakeTimers(now) {
var clock = sinon.clock.create(now);
clock.restore = restore;
clock.methods = Array.prototype.slice.call(arguments,
typeof now == "number" ? 1 : 0);
if (clock.methods.length === 0) {
clock.methods = methods;
}
for (var i = 0, l = clock.methods.length; i < l; i++) {
stubGlobal(clock.methods[i], clock);
}
return clock;
};
}(typeof global != "undefined" && typeof global !== "function" ? global : this));
sinon.timers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined),
clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined),
setInterval: setInterval,
clearInterval: clearInterval,
Date: Date
};
if (typeof module !== 'undefined' && module.exports) {
module.exports = sinon;
}

View file

@ -0,0 +1,66 @@
/**
* Sinon.JS 1.8.2, 2014/02/13
*
* @author Christian Johansen (christian@cjohansen.no)
* @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
*
* (The BSD License)
*
* Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, setTimeout, setInterval, clearTimeout, clearInterval, Date*/
/**
* Helps IE run the fake timers. By defining global functions, IE allows
* them to be overwritten at a later point. If these are not defined like
* this, overwriting them will result in anything from an exception to browser
* crash.
*
* If you don't require fake timers to work in IE, don't include this file.
*
* @author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright (c) 2010-2013 Christian Johansen
*/
function setTimeout() {}
function clearTimeout() {}
function setImmediate() {}
function clearImmediate() {}
function setInterval() {}
function clearInterval() {}
function Date() {}
// Reassign the original functions. Now their writable attribute
// should be true. Hackish, I know, but it works.
setTimeout = sinon.timers.setTimeout;
clearTimeout = sinon.timers.clearTimeout;
setImmediate = sinon.timers.setImmediate;
clearImmediate = sinon.timers.clearImmediate;
setInterval = sinon.timers.setInterval;
clearInterval = sinon.timers.clearInterval;
Date = sinon.timers.Date;

4728
test/utils/sinon/sinon.js Normal file

File diff suppressed because it is too large Load diff