Browse Source

EAGLESIX-2651: Object: sync w/ 2.6.5 code; fix tests

Kyle P Davis 11 years ago
parent
commit
c6610ed3fb

+ 18 - 16
lib/pipeline/DepsTracker.js

@@ -15,7 +15,8 @@ var DepsTracker = module.exports = function DepsTracker() {
 	this.needTextScore = false;
 }, klass = DepsTracker, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-var ParsedDeps = require("./ParsedDeps");
+var ParsedDeps = require("./ParsedDeps"),
+	Document = require("./Document");
 
 /**
  * Returns a projection object covering the dependencies tracked by this class.
@@ -26,7 +27,7 @@ proto.toProjection = function toProjection() {
 	var proj = {};
 
 	// if(this.needTextScore) {
-		// bb.append(Document::metaFieldTextScore, BSON("$meta" << "textScore"));
+	// 	bb.append(Document::metaFieldTextScore, BSON("$meta" << "textScore"));
 	// }
 
 	if (this.needWholeDocument) {
@@ -40,17 +41,16 @@ proto.toProjection = function toProjection() {
 		return proj;
 	}
 
-	var last = "",
-		needId = false;
-
-	Object.keys(this.fields).sort().forEach(function (it) {
-		if (it.slice(0,3) == "_id" && (it.length == 3 || it.charAt(3) == ".")) {
+	var needId = false,
+		last = "";
+	Object.keys(this.fields).sort().forEach(function(it) {
+		if (it.indexOf("_id") === 0 && (it.length === 3 || it[3] === ".")) {
 			// _id and subfields are handled specially due in part to SERVER-7502
 			needId = true;
 			return;
 		}
 
-		if (last !== "" && it.slice(0, last.length) === last) {
+		if (last !== "" && it.indexOf(last) === 0) {
 			// we are including a parent of *it so we don't need to include this
 			// field explicitly. In fact, due to SERVER-6527 if we included this
 			// field, the parent wouldn't be fully included. This logic relies
@@ -63,7 +63,7 @@ proto.toProjection = function toProjection() {
 		proj[it] = 1;
 	});
 
-	if (needId)
+	if (needId) // we are explicit either way
 		proj._id = 1;
 	else
 		proj._id = 0;
@@ -71,23 +71,26 @@ proto.toProjection = function toProjection() {
 	return proj;
 };
 
+// ParsedDeps::_fields is a simple recursive look-up table. For each field:
+//      If the value has type==Bool, the whole field is needed
+//      If the value has type==Object, the fields in the subobject are needed
+//      All other fields should be missing which means not needed
 /**
  * Takes a depsTracker and builds a simple recursive lookup table out of it.
  * @method toParsedDeps
  * @return {ParsedDeps}
  */
 proto.toParsedDeps = function toParsedDeps() {
-	var doc = {};
+	var obj = {};
 
 	if (this.needWholeDocument || this.needTextScore) {
 		// can't use ParsedDeps in this case
-		// TODO: not sure what appropriate equivalent to boost::none is
-		return;
+		return undefined; // TODO: is this equivalent to boost::none ?
 	}
 
 	var last = "";
 	Object.keys(this.fields).sort().forEach(function (it) {
-		if (last !== "" && it.slice(0, last.length) === last) {
+		if (last !== "" && it.indexOf(last) === 0) {
 			// we are including a parent of *it so we don't need to include this
 			// field explicitly. In fact, due to SERVER-6527 if we included this
 			// field, the parent wouldn't be fully included. This logic relies
@@ -97,9 +100,8 @@ proto.toParsedDeps = function toParsedDeps() {
 		}
 
 		last = it + ".";
-		// TODO: set nested field to true; i.e. a.b.c = true, not a = true
-		doc[it] = true;
+		Document.setNestedField(obj, it, true);
 	});
 
-	return new ParsedDeps(doc);
+	return new ParsedDeps(obj);
 };

+ 4 - 0
lib/pipeline/expressions/ConstantExpression.js

@@ -58,3 +58,7 @@ Expression.registerExpression("$literal", klass.parse); // alias
 proto.getOpName = function getOpName() {
 	return "$const";
 };
+
+proto.getValue = function getValue() {
+    return this.value;
+};

+ 172 - 170
lib/pipeline/expressions/ObjectExpression.js

@@ -7,132 +7,114 @@
  * @module mungedb-aggregate
  * @extends mungedb-aggregate.pipeline.expressions.Expression
  * @constructor
- **/
-var ObjectExpression = module.exports = function ObjectExpression(atRoot){
-	if (arguments.length !== 1) throw new Error("one arg expected");
-	this.excludeId = false;	/// <Boolean> for if _id is to be excluded
-	this.atRoot = atRoot;
-	this._expressions = {};	/// <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document
-	this._order = []; /// <Array<String>> this is used to maintain order for generated fields not in the source document
+ */
+var ObjectExpression = module.exports = function ObjectExpression(atRoot) {
+	if (arguments.length !== 1) throw new Error(klass.name + ": expected args: atRoot");
+	this.excludeId = false;
+	this._atRoot = atRoot;
+	this._expressions = {};
+	this._order = [];
 }, klass = ObjectExpression, Expression = require("./Expression"), base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-klass.create = function create() {
-	return new ObjectExpression(false);
-};
-
-klass.createRoot = function createRoot() {
-	return new ObjectExpression(true);
-};
 
-// DEPENDENCIES
 var Document = require("../Document"),
-	FieldPath = require("../FieldPath");
+	Value = require("../Value"),
+	FieldPath = require("../FieldPath"),
+	ConstantExpression = require("./ConstantExpression");
 
-// INSTANCE VARIABLES
-/**
- * <Boolean> for if _id is to be excluded
- * @property excludeId
- **/
-proto.excludeId = undefined;
-
-/**
- * <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document
- **/
-proto._expressions = undefined;
 
-//TODO: might be able to completely ditch _order everywhere in here since `Object`s are mostly ordered anyhow but need to come back and revisit that later
 /**
- * <Array<String>> this is used to maintain order for generated fields not in the source document
- **/
-proto._order = [];
-
+ * Create an empty expression.
+ * Until fields are added, this will evaluate to an empty document.
+ * @method create
+ * @static
+ */
+klass.create = function create() {
+	return new ObjectExpression(false);
+};
 
-// PROTOTYPE MEMBERS
 
 /**
- * evaluateInternal(), but return a Document instead of a Value-wrapped Document.
- * @method evaluateDocument
- * @param currentDoc the input Document
- * @returns the result document
- **/
-proto.evaluateDocument = function evaluateDocument(vars) {
-	// create and populate the result
-	var pResult = {_id:0};
-	this.addToDocument(pResult, pResult, vars); // No inclusion field matching.
-	return pResult;
+ * Like create but uses special handling of _id for root object of $project.
+ * @method createRoot
+ * @static
+ */
+klass.createRoot = function createRoot() {
+	return new ObjectExpression(true);
 };
 
-proto.evaluateInternal = function evaluateInternal(vars) { //TODO: collapse with #evaluateDocument()?
-	return this.evaluateDocument(vars);
-};
 
-proto.optimize = function optimize(){
+proto.optimize = function optimize() {
 	for (var key in this._expressions) {
+		if (!this._expressions.hasOwnProperty(key)) continue;
 		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null) this._expressions[key] = expr.optimize();
+		if (expr)
+			expr.optimize();
 	}
 	return this;
 };
 
-proto.isSimple = function isSimple(){
+
+proto.isSimple = function isSimple() {
 	for (var key in this._expressions) {
+		if (!this._expressions.hasOwnProperty(key)) continue;
 		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null && !expr.isSimple()) return false;
+		if (expr && !expr.isSimple())
+			return false;
 	}
 	return true;
 };
 
-proto.addDependencies = function addDependencies(depsTracker, path){
-	if (!depsTracker.fields){
-		depsTracker.fields = {};
-	}
+
+proto.addDependencies = function addDependencies(deps, path) {
 	var pathStr = "";
-	if (path instanceof Array) {
+	if (path) {
 		if (path.length === 0) {
 			// we are in the top level of a projection so _id is implicit
-			if (!this.excludeId) {
-				depsTracker.fields[Document.ID_PROPERTY_NAME] = 1;
-			}
+			if (!this.excludeId)
+				deps.fields[Document.ID_PROPERTY_NAME] = 1;
 		} else {
-			pathStr = new FieldPath(path).getPath() + ".";
+			var f = new FieldPath(path);
+			pathStr = f.getPath(false);
+			pathStr += ".";
 		}
 	} else {
-		if (this.excludeId) throw new Error("excludeId is true!");
+		if (this.excludeId) throw new Error("Assertion error");
 	}
 	for (var key in this._expressions) {
 		var expr = this._expressions[key];
-		if (expr !== undefined && expr !== null) {
-			if (path instanceof Array) path.push(key);
-			expr.addDependencies(depsTracker, path);
-			if (path instanceof Array) path.pop();
+		if (expr instanceof Expression) {
+			if (path) path.push(key);
+			expr.addDependencies(deps, path);
+			if (path) path.pop();
 		} else { // inclusion
-			if (path === undefined || path === null) throw new Error("inclusion not supported in objects nested in $expressions; uassert code 16407");
-			depsTracker.fields[pathStr + key] = 1;
+			if (!path) throw new Error("inclusion not supported in objects nested in $expressions; uassert code 16407");
+			deps.fields[pathStr + key] = 1;
 		}
 	}
 
-	return depsTracker;	// NOTE: added to munge as a convenience
+	return deps;	// NOTE: added to munge as a convenience
 };
 
-/**
- * evaluateInternal(), but add the evaluated fields to a given document instead of creating a new one.
- * @method addToDocument
- * @param pResult the Document to add the evaluated expressions to
- * @param currentDoc the input Document for this level
- * @param vars the root of the whole input document
- **/
-proto.addToDocument = function addToDocument(out, currentDoc, vars){
 
 
+/**
+* evaluateInternal(), but add the evaluated fields to a given document instead of creating a new one.
+* @method addToDocument
+* @param pResult the Document to add the evaluated expressions to
+* @param currentDoc the input Document for this level
+* @param vars the root of the whole input document
+*/
+proto.addToDocument = function addToDocument(out, currentDoc, vars) {
 	var doneFields = {};	// This is used to mark fields we've done so that we can add the ones we haven't
 
-	for(var fieldName in currentDoc){
+	for (var fieldName in currentDoc) {
 		if (!currentDoc.hasOwnProperty(fieldName)) continue;
 		var fieldValue = currentDoc[fieldName];
 
 		// This field is not supposed to be in the output (unless it is _id)
 		if (!this._expressions.hasOwnProperty(fieldName)) {
-			if (!this.excludeId && this.atRoot && fieldName == Document.ID_PROPERTY_NAME) {
+			if (!this.excludeId && this._atRoot && fieldName === Document.ID_PROPERTY_NAME) {
 				// _id from the root doc is always included (until exclusion is supported)
 				// not updating doneFields since "_id" isn't in _expressions
 				out[fieldName] = fieldValue;
@@ -143,63 +125,97 @@ proto.addToDocument = function addToDocument(out, currentDoc, vars){
 		// make sure we don't add this field again
 		doneFields[fieldName] = true;
 
-		// This means pull the matching field from the input document
 		var expr = this._expressions[fieldName];
-		if (!(expr instanceof Expression)) {
+		if (!(expr instanceof Expression)) expr = undefined;
+		if (!expr) {
+			// This means pull the matching field from the input document
 			out[fieldName] = fieldValue;
 			continue;
 		}
 
-		// Check if this expression replaces the whole field
-		if (!(fieldValue instanceof Object) || (fieldValue.constructor !== Object && fieldValue.constructor !== Array) || !(expr instanceof ObjectExpression)) {
+		var objExpr = expr instanceof ObjectExpression ? expr : undefined,
+			valueType = Value.getType(fieldValue);
+		if ((valueType !== "Object" && valueType !== "Array") || !objExpr) {
+			// This expression replace the whole field
 			var pValue = expr.evaluateInternal(vars);
 
 			// don't add field if nothing was found in the subobject
-			if (expr instanceof ObjectExpression && pValue instanceof Object && Object.getOwnPropertyNames(pValue).length === 0) continue;
+			if (objExpr && Object.getOwnPropertyNames(pValue).length === 0)
+				continue;
+
+			/*
+			 * Don't add non-existent values (note:  different from NULL or Undefined);
+			 * this is consistent with existing selection syntax which doesn't
+			 * force the appearance of non-existent fields.
+			 */
+			// if (pValue !== undefined)
+				out[fieldName] = pValue; //NOTE: DEVIATION FROM MONGO: we want to keep these in JS
 
-			// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
-			// TODO make missing distinct from Undefined
-			if (pValue !== undefined) out[fieldName] = pValue;
 			continue;
 		}
 
-		// Check on the type of the input value.  If it's an object, just walk down into that recursively, and add it to the result.
-		if (fieldValue instanceof Object && fieldValue.constructor === Object) {
-			out[fieldName] = expr.addToDocument({}, fieldValue, vars);	//TODO: pretty sure this is broken;
-		} else if (fieldValue instanceof Object && fieldValue.constructor === Array) {
-			// If it's an array, we have to do the same thing, but to each array element.  Then, add the array of results to the current document.
-			var result = [];
-			for(var fvi = 0, fvl = fieldValue.length; fvi < fvl; fvi++){
-				var subValue = fieldValue[fvi];
-				if (subValue.constructor !== Object) continue;	// can't look for a subfield in a non-object value.
-				result.push(expr.addToDocument({}, subValue, vars));
+		/*
+		 * Check on the type of the input value.  If it's an
+		 * object, just walk down into that recursively, and
+		 * add it to the result.
+		 */
+		if (valueType === "Object") {
+			var sub = {};
+			objExpr.addToDocument(sub, fieldValue, vars);
+			out[fieldName] = sub;
+		} else if (valueType === "Array") {
+			/*
+			 * If it's an array, we have to do the same thing,
+			 * but to each array element.  Then, add the array
+			 * of results to the current document.
+			 */
+			var result = [],
+				input = fieldValue;
+			for (var fvi = 0, fvl = input.length; fvi < fvl; fvi++) {
+				// can't look for a subfield in a non-object value.
+				if (Value.getType(input[fvi]) !== "Object")
+					continue;
+
+				var doc = {};
+				objExpr.addToDocument(doc, input[fvi], vars);
+				result.push(doc);
 			}
+
 			out[fieldName] = result;
 		} else {
-			throw new Error("should never happen");	//verify( false );
+			throw new Error("Assertion failure");
 		}
 	}
 
-	if (Object.getOwnPropertyNames(doneFields).length == Object.getOwnPropertyNames(this._expressions).length) return out;	//NOTE: munge returns result as a convenience
+	if (Object.getOwnPropertyNames(doneFields).length === Object.getOwnPropertyNames(this._expressions).length)
+		return out;	//NOTE: munge returns result as a convenience
 
 	// add any remaining fields we haven't already taken care of
-	for(var i = 0, l = this._order.length; i < l; i++){
-		var fieldName2 = this._order[i];
-		var expr2 = this._expressions[fieldName2];
+	for (var i = 0, l = this._order.length; i < l; i++) {
+		var fieldName2 = this._order[i],
+			expr2 = this._expressions[fieldName2];
 
 		// if we've already dealt with this field, above, do nothing
-		if (doneFields.hasOwnProperty(fieldName2)) continue;
+		if (doneFields.hasOwnProperty(fieldName2))
+			continue;
 
 		// this is a missing inclusion field
-		if (!expr2) continue;
+		if (expr2 === null || expr2 === undefined)
+			continue;
 
 		var value = expr2.evaluateInternal(vars);
 
-		// Don't add non-existent values (note:  different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
-		if (value === undefined || (typeof(value) == 'object' && value !== null && Object.keys(value).length === 0)) continue;
+		/*
+		 * Don't add non-existent values (note:  different from NULL or Undefined);
+		 * this is consistent with existing selection syntax which doesn't
+		 * force the appearnance of non-existent fields.
+		 */
+		if (value === undefined && !(expr2 instanceof ConstantExpression)) //NOTE: DEVIATION FROM MONGO: only if not {$const:undefined}
+			continue;
 
 		// don't add field if nothing was found in the subobject
-		if (expr2 instanceof ObjectExpression && value && value instanceof Object && Object.getOwnPropertyNames(value) == {} ) continue;
+		if (expr2 instanceof ObjectExpression && Object.getOwnPropertyNames(value).length === 0)
+			continue;
 
 		out[fieldName2] = value;
 	}
@@ -207,22 +223,31 @@ proto.addToDocument = function addToDocument(out, currentDoc, vars){
 	return out;	//NOTE: munge returns result as a convenience
 };
 
+
 /**
- * estimated number of fields that will be output
- * @method getSizeHint
- **/
-proto.getSizeHint = function getSizeHint(){
+* estimated number of fields that will be output
+* @method getSizeHint
+*/
+proto.getSizeHint = function getSizeHint() {
 	// Note: this can overestimate, but that is better than underestimating
 	return Object.getOwnPropertyNames(this._expressions).length + (this.excludeId ? 0 : 1);
 };
 
 
+/**
+* evaluateInternal(), but return a Document instead of a Value-wrapped Document.
+* @method evaluateDocument
+* @param currentDoc the input Document
+* @returns the result document
+*/
 proto.evaluateDocument = function evaluateDocument(vars) {
+	// create and populate the result
 	var out = {};
-	this.addToDocument(out, {}, vars);
+	this.addToDocument(out, {}, vars);	// No inclusion field matching.
 	return out;
 };
 
+
 proto.evaluateInternal = function evaluateInternal(vars) {
 	return this.evaluateDocument(vars);
 };
@@ -233,45 +258,52 @@ proto.evaluateInternal = function evaluateInternal(vars) {
  * @method addField
  * @param fieldPath the path the evaluated expression will have in the result Document
  * @param pExpression the expression to evaluateInternal obtain this field's Value in the result Document
- **/
-proto.addField = function addField(fieldPath, pExpression){
-	if(!(fieldPath instanceof FieldPath)) fieldPath = new FieldPath(fieldPath);
+ */
+proto.addField = function addField(fieldPath, pExpression) {
+	if (!(fieldPath instanceof FieldPath)) fieldPath = new FieldPath(fieldPath);
 	var fieldPart = fieldPath.getFieldName(0),
 		haveExpr = this._expressions.hasOwnProperty(fieldPart),
-		subObj = this._expressions[fieldPart];	// inserts if !haveExpr //NOTE: not in munge & JS it doesn't, handled manually below
+		expr = this._expressions[fieldPart],
+		subObj = expr instanceof ObjectExpression ? expr : undefined;	// inserts if !haveExpr
 
 	if (!haveExpr) {
 		this._order.push(fieldPart);
 	} else { // we already have an expression or inclusion for this field
-		if (fieldPath.getPathLength() == 1) { // This expression is for right here
-			if (!(subObj instanceof ObjectExpression && typeof pExpression == "object" && pExpression instanceof ObjectExpression)){
-				throw new Error("can't add an expression for field `" + fieldPart + "` because there is already an expression for that field or one of its sub-fields; uassert code 16400"); // we can merge them
-			}
+		if (fieldPath.getPathLength() === 1) {
+			// This expression is for right here
+
+			var newSubObj = pExpression instanceof ObjectExpression ? pExpression : undefined;
+			if (!(subObj && newSubObj))
+				throw new Error("can't add an expression for field " + fieldPart + " because there is already an expression for that field or one of its sub-fields; uassert code 16400"); // we can merge them
 
 			// Copy everything from the newSubObj to the existing subObj
 			// This is for cases like { $project:{ 'b.c':1, b:{ a:1 } } }
-			for (var key in pExpression._expressions) {
-				if (pExpression._expressions.hasOwnProperty(key)) {
-					subObj.addField(key, pExpression._expressions[key]); // asserts if any fields are dupes
-				}
+			for (var i = 0, l = newSubObj._order.length; i < l; ++i) {
+				var key = newSubObj._order[i];
+				// asserts if any fields are dupes
+				subObj.addField(key, newSubObj._expressions[key]);
 			}
 			return;
-		} else { // This expression is for a subfield
-			if(!subObj) throw new Error("can't add an expression for a subfield of `" + fieldPart + "` because there is already an expression that applies to the whole field; uassert code 16401");
+		} else {
+			// This expression is for a subfield
+			if(!subObj)
+				throw new Error("can't add an expression for a subfield of " + fieldPart + " because there is already an expression that applies to the whole field; uassert code 16401");
 		}
 	}
 
-	if (fieldPath.getPathLength() == 1) {
-		if(haveExpr) throw new Error("Internal error."); // haveExpr case handled above.
+	if (fieldPath.getPathLength() === 1) {
+		if (haveExpr) throw new Error("Assertion error."); // haveExpr case handled above.
 		this._expressions[fieldPart] = pExpression;
 		return;
 	}
 
-	if (!haveExpr) subObj = this._expressions[fieldPart] = new ObjectExpression(false);
+	if (!haveExpr)
+		this._expressions[fieldPart] = subObj = ObjectExpression.create();
 
 	subObj.addField(fieldPath.tail(), pExpression);
 };
 
+
 /**
  * Add a field path to the set of those to be included.
  *
@@ -279,68 +311,38 @@ proto.addField = function addField(fieldPath, pExpression){
  *
  * @method includePath
  * @param fieldPath the name of the field to be included
- **/
-proto.includePath = function includePath(path){
-	this.addField(path, null);
+ */
+proto.includePath = function includePath(theFieldPath) {
+	this.addField(theFieldPath, null);
 };
 
 
 proto.serialize = function serialize(explain) {
 	var valBuilder = {};
 
-	if(this._excludeId) {
-		valBuilder._id = false;
-	}
+	if (this.excludeId)
+		valBuilder[Document.ID_PROPERTY_NAME] = false;
 
-	for(var ii = 0; ii < this._order.length; ii ++) {
-		var fieldName = this._order[ii],
-			expr = this._expressions[fieldName];
+	for (var i = 0, l = this._order.length; i < l; ++i) {
+		var fieldName = this._order[i];
+		if (!this._expressions.hasOwnProperty(fieldName)) throw new Error("Assertion failure");
+		var expr = this._expressions[fieldName];
 
-		if(expr === undefined || expr === null) {
-			valBuilder[fieldName] = {$const:expr};
+		if (!expr) {
+			valBuilder[fieldName] = true;
 		} else {
 			valBuilder[fieldName] = expr.serialize(explain);
 		}
-
 	}
 	return valBuilder;
 };
 
 
-
 /**
  * Get a count of the added fields.
  * @method getFieldCount
  * @returns how many fields have been added
- **/
-proto.getFieldCount = function getFieldCount(){
+ */
+proto.getFieldCount = function getFieldCount() {
 	return Object.getOwnPropertyNames(this._expressions).length;
 };
-
-///**
-//* Specialized BSON conversion that allows for writing out a $project specification.
-//* This creates a standalone object, which must be added to a containing object with a name
-//*
-//* @param pBuilder where to write the object to
-//* @param requireExpression see Expression::addToBsonObj
-//**/
-//TODO:	proto.documentToBson = ...?
-//TODO:	proto.addToBsonObj = ...?
-//TODO: proto.addToBsonArray = ...?
-
-//NOTE: in `munge` we're not passing the `Object`s in and allowing `toJSON` (was `documentToBson`) to modify it directly and are instead building and returning a new `Object` since that's the way it's actually used
-proto.toJSON = function toJSON(requireExpression){
-	var o = {};
-	if (this.excludeId) o[Document.ID_PROPERTY_NAME] = false;
-	for (var i = 0, l = this._order.length; i < l; i++) {
-		var fieldName = this._order[i];
-		if (!this._expressions.hasOwnProperty(fieldName)) throw new Error("internal error: fieldName from _ordered list not found in _expressions");
-		var fieldValue = this._expressions[fieldName];
-		if (fieldValue === undefined) {
-			o[fieldName] = true; // this is inclusion, not an expression
-		} else {
-			o[fieldName] = fieldValue.toJSON(requireExpression);
-		}
-	}
-	return o;
-};

+ 606 - 611
test/lib/pipeline/expressions/ObjectExpression.js

@@ -1,22 +1,25 @@
 "use strict";
 var assert = require("assert"),
 	ObjectExpression = require("../../../../lib/pipeline/expressions/ObjectExpression"),
+	Expression = require("../../../../lib/pipeline/expressions/Expression"),
 	ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
 	FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
 	AndExpression = require("../../../../lib/pipeline/expressions/AndExpression"),
 	Variables = require("../../../../lib/pipeline/expressions/Variables"),
-	DepsTracker = require("../../../../lib/pipeline/DepsTracker");
+	DepsTracker = require("../../../../lib/pipeline/DepsTracker"),
+	utils = require("./utils");
 
+// Mocha one-liner to make these tests self-hosted
+if(!module.parent)return(require.cache[__filename]=null,(new(require("mocha"))({ui:"exports",reporter:"spec",grep:process.env.TEST_GREP})).addFile(__filename).run(process.exit));
 
-function assertEqualJson(actual, expected, message){
-	if(actual.sort) {
-		actual.sort();
-		if(expected.sort) {
-			expected.sort();
-		}
-	}
-	assert.strictEqual(message + ":  " + JSON.stringify(actual), message + ":  " + JSON.stringify(expected));
+var constify = utils.constify;
+//SKIPPED: assertBinaryEqual
+//SKIPPED: toJson
+function expressionToJson(expr) {
+	return expr.serialize(false);
 }
+//SKIPPED: fromJson
+//SKIPEPD: valueFromBson
 
 function assertDependencies(expectedDependencies, expression, includePath) {
 	if (includePath === undefined) includePath = true;
@@ -36,12 +39,12 @@ function assertExpectedResult(args) {
 		if (!("expected" in args)) throw new Error("missing arg: `args.expected` is required");
 		if (!("expectedDependencies" in args)) throw new Error("missing arg: `args.expectedDependencies` is required");
 		if (!("expectedJsonRepresentation" in args)) throw new Error("missing arg: `args.expectedJsonRepresentation` is required");
-	}// check for required args
+	}
 	{// base args if none provided
 		if (args.source === undefined) args.source = {_id:0, a:1, b:2};
 		if (args.expectedIsSimple === undefined) args.expectedIsSimple = true;
 		if (args.expression === undefined) args.expression = ObjectExpression.createRoot(); //NOTE: replaces prepareExpression + _expression assignment
-	}// base args if none provided
+	}
 	// run implementation
 	var doc = args.source,
 		result = {},
@@ -49,623 +52,615 @@ function assertExpectedResult(args) {
 	args.expression.addToDocument(result, doc, vars);
 	assert.deepEqual(result, args.expected);
 	assertDependencies(args.expectedDependencies, args.expression);
-	assert.deepEqual(args.expression.serialize(false), args.expectedJsonRepresentation);
+	assert.deepEqual(expressionToJson(args.expression), args.expectedJsonRepresentation);
 	assert.deepEqual(args.expression.isSimple(), args.expectedIsSimple);
 }
 
+exports.ObjectExpression = {
+
+	"constructor()": {
 
-module.exports = {
+		"should return instance if given arg": function() {
+			assert(new ObjectExpression(false) instanceof Expression);
+			assert(new ObjectExpression(true) instanceof Expression);
+		},
 
-	"ObjectExpression": {
+		"should throw Error when constructing without args": function() {
+			assert.throws(function() {
+				new ObjectExpression();
+			});
+		},
 
-		"constructor()": {
+	},
 
-			"should not throw Error when constructing without args": function(){
-				assert.doesNotThrow(function(){
-					ObjectExpression.create();
-				});
-			}
+	"#addDependencies": {
 
+		"should be able to get dependencies for non-inclusion expressions": function testNonInclusionDependencies() {
+			/** Dependencies for non inclusion expressions. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assertDependencies(["_id"], expr, true);
+			assertDependencies([], expr, false);
+			expr.addField("b", FieldPathExpression.create("c.d"));
+			assertDependencies(["_id", "c.d"], expr, true);
+			assertDependencies(["c.d"], expr, false);
 		},
 
-		"#addDependencies":{
+		"should be able to get dependencies for inclusion expressions": function testInclusionDependencies() {
+			/** Dependencies for inclusion expressions. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a");
+			assertDependencies(["_id", "a"], expr, true);
+			var unused = new DepsTracker();
+			assert.throws(function() {
+				expr.addDependencies(unused);
+			}, Error);
+		},
 
-			"should be able to get dependencies for non-inclusion expressions": function testNonInclusionDependencies(){
-				/** Dependencies for non inclusion expressions. */
-				var expr = ObjectExpression.create();
-				expr.addField("a", ConstantExpression.create(5));
-				var depsTracker = {fields:{}};
-				assertEqualJson(expr.addDependencies(depsTracker, [/*FAKING: includePath=true*/]), {fields:{"_id":1}}, "Message");
-				expr.excludeId = true;
-				assertEqualJson(expr.addDependencies(depsTracker, []), {fields:{"_id":1}});
-				expr.addField("b", FieldPathExpression.create("c.d"));
-				//var deps = {};
-				depsTracker = {fields:{}};
-				expr.addDependencies(depsTracker, []);
-				assert.deepEqual(depsTracker, {fields:{"c.d":1}});
-				expr.excludeId = false;
-				//deps = {};
-				depsTracker = {fields:{}}
-				expr.addDependencies(depsTracker, []);
-				assert.deepEqual(depsTracker, {fields:{"_id":1,"c.d":1}});
-			},
-
-			"should be able to get dependencies for inclusion expressions": function testInclusionDependencies(){
-				/** Dependencies for inclusion expressions. */
-				var expr = ObjectExpression.create();
-				expr.includePath( "a" );
-				var depsTracker = {fields:{}};
-				assertEqualJson(expr.addDependencies(depsTracker, [/*FAKING: includePath=true*/]), {"fields":{"_id":1,"a":1}});
-				assert.throws(function(){
-					expr.addDependencies({});
-				}, Error);
-			}
-
-		},
-
-		"#toJSON": {
-
-			"should be able to convert to JSON representation and have constants represented by expressions": function testJson(){
-				/** Serialize to a BSONObj, with constants represented by expressions. */
-				var expr = ObjectExpression.create(true);
-				expr.addField("foo.a", ConstantExpression.create(5));
-				assertEqualJson({foo:{a:{$const:5}}}, expr.serialize());
-			}
-
-		},
-
-		"#optimize": {
-
-			"should be able to optimize expression and sub-expressions": function testOptimize(){
-				/** Optimizing an object expression optimizes its sub expressions. */
-				var expr = ObjectExpression.createRoot();
-				// Add inclusion.
-				expr.includePath("a");
-				// Add non inclusion.
-				expr.addField("b", new AndExpression());
-				expr.optimize();
-				// Optimizing 'expression' optimizes its non inclusion sub expressions, while inclusion sub expressions are passed through.
-				assertEqualJson({a:true, b:{$const:true}}, expr.serialize());
-			}
-
-		},
-
-		"#evaluate()": {
-
-			"should be able to provide an empty object": function testEmpty(){
-				/** Empty object spec. */
-				var expr = ObjectExpression.createRoot();
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {}
-				});
-			},
-
-			"should be able to include 'a' field only": function testInclude(){
-				/** Include 'a' field only. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0, a:1},
-					expectedDependencies: ["_id", "a"],
-					expectedJsonRepresentation: {a:true}
-				});
-			},
-
-			"should NOT be able to include missing 'a' field": function testMissingInclude(){
-				/** Cannot include missing 'a' field. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a");
-				assertExpectedResult({
-					source: {_id:0, b:2},
-					expression: expr,
-					expected: {_id:0},
-					expectedDependencies: ["_id", "a"],
-					expectedJsonRepresentation: {a:true}
-				});
-			},
-
-			"should be able to include '_id' field only": function testIncludeId(){
-				/** Include '_id' field only. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("_id");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {_id:true}
-				});
-			},
-
-			"should be able to exclude '_id' field": function testExcludeId(){
-				/** Exclude '_id' field. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("b");
-				expr.excludeId = true;
-				assertExpectedResult({
-					expression: expr,
-					expected: {b:2},
-					expectedDependencies: ["b"],
-					expectedJsonRepresentation: {_id:false, b:true}
-				});
-			},
-
-			"should be able to include fields in source document order regardless of inclusion order": function testSourceOrder(){
-				/** Result order based on source document field order, not inclusion spec field order. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("b");
+	},
+
+	"#serialize": {
+
+		"should be able to convert to JSON representation and have constants represented by expressions": function testJson() {
+			/** Serialize to a BSONObj, with constants represented by expressions. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("foo.a", ConstantExpression.create(5));
+			assert.deepEqual({foo:{a:{$const:5}}}, expr.serialize());
+		},
+
+	},
+
+	"#optimize": {
+
+		"should be able to optimize expression and sub-expressions": function testOptimize() {
+			/** Optimizing an object expression optimizes its sub expressions. */
+			var expr = ObjectExpression.createRoot();
+			// Add inclusion.
+			expr.includePath("a");
+			// Add non inclusion.
+			var andExpr = new AndExpression();
+			expr.addField("b", andExpr);
+			expr.optimize();
+			// Optimizing 'expression' optimizes its non inclusion sub expressions, while
+			// inclusion sub expressions are passed through.
+			assert.deepEqual({a:true, b:{$const:true}}, expressionToJson(expr));
+		},
+
+	},
+
+	"#evaluate()": {
+
+		"should be able to provide an empty object": function testEmpty() {
+			/** Empty object spec. */
+			var expr = ObjectExpression.createRoot();
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {}
+			});
+		},
+
+		"should be able to include 'a' field only": function testInclude() {
+			/** Include 'a' field only. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0, a:1},
+				expectedDependencies: ["_id", "a"],
+				expectedJsonRepresentation: {a:true}
+			});
+		},
+
+		"should NOT be able to include missing 'a' field": function testMissingInclude() {
+			/** Cannot include missing 'a' field. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a");
+			assertExpectedResult({
+				source: {_id:0, b:2},
+				expression: expr,
+				expected: {_id:0},
+				expectedDependencies: ["_id", "a"],
+				expectedJsonRepresentation: {a:true}
+			});
+		},
+
+		"should be able to include '_id' field only": function testIncludeId() {
+			/** Include '_id' field only. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("_id");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {_id:true}
+			});
+		},
+
+		"should be able to exclude '_id' field": function testExcludeId() {
+			/** Exclude '_id' field. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("b");
+			expr.excludeId = true;
+			assertExpectedResult({
+				expression: expr,
+				expected: {b:2},
+				expectedDependencies: ["b"],
+				expectedJsonRepresentation: {_id:false, b:true}
+			});
+		},
+
+		"should be able to include fields in source document order regardless of inclusion order": function testSourceOrder() {
+			/** Result order based on source document field order, not inclusion spec field order. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("b");
+			expr.includePath("a");
+			assertExpectedResult({
+				expression: expr,
+				get expected() { return this.source; },
+				expectedDependencies: ["_id", "a", "b"],
+				expectedJsonRepresentation: {b:true, a:true}
+			});
+		},
+
+		"should be able to include a nested field": function testIncludeNested() {
+			/** Include a nested field. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0, a:{b:5}},
+				source: {_id:0, a:{b:5, c:6}, z:2},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:true}}
+			});
+		},
+
+		"should be able to include two nested fields": function testIncludeTwoNested() {
+			/** Include two nested fields. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			expr.includePath("a.c");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0, a:{b:5, c:6}},
+				source: {_id:0, a:{b:5,c:6}, z:2},
+				expectedDependencies: ["_id", "a.b", "a.c"],
+				expectedJsonRepresentation: {a:{b:true, c:true}}
+			});
+		},
+
+		"should be able to include two fields nested within different parents": function testIncludeTwoParentNested() {
+			/** Include two fields nested within different parents. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			expr.includePath("c.d");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0, a:{b:5}, c:{d:6}},
+				source: {_id:0, a:{b:5}, c:{d:6}, z:2},
+				expectedDependencies: ["_id", "a.b", "c.d"],
+				expectedJsonRepresentation: {a:{b:true}, c:{d:true}}
+			});
+		},
+
+		"should be able to attempt to include a missing nested field": function testIncludeMissingNested() {
+			/** Attempt to include a missing nested field. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0, a:{}},
+				source: {_id:0, a:{c:6}, z:2},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:true}}
+			});
+		},
+
+		"should be able to attempt to include a nested field within a non object": function testIncludeNestedWithinNonObject() {
+			/** Attempt to include a nested field within a non object. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0},
+				source: {_id:0, a:2, z:2},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:true}}
+			});
+		},
+
+		"should be able to include a nested field within an array": function testIncludeArrayNested() {
+			/** Include a nested field within an array. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			assertExpectedResult({
+				expression: expr,
+				expected: {_id:0,a:[{b:5},{b:2},{}]},
+				source: {_id:0,a:[{b:5,c:6},{b:2,c:9},{c:7},[],2],z:1},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:true}}
+			});
+		},
+
+		"should NOT include non-root '_id' field implicitly": function testExcludeNonRootId() {
+			/** Don't include not root '_id' field implicitly. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a.b");
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0, a:{_id:1, b:1}},
+				expected: {_id:0, a:{b:1}},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:true}}
+			});
+		},
+
+		"should be able to project a computed expression": function testComputed() {
+			/** Project a computed expression. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:5},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{$const:5}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a computed expression replacing an existing field": function testComputedReplacement() {
+			/** Project a computed expression replacing an existing field. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0, a:99},
+				expected: {_id:0, a:5},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{$const:5}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should NOT be able to project an undefined value": function testComputedUndefined() {
+			/** An undefined value is passed through */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(undefined));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:undefined},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{$const:undefined}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a computed expression replacing an existing field with Undefined": function testComputedUndefinedReplacement() {
+			/** Project a computed expression replacing an existing field with Undefined. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(undefined));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0, a:99},
+				expected: {_id:0, a:undefined},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{$const:undefined}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a null value": function testComputedNull() {
+			/** A null value is projected. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(null));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:null},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{$const:null}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a nested value": function testComputedNested() {
+			/** A nested value is projected. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(5));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:5}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:5}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a field path": function testComputedFieldPath() {
+			/** A field path is projected. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", FieldPathExpression.create("x"));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0, x:4},
+				expected: {_id:0, a:4},
+				expectedDependencies: ["_id", "x"],
+				expectedJsonRepresentation: {a:"$x"},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a nested field path": function testComputedNestedFieldPath() {
+			/** A nested field path is projected. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", FieldPathExpression.create("x.y"));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0, x:{y:4}},
+				expected: {_id:0, a:{b:4}},
+				expectedDependencies: ["_id", "x.y"],
+				expectedJsonRepresentation: {a:{b:"$x.y"}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should NOT project an empty subobject expression for a missing field": function testEmptyNewSubobject() {
+			/** An empty subobject expression for a missing field is not projected. */
+			var expr = ObjectExpression.createRoot();
+			// Create a sub expression returning an empty object.
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("b", FieldPathExpression.create("a.b"));
+			expr.addField("a", subExpr);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0},
+				expectedDependencies: ["_id", "a.b"],
+				expectedJsonRepresentation: {a:{b:"$a.b"}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project a non-empty new subobject": function testNonEmptyNewSubobject() {
+			/** A non empty subobject expression for a missing field is projected. */
+			var expr = ObjectExpression.createRoot();
+			// Create a sub expression returning an empty object.
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("b", ConstantExpression.create(6));
+			expr.addField("a", subExpr);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project two computed fields within a common parent": function testAdjacentDottedComputedFields() {
+			/** Two computed fields within a common parent. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(6));
+			expr.addField("a.c", ConstantExpression.create(7));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6, c:7}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project two computed fields within a common parent (w/ one case dotted)": function testAdjacentDottedAndNestedComputedFields() {
+			/** Two computed fields within a common parent, in one case dotted. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(6));
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("c", ConstantExpression.create(7));
+			expr.addField("a", subExpr);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6, c:7}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project two computed fields within a common parent (in another case dotted)": function testAdjacentNestedAndDottedComputedFields() {
+			/** Two computed fields within a common parent, in another case dotted. */
+			var expr = ObjectExpression.createRoot();
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("b", ConstantExpression.create(6));
+			expr.addField("a", subExpr);
+			expr.addField("a.c", ConstantExpression.create(7));
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6, c:7}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project two computed fields within a common parent (nested rather than dotted)": function testAdjacentNestedComputedFields() {
+			/** Two computed fields within a common parent, nested rather than dotted. */
+			var expr = ObjectExpression.createRoot();
+			var subExpr1 = ObjectExpression.create();
+			subExpr1.addField("b", ConstantExpression.create(6));
+			expr.addField("a", subExpr1);
+			var subExpr2 = ObjectExpression.create();
+			subExpr2.addField("c", ConstantExpression.create(7));
+			expr.addField("a", subExpr2);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6, c:7}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project multiple nested fields out of order without affecting output order": function testAdjacentNestedOrdering() {
+			/** Field ordering is preserved when nested fields are merged. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(6));
+			var subExpr = ObjectExpression.create();
+			// Add field 'd' then 'c'.  Expect the same field ordering in the result doc.
+			subExpr.addField("d", ConstantExpression.create(7));
+			subExpr.addField("c", ConstantExpression.create(8));
+			expr.addField("a", subExpr);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:6, d:7, c:8}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{$const:6},d:{$const:7},c:{$const:8}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should be able to project adjacent fields two levels deep": function testMultipleNestedFields() {
+			/** Adjacent fields two levels deep. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b.c", ConstantExpression.create(6));
+			var bSubExpression = ObjectExpression.create();
+			bSubExpression.addField("d", ConstantExpression.create(7));
+			var aSubExpression = ObjectExpression.create();
+			aSubExpression.addField("b", bSubExpression);
+			expr.addField("a", aSubExpression);
+			assertExpectedResult({
+				expression: expr,
+				source: {_id:0},
+				expected: {_id:0, a:{b:{c:6, d:7}}},
+				expectedDependencies: ["_id"],
+				expectedJsonRepresentation: {a:{b:{c:{$const:6},d:{$const:7}}}},
+				expectedIsSimple: false
+			});
+		},
+
+		"should throw an Error if two expressions generate the same field": function testConflictingExpressionFields() {
+			/** Two expressions cannot generate the same field. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assert.throws(function() {
+				expr.addField("a", ConstantExpression.create(6)); // Duplicate field.
+			}, Error);
+		},
+
+		"should throw an Error if an expression field conflicts with an inclusion field": function testConflictingInclusionExpressionFields() {
+			/** An expression field conflicts with an inclusion field. */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a");
+			assert.throws(function() {
+				expr.addField("a", ConstantExpression.create(6));
+			}, Error);
+		},
+
+		"should throw an Error if an inclusion field conflicts with an expression field": function testConflictingExpressionInclusionFields() {
+			/** An inclusion field conflicts with an expression field. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assert.throws(function() {
 				expr.includePath("a");
-				assertExpectedResult({
-					expression: expr,
-					get expected() { return this.source; },
-					expectedDependencies: ["_id", "a", "b"],
-					expectedJsonRepresentation: {b:true, a:true}
-				});
-			},
-
-			"should be able to include a nested field": function testIncludeNested(){
-				/** Include a nested field. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0, a:{b:5}},
-					source: {_id:0, a:{b:5, c:6}, z:2},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:true}}
-				});
-			},
-
-			"should be able to include two nested fields": function testIncludeTwoNested(){
-				/** Include two nested fields. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				expr.includePath("a.c");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0, a:{b:5, c:6}},
-					source: {_id:0, a:{b:5,c:6}, z:2},
-					expectedDependencies: ["_id", "a.b", "a.c"],
-					expectedJsonRepresentation: {a:{b:true, c:true}}
-				});
-			},
-
-			"should be able to include two fields nested within different parents": function testIncludeTwoParentNested(){
-				/** Include two fields nested within different parents. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				expr.includePath("c.d");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0, a:{b:5}, c:{d:6}},
-					source: {_id:0, a:{b:5}, c:{d:6}, z:2},
-					expectedDependencies: ["_id", "a.b", "c.d"],
-					expectedJsonRepresentation: {a:{b:true}, c:{d:true}}
-				});
-			},
-
-			"should be able to attempt to include a missing nested field": function testIncludeMissingNested(){
-				/** Attempt to include a missing nested field. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0, a:{}},
-					source: {_id:0, a:{c:6}, z:2},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:true}}
-				});
-			},
-
-			"should be able to attempt to include a nested field within a non object": function testIncludeNestedWithinNonObject(){
-				/** Attempt to include a nested field within a non object. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0},
-					source: {_id:0, a:2, z:2},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:true}}
-				});
-			},
-
-			"should be able to include a nested field within an array": function testIncludeArrayNested(){
-				/** Include a nested field within an array. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				assertExpectedResult({
-					expression: expr,
-					expected: {_id:0,a:[{b:5},{b:2},{}]},
-					source: {_id:0,a:[{b:5,c:6},{b:2,c:9},{c:7},[],2],z:1},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:true}}
-				});
-			},
-
-			"should NOT include non-root '_id' field implicitly": function testExcludeNonRootId(){
-				/** Don't include not root '_id' field implicitly. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a.b");
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0, a:{_id:1, b:1}},
-					expected: {_id:0, a:{b:1}},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:true}}
-				});
-			},
-
-			"should be able to project a computed expression": function testComputed(){
-				/** Project a computed expression. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(5));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:5},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{$const:5}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a computed expression replacing an existing field": function testComputedReplacement(){
-				/** Project a computed expression replacing an existing field. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(5));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0, a:99},
-					expected: {_id:0, a:5},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{$const:5}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should NOT be able to project an undefined value": function testComputedUndefined(){
-				/** An undefined value is passed through */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(undefined));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:undefined},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{$const:undefined}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a computed expression replacing an existing field with Undefined": function testComputedUndefinedReplacement(){
-				/** Project a computed expression replacing an existing field with Undefined. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(5));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0, a:99},
-					expected: {_id:0, a:undefined},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{$const:undefined}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a null value": function testComputedNull(){
-				/** A null value is projected. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(null));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:null},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{$const:null}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a nested value": function testComputedNested(){
-				/** A nested value is projected. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", ConstantExpression.create(5));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:5}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:5}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a field path": function testComputedFieldPath(){
-				/** A field path is projected. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", FieldPathExpression.create("x"));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0, x:4},
-					expected: {_id:0, a:4},
-					expectedDependencies: ["_id", "x"],
-					expectedJsonRepresentation: {a:"$x"},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a nested field path": function testComputedNestedFieldPath(){
-				/** A nested field path is projected. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", FieldPathExpression.create("x.y"));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0, x:{y:4}},
-					expected: {_id:0, a:{b:4}},
-					expectedDependencies: ["_id", "x,y"],
-					expectedJsonRepresentation: {a:{b:"$x.y"}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should NOT project an empty subobject expression for a missing field": function testEmptyNewSubobject(){
-				/** An empty subobject expression for a missing field is not projected. */
-				var expr = ObjectExpression.createRoot();
-				// Create a sub expression returning an empty object.
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("b", FieldPathExpression.create("a.b"));
-				expr.addField("a", subExpr);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0},
-					expectedDependencies: ["_id", "a.b"],
-					expectedJsonRepresentation: {a:{b:"$a.b"}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project a non-empty new subobject": function testNonEmptyNewSubobject(){
-				/** A non empty subobject expression for a missing field is projected. */
-				var expr = ObjectExpression.createRoot();
-				// Create a sub expression returning an empty object.
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("b", ConstantExpression.create(6));
-				expr.addField("a", subExpr);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project two computed fields within a common parent": function testAdjacentDottedComputedFields(){
-				/** Two computed fields within a common parent. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", ConstantExpression.create(6));
-				expr.addField("a.c", ConstantExpression.create(7));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6, c:7}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project two computed fields within a common parent (w/ one case dotted)": function testAdjacentDottedAndNestedComputedFields(){
-				/** Two computed fields within a common parent, in one case dotted. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", ConstantExpression.create(6));
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("c", ConstantExpression.create(7));
-				expr.addField("a", subExpr);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6, c:7}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project two computed fields within a common parent (in another case dotted)": function testAdjacentNestedAndDottedComputedFields(){
-				/** Two computed fields within a common parent, in another case dotted. */
-				var expr = ObjectExpression.createRoot();
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("b", ConstantExpression.create(6));
-				expr.addField("a", subExpr);
-				expr.addField("a.c", ConstantExpression.create(7));
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6, c:7}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project two computed fields within a common parent (nested rather than dotted)": function testAdjacentNestedComputedFields(){
-				/** Two computed fields within a common parent, nested rather than dotted. */
-				var expr = ObjectExpression.createRoot();
-				var subExpr1 = ObjectExpression.create();
-				subExpr1.addField("b", ConstantExpression.create(6));
-				expr.addField("a", subExpr1);
-				var subExpr2 = ObjectExpression.create();
-				subExpr2.addField("c", ConstantExpression.create(7));
-				expr.addField("a", subExpr2);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6, c:7}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project multiple nested fields out of order without affecting output order": function testAdjacentNestedOrdering(){
-				/** Field ordering is preserved when nested fields are merged. */
-				var expr = ObjectExpression.createRoot();
+			}, Error);
+		},
+
+		"should throw an Error if an object expression conflicts with a constant expression": function testConflictingObjectConstantExpressionFields() {
+			/** An object expression conflicts with a constant expression. */
+			var expr = ObjectExpression.createRoot();
+			var subExpr = ObjectExpression.create();
+			subExpr.includePath("b");
+			expr.addField("a", subExpr);
+			assert.throws(function() {
 				expr.addField("a.b", ConstantExpression.create(6));
-				var subExpr = ObjectExpression.create();
-				// Add field 'd' then 'c'.  Expect the same field ordering in the result doc.
-				subExpr.addField("d", ConstantExpression.create(7));
-				subExpr.addField("c", ConstantExpression.create(8));
-				expr.addField("a", subExpr);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:6, d:7, c:8}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{$const:6},d:{$const:7},c:{$const:8}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should be able to project adjacent fields two levels deep": function testMultipleNestedFields(){
-				/** Adjacent fields two levels deep. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b.c", ConstantExpression.create(6));
-				var bSubExpression = ObjectExpression.create();
-				bSubExpression.addField("d", ConstantExpression.create(7));
-				var aSubExpression = ObjectExpression.create();
-				aSubExpression.addField("b", bSubExpression);
-				expr.addField("a", aSubExpression);
-				assertExpectedResult({
-					expression: expr,
-					source: {_id:0},
-					expected: {_id:0, a:{b:{c:6, d:7}}},
-					expectedDependencies: ["_id"],
-					expectedJsonRepresentation: {a:{b:{c:{$const:6},d:{$const:7}}}},
-					expectedIsSimple: false
-				});
-			},
-
-			"should throw an Error if two expressions generate the same field": function testConflictingExpressionFields(){
-				/** Two expressions cannot generate the same field. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.addField("a", ConstantExpression.create(6)); // Duplicate field.
-				}, Error);
-			},
-
-			"should throw an Error if an expression field conflicts with an inclusion field": function testConflictingInclusionExpressionFields(){
-				/** An expression field conflicts with an inclusion field. */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a");
-				assert.throws(function(){
-					expr.addField("a", ConstantExpression.create(6));
-				}, Error);
-			},
-
-			"should throw an Error if an inclusion field conflicts with an expression field": function testConflictingExpressionInclusionFields(){
-				/** An inclusion field conflicts with an expression field. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.includePath("a");
-				}, Error);
-			},
-
-			"should throw an Error if an object expression conflicts with a constant expression": function testConflictingObjectConstantExpressionFields(){
-				/** An object expression conflicts with a constant expression. */
-				var expr = ObjectExpression.createRoot();
-				var subExpr = ObjectExpression.create();
-				subExpr.includePath("b");
+			}, Error);
+		},
+
+		"should throw an Error if a constant expression conflicts with an object expression": function testConflictingConstantObjectExpressionFields() {
+			/** A constant expression conflicts with an object expression. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(6));
+			var subExpr = ObjectExpression.create();
+			subExpr.includePath("b");
+			assert.throws(function() {
 				expr.addField("a", subExpr);
-				assert.throws(function(){
-					expr.addField("a.b", ConstantExpression.create(6));
-				}, Error);
-			},
-
-			"should throw an Error if a constant expression conflicts with an object expression": function testConflictingConstantObjectExpressionFields(){
-				/** A constant expression conflicts with an object expression. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", ConstantExpression.create(6));
-				var subExpr = ObjectExpression.create();
-				subExpr.includePath("b");
-				assert.throws(function(){
-					expr.addField("a", subExpr);
-				}, Error);
-			},
-
-			"should throw an Error if two nested expressions cannot generate the same field": function testConflictingNestedFields(){
-				/** Two nested expressions cannot generate the same field. */
-				var expr = ObjectExpression.createRoot();
+			}, Error);
+		},
+
+		"should throw an Error if two nested expressions cannot generate the same field": function testConflictingNestedFields() {
+			/** Two nested expressions cannot generate the same field. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(5));
+			assert.throws(function() {
+				expr.addField("a.b", ConstantExpression.create(6));	// Duplicate field.
+			}, Error);
+		},
+
+		"should throw an Error if an expression is created for a subfield of another expression": function testConflictingFieldAndSubfield() {
+			/** An expression cannot be created for a subfield of another expression. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			assert.throws(function() {
 				expr.addField("a.b", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.addField("a.b", ConstantExpression.create(6));	// Duplicate field.
-				}, Error);
-			},
-
-			"should throw an Error if an expression is created for a subfield of another expression": function testConflictingFieldAndSubfield(){
-				/** An expression cannot be created for a subfield of another expression. */
-				var expr = ObjectExpression.createRoot();
+			}, Error);
+		},
+
+		"should throw an Error if an expression is created for a nested field of another expression.": function testConflictingFieldAndNestedField() {
+			/** An expression cannot be created for a nested field of another expression. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a", ConstantExpression.create(5));
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("b", ConstantExpression.create(5));
+			assert.throws(function() {
+				expr.addField("a", subExpr);
+			}, Error);
+		},
+
+		"should throw an Error if an expression is created for a parent field of another expression": function testConflictingSubfieldAndField() {
+			/** An expression cannot be created for a parent field of another expression. */
+			var expr = ObjectExpression.createRoot();
+			expr.addField("a.b", ConstantExpression.create(5));
+			assert.throws(function() {
 				expr.addField("a", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.addField("a.b", ConstantExpression.create(5));
-				}, Error);
-			},
-
-			"should throw an Error if an expression is created for a nested field of another expression.": function testConflictingFieldAndNestedField(){
-				/** An expression cannot be created for a nested field of another expression. */
-				var expr = ObjectExpression.createRoot();
+			}, Error);
+		},
+
+		"should throw an Error if an expression is created for a parent of a nested field": function testConflictingNestedFieldAndField() {
+			/** An expression cannot be created for a parent of a nested field. */
+			var expr = ObjectExpression.createRoot();
+			var subExpr = ObjectExpression.create();
+			subExpr.addField("b", ConstantExpression.create(5));
+			expr.addField("a", subExpr);
+			assert.throws(function() {
 				expr.addField("a", ConstantExpression.create(5));
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("b", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.addField("a", subExpr);
-				}, Error);
-			},
-
-			"should throw an Error if an expression is created for a parent field of another expression": function testConflictingSubfieldAndField(){
-				/** An expression cannot be created for a parent field of another expression. */
-				var expr = ObjectExpression.createRoot();
-				expr.addField("a.b", ConstantExpression.create(5));
-				assert.throws(function(){
-					expr.addField("a", ConstantExpression.create(5));
-				}, Error);
-			},
-
-			"should throw an Error if an expression is created for a parent of a nested field": function testConflictingNestedFieldAndField(){
-				/** An expression cannot be created for a parent of a nested field. */
-				var expr = ObjectExpression.createRoot();
-				var subExpr = ObjectExpression.create();
-				subExpr.addField("b", ConstantExpression.create(5));
-				expr.addField("a", subExpr);
-				assert.throws(function(){
-					expr.addField("a", ConstantExpression.create(5));
-				}, Error);
-			},
-
-			"should be able to evaluate expressions in general": function testEvaluate(){
-				/**
-				 * evaluate() does not supply an inclusion document.
-				 * Inclusion spec'd fields are not included.
-				 * (Inclusion specs are not generally expected/allowed in cases where evaluate is called instead of addToDocument.)
-				 */
-				var expr = ObjectExpression.createRoot();
-				expr.includePath("a");
-				expr.addField("b", ConstantExpression.create(5));
-				expr.addField("c", FieldPathExpression.create("a"));
-				var res = expr.evaluateInternal(new Variables(1, {_id:0, a:1}));
-				assert.deepEqual({"b":5, "c":1}, res);
-			}
-		}
+			}, Error);
+		},
 
-	}
+		"should be able to evaluate expressions in general": function testEvaluate() {
+			/**
+			 * evaluate() does not supply an inclusion document.
+			 * Inclusion spec'd fields are not included.
+			 * (Inclusion specs are not generally expected/allowed in cases where evaluate is called instead of addToDocument.)
+			 */
+			var expr = ObjectExpression.createRoot();
+			expr.includePath("a");
+			expr.addField("b", ConstantExpression.create(5));
+			expr.addField("c", FieldPathExpression.create("a"));
+			var res = expr.evaluateInternal(new Variables(1, {_id:0, a:1}));
+			assert.deepEqual({"b":5, "c":1}, res);
+		},
 
-};
+	},
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+};