Просмотр исходного кода

EAGLESIX-2651: Compare: missed test, reduce deviations w/ 2.6.5 code, simplified tests a bit, minor formatting

Kyle P Davis 11 лет назад
Родитель
Сommit
716d24eafa

+ 77 - 80
lib/pipeline/expressions/CompareExpression.js

@@ -6,120 +6,117 @@
  * @namespace mungedb-aggregate.pipeline.expressions
  * @module mungedb-aggregate
  * @constructor
- **/
+ */
 var CompareExpression = module.exports = function CompareExpression(cmpOp) {
-	if (!(arguments.length === 1 && typeof cmpOp === "string")) throw new Error("string arg expected: cmpOp");
+	if (!(arguments.length === 1 && typeof cmpOp === "string")) throw new Error(klass.name + ": args expected: cmpOp");
     this.cmpOp = cmpOp;
     base.call(this);
-}, klass = CompareExpression,
-    FixedArityExpression = require("./FixedArityExpressionT")(klass, 2),
-	base = FixedArityExpression,
-    proto = klass.prototype = Object.create(base.prototype, {
-		constructor: {
-			value: klass
-		}
-	});
-
-// DEPENDENCIES
-var Value = require("../Value");
-var Expression = require("./Expression");
-var ConstantExpression = require("./ConstantExpression");
-var FieldPathExpression = require("./FieldPathExpression");
-var FieldRangeExpression = require("./FieldRangeExpression");
-var NaryExpression = require("./NaryExpression");
-
-// NESTED CLASSES
+}, klass = CompareExpression, base = require("./FixedArityExpressionT")(CompareExpression, 2), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
+
+
+var Value = require("../Value"),
+	Expression = require("./Expression"),
+	ConstantExpression = require("./ConstantExpression"),
+	FieldPathExpression = require("./FieldPathExpression"),
+	FieldRangeExpression = require("./FieldRangeExpression"),
+	NaryExpression = require("./NaryExpression");
+
+
+klass.parse = function parse(jsonExpr, vps, op) {
+	var expr = new CompareExpression(op),
+		args = base.parseArguments(jsonExpr, vps);
+	expr.validateArguments(args);
+	expr.operands = args;
+	return expr;
+};
+
+
 /**
  * Lookup table for truth value returns
- *
  * @param truthValues   truth value for -1, 0, 1
  * @param reverse               reverse comparison operator
  * @param name                  string name
- **/
-var CmpLookup = (function() { // emulating a struct
-	// CONSTRUCTOR
-	var klass = function CmpLookup(truthValues, reverse, name) {
-		if (arguments.length !== 3) throw new Error("args expected: truthValues, reverse, name");
-		this.truthValues = truthValues;
-		this.reverse = reverse;
-		this.name = name;
-	}, base = Object,
-		proto = klass.prototype = Object.create(base.prototype, {
-			constructor: {
-				value: klass
-			}
-		});
-	return klass;
-})();
-
-
-klass.EQ = "$eq";
-klass.NE = "$ne";
-klass.GT = "$gt";
-klass.GTE = "$gte";
-klass.LT = "$lt";
-klass.LTE = "$lte";
-klass.CMP = "$cmp";
-
-// verify we need this below
-// PRIVATE STATIC MEMBERS
+ */
+var CmpLookup = function CmpLookup(truthValues, reverse, name) { // emulating a struct
+	if (arguments.length !== 3) throw new Error("args expected: truthValues, reverse, name");
+	this.truthValues = truthValues;
+	this.reverse = reverse;
+	this.name = name;
+};
+
+
+/**
+ * Enumeration of comparison operators. Any changes to these values require adjustment of
+ * the lookup table in the implementation.
+ */
+var CmpOp = klass.CmpOp = {
+	EQ: "$eq",
+	NE: "$ne",
+	GT: "$gt",
+	GTE: "$gte",
+	LT: "$lt",
+	LTE: "$lte",
+	CMP: "$cmp",
+};
+
+
 /**
  * a table of cmp type lookups to truth values
  * @private
- **/
+ */
 var cmpLookupMap = [ //NOTE: converted from this Array to a Dict/Object below using CmpLookup#name as the key
 	//              -1      0      1      reverse             name     (taking advantage of the fact that our 'enums' are strings below)
-	new CmpLookup([false, true, false], CompareExpression.EQ, CompareExpression.EQ),
-	new CmpLookup([true, false, true], CompareExpression.NE, CompareExpression.NE),
-	new CmpLookup([false, false, true], CompareExpression.LT, CompareExpression.GT),
-	new CmpLookup([false, true, true], CompareExpression.LTE, CompareExpression.GTE),
-	new CmpLookup([true, false, false], CompareExpression.GT, CompareExpression.LT),
-	new CmpLookup([true, true, false], CompareExpression.GTE, CompareExpression.LTE),
+	new CmpLookup([false, true, false], CmpOp.EQ, CmpOp.EQ),
+	new CmpLookup([true, false, true], CmpOp.NE, CmpOp.NE),
+	new CmpLookup([false, false, true], CmpOp.LT, CmpOp.GT),
+	new CmpLookup([false, true, true], CmpOp.LTE, CmpOp.GTE),
+	new CmpLookup([true, false, false], CmpOp.GT, CmpOp.LT),
+	new CmpLookup([true, true, false], CmpOp.GTE, CmpOp.LTE),
 
 	// CMP is special. Only name is used.
-	new CmpLookup([false, false, false], CompareExpression.CMP, CompareExpression.CMP)
+	new CmpLookup([false, false, false], CmpOp.CMP, CmpOp.CMP)
 ].reduce(function(r, o) {
 	r[o.name] = o;
 	return r;
 }, {});
 
 
-//NOTE: DEVIATION FROM MONGO: moving op to the first argument slot so we can bind it
-klass.parse = function parse(op, jsonExpr, vps) {
-	var expr = new CompareExpression(op),
-		args = base.parseArguments(jsonExpr, vps);
-	expr.validateArguments(args);
-	expr.operands = args;
-	return expr;
-};
-
-// PROTOTYPE MEMBERS
 proto.evaluateInternal = function evaluateInternal(vars) {
 	var left = this.operands[0].evaluateInternal(vars),
 		right = this.operands[1].evaluateInternal(vars),
 		cmp = Value.compare(left, right);
 
-	if(cmp === 0) {
+    // Make cmp one of 1, 0, or -1.
+	if (cmp === 0) {
 		//leave as 0
-	} else if(cmp < 0) {
+	} else if (cmp < 0) {
 		cmp = -1;
-	} else if(cmp > 0) {
+	} else if (cmp > 0) {
 		cmp = 1;
 	}
 
-	if (this.cmpOp === klass.CMP) return cmp;
-	return cmpLookupMap[this.cmpOp].truthValues[cmp + 1];
+	if (this.cmpOp === CmpOp.CMP)
+		return cmp;
+
+	var returnValue = cmpLookupMap[this.cmpOp].truthValues[cmp + 1];
+	return returnValue;
 };
 
+
 proto.getOpName = function getOpName() {
 	return this.cmpOp;
 };
 
-/** Register Expression */
-Expression.registerExpression("$eq", klass.parse.bind(klass, klass.EQ));
-Expression.registerExpression("$ne", klass.parse.bind(klass, klass.NE));
-Expression.registerExpression("$gt", klass.parse.bind(klass, klass.GT));
-Expression.registerExpression("$gte", klass.parse.bind(klass, klass.GTE));
-Expression.registerExpression("$lt", klass.parse.bind(klass, klass.LT));
-Expression.registerExpression("$lte", klass.parse.bind(klass, klass.LTE));
-Expression.registerExpression("$cmp", klass.parse.bind(klass, klass.CMP));
+
+function bindLast(fn, lastArg) { // similar to the boost::bind used in the mongo code
+	return function() {
+		return fn.apply(this, Array.prototype.slice.call(arguments).concat([lastArg]));
+	};
+}
+Expression.registerExpression("$cmp", bindLast(klass.parse, CmpOp.CMP));
+Expression.registerExpression("$eq", bindLast(klass.parse, CmpOp.EQ));
+Expression.registerExpression("$gt", bindLast(klass.parse, CmpOp.GT));
+Expression.registerExpression("$gte", bindLast(klass.parse, CmpOp.GTE));
+Expression.registerExpression("$lt", bindLast(klass.parse, CmpOp.LT));
+Expression.registerExpression("$lte", bindLast(klass.parse, CmpOp.LTE));
+Expression.registerExpression("$ne", bindLast(klass.parse, CmpOp.NE));

+ 405 - 471
test/lib/pipeline/expressions/CompareExpression.js

@@ -10,525 +10,459 @@ var assert = require("assert"),
 	constify = utils.constify,
 	expressionToJson = utils.expressionToJson;
 
-module.exports = {
+// 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));
+
+var TestBase = function TestBase(overrides) {
+		//NOTE: DEVIATION FROM MONGO: using this base class to make things easier to initialize
+		for (var key in overrides)
+			this[key] = overrides[key];
+	},
+	OptimizeBase = (function() {
+		var klass = function OptimizeBase() {
+				base.apply(this, arguments);
+			},
+			base = TestBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.run = function() {
+			var specElement = this.spec,
+				idGenerator = new VariablesIdGenerator(),
+				vps = new VariablesParseState(idGenerator),
+				expression = Expression.parseOperand(specElement, vps),
+				optimized = expression.optimize();
+			assert.deepEqual(constify(this.expectedOptimized()), expressionToJson(optimized));
+		};
+		return klass;
+	})(),
+	FieldRangeOptimize = (function() {
+		var klass = function FieldRangeOptimize() {
+				base.apply(this, arguments);
+			},
+			base = OptimizeBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.expectedOptimized = function(){
+			return this.spec;
+		};
+		return klass;
+	})(),
+	NoOptimize = (function() {
+		var klass = function NoOptimize() {
+				base.apply(this, arguments);
+			},
+			base = OptimizeBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.expectedOptimized = function(){
+			return this.spec;
+		};
+		return klass;
+	})(),
+	ExpectedResultBase = (function() {
+		/** Check expected result for expressions depending on constants. */
+		var klass = function ExpectedResultBase() {
+				base.apply(this, arguments);
+			},
+			base = OptimizeBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.run = function() {
+			base.prototype.run.call(this);
+			var specElement = this.spec,
+				idGenerator = new VariablesIdGenerator(),
+				vps = new VariablesParseState(idGenerator),
+				expression = Expression.parseOperand(specElement, vps);
+			// Check expression spec round trip.
+			assert.deepEqual(expressionToJson(expression), constify(specElement));
+			// Check evaluation result.
+			assert.strictEqual(expression.evaluate({}), this.expectedResult);
+			// Check that the result is the same after optimizing.
+			var optimized = expression.optimize();
+			assert.strictEqual(optimized.evaluate({}), this.expectedResult);
+		};
+		proto.expectedOptimized = function() {
+			return {$const:this.expectedResult};
+		};
+		return klass;
+	})(),
+	ExpectedTrue = (function(){
+		var klass = function ExpectedTrue() {
+				base.apply(this, arguments);
+			},
+			base = ExpectedResultBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.expectedResult = true;
+		return klass;
+	})(),
+	ExpectedFalse = (function(){
+		var klass = function ExpectedFalse() {
+				base.apply(this, arguments);
+			},
+			base = ExpectedResultBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.expectedResult = false;
+		return klass;
+	})(),
+	ParseError = (function(){
+		var klass = function ParseError() {
+				base.apply(this, arguments);
+			},
+			base = TestBase,
+			proto = klass.prototype = Object.create(base.prototype);
+		proto.run = function() {
+			var specElement = this.spec,
+				idGenerator = new VariablesIdGenerator(),
+				vps = new VariablesParseState(idGenerator);
+			assert.throws(function() {
+				Expression.parseOperand(specElement, vps);
+			});
+		};
+		return klass;
+	})();
+
+exports.CompareExpression = {
+
+	"constructor()": {
+
+		"should throw Error if no args": function() {
+			assert.throws(function() {
+				new CompareExpression();
+			});
+		},
 
-	"CompareExpression": {
+		"should throw if more than 1 args": function() {
+			assert.throws(function() {
+				new CompareExpression(1,2);
+			});
+		},
 
-		"constructor()": {
+		"should not throw if 1 arg and arg is string": function() {
+			assert.doesNotThrow(function() {
+				new CompareExpression("a");
+			});
+		},
 
-			"should throw Error if no args": function testConstructor() {
-				assert.throws(function() {
-					new CompareExpression();
-				});
-			},
+	},
 
-			"should throw if more than 1 args": function testConstructor() {
-				assert.throws(function() {
-					new CompareExpression(1,2);
-				});
-			},
+	"#getOpName()": {
 
-			"should not throw if 1 arg and arg is string": function testConstructor() {
-				assert.doesNotThrow(function() {
-					new CompareExpression("a");
-				});
-			}
-
-		},
-
-		"#getOpName()": {
-
-			"should return the correct op name; $eq, $ne, $gt, $gte, $lt, $lte, $cmp": function testOpName() {
-				assert.equal((new CompareExpression(CompareExpression.EQ)).getOpName(), "$eq");
-				assert.equal((new CompareExpression(CompareExpression.NE)).getOpName(), "$ne");
-				assert.equal((new CompareExpression(CompareExpression.GT)).getOpName(), "$gt");
-				assert.equal((new CompareExpression(CompareExpression.GTE)).getOpName(), "$gte");
-				assert.equal((new CompareExpression(CompareExpression.LT)).getOpName(), "$lt");
-				assert.equal((new CompareExpression(CompareExpression.LTE)).getOpName(), "$lte");
-				assert.equal((new CompareExpression(CompareExpression.CMP)).getOpName(), "$cmp");
-			}
-
-		},
-
-		"#evaluate()": {
-
-			before: function before() {
-				var self = this;
-				//OptimizeBase
-				this.OptimizeBase = function() {};
-				var oBProto = this.OptimizeBase.prototype;
-				oBProto.run = function run() {
-					var specElement = this.spec(),
-						idGenerator = new VariablesIdGenerator(),
-						vps = new VariablesParseState(idGenerator),
-						expression = Expression.parseOperand(specElement, vps),
-						optimized = expression.optimize();
-					assert.deepEqual(constify(this.expectedOptimized()), expressionToJson(optimized));
-				};
-				//FieldRangeOptimize
-				this.FieldRangeOptimize = function FieldRangeOptimize() {};
-				var fROProto = Object.create(oBProto);
-				this.FieldRangeOptimize.prototype = fROProto;
-				fROProto.expectedOptimized = function() {
-					return this.spec();
-				};
-				//NoOptimize
-				this.NoOptimize = function NoOptimize() {};
-				var nOProto = Object.create(oBProto);
-				this.NoOptimize.prototype = nOProto;
-				nOProto.expectedOptimized = function() {
-					return this.spec();
-				};
-				//ExpectedResultBase
-				this.ExpectedResultBase = function ExpectedResultBase() {};
-				var eRBProto = Object.create(oBProto);
-				this.ExpectedResultBase.prototype = eRBProto;
-				eRBProto.run = function run() {
-					oBProto.run.call(this);
-					var specElement = this.spec(),
-						idGenerator = new VariablesIdGenerator(),
-						vps = new VariablesParseState(idGenerator),
-						expression = Expression.parseOperand(specElement, vps);
-					assert.deepEqual(expressionToJson(expression),
-						constify(specElement));
-					assert.strictEqual(expression.evaluate({}),
-						this.expectedResult());
-					var optimized = expression.optimize();
-					assert.strictEqual(optimized.evaluate({}),
-						this.expectedResult());
-				};
-				eRBProto.expectedOptimized = function expectedOptimized() {
-					return {$const:this.expectedResult()};
-				};
-				//ExpectedTrue
-				this.ExpectedTrue = function ExpectedTrue() {};
-				var eTProto = Object.create(eRBProto);
-				this.ExpectedTrue.prototype = eTProto;
-				eTProto.expectedResult = function() {
-					return true;
-				};
-				//ExpectedTrue
-				this.ExpectedFalse = function ExpectedFalse() {};
-				var eFProto = Object.create(eRBProto);
-				this.ExpectedFalse.prototype = eFProto;
-				eFProto.expectedResult = function() {
-					return false;
-				};
-				//ParseError
-				this.ParseError = function ParseError() {};
-				var pEProto = this.ParseError.prototype;
-				pEProto.run = function run() {
-					var specElement = this.spec(),
-						idGenerator = new VariablesIdGenerator(),
-						vps = new VariablesParseState(idGenerator);
-					assert.throws(function() {
-						Expression.parseOperand(specElement, vps);
-					});
-				};
-				//IncompatibleTypes
-				this.IncompatibleTypes = function IncompatibleTypes() {};
-				var iTProto = this.IncompatibleTypes.prototype;
-				iTProto.run = function() {
-					var specElement = {$ne:["a", 1]},
-						idGenerator = new VariablesIdGenerator(),
-						vps = new VariablesParseState(idGenerator),
-						expression = Expression.parseOperand(specElement, vps);
-					assert.strictEqual(expression.evaluate({}), true);
-				};
-			},
+		"should return the correct op name; $eq, $ne, $gt, $gte, $lt, $lte, $cmp": function() {
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.EQ).getOpName(), "$eq");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.NE).getOpName(), "$ne");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.GT).getOpName(), "$gt");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.GTE).getOpName(), "$gte");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.LT).getOpName(), "$lt");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.LTE).getOpName(), "$lte");
+			assert.equal(new CompareExpression(CompareExpression.CmpOp.CMP).getOpName(), "$cmp");
+		},
 
-			/** $eq with first < second. */
-			"EqLt": function EqLt() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$eq:[1,2]};
-				};
-				test.run();
-			},
+	},
 
-			/** $eq with first == second. */
-			"EqEq": function EqEq() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$eq:[1,1]};
-				};
-				test.run();
-			},
+	"#evaluate()": {
 
-			/** $eq with first > second. */
-			"EqGt": function EqEq() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$eq:[1,0]};
-				};
-				test.run();
-			},
+		/** $eq with first < second. */
+		"EqLt": function EqLt() {
+			new ExpectedFalse({
+				spec: {$eq:[1,2]},
+			}).run();
+		},
 
-			/** $ne with first < second. */
-			"NeLt": function NeLt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$ne:[1,2]};
-				};
-				test.run();
-			},
+		/** $eq with first == second. */
+		"EqEq": function EqEq() {
+			new ExpectedTrue({
+				spec: {$eq:[1,1]},
+			}).run();
+		},
 
-			/** $ne with first == second. */
-			"NeEq": function NeEq() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$ne:[1,1]};
-				};
-				test.run();
-			},
+		/** $eq with first > second. */
+		"EqGt": function EqEq() {
+			new ExpectedFalse({
+				spec: {$eq:[1,0]},
+			}).run();
+		},
 
-			/** $ne with first > second. */
-			"NeGt": function NeGt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$ne:[1,0]};
-				};
-				test.run();
-			},
+		/** $ne with first < second. */
+		"NeLt": function NeLt() {
+			new ExpectedTrue({
+				spec: {$ne:[1,2]},
+			}).run();
+		},
 
-			/** $gt with first < second. */
-			"GtLt": function GtLt() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$gt:[1,2]};
-				};
-				test.run();
-			},
+		/** $ne with first == second. */
+		"NeEq": function NeEq() {
+			new ExpectedFalse({
+				spec: {$ne:[1,1]},
+			}).run();
+		},
 
-			/** $gt with first == second. */
-			"GtEq": function GtEq() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$gt:[1,1]};
-				};
-				test.run();
-			},
+		/** $ne with first > second. */
+		"NeGt": function NeGt() {
+			new ExpectedTrue({
+				spec: {$ne:[1,0]},
+			}).run();
+		},
 
-			/** $gt with first > second. */
-			"GtGt": function GtGt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$gt:[1,0]};
-				};
-				test.run();
-			},
+		/** $gt with first < second. */
+		"GtLt": function GtLt() {
+			new ExpectedFalse({
+				spec: {$gt:[1,2]},
+			}).run();
+		},
 
-			/** $gte with first < second. */
-			"GteLt": function GteLt() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$gte:[1,2]};
-				};
-				test.run();
-			},
+		/** $gt with first == second. */
+		"GtEq": function GtEq() {
+			new ExpectedFalse({
+				spec: {$gt:[1,1]},
+			}).run();
+		},
 
-			/** $gte with first == second. */
-			"GteEq": function GteEq() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$gte:[1,1]};
-				};
-				test.run();
-			},
+		/** $gt with first > second. */
+		"GtGt": function GtGt() {
+			new ExpectedTrue({
+				spec: {$gt:[1,0]},
+			}).run();
+		},
 
-			/** $gte with first > second. */
-			"GteGt": function GteGt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$gte:[1,0]};
-				};
-				test.run();
-			},
+		/** $gte with first < second. */
+		"GteLt": function GteLt() {
+			new ExpectedFalse({
+				spec: {$gte:[1,2]},
+			}).run();
+		},
 
-			/** $gte with first > second. */
-			"LtLt": function LtLt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$lt:[1,2]};
-				};
-				test.run();
-			},
+		/** $gte with first == second. */
+		"GteEq": function GteEq() {
+			new ExpectedTrue({
+				spec: {$gte:[1,1]},
+			}).run();
+		},
 
-			/** $lt with first == second. */
-			"LtEq": function LtEq() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$lt:[1,1]};
-				};
-				test.run();
-			},
+		/** $gte with first > second. */
+		"GteGt": function GteGt() {
+			new ExpectedTrue({
+				spec: {$gte:[1,0]},
+			}).run();
+		},
 
-			/** $lt with first > second. */
-			"LtGt": function LtGt() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$lt:[1,0]};
-				};
-				test.run();
-			},
+		/** $gte with first > second. */
+		"LtLt": function LtLt() {
+			new ExpectedTrue({
+				spec: {$lt:[1,2]},
+			}).run();
+		},
 
-			/** $lte with first < second. */
-			"LteLt": function LteLt() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$lte:[1,2]};
-				};
-				test.run();
-			},
+		/** $lt with first == second. */
+		"LtEq": function LtEq() {
+			new ExpectedFalse({
+				spec: {$lt:[1,1]},
+			}).run();
+		},
 
-			/** $lte with first == second. */
-			"LteEq": function LteEq() {
-				var test = new this.ExpectedTrue();
-				test.spec = function() {
-					return {$lte:[1,1]};
-				};
-				test.run();
-			},
+		/** $lt with first > second. */
+		"LtGt": function LtGt() {
+			new ExpectedFalse({
+				spec: {$lt:[1,0]},
+			}).run();
+		},
 
-			/** $lte with first > second. */
-			"LteGt": function LteGt() {
-				var test = new this.ExpectedFalse();
-				test.spec = function() {
-					return {$lte:[1,0]};
-				};
-				test.run();
-			},
+		/** $lte with first < second. */
+		"LteLt": function LteLt() {
+			new ExpectedTrue({
+				spec: {$lte:[1,2]},
+			}).run();
+		},
 
-			/** $cmp with first < second. */
-			"CmpLt": function CmpLt() {
-				var test = new this.ExpectedResultBase();
-				test.spec = function() {
-					return {$cmp:[1,2]};
-				};
-				test.expectedResult = function() {
-					return -1;
-				};
-				test.run();
-			},
+		/** $lte with first == second. */
+		"LteEq": function LteEq() {
+			new ExpectedTrue({
+				spec: {$lte:[1,1]},
+			}).run();
+		},
 
-			/** $cmp with first == second. */
-			"CmpEq": function CmpEq() {
-				var test = new this.ExpectedResultBase();
-				test.spec = function() {
-					return {$cmp:[1,1]};
-				};
-				test.expectedResult = function() {
-					return 0;
-				};
-				test.run();
-			},
+		/** $lte with first > second. */
+		"LteGt": function LteGt() {
+			new ExpectedFalse({
+				spec: {$lte:[1,0]},
+			}).run();
+		},
 
-			/** $cmp with first > second. */
-			"CmpGt": function CmpGt() {
-				var test = new this.ExpectedResultBase();
-				test.spec = function() {
-					return {$cmp:[1,0]};
-				};
-				test.expectedResult = function() {
-					return 1;
-				};
-				test.run();
-			},
+		/** $cmp with first < second. */
+		"CmpLt": function CmpLt() {
+			new ExpectedResultBase({
+				spec: {$cmp:[1,2]},
+				expectedResult: -1,
+			}).run();
+		},
 
-			/** $cmp results are bracketed to an absolute value of 1. */
-			"CmpBracketed": function CmpBracketed() {
-				var test = new this.ExpectedResultBase();
-				test.spec = function() {
-					return {$cmp:["z","a"]};
-				};
-				test.expectedResult = function() {
-					return 1;
-				};
-				test.run();
-			},
+		/** $cmp with first == second. */
+		"CmpEq": function CmpEq() {
+			new ExpectedResultBase({
+				spec: {$cmp:[1,1]},
+				expectedResult: 0,
+			}).run();
+		},
 
-			/** Zero operands provided. */
-			"ZeroOperands": function ZeroOperands() {
-				var test = new this.ParseError();
-				test.spec = function() {
-					return {$ne:[]};
-				};
-				test.run();
-			},
+		/** $cmp with first > second. */
+		"CmpGt": function CmpGt() {
+			new ExpectedResultBase({
+				spec: {$cmp:[1,0]},
+				expectedResult: 1,
+			}).run();
+		},
 
-			/** One operands provided. */
-			"OneOperand": function OneOperand() {
-				var test = new this.ParseError();
-				test.spec = function() {
-					return {$eq:[1]};
-				};
-				test.run();
-			},
+		/** $cmp results are bracketed to an absolute value of 1. */
+		"CmpBracketed": function CmpBracketed() {
+			var test = new ExpectedResultBase({
+				spec: {$cmp:["z","a"]},
+				expectedResult: 1,
+			}).run();
+		},
 
-			/** Three operands provided. */
-			"ThreeOperands": function ThreeOperands() {
-				var test = new this.ParseError();
-				test.spec = function() {
-					return {$gt:[2,3,4]};
-				};
-				test.run();
-			},
+		/** Zero operands provided. */
+		"ZeroOperands": function ZeroOperands() {
+			new ParseError({
+				spec: {$ne:[]},
+			}).run();
+		},
 
-			/**
-			 * An expression depending on constants is optimized to a constant via
-			 * ExpressionNary::optimize().
-			 */
-			"OptimizeConstants": function OptimizeConstants() {
-				var test = new this.OptimizeBase();
-				test.spec = function() {
-					return {$eq:[1,1]};
-				};
-				test.expectedOptimized = function() {
-					return {$const:true};
-				};
-				test.run();
-			},
+		/** One operands provided. */
+		"OneOperand": function OneOperand() {
+			new ParseError({
+				spec: {$eq:[1]},
+			}).run();
+		},
 
-			/** $cmp is not optimized. */
-			"NoOptimizeCmp": function NoOptimizeCmp() {
-				var test = new this.NoOptimize();
-				test.spec = function() {
-					return {$cmp:[1,"$a"]};
-				};
-				test.run();
-			},
+        /** Incompatible types can be compared. */
+		"IncompatibleTypes": function IncompatibleTypes() {
+			var specElement = {$ne:["a",1]},
+				idGenerator = new VariablesIdGenerator(),
+				vps = new VariablesParseState(idGenerator),
+				expr = Expression.parseOperand(specElement, vps);
+			assert.deepEqual(expr.evaluate({}), true);
+		},
 
-			/** $ne is not optimized. */
-			"NoOptimizeNe": function NoOptimizeNe() {
-				var test = new this.NoOptimize();
-				test.spec = function() {
-					return {$ne:[1,"$a"]};
-				};
-				test.run();
-			},
+		/** Three operands provided. */
+		"ThreeOperands": function ThreeOperands() {
+			new ParseError({
+				spec: {$gt:[2,3,4]},
+			}).run();
+		},
 
-			/** No optimization is performend without a constant. */
-			"NoOptimizeNoConstant": function NoOptimizeNoConstant() {
-				var test = new this.NoOptimize();
-				test.spec = function() {
-					return {$ne:["$a", "$b"]};
-				};
-				test.run();
-			},
+		/**
+		 * An expression depending on constants is optimized to a constant via
+		 * ExpressionNary::optimize().
+		 */
+		"OptimizeConstants": function OptimizeConstants() {
+			new OptimizeBase({
+				spec: {$eq:[1,1]},
+				expectedOptimized: function() {
+					return {$const: true};
+				},
+			}).run();
+		},
 
-			/** No optimization is performend without an immediate field path. */
-			"NoOptimizeWithoutFieldPath": function NoOptimizeWithoutFieldPath() {
-				var test = new this.NoOptimize();
-				test.spec = function() {
-					return {$eq:[{$and:["$a"]},1]};
-				};
-				test.run();
-			},
+		/** $cmp is not optimized. */
+		"NoOptimizeCmp": function NoOptimizeCmp() {
+			new NoOptimize({
+				spec: {$cmp:[1,"$a"]},
+			}).run();
+		},
 
-			/** No optimization is performend without an immediate field path. */
-			"NoOptimizeWithoutFieldPathReverse": function NoOptimizeWithoutFieldPathReverse() {
-				var test = new this.NoOptimize();
-				test.spec = function() {
-					return {$eq:[1,{$and:["$a"]}]};
-				};
-				test.run();
-			},
+		/** $ne is not optimized. */
+		"NoOptimizeNe": function NoOptimizeNe() {
+			new NoOptimize({
+				spec: {$ne:[1,"$a"]},
+			}).run();
+		},
 
-			/** An equality expression is optimized. */
-			"OptimizeEq": function OptimizeEq() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$eq:["$a",1]};
-				};
-				test.run();
-			},
+		/** No optimization is performend without a constant. */
+		"NoOptimizeNoConstant": function NoOptimizeNoConstant() {
+			new NoOptimize({
+				spec: {$ne:["$a", "$b"]},
+			}).run();
+		},
 
-			/** A reverse sense equality expression is optimized. */
-			"OptimizeEqReverse": function OptimizeEqReverse() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$eq:[1,"$a"]};
-				};
-				test.run();
-			},
+		/** No optimization is performend without an immediate field path. */
+		"NoOptimizeWithoutFieldPath": function NoOptimizeWithoutFieldPath() {
+			new NoOptimize({
+				spec: {$eq:[{$and:["$a"]},1]},
+			}).run();
+		},
 
-			/** A $lt expression is optimized. */
-			"OptimizeLt": function OptimizeLt() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$lt:["$a",1]};
-				};
-				test.run();
-			},
+		/** No optimization is performend without an immediate field path. */
+		"NoOptimizeWithoutFieldPathReverse": function NoOptimizeWithoutFieldPathReverse() {
+			new NoOptimize({
+				spec: {$eq:[1,{$and:["$a"]}]},
+			}).run();
+		},
 
-			/** A reverse sense $lt expression is optimized. */
-			"OptimizeLtReverse": function OptimizeLtReverse() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$lt:[1,"$a"]};
-				};
-				test.run();
-			},
+		/** An equality expression is optimized. */
+		"OptimizeEq": function OptimizeEq() {
+			new FieldRangeOptimize({
+				spec: {$eq:["$a",1]},
+			}).run();
+		},
 
-			/** A $lte expression is optimized. */
-			"OptimizeLte": function OptimizeLte() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$lte:["$b",2]};
-				};
-				test.run();
-			},
+		/** A reverse sense equality expression is optimized. */
+		"OptimizeEqReverse": function OptimizeEqReverse() {
+			new FieldRangeOptimize({
+				spec: {$eq:[1,"$a"]},
+			}).run();
+		},
 
-			/** A reverse sense $lte expression is optimized. */
-			"OptimizeLteReverse": function OptimizeLteReverse() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$lte:[2,"$b"]};
-				};
-				test.run();
-			},
+		/** A $lt expression is optimized. */
+		"OptimizeLt": function OptimizeLt() {
+			new FieldRangeOptimize({
+				spec: {$lt:["$a",1]},
+			}).run();
+		},
 
-			/** A $gt expression is optimized. */
-			"OptimizeGt": function OptimizeGt() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$gt:["$b",2]};
-				};
-				test.run();
-			},
+		/** A reverse sense $lt expression is optimized. */
+		"OptimizeLtReverse": function OptimizeLtReverse() {
+			new FieldRangeOptimize({
+				spec: {$lt:[1,"$a"]},
+			}).run();
+		},
 
-			/** A reverse sense $gt expression is optimized. */
-			"OptimizeGtReverse": function OptimizeGtReverse() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$gt:["$b",2]};
-				};
-				test.run();
-			},
+		/** A $lte expression is optimized. */
+		"OptimizeLte": function OptimizeLte() {
+			new FieldRangeOptimize({
+				spec: {$lte:["$b",2]},
+			}).run();
+		},
 
-			/** A $gte expression is optimized. */
-			"OptimizeGte": function OptimizeGte() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$gte:["$b",2]};
-				};
-				test.run();
-			},
+		/** A reverse sense $lte expression is optimized. */
+		"OptimizeLteReverse": function OptimizeLteReverse() {
+			new FieldRangeOptimize({
+				spec: {$lte:[2,"$b"]},
+			}).run();
+		},
 
-			/** A reverse sense $gte expression is optimized. */
-			"OptimizeGteReverse": function OptimizeGteReverse() {
-				var test = new this.FieldRangeOptimize();
-				test.spec = function() {
-					return {$gte:[2,"$b"]};
-				};
-				test.run();
-			},
+		/** A $gt expression is optimized. */
+		"OptimizeGt": function OptimizeGt() {
+			new FieldRangeOptimize({
+				spec: {$gt:["$b",2]},
+			}).run();
+		},
 
+		/** A reverse sense $gt expression is optimized. */
+		"OptimizeGtReverse": function OptimizeGtReverse() {
+			new FieldRangeOptimize({
+				spec: {$gt:["$b",2]},
+			}).run();
+		},
 
-		}
+		/** A $gte expression is optimized. */
+		"OptimizeGte": function OptimizeGte() {
+			new FieldRangeOptimize({
+				spec: {$gte:["$b",2]},
+			}).run();
+		},
 
-	}
+		/** A reverse sense $gte expression is optimized. */
+		"OptimizeGteReverse": function OptimizeGteReverse() {
+			new FieldRangeOptimize({
+				spec: {$gte:[2,"$b"]},
+			}).run();
+		},
 
-};
+	},
 
-if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);
+};