Skip to content

Commit

Permalink
FLUID-5475, FLUID-5468: Improve quality of logging information from t…
Browse files Browse the repository at this point in the history
…he framework in node, and provide simple internal self-test for the node module driver
  • Loading branch information
amb26 committed Jul 17, 2014
1 parent f3ae72f commit 584ef9c
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 122 deletions.
1 change: 0 additions & 1 deletion .npmignore
Expand Up @@ -24,5 +24,4 @@ tests/all-tests.html
tests/lib/jquery-ui
tests/lib/mockjax
tests/lib/README
tests/test-core/testTests
tests/test-core/utils/js/DebugFocus.js
72 changes: 50 additions & 22 deletions src/framework/core/js/FluidDebugging.js
Expand Up @@ -19,6 +19,11 @@ var fluid = fluid || fluid_2_0;
(function ($, fluid) {
"use strict";

/** Render a timestamp from a Date object into a helpful fixed format for debug logs to millisecond accuracy
* @param date {Date} The date to be rendered
* @return {String} A string format consisting of hours:minutes:seconds.millis for the datestamp padded to fixed with
*/

fluid.renderTimestamp = function (date) {
var zeropad = function (num, width) {
if (!width) { width = 2; }
Expand Down Expand Up @@ -47,10 +52,10 @@ var fluid = fluid || fluid_2_0;
}
}
var toReallyGo = [];
fluid.each(togo, function(el, path) {
fluid.each(togo, function (el, path) {
toReallyGo.push({path: path, count: el});
});
toReallyGo.sort(function(a, b) {return b.count - a.count;});
toReallyGo.sort(function (a, b) {return b.count - a.count;});
return toReallyGo;
};

Expand All @@ -61,7 +66,7 @@ var fluid = fluid || fluid_2_0;
prefixCount[prefix] = 0;
});
var togo = [];
fluid.each(pathCount, function(el) {
fluid.each(pathCount, function (el) {
var path = el.path;
if (!fluid.find(prefixes, function(prefix) {
if (path.indexOf(prefix) === 0) {
Expand Down Expand Up @@ -101,7 +106,7 @@ var fluid = fluid || fluid_2_0;
return stackStyle;
};

fluid.obtainException = function() {
fluid.obtainException = function () {
try {
throw new Error("Trace exception");
}
Expand All @@ -114,69 +119,92 @@ var fluid = fluid || fluid_2_0;

fluid.registerNamespace("fluid.exceptionDecoders");

fluid.decodeStack = function() {
fluid.decodeStack = function () {
if (stackStyle.style !== "firefox") {
return null;
}
var e = fluid.obtainException();
return fluid.exceptionDecoders[stackStyle.style](e);
};

fluid.exceptionDecoders.firefox = function(e) {
fluid.exceptionDecoders.firefox = function (e) {
var lines = e.stack.replace(/(?:\n@:0)?\s+$/m, "").replace(/^\(/gm, "{anonymous}(").split("\n");
return fluid.transform(lines, function(line) {
return fluid.transform(lines, function (line) {
var atind = line.indexOf("@");
return atind === -1? [line] : [line.substring(atind + 1), line.substring(0, atind)];
});
};

fluid.getCallerInfo = function(atDepth) {
// Main entry point for callers.
// TODO: This infrastructure is several years old and probably still only works on Firefox if there
fluid.getCallerInfo = function (atDepth) {
atDepth = (atDepth || 3) - stackStyle.offset;
var stack = fluid.decodeStack();
return stack? stack[atDepth][0] : null;
};

function generate(c, count) {
/** Generates a string for padding purposes by replicating a character a given number of times
* @param c {Character} A character to be used for padding
* @param count {Integer} The number of times to repeat the character
* @return A string of length <code>count</code> consisting of repetitions of the supplied character
*/
// UNOPTIMISED
fluid.generatePadding = function (c, count) {
var togo = "";
for (var i = 0; i < count; ++ i) {
togo += c;
}
return togo;
}

function printImpl(obj, small, options) {
var big = small + options.indentChars;
function printImpl (obj, small, options) {
var big = small + options.indentChars, togo;
if (obj === null) {
return "null";
togo = "null";
} else if (obj === undefined) {
togo = "undefined"; // NB - object invalid for JSON interchange
}
else if (fluid.isPrimitive(obj)) {
return JSON.stringify(obj);
togo = JSON.stringify(obj);
}
else {
if ($.inArray(obj, options.stack) !== -1) {
return "(CIRCULAR)"; // NB - object invalid for JSON interchange
}
options.stack.push(obj);
var j = [];
var i;
if (fluid.isArrayable(obj)) {
if (obj.length === 0) {
return "[]";
}
for (i = 0; i < obj.length; ++ i) {
j[i] = printImpl(obj[i], big, options);
togo = "[]";
} else {
for (i = 0; i < obj.length; ++ i) {
j[i] = printImpl(obj[i], big, options);
}
togo = "[\n" + big + j.join(",\n" + big) + "\n" + small + "]";
}
return "[\n" + big + j.join(",\n" + big) + "\n" + small + "]";
}
else {
i = 0;
fluid.each(obj, function(value, key) {
j[i++] = JSON.stringify(key) + ": " + printImpl(value, big, options);
});
return "{\n" + big + j.join(",\n" + big) + "\n" + small + "}";
togo = "{\n" + big + j.join(",\n" + big) + "\n" + small + "}";
}
options.stack.pop();
}
return togo;
}

fluid.prettyPrintJSON = function(obj, options) {
options = $.extend({indent: 4}, options);
options.indentChars = generate(" ", options.indent);
/** Render a complex JSON object into a nicely indented format suitable for human readability.
* @param obj {Object} The object to be rendered
* @param options {Object} An options structure governing the rendering process. The only option which
* is currently supported is <code>indentChars</code> holding the number of space characters to be used to
* indent each level of containment.
*/
fluid.prettyPrintJSON = function (obj, options) {
options = $.extend({indent: 4, stack: []}, options);
options.indentChars = fluid.generatePadding(" ", options.indent);
return printImpl(obj, "", options);
};

Expand Down
21 changes: 20 additions & 1 deletion src/module/fluid.js
Expand Up @@ -32,7 +32,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt

context.window = context;

/** Load a standard, non-require-aware Fluid framework file into the Fluid context **/
/** Load a standard, non-require-aware Fluid framework file into the Fluid context, given a filename
* relative to this directory (src/module) **/

var loadInContext = function (path) {
var fullpath = buildPath(path);
Expand All @@ -53,6 +54,24 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
// FLUID-4913: QUnit calls window.addEventListener on load. We need to add
// it to the context it will be loaded in.
context.addEventListener = fluid.identity;

fluid.logObjectRenderChars = 1024;

// Convert an argument intended for console.log in the node environment to a readable form (the
// default action of util.inspect censors at depth 1)
fluid.renderLoggingArg = function (arg) {
var togo = arg && fluid.isPrimitive(arg) ? arg : fluid.prettyPrintJSON(arg);
if (typeof(togo) === "string" && togo.length > fluid.logObjectRenderChars) {
togo = togo.substring(0, fluid.logObjectRenderChars) + " .... [output suppressed at " + fluid.logObjectRenderChars + " chars - for more output, increase fluid.logObjectRenderChars]";
}
return togo;
};

// Monkey-patch the built-in fluid.doLog utility to improve its behaviour within node.js - see FLUID-5475
fluid.doLog = function (args) {
args = fluid.transform(args, fluid.renderLoggingArg);
console.log(args.join(""));
};

fluid.loadInContext = loadInContext;
fluid.loadIncludes = loadIncludes;
Expand Down
1 change: 1 addition & 0 deletions tests/all-tests.html
Expand Up @@ -17,6 +17,7 @@
QUnit.testSuites("Framework Tests", [
"./framework-tests/core/html/FluidJS-test.html",
"./framework-tests/core/html/FluidJSStandalone-test.html",
"./framework-tests/core/html/FluidDebugging-test.html",
"./framework-tests/core/html/keyboard-a11y-test.html",
"./framework-tests/core/html/ModelTransformation-test.html",
"./framework-tests/core/html/DataBinding-test.html",
Expand Down
31 changes: 31 additions & 0 deletions tests/framework-tests/core/html/FluidDebugging-test.html
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>FluidDebugging.js Tests</title>

<!-- This is the jqUnit test css file -->
<link rel="stylesheet" media="screen" href="../../../lib/qunit/css/qunit.css" />

<!-- These are the required javascript modules -->
<script type="text/javascript" src="../../../../src/lib/jquery/core/js/jquery.js"></script>
<script type="text/javascript" src="../../../../src/framework/core/js/Fluid.js"></script>
<script type="text/javascript" src="../../../../src/framework/core/js/FluidDebugging.js"></script>

<!-- These are the jqUnit test js files -->
<script type="text/javascript" src="../../../lib/qunit/js/qunit.js"></script>
<script type="text/javascript" src="../../../test-core/jqUnit/js/jqUnit.js"></script>

<!-- These are tests that have been written using this page as data -->
<script type="text/javascript" src="../js/FluidDebuggingTests.js"></script>

</head>
<body>
<h1 id="qunit-header">Fluid Debugging Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>

</body>
</html>
47 changes: 47 additions & 0 deletions tests/framework-tests/core/js/FluidDebuggingTests.js
@@ -0,0 +1,47 @@
/*
Copyright 2010-2011 Lucendo Development Ltd.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
*/

// Declare dependencies
/* global fluid, jqUnit */

(function ($) {
"use strict";

fluid.setLogging(true);

fluid.registerNamespace("fluid.tests");

jqUnit.module("Fluid Debugging JS Tests");

jqUnit.test("fluid.prettyPrintJSON", function () {
jqUnit.assertEquals("Render null", "null", fluid.prettyPrintJSON(null));
jqUnit.assertEquals("Render undefined", "undefined", fluid.prettyPrintJSON(undefined));
var circular = {};
circular.field = circular;
var renderCircular = fluid.prettyPrintJSON(circular);
jqUnit.assertValue("Render circular: " + renderCircular, circular);
var complex = {
"null": null,
"boolean": true,
"number": 3.5,
fields: {
a: 3,
b: {
c: [],
d: [1, false]
}
}
};
var renderedComplex = fluid.prettyPrintJSON(complex);
var reparsed = JSON.parse(renderedComplex);
jqUnit.assertDeepEq("Round-tripping complex object", complex, reparsed);
});
})();
12 changes: 12 additions & 0 deletions tests/node-tests/README.md
@@ -0,0 +1,12 @@
This directory contains a simple test fixture to verify the basic packaging of Fluid Infusion as a node module
performed by the material in src/module/fluid.js .

To run the tests, execute

node basic-node-tests.js

from the command line. They should terminate with the message

Self-test OK - 3/3 tests passed

The comprehensive test suite for Infusion should be run in the browser by loading the markup file `tests/all-tests.html`
34 changes: 34 additions & 0 deletions tests/node-tests/basic-node-test.js
@@ -0,0 +1,34 @@
"use strict";

var fluid = require("../../src/module/fluid.js");

fluid.loadTestingSupport();

fluid.registerNamespace("fluid.tests");

fluid.loadInContext("../../tests/test-core/testTests/js/TestingTests.js");

var QUnit = fluid.registerNamespace("QUnit");
var jqUnit = fluid.registerNamespace("jqUnit");

fluid.setLogging(true);

QUnit.testDone(function (data) {
fluid.log("Test concluded - " + data.name + ": " + data.passed + " passed");
});

var expected = 3;

QUnit.done(function (data) {
fluid.log((expected === data.passed ? "Self-test OK" : "Self-test FAILED") + " - " + data.passed + "/" + expected + " tests passed");
});

fluid.test.runTests(["fluid.tests.myTestTree"]);

jqUnit.test("Rendering truncation test", function () {
var rendered = fluid.renderLoggingArg(fluid);
jqUnit.assertTrue("Large object truncated", rendered.length < fluid.logObjectRenderChars + 100);
console.log("Large log rendering object truncated to " + rendered.length + " chars");
});

QUnit.load();

0 comments on commit 584ef9c

Please sign in to comment.