Merge pull request +6956 from c9/sdk-tests

Add test running feature for SDK plugins
pull/85/head
Harutyun Amirjanyan 2015-04-21 21:47:22 +04:00
commit 49991c7706
13 zmienionych plików z 342 dodań i 256 usunięć

Wyświetl plik

@ -21,6 +21,7 @@ module.exports = function(options) {
var workspaceDir = options.workspaceDir;
var debug = options.debug !== undefined ? options.debug : false;
var collab = options.collab;
var packaging = options.packaging;
var staticPrefix = options.staticPrefix;
@ -99,6 +100,10 @@ module.exports = function(options) {
{
packagePath: "plugins/c9.ide.plugins/market"
},
{
packagePath: "plugins/c9.ide.plugins/test",
staticPrefix: staticPrefix + "/plugins/c9.ide.plugins"
},
// VFS
"plugins/c9.vfs.client/vfs.ping",

2
node_modules/architect/architect.js wygenerowano vendored
Wyświetl plik

@ -7,7 +7,7 @@ var EventEmitter = events.EventEmitter;
var exports = {};
var DEBUG = typeof location != "undefined" && location.href.match(/debug=[12]/) ? true : false;
var DEBUG = typeof location != "undefined" && location.href.match(/debug=[123]/) ? true : false;
// Only define Node-style usage using sync I/O if in node.
if (typeof module === "object") (function () {

Wyświetl plik

@ -88,7 +88,7 @@
"c9.ide.navigate": "#64156c7f4a",
"c9.ide.newresource": "#f1f0624768",
"c9.ide.openfiles": "#28a4f5af16",
"c9.ide.preview": "#dba2f4214d",
"c9.ide.preview": "#0bd8dd6e8c",
"c9.ide.preview.browser": "#ac18aaf31d",
"c9.ide.preview.markdown": "#ab8d30ad9f",
"c9.ide.pubsub": "#b83cf15ade",

Wyświetl plik

@ -372,11 +372,10 @@ define(function(require, exports, module) {
console.warn("WARNING: Plugin '" + name + "' is not listed in package.json.");
warned = true;
}
// @TODO temporarily disabled the requirement for tests while tests cannot actually run yet
// else if (!fs.existsSync(join(cwd, name.replace(/\.js$/, "_test.js")))) {
// console.warn("ERROR: Plugin '" + name + "' has no test associated with it.");
// failed = true;
// }
else if (!fs.existsSync(join(cwd, name.replace(/\.js$/, "_test.js")))) {
console.warn("ERROR: Plugin '" + name + "' has no test associated with it. There must be a file called '" + name + "_test.js' containing tests.");
failed = true;
}
});
if (failed)

Wyświetl plik

@ -35,10 +35,10 @@ define(function(require, exports, module) {
if (!drawn) return;
var list = getThemes();
plugin.form.update({
plugin.form.update([{
id: "syntax",
items: list
});
}]);
}
ace.on("addTheme", update);

Wyświetl plik

@ -36,7 +36,9 @@ define(function(require, exports, module) {
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
// var emit = plugin.getEmitter();
var emit = plugin.getEmitter();
var plugins = [];
var ENABLED = c9.location.indexOf("debug=2") > -1;
var HASSDK = c9.location.indexOf("sdk=0") === -1;
@ -53,14 +55,17 @@ define(function(require, exports, module) {
menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin);
menus.addItemByPath("Tools/Developer", null, 100100, plugin);
menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({
onclick: function(){
var url = location.href + (location.href.indexOf("?") > -1
? "&debug=2"
: "?debug=2");
window.open(url);
}
}), 100100, plugin);
if (!ENABLED) {
menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({
onclick: function(){
var url = location.href + (location.href.indexOf("?") > -1
? "&debug=2"
: "?debug=2");
window.open(url);
}
}), 900, plugin);
}
if (!ENABLED) return;
@ -104,7 +109,7 @@ define(function(require, exports, module) {
menus.addItemByPath("Tools/Developer/Restart Plugin", new ui.item({
command: "restartplugin"
}), 100100, plugin);
}), 1000, plugin);
}
/***** Methods *****/
@ -177,6 +182,7 @@ define(function(require, exports, module) {
cfg.apikey = "0000000000000000000000000000=";
config.push(cfg);
plugins.push(name + "/" + path);
});
// Set version for package manager
@ -234,6 +240,8 @@ define(function(require, exports, module) {
load();
}, function(){
emit.sticky("ready");
if (!config.length) return;
// Load config
@ -490,11 +498,23 @@ define(function(require, exports, module) {
*/
get architect(){ throw new Error(); },
set architect(v){ architect = v; },
/**
*
*/
get plugins(){ return plugins; },
_events: [
/**
* @event ready
*/
"ready"
],
/**
*
*/
addStaticPlugin: addStaticPlugin,
/**
*
*/

Wyświetl plik

@ -21,7 +21,7 @@ define(function(require, exports, module) {
/***** Initialization *****/
describe("The module", function(){
describe(myplugin.name, function(){
this.timeout(2000);
beforeEach(function() {

Wyświetl plik

@ -0,0 +1,60 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test Runner</title>
<link rel="stylesheet" href="/static/lib/mocha/mocha.css" />
<style>
HTML { overflow: auto !important }
body {
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 60px 50px;
}
</style>
<script>
/*global mocha*/
var plugin, chai;
function start(p){
if (p) plugin = p;
if (plugin && chai) {
// Pass chai and mocha to the plugin
plugin.setReferences(chai, {
describe: describe,
it: it,
before: before,
after: after,
beforeEach: beforeEach,
afterEach: afterEach,
run: function(cb){
mocha.run(cb);
}
});
}
}
</script>
</head>
<body>
<div id="mocha"></div>
<div id='jserror' width='100%' height='20px' style='font: 10px \"courier new\"; color: red; display: none;'></div>
<script src="/static/require.js"></script>
<script src="/static/lib/mocha/mocha.js"></script>
<script>
/*global mocha*/
mocha.setup('bdd');
mocha.bail(false);
mocha.ignoreLeaks(true);
window.onerror = function(msg){
var el = document.getElementById('jserror');
el.innerHTML+="<div class='jserr'>"+msg+"</div>";
};
require(["/static/lib/chai/chai.js"], function(c){
chai = c;
start();
});
//--></script>
</body>
</html>

Wyświetl plik

@ -1,85 +1,28 @@
//@TODO look at jasmine instead
require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], function() {
mocha.setup('bdd');
mocha.bail(false);
mocha.ignoreLeaks(true);
mocha.run(done)
/*global Mocha, mocha*/
mocha.reporter(function(runner) {
Mocha.reporters.Base.call(this, runner);
Mocha.reporters.HTML.call(this, runner);
var tests = [];
var stats = this.stats;
mocha.report = { stats: stats, tests: tests };
runner.on('test end', function(test) {
stats.percent = stats.tests / runner.total * 100 | 0;
tests.push(clean(test));
});
runner.on('end', function() {
console.log(JSON.stringify(mocha.report, null, 4));
});
function parseError(err) {
var str = err.stack || err.toString();
// FF / Opera do not add the message
if (!~str.indexOf(err.message)) {
str = err.message + '\n' + str;
}
// <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
// check for the result of the stringifying.
if ('[object Error]' == str) str = err.message;
// Safari doesn't give you a stack. Let's at least provide a source line.
if (!err.stack && err.sourceURL && err.line !== undefined) {
str += "\n(" + err.sourceURL + ":" + err.line + ")";
}
return str;
}
function clean(test) {
return {
title: test.title,
duration: test.duration,
error: test.err && parseError(test.err),
speed: test.speed,
state: test.state
};
}
});
/*global requirejs*/
/* global requirejs */
define(function(require, exports, module) {
main.consumes = [
"Plugin", "vfs", "fs", "plugin.loader", "c9", "ext", "watcher",
"dialog.notification"
"Plugin", "plugin.debug", "c9", "menus", "ui", "ext", "preview",
"preview.browser"
];
main.provides = ["plugin.editor"];
main.provides = ["plugin.test"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var vfs = imports.vfs;
var watcher = imports.watcher;
var ext = imports.ext;
var fs = imports.fs;
var c9 = imports.c9;
var loader = imports["plugin.loader"];
var notify = imports["dialog.notification"].show;
var dirname = require("path").dirname;
var architect;
var menus = imports.menus;
var preview = imports.preview;
var ext = imports.ext;
var ui = imports.ui;
var debug = imports["plugin.debug"];
var browser = imports["preview.browser"];
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
// var emit = plugin.getEmitter();
var emit = plugin.getEmitter();
var chai, mocha, iframe, architect;
var ENABLED = c9.location.indexOf("debug=2") > -1;
@ -90,187 +33,133 @@ define(function(require, exports, module) {
if (!ENABLED) return;
notify("<div class='c9-readonly'>You are in <span style='color:rgb(245, 234, 15)'>Debug</span> Mode. "
+ "Don't forget to open the browser's dev tools to see any errors.",
false);
fs.readdir("~/.c9/plugins", function(err, list){
if (err) return console.error(err);
debug.once("ready", function(){
menus.addItemByPath("Tools/Developer/Tests", null, 200, plugin);
var names = loader.plugins;
var toLoad = [];
list.forEach(function(stat){
var name = stat.name;
// If the plugin doesn't exist
if (names.indexOf(name) == -1 && name.charAt(0) != ".")
toLoad.push(name);
debug.plugins.forEach(function(name, i){
menus.addItemByPath("Tools/Developer/Tests/" + name.replace(/\//g, "\\/"), new ui.item({
onclick: function(){
run(name, function(err){
if (err) console.error(err);
});
}
}), i + 1, plugin);
});
loadPlugins(toLoad);
});
ext.on("register", function(){
// TODO
}, plugin);
ext.on("unregister", function(){
// TODO
}, plugin);
var reloading;
function loadPreview(url, session){
var idx = url.indexOf(options.staticPrefix);
if (!reloading && idx > -1) {
reloading = true;
var name = session.doc.meta.pluginName;
run(name, function(err){
if (err) console.error(err);
});
reloading = false;
}
}
browser.on("reload", function(e){
loadPreview(e.session.path, e.session);
});
}
/***** Methods *****/
function loadPlugins(list){
if (!vfs.connected) {
vfs.once("connect", loadPlugins.bind(this, config));
return;
function setReferences(c, m){
chai = c;
mocha = m;
emit("ready");
}
function loadIframe(pluginName, callback){
var url = options.staticPrefix + "/test.html";
if (url.indexOf("http") !== 0)
url = location.origin + url;
var tab = preview.openPreview(url, null, true);
iframe = tab.document.getSession().iframe;
iframe.addEventListener("load", handle);
iframe.addEventListener("error", onError);
function handle(err){
iframe.removeEventListener("load", handle);
iframe.removeEventListener("error", onError);
callback(err instanceof Error ? err : null, tab);
}
var config = [];
var count = list.length;
function onError(e){
debugger; // e.??
handle(new Error());
}
function next(name){
if (!name) {
if (--count === 0) finish();
return;
}
tab.document.meta.ignoreState = true;
tab.document.meta.pluginName = pluginName;
}
function loadTestSuite(name, callback){
// Clear require cache
requirejs.undef("plugins/" + name + "_test"); // global
// Load plugin
architect.loadAdditionalPlugins([{
packagePath: "plugins/" + name + "_test"
}], function(err){
callback(err);
});
}
function run(pluginName, callback){
// Load test runner
loadIframe(pluginName, function(err, tab){
if (err) return callback(err);
// Fetch package.json
fs.readFile("~/.c9/plugins/" + name + "/package.json", function(err, data){
if (err) {
console.error(err);
return next();
}
tab.editor.setLocation("test://" + pluginName)
// Wait until iframe is loaded
plugin.once("ready", function(){
try{
var options = JSON.parse(data);
if (!options.plugins)
throw new Error("Missing plugins property in package.json of " + name);
}
catch(e){
console.error(err);
return next();
}
options.plugins.forEach(function(path){
var pluginPath = "~/.c9/plugins/" + name + "/" + path + ".js";
var url = vfs.url(pluginPath);
// Load the test for the plugin
loadTestSuite(pluginName, function(err){
if (err) return callback(err);
// Watch project path
watch(pluginPath);
config.push({
packagePath: url,
staticPrefix: dirname(url),
apikey: "00000000-0000-4000-y000-" + String(config.length).pad(12, "0")
// Run the test
mocha.run(function(){
// Done
callback();
});
});
next();
});
}
function finish(){
// Load config
architect.loadAdditionalPlugins(config, function(err){
if (err) console.error(err);
});
}
list.forEach(next);
}
// Check if require.s.contexts._ can help watching all dependencies
function watch(path){
watcher.watch(path);
watcher.on("change", function(e){
if (e.path == path)
reloadPackage(path.replace(/^~\/\.c9\//, ""));
});
watcher.on("delete", function(e){
if (e.path == path)
reloadPackage(path.replace(/^~\/\.c9\//, ""));
});
watcher.on("failed", function(e){
if (e.path == path) {
setTimeout(function(){
watcher.watch(path); // Retries once after 1s
});
}
});
}
function reloadPackage(path){
var unloaded = [];
function recurUnload(name){
var plugin = architect.services[name];
unloaded.push(name);
// Find all the dependencies
var deps = ext.getDependencies(plugin.name);
// Unload all the dependencies (and their deps)
deps.forEach(function(name){
recurUnload(name);
});
// Unload plugin
plugin.unload();
}
// Recursively unload plugin
var p = architect.lut[path];
if (p.provides) { // Plugin might not been initialized all the way
p.provides.forEach(function(name){
recurUnload(name);
});
}
// create reverse lookup table
var rlut = {};
for (var packagePath in architect.lut) {
var provides = architect.lut[packagePath].provides;
if (provides) { // Plugin might not been initialized all the way
provides.forEach(function(name){
rlut[name] = packagePath;
});
}
}
// Build config of unloaded plugins
var config = [], done = {};
unloaded.forEach(function(name){
var packagePath = rlut[name];
// Make sure we include each plugin only once
if (done[packagePath]) return;
done[packagePath] = true;
var options = architect.lut[packagePath];
delete options.provides;
delete options.consumes;
delete options.setup;
config.push(options);
// Clear require cache
requirejs.undef(options.packagePath); // global
});
// Load all plugins again
architect.loadAdditionalPlugins(config, function(err){
if (err) console.error(err);
});
// Load iframe with new test runner frame
iframe.contentWindow.start(plugin);
})
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
loaded = false;
chai = null;
mocha = null;
iframe = null;
architect = null;
});
/***** Register and define API *****/
@ -288,12 +177,104 @@ define(function(require, exports, module) {
/**
*
*/
reloadPackage: reloadPackage
get describe(){ return mocha.describe; },
/**
*
*/
get it(){ return mocha.it; },
/**
*
*/
get before(){ return mocha.before; },
/**
*
*/
get after(){ return mocha.after; },
/**
*
*/
get beforeEach(){ return mocha.beforeEach; },
/**
*
*/
get afterEach(){ return mocha.afterEach; },
/**
*
*/
get assert(){ return chai.assert; },
/**
*
*/
get expect(){ return chai.expect; },
/**
*
*/
setReferences: setReferences,
/**
*
*/
run: run
});
register(null, {
"plugin.editor": plugin
"plugin.test": plugin
});
}
});
});
//@TODO look at jasmine instead
// require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], function() {
// mocha.setup('bdd');
// mocha.bail(false);
// mocha.ignoreLeaks(true);
// mocha.run(done)
// /*global Mocha, mocha*/
// mocha.reporter(function(runner) {
// Mocha.reporters.Base.call(this, runner);
// Mocha.reporters.HTML.call(this, runner);
// var tests = [];
// var stats = this.stats;
// mocha.report = { stats: stats, tests: tests };
// runner.on('test end', function(test) {
// stats.percent = stats.tests / runner.total * 100 | 0;
// tests.push(clean(test));
// });
// runner.on('end', function() {
// console.log(JSON.stringify(mocha.report, null, 4));
// });
// function parseError(err) {
// var str = err.stack || err.toString();
// // FF / Opera do not add the message
// if (!~str.indexOf(err.message)) {
// str = err.message + '\n' + str;
// }
// // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
// // check for the result of the stringifying.
// if ('[object Error]' == str) str = err.message;
// // Safari doesn't give you a stack. Let's at least provide a source line.
// if (!err.stack && err.sourceURL && err.line !== undefined) {
// str += "\n(" + err.sourceURL + ":" + err.line + ")";
// }
// return str;
// }
// function clean(test) {
// return {
// title: test.title,
// duration: test.duration,
// error: test.err && parseError(test.err),
// speed: test.speed,
// state: test.state
// };
// }
// });

Wyświetl plik

@ -181,6 +181,21 @@ define(function(require, exports, module) {
/***** Methods *****/
function splitSafe(path){
var pieces = [], escaped;
path.split("/").forEach(function(n){
if (escaped) n = escaped + "/" + n;
escaped = n.substr(-1) == "\\" ? n : false; //.substr(n, n.length - 1)
if (!escaped) pieces.push(n);
});
if (escaped) pieces.push(escaped);
return pieces;
}
function popSafe(path){
return splitSafe(path).pop().replace(/\\\//g, "/");
}
function init(){
inited = true;
layout.initMenus(plugin);
@ -365,7 +380,8 @@ define(function(require, exports, module) {
if (item) {
item.setAttribute("submenu", menu);
item.setAttribute("caption",
apf.escapeXML((debug ? "(" + index + ")" : "") + name.split("/").pop()));
apf.escapeXML((debug ? "(" + index + ")" : "")
+ popSafe(name)));
items[name] = item;
}
else {
@ -374,7 +390,7 @@ define(function(require, exports, module) {
item = items[name] = new apf.item({
submenu: menu,
caption: (debug ? "(" + index + ") " : "") +
name.split("/").pop()
popSafe(name)
});
}
else {
@ -402,7 +418,7 @@ define(function(require, exports, module) {
function setMenuItem(parent, name, menuItem, index, item, plugin) {
if (item && !item.nodeFunc) plugin = item, item = null;
var itemName = name.split("/").pop();
var itemName = popSafe(name);
if (itemName == "~")
name += index;
@ -462,7 +478,7 @@ define(function(require, exports, module) {
assert(plugin !== undefined, "addItemByPath requires a plugin argument");
var steps = path.split("/"), name, p = [], isLast;
var steps = splitSafe(path), name, p = [], isLast;
var curpath;
if (!menuItem)
@ -568,7 +584,7 @@ define(function(require, exports, module) {
if (!items[path])
throw new Error("Could not find menu item " + path);
var steps = path.split("/"), p = [], item;
var steps = splitSafe(path), p = [], item;
var curpath;
for (var name, i = 0, l = steps.length; i < l; i++) {

Wyświetl plik

@ -39,6 +39,9 @@ define(function(require, exports, module) {
var protocolVersion = require("kaefer/version").protocol;
var smith = require("smith");
var URL = require("url");
var DEBUG = options.debug
&& (typeof location == "undefined"
|| location.href.indexOf("debug=3") > -1);
// The connected vfs unique id
var id;
@ -76,11 +79,11 @@ define(function(require, exports, module) {
if (loaded) return false;
loaded = true;
smith.debug = options.debug;
smith.debug = DEBUG;
connection = connectClient(connectEngine, {
preConnectCheck: preConnectCheck,
debug: options.debug
debug: DEBUG
});
connection.on("away", emit.bind(null, "away"));

Wyświetl plik

@ -119,7 +119,7 @@ function plugin(options, imports, register) {
token: req.params.token
});
opts.options.debug = req.params.debug == 1;
opts.options.debug = req.params.debug !== undefined;
res.setHeader("Cache-Control", "no-cache, no-store");
res.render(__dirname + "/views/standalone.html.ejs", {
architectConfig: getConfig(configType, opts),

Wyświetl plik

@ -101,7 +101,9 @@
});
app.on("service", function(name, plugin, options){
if (name == "plugin.loader" || name == "plugin.installer" || name == "plugin.debug" || name == "plugin.manager")
if (name == "plugin.loader" || name == "plugin.installer"
|| name == "plugin.debug" || name == "plugin.manager"
|| name == "plugin.test")
plugin.architect = app;
if (!plugin.name)
plugin.name = name;