Major updates for testing and grunt, jenkins tests are now powered by the jenkins.sh script in the scripts directory.

This commit is contained in:
Spencer Alger
2013-12-12 15:39:42 -07:00
parent 270763e0a7
commit 345ac776ef
68 changed files with 1628 additions and 8790 deletions

View File

@ -1,24 +0,0 @@
var force = process.env.FORCE || process.env.FORCE_GEN;
if (!force) {
var argv = require('optimist')
.options({
force: {
alias: 'f',
default: false,
boolean: true
}
});
if (process.env.npm_config_argv) {
// when called by NPM
argv = argv.parse(JSON.parse(process.env.npm_config_argv).original);
} else {
// when called directly
argv = argv.argv;
}
force = argv.force;
}
module.exports = force;

71
scripts/generate/index.js Normal file
View File

@ -0,0 +1,71 @@
var cp = require('child_process');
var async = require('async');
var argv = require('optimist')
.options({
force: {
alias: 'f',
default: false,
boolean: true
},
verbose: {
alias: 'v',
default: false,
boolean: true
},
api: {
default: true,
boolean: true
},
tests: {
default: true,
boolean: true
}
});
if (process.env.npm_config_argv) {
// when called by NPM
argv = argv.parse(JSON.parse(process.env.npm_config_argv).original);
} else {
// when called directly
argv = argv.argv;
}
if (!argv.force && process.env.FORCE || process.env.FORCE_GEN) {
argv.force = argv.f = process.env.FORCE || process.env.FORCE_GEN;
}
function updateSubmodules(done) {
cp.exec('git submodule update --init --recursive', function (err, stdout, stderr) {
stdout = stdout.trim();
stderr = stderr.trim();
if (err) {
done(new Error('Unable to update submodules: ' + err.message));
return;
} else if (argv.verbose && stdout) {
console.log(stdout);
}
if (stderr) {
console.error(stderr);
}
done();
});
}
updateSubmodules(function (err) {
if (err) {
throw err;
}
var tasks = [];
if (argv.api) {
tasks.push(require('./js_api'));
}
if (argv.tests) {
tasks.push(require('./yaml_tests'));
}
async.parallel(tasks, function () {});
});

344
scripts/generate/js_api.js Normal file
View File

@ -0,0 +1,344 @@
var aliases; // defined at the bottom of this file.
module.exports = function (done) {
/**
* Read the API actions form the rest-api-spec repo.
* @type {[type]}
*/
var _ = require('../../src/lib/utils');
var fs = require('relative-fs').relativeTo(__dirname);
var async = require('async');
var templates = require('./templates');
var castExistsRE = /exists/;
var usesBulkBodyRE = /^(bulk|msearch)$/;
var urlParamRE = /\{(\w+)\}/g;
var files; // populated in readSpecFiles
var apiSpec; // populated by parseSpecFiles
// generate the API
async.series([
readSpecFiles,
parseSpecFiles,
writeApiFile,
ensureDocsDir,
writeMethodList,
writeMethodDocs
], done);
function readSpecFiles(done) {
var apiDir = '../../src/rest-api-spec/api/';
files = fs.readdirSync(apiDir).map(function (filename) {
return require(apiDir + filename);
});
done();
}
function parseSpecFiles(done) {
var actions = [];
files.forEach(function (spec) {
__puke__transformSpec(spec).forEach(function (action) {
actions.push(action);
});
});
// collect the namespaces from the action locations
var namespaces = _.filter(_.map(actions, function (action) {
if (~action.location.indexOf('.')) {
var path = action.location.split('.').slice(0, -1);
_.pull(path, 'prototype');
return path.join('.');
}
}));
// seperate the proxy actions
var groups = _.groupBy(actions, function (action) {
return action.proxy ? 'proxies' : 'normal';
});
apiSpec = {
actions: groups.normal,
proxies: groups.proxies,
namespaces: _.unique(namespaces.sort(), true)
};
done();
}
function writeApiFile(done) {
var outputPath = require('path').join(__dirname, '../../src/lib/api.js');
console.log('writing', apiSpec.actions.length, 'api actions to', outputPath);
fs.writeFile(outputPath, templates.apiFile(apiSpec), done);
}
function ensureDocsDir(done) {
fs.stat('../../docs', function (err, stat) {
if (err) {
if (err.message.match(/enoent/i)) {
fs.mkdir('../../docs', done);
} else {
done(err);
}
} else if (stat.isDirectory()) {
done();
} else {
done(new Error('../../docs exists, but it is not a directory'));
}
});
}
function writeMethodList(done) {
fs.writeFile(
'../../docs/_method_list.jade',
templates.apiMethodList(apiSpec),
done
);
}
function writeMethodDocs(done) {
fs.writeFile(
'../../docs/_methods.jade',
templates.apiMethods(apiSpec),
done
);
}
function __puke__transformSpec(spec) {
var actions = [];
// itterate all of the specs within the file, should only be one
_.each(spec, function (def, name) {
//camelcase the name
name = _.map(name.split('.'), _.camelCase).join('.');
var steps = name.split('.');
function transformParamKeys(note, param, key) {
var cmlKey = _.camelCase(key);
if (cmlKey !== key) {
param.name = key;
}
note[cmlKey] = param;
}
def.url.params = _.transform(def.url.params, transformParamKeys, {});
def.url.parts = _.transform(def.url.parts, transformParamKeys, {});
var allParams = _.extend({}, def.url.params, def.url.parts);
var spec = {
name: name,
methods: _.map(def.methods, function (m) { return m.toUpperCase(); }),
params: def.url.params,
body: def.body || null,
path2lib: _.repeat('../', steps.length + 1) + 'lib/'
};
if (def.body && def.body.requires) {
spec.needBody = true;
}
if (usesBulkBodyRE.test(name)) {
spec.bulkBody = true;
}
if (castExistsRE.test(name)) {
spec.castExists = true;
}
var urls = _.difference(def.url.paths, aliases[name]);
urls = _.map(urls, function (url) {
var optionalVars = {};
var requiredVars = {};
var param;
var name;
var target;
var match;
if (url.charAt(0) !== '/') {
url = '/' + url;
}
while (match = urlParamRE.exec(url)) {
name = _.camelCase(match[1]);
param = def.url.parts[name] || {};
target = (param.required || !param.default) ? requiredVars : optionalVars;
target[name] = _.omit(param, 'required', 'description', 'name');
}
return _.omit({
fmt: url.replace(urlParamRE, function (full, match) {
return '<%=' + _.camelCase(match) + '%>';
}),
opt: _.size(optionalVars) ? optionalVars : null,
req: _.size(requiredVars) ? requiredVars : null,
sortOrder: _.size(requiredVars) * -1
}, function (v) {
return !v;
});
});
if (urls.length > 1) {
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
return _.omit(url, 'sortOrder');
});
} else {
spec.url = urls[0];
}
spec.params = _.transform(spec.params, function (note, param, name) {
// param.name = name;
note[name] = _.pick(param, [
'type', 'default', 'options', 'required', 'name'
]);
}, {});
if (_.size(spec.params) === 0) {
delete spec.params;
}
// escape method names with "special" keywords
var location = spec.name.split('.').join('.prototype.')
.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
var action = {
_methods: spec.methods,
spec: _.pick(spec, [
'params',
'url',
'urls',
'needBody',
'bulkBody',
'castExists',
'castNotFound'
]),
location: location,
docUrl: def.documentation,
name: spec.name,
allParams: allParams
};
function hasMethod(/* ...methods */) {
for (var i = 0; i < arguments.length; i++) {
if (~action._methods.indexOf(arguments[i])) {
continue;
} else {
return false;
}
}
return true;
}
function methodsAre(/* ...methods */) {
return hasMethod.apply(null, arguments) && arguments.length === action._methods.length;
}
var method;
if (action._methods.length === 1) {
method = action._methods[0];
} else {
// we need to define what the default method(s) will be
if (hasMethod('DELETE', 'POST')) {
method = 'POST';
}
else if (methodsAre('DELETE')) {
method = 'DELETE';
}
else if (methodsAre('POST', 'PUT')) {
method = action.name.match(/put/i) ? 'PUT' : 'POST';
}
else if (methodsAre('GET', 'POST')) {
method = 'POST';
}
else if (methodsAre('GET', 'HEAD')) {
if (action.spec.castExists) {
method = 'HEAD';
} else {
method = 'GET';
}
}
}
if (method) {
if (method !== 'GET') {
action.spec.method = method;
}
} else {
throw new Error('unable to pick a method for ' + JSON.stringify(action, null, ' '));
}
if (action.name === 'create') {
action.proxy = 'index';
action.transformBody = 'params.op_type = \'create\';';
}
actions.push(action);
});
return actions;
}
};
aliases = {
'cluster.nodeHotThreads': [
'/_cluster/nodes/hotthreads',
'/_cluster/nodes/hot_threads',
'/_nodes/hot_threads',
'/_cluster/nodes/{node_id}/hotthreads',
'/_cluster/nodes/{node_id}/hot_threads',
'/_nodes/{node_id}/hot_threads'
],
'cluster.nodeInfo': [
'/_cluster/nodes',
'/_nodes/settings',
'/_nodes/os',
'/_nodes/process',
'/_nodes/jvm',
'/_nodes/thread_pool',
'/_nodes/network',
'/_nodes/transport',
'/_nodes/http',
'/_nodes/plugin',
'/_cluster/nodes/{node_id}',
'/_nodes/{node_id}/settings',
'/_nodes/{node_id}/os',
'/_nodes/{node_id}/process',
'/_nodes/{node_id}/jvm',
'/_nodes/{node_id}/thread_pool',
'/_nodes/{node_id}/network',
'/_nodes/{node_id}/transport',
'/_nodes/{node_id}/http',
'/_nodes/{node_id}/plugin'
],
'cluster.nodeShutdown': [
'/_cluster/nodes/_shutdown'
],
'cluster.nodeStats': [
'/_cluster/nodes/stats',
'/_nodes/stats/{metric_family}',
'/_nodes/stats/indices/{metric}/{fields}',
'/_cluster/nodes/{node_id}/stats',
'/_nodes/{node_id}/stats/{metric_family}',
'/_nodes/{node_id}/stats/indices/{metric}/{fields}'
],
'get': [
'/{index}/{type}/{id}/_source'
],
'indices.deleteMapping': [
'/{index}/{type}/_mapping'
],
'indices.stats': [
'_stats/{metric_family}',
'/_stats/indexing',
'/_stats/indexing/{indexing_types}',
'/_stats/search/{search_groups}',
'/_stats/fielddata/{fields}',
'/{index}/_stats/{metric_family}',
'/{index}/_stats/indexing',
'/{index}/_stats/search/{search_groups}',
'/{index}/_stats/fielddata/{fields}'
],
'search': [
'/_search'
]
};

View File

@ -1,3 +0,0 @@
Generate the API using the es [REST api spec](https://github.com/elasticsearch/elasticsearch-rest-api-spec).
run by calling `npm run generate`. Force it to update, even if their has not been a new commit, but calling with `--force` or `-f`

View File

@ -1,240 +0,0 @@
var _ = require('../../../src/lib/utils');
var EventEmitter = require('events').EventEmitter;
var aliases = require('./aliases');
var castExistsRE = /exists/;
var usesBulkBodyRE = /^(bulk|msearch)$/;
var urlParamRE = /\{(\w+)\}/g;
var specCount = 0;
var actions = [];
var doneParsing = false;
require('../../get_spec')
.get('api/*.json')
.on('entry', transformFile)
.on('end', function () {
doneParsing = true;
if (actions.length === specCount) {
module.exports.emit('ready', actions);
}
});
function transformFile(entry) {
specCount++;
// itterate all of the specs within the file, should only be one
_.each(JSON.parse(entry.data), function (def, name) {
//camelcase the name
name = _.map(name.split('.'), _.camelCase).join('.');
var steps = name.split('.');
function transformParamKeys(note, param, key) {
var cmlKey = _.camelCase(key);
if (cmlKey !== key) {
param.name = key;
}
note[cmlKey] = param;
}
def.url.params = _.transform(def.url.params, transformParamKeys, {});
def.url.parts = _.transform(def.url.parts, transformParamKeys, {});
var allParams = _.extend({}, def.url.params, def.url.parts);
var spec = {
name: name,
methods: _.map(def.methods, function (m) { return m.toUpperCase(); }),
params: def.url.params,
body: def.body || null,
path2lib: _.repeat('../', steps.length + 1) + 'lib/'
};
if (def.body && def.body.requires) {
spec.needBody = true;
}
if (usesBulkBodyRE.test(name)) {
spec.bulkBody = true;
}
if (castExistsRE.test(name)) {
spec.castExists = true;
}
var urls = _.difference(def.url.paths, aliases[name]);
urls = _.map(urls, function (url) {
var optionalVars = {};
var requiredVars = {};
var param;
var name;
var target;
var match;
if (url.charAt(0) !== '/') {
url = '/' + url;
}
while (match = urlParamRE.exec(url)) {
name = _.camelCase(match[1]);
param = def.url.parts[name] || {};
target = (param.required || !param.default) ? requiredVars : optionalVars;
target[name] = _.omit(param, 'required', 'description', 'name');
}
return _.omit({
fmt: url.replace(urlParamRE, function (full, match) {
return '<%=' + _.camelCase(match) + '%>';
}),
opt: _.size(optionalVars) ? optionalVars : null,
req: _.size(requiredVars) ? requiredVars : null,
sortOrder: _.size(requiredVars) * -1
}, function (v) {
return !v;
});
});
if (urls.length > 1) {
spec.urls = _.map(_.sortBy(urls, 'sortOrder'), function (url) {
return _.omit(url, 'sortOrder');
});
} else {
spec.url = urls[0];
}
spec.params = _.transform(spec.params, function (note, param, name) {
// param.name = name;
note[name] = _.pick(param, [
'type', 'default', 'options', 'required', 'name'
]);
}, {});
if (_.size(spec.params) === 0) {
delete spec.params;
}
// escape method names with "special" keywords
var location = spec.name.split('.').join('.prototype.')
.replace(/(^|\.)(delete|default)(\.|$)/g, '[\'$2\']');
var action = {
_methods: spec.methods,
spec: _.pick(spec, [
'params',
'url',
'urls',
'needBody',
'bulkBody',
'castExists',
'castNotFound'
]),
location: location,
docUrl: def.documentation,
name: spec.name,
allParams: allParams
};
function hasMethod(/* ...methods */) {
for (var i = 0; i < arguments.length; i++) {
if (~action._methods.indexOf(arguments[i])) {
continue;
} else {
return false;
}
}
return true;
}
function methodsAre(/* ...methods */) {
return hasMethod.apply(null, arguments) && arguments.length === action._methods.length;
}
var method;
if (action._methods.length === 1) {
method = action._methods[0];
} else {
// we need to define what the default method(s) will be
if (hasMethod('DELETE', 'POST')) {
method = 'POST';
}
else if (methodsAre('DELETE')) {
method = 'DELETE';
}
else if (methodsAre('POST', 'PUT')) {
method = action.name.match(/put/i) ? 'PUT' : 'POST';
}
else if (methodsAre('GET', 'POST')) {
method = 'POST';
}
else if (methodsAre('GET', 'HEAD')) {
if (action.spec.castExists) {
method = 'HEAD';
} else {
method = 'GET';
}
}
}
if (method) {
if (method !== 'GET') {
action.spec.method = method;
}
} else {
throw new Error('unable to pick a method for ' + JSON.stringify(action, null, ' '));
}
if (action.name === 'create') {
action.proxy = 'index';
action.transformBody = 'params.op_type = \'create\';';
}
if (actions.push(action) === specCount && doneParsing) {
module.exports.emit('ready', actions);
}
});
}
/**
* un-comment to print out the default method for any action that has multiple options
*/
module.exports = new EventEmitter();
// module.exports.on('ready', function (actions) {
// var longestName = 0;
// var reports = {
// multi_methods: [],
// get_with_body: []
// };
// actions.forEach(function (action) {
// var name;
// // console.log(action);
// if (action._methods.length > 1) {
// name = action.name + ' (' + action._methods.join('/') + ')';
// longestName = Math.max(name.length, longestName);
// reports.multi_methods.push([name, action.spec.method || 'GET', action.docUrl]);
// }
// if (action._methods.length === 1 && action._methods[0] === 'GET' && action.body) {
// name = action.name + ' (' + action._methods.join('/') + ')';
// longestName = Math.max(name.length, longestName);
// reports.get_with_body.push([name, action.spec.method || 'GET', action.docUrl]);
// }
// });
// Object.keys(reports).forEach(function (key) {
// console.log('\n' + key);
// if (reports[key].length) {
// reports[key].forEach(function (line) {
// var name = line[0];
// var def = line[1];
// var docUrl = line[2];
// var spacing = (new Array(longestName - name.length + 1)).join(' ');
// console.log(name + spacing + ' [' + def + (def.length === 3 ? ' ' : '') + '] -> ' + docUrl);
// });
// } else {
// console.log('--nada--');
// }
// console.log('\n');
// });
// });

View File

@ -1,63 +0,0 @@
module.exports = {
'cluster.nodeHotThreads': [
'/_cluster/nodes/hotthreads',
'/_cluster/nodes/hot_threads',
'/_nodes/hot_threads',
'/_cluster/nodes/{node_id}/hotthreads',
'/_cluster/nodes/{node_id}/hot_threads',
'/_nodes/{node_id}/hot_threads'
],
'cluster.nodeInfo': [
'/_cluster/nodes',
'/_nodes/settings',
'/_nodes/os',
'/_nodes/process',
'/_nodes/jvm',
'/_nodes/thread_pool',
'/_nodes/network',
'/_nodes/transport',
'/_nodes/http',
'/_nodes/plugin',
'/_cluster/nodes/{node_id}',
'/_nodes/{node_id}/settings',
'/_nodes/{node_id}/os',
'/_nodes/{node_id}/process',
'/_nodes/{node_id}/jvm',
'/_nodes/{node_id}/thread_pool',
'/_nodes/{node_id}/network',
'/_nodes/{node_id}/transport',
'/_nodes/{node_id}/http',
'/_nodes/{node_id}/plugin'
],
'cluster.nodeShutdown': [
'/_cluster/nodes/_shutdown'
],
'cluster.nodeStats': [
'/_cluster/nodes/stats',
'/_nodes/stats/{metric_family}',
'/_nodes/stats/indices/{metric}/{fields}',
'/_cluster/nodes/{node_id}/stats',
'/_nodes/{node_id}/stats/{metric_family}',
'/_nodes/{node_id}/stats/indices/{metric}/{fields}'
],
'get': [
'/{index}/{type}/{id}/_source'
],
'indices.deleteMapping': [
'/{index}/{type}/_mapping'
],
'indices.stats': [
'_stats/{metric_family}',
'/_stats/indexing',
'/_stats/indexing/{indexing_types}',
'/_stats/search/{search_groups}',
'/_stats/fielddata/{fields}',
'/{index}/_stats/{metric_family}',
'/{index}/_stats/indexing',
'/{index}/_stats/search/{search_groups}',
'/{index}/_stats/fielddata/{fields}'
],
'search': [
'/_search'
]
};

View File

@ -1,59 +0,0 @@
module.exports = function (force) {
var _ = require('../../../src/lib/utils');
var fs = require('fs');
var templates = require('./templates');
var restSpecUpdated = require('../../rest_spec_updated');
var outputPath = _.joinPath(__dirname, '../../../src/lib/api.js');
var docOutputDir = _.joinPath(__dirname, '../../../docs/');
function download() {
require('./actions').on('ready', function (actions) {
var namespaces = _.filter(_.map(actions, function (action) {
if (~action.location.indexOf('.')) {
var path = action.location.split('.').slice(0, -1);
_.pull(path, 'prototype');
return path.join('.');
}
}));
// seperate the proxy actions
var groups = _.groupBy(actions, function (action) {
return action.proxy ? 'proxies' : 'normal';
});
fs.unlink(outputPath, function () {
console.log('writing', actions.length, 'api actions to', outputPath);
fs.writeFileSync(outputPath, templates.apiFile({
actions: groups.normal,
proxies: groups.proxies,
namespaces: _.unique(namespaces.sort(), true)
}));
if (!fs.existsSync(docOutputDir)) {
fs.mkdirSync(docOutputDir);
}
fs.writeFileSync(docOutputDir + '_method_list.jade', templates.apiMethodList({
actions: actions
}));
fs.writeFileSync(docOutputDir + '_methods.jade', templates.apiMethods({
actions: actions
}));
});
});
}
if (force) {
download();
} else {
restSpecUpdated(function (err, updated) {
if (err || updated) {
download();
}
});
}
};

View File

@ -1 +0,0 @@
require('./generate')(require('../_force'));

View File

@ -19,9 +19,6 @@ var argv = require('optimist')
})
.argv;
// Error.stackTraceLimit = Infinity;
// process.exit();
var es = require('../../../src/elasticsearch');
var _ = require('../../../src/lib/utils');
var async = require('async');

View File

@ -22,6 +22,7 @@ api.<%= namespace %> = function <%= className %>(transport) {
});
_.each(proxies, function (action) {%>
<%= partials.client_action_proxy(action) %><%
});
%>
%>

View File

@ -9,4 +9,4 @@ _.each(allParams, function(param, paramName) { %>
}
%><% }) %>
*/
api<%= (location[0] === '[' ? '' : '.') + location %> = ca(<%= stringify(spec, true) %>);
api<%= (location[0] === '[' ? '' : '.') + location %> = ca(<%= stringify(spec, true) %>);

View File

@ -16,4 +16,4 @@ if (typeof transformBody === 'string') { %>, {
}
}<%
}
%>);
%>);

View File

@ -1,5 +1,5 @@
var _ = require('../../../../src/lib/utils');
var _ = require('../../../src/lib/utils');
var fs = require('fs');
var path = require('path');

View File

@ -0,0 +1,46 @@
module.exports = function (done) {
/**
* Creates a JSON version of the YAML test suite that can be simply bundled for use in the browser.
*/
var jsYaml = require('js-yaml');
var fs = require('relative-fs').relativeTo(__dirname);
var async = require('async');
var path = require('path');
var tests = {}; // populated in readYamlTests
// generate the yaml tests
async.series([
readYamlTests,
writeYamlTests
], done);
function readYamlTests(done) {
var testDir = path.join(__dirname, '../../src/rest-api-spec/test/');
function readDirectories(dir) {
fs.readdirSync(dir).forEach(function (filename) {
var filePath = path.join(dir, filename);
var stat = fs.statSync(filePath);
if (stat.isDirectory()) {
readDirectories(filePath);
} else if (filename.match(/\.yaml$/)) {
var file = tests[path.relative(testDir, filePath)] = [];
jsYaml.loadAll(fs.readFileSync(filePath, 'utf8'), function (doc) {
file.push(doc);
});
}
});
}
readDirectories(testDir);
done();
}
function writeYamlTests(done) {
var testFile = require('path').resolve(__dirname, '../../test/integration/yaml_suite/yaml_tests.json');
fs.writeFileSync(testFile, JSON.stringify(tests, null, ' '), 'utf8');
console.log('wrote YAML tests as JSON to', testFile);
done();
}
};

View File

@ -1,3 +0,0 @@
Download the yaml suite's test specs, runs before each run of the suite to ensure they are up to date.
run by calling `npm run generate`. Force it to update, even if their has not been a new commit, but calling with `--force` or `-f`

View File

@ -1,44 +0,0 @@
module.exports = function (force) {
/**
* Check that the test directory exists, and is less than a day old, otherwise wipe it out
* and rebuild
*/
var fs = require('fs');
var path = require('path');
var jsYaml = require('js-yaml');
var spec = require('../../get_spec');
var restSpecUpdated = require('../../rest_spec_updated');
var testFile = path.resolve(__dirname, '../../../test/integration/yaml_suite/yaml_tests.json');
function download() {
var tests = {};
fs.unlink(testFile, function () {
spec.get('test/**/*.yaml')
.on('entry', function (entry) {
var filename = path.relative('test', entry.path);
var file = tests[filename] = [];
jsYaml.loadAll(entry.data, function (doc) {
file.push(doc);
});
})
.on('end', function () {
fs.writeFileSync(testFile, JSON.stringify(tests, null, ' '), 'utf8');
console.log('download yaml tests to', testFile);
});
});
}
if (force) {
download();
} else {
restSpecUpdated(function (err, updated) {
if (err || updated) {
download();
}
});
}
};

View File

@ -1 +0,0 @@
require('./generate')(require('../_force'));