diff --git a/Makefile b/Makefile index 0cd42a0..16da221 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,10 @@ PATH_TOOLS=${CWD}/tools PATH_COMBINE_OUTPUT=${PATH_BUILD}/ease.js PATH_COMBINE_OUTPUT_FULL=${PATH_BUILD}/ease-full.js PATH_BROWSER_TEST=${PATH_TOOLS}/browser-test.html +PATH_TEST=./test +PATH_PERF_TEST=${PATH_TEST}/perf + +PERF_TESTS := $(shell find "$(PATH_PERF_TEST)" -name 'perf-*.js') PATH_DOC=${CWD}/doc PATH_DOC_OUTPUT=${PATH_BUILD}/doc @@ -17,11 +21,14 @@ PATH_MANUAL_TEXI=${PATH_DOC}/manual.texi COMBINE=${PATH_TOOLS}/combine -TESTS_JS := $(shell find "./test" -name 'test-*.js') -TESTS_SHELL := $(shell find "./test" -name 'test-[^\.]*') +TESTS := $(shell find "$(PATH_TEST)" \ + -name 'test-*' \ + -a ! -name 'test-combine.js'\ +) +TEST_COMBINE := $(PATH_TEST)/test-combine.js -.PHONY: test doc +.PHONY: test test-combine doc default: combine @@ -39,11 +46,17 @@ combine: mkbuild cp "${PATH_BROWSER_TEST}" "${PATH_BUILD}" # run tests -test: default $(TESTS_JS) $(TESTS_SHELL) +test: default $(TESTS) test-combine +test-combine: default $(TEST_COMBINE) test-%.js: default - node $@ + node $@ test-%: default - ./$@ + ./$@ + +# performance tests +perf: default $(PERF_TESTS) +perf-%.js: default + @node $@ # generate texinfo documentation (twice to generate TOC), then remove the extra # files that were generated diff --git a/TODO b/TODO index 925d8c8..0c9e606 100644 --- a/TODO +++ b/TODO @@ -5,6 +5,9 @@ [ target: 0.1.0 ] Misc - Class module is becoming too large; refactor + - Disallow member redeclaration in definition + - Permit binding on class methods + - Provide ability to free class from memory (class data stored in internal vars) Member Keywords - Restrictions; throw exceptions when unknown keywords are used diff --git a/lib/class.js b/lib/class.js index cfae254..3aae42e 100644 --- a/lib/class.js +++ b/lib/class.js @@ -457,6 +457,9 @@ var extend = ( function( extending ) } } + // increment class identifier + class_id++; + util.propParse( props, { each: function( name, value, keywords ) { @@ -498,7 +501,8 @@ var extend = ( function( extending ) method: function( name, func, is_abstract, keywords ) { member_builder.buildMethod( - members, null, name, func, keywords, getMethodInstance + members, null, name, func, keywords, getMethodInstance, + class_id ); if ( is_abstract ) @@ -531,13 +535,13 @@ var extend = ( function( extending ) // set up the new class var new_class = createCtor( cname, abstract_methods, members ); - attachPropInit( prototype, prop_init, members ); + attachPropInit( prototype, prop_init, members, class_id ); new_class.prototype = prototype; new_class.constructor = new_class; // important: call after setting prototype - setupProps( new_class, abstract_methods, ++class_id ); + setupProps( new_class, abstract_methods, class_id ); // lock down the new class (if supported) to ensure that we can't add // members at runtime @@ -761,24 +765,27 @@ function initInstance( iid, instance ) * ensuring that their data is not shared with other instances (this is not a * problem with primitive data types). * - * The __initProps() method will also initialize any parent properties - * (recursive) to ensure that subtypes do not have a referencing issue, and - * subtype properties take precedence over those of the parent. + * The method will also initialize any parent properties (recursive) to ensure + * that subtypes do not have a referencing issue, and subtype properties take + * precedence over those of the parent. * * @param {Object} prototype prototype to attach method to * @param {Object} properties properties to initialize + * @param {number} cid class id * * @param {{public: Object, protected: Object, private: Object}} members * * @return {undefined} */ -function attachPropInit( prototype, properties, members ) +function attachPropInit( prototype, properties, members, cid ) { util.defineSecureProp( prototype, '__initProps', function( inherit ) { - // defaults to false + // defaults to false, sid = super identifier inherit = !!inherit; + var iid = this.__iid; + // first initialize the parent's properties, so that ours will overwrite // them var parent_init = prototype.parent.__initProps; @@ -790,21 +797,17 @@ function attachPropInit( prototype, properties, members ) parent_init.call( this, true ); } + // this will return our property proxy, if supported by our environment, + // otherwise just a normal object with everything merged in var inst_props = propobj.createPropProxy( - this, class_instance[ this.__iid ], properties[ 'public' ] + this, class_instance[ iid ], properties[ 'public' ] ); - // use whatever was returned by the property proxy (which may not be a - // proxy, depending on JS engine support) - class_instance[ this.__iid ] = inst_props; - // if we're inheriting, perform a setup that doesn't include everything // that we don't want (e.g. private properties) - var setup = ( inherit ) - ? propobj.setupInherited - : propobj.setup - ; - setup( inst_props, properties, members ); + class_instance[ iid ][ cid ] = propobj.setup( + inst_props, properties, members + ); }); } @@ -981,21 +984,28 @@ function getMeta( id ) /** * Returns the instance object associated with the given method * - * The instance object contains the protected and private members. This object - * can be passed as the context when calling a method in order to give that - * method access to those members. + * The instance object contains the protected members. This object can be passed + * as the context when calling a method in order to give that method access to + * those members. + * + * One level above the instance object on the prototype chain is the object + * containing the private members. This is swappable, depending on the class id + * associated with the provided method call. This allows methods that were not + * overridden by the subtype to continue to use the private members of the + * supertype. * * @param {function()} method method to look up instance object for + * @param {number} cid class id * * @return {Object,null} instance object if found, otherwise null */ -function getMethodInstance( method ) +function getMethodInstance( inst, cid ) { - var iid = method.__iid, - data = class_instance[ method.__iid ]; + var iid = inst.__iid, + data = class_instance[ iid ]; return ( iid && data ) - ? data + ? data[ cid ] : null ; } diff --git a/lib/member_builder.js b/lib/member_builder.js index 5daf630..3df3f4e 100644 --- a/lib/member_builder.js +++ b/lib/member_builder.js @@ -62,11 +62,12 @@ exports.initMembers = function( mpublic, mprotected, mprivate ) * @param {Object=} instCallback function to call in order to retrieve * object to bind 'this' keyword to + * @param {number} cid class id * * @return {undefined} */ exports.buildMethod = function( - members, meta, name, value, keywords, instCallback + members, meta, name, value, keywords, instCallback, cid ) { var prev; @@ -109,7 +110,7 @@ exports.buildMethod = function( if ( prev ) { // override the method - dest[ name ] = overrideMethod( prev, value, instCallback ); + dest[ name ] = overrideMethod( prev, value, instCallback, cid ); } else if ( keywords[ 'abstract' ] ) { @@ -120,7 +121,7 @@ exports.buildMethod = function( { // we are not overriding the method, so simply copy it over, wrapping it // to ensure privileged calls will work properly - dest[ name ] = overrideMethod( value, null, instCallback ); + dest[ name ] = overrideMethod( value, null, instCallback, cid ); } }; @@ -303,10 +304,11 @@ function scanMembers( members, name, cmp ) * * @param {Object=} instCallback function to call in order to retrieve * object to bind 'this' keyword to + * @param {number} cid class id * * @return {function()} override method */ -function overrideMethod( super_method, new_method, instCallback ) +function overrideMethod( super_method, new_method, instCallback, cid ) { instCallback = instCallback || function() {}; @@ -319,7 +321,7 @@ function overrideMethod( super_method, new_method, instCallback ) { override = function() { - var context = instCallback( this ) || this, + var context = instCallback( this, cid ) || this, retval = undefined ; @@ -344,7 +346,7 @@ function overrideMethod( super_method, new_method, instCallback ) // we are defining a new method override = function() { - var context = instCallback( this ) || this, + var context = instCallback( this, cid ) || this, retval = undefined ; diff --git a/lib/propobj.js b/lib/propobj.js index c60d431..7ea372d 100644 --- a/lib/propobj.js +++ b/lib/propobj.js @@ -31,43 +31,37 @@ var util = require( './util' ), /** - * Sets up properties when inheriting + * Sets up properties (non-inheriting) * - * This does not include private members. + * This includes all members (including private). Private members will be set up + * in a separate object, so that they can be easily removed from the mix. That + * object will include the destination object in the prototype, so that the + * access should be transparent. This object is returned. * * @param {Object} dest destination object * @param {Object} properties properties to copy * @param {Object=} methods methods to copy * - * @return {undefined} + * @return {Object} object containing private members and dest as prototype */ -exports.setupInherited = function( dest, properties, methods ) +exports.setup = function( dest, properties, methods ) { + // this constructor is an extra layer atop of the destination object, which + // will contain the private methods + var obj_ctor = function() {}; + obj_ctor.prototype = dest; + + var obj = new obj_ctor(); + // initialize each of the properties for this instance to // ensure we're not sharing references to prototype values doSetup( dest, properties[ 'public' ] ); doSetup( dest, properties[ 'protected' ], methods[ 'protected'] ); -}; - - -/** - * Sets up properties (non-inheriting) - * - * This includes all members (including private). - * - * @param {Object} dest destination object - * @param {Object} properties properties to copy - * @param {Object=} methods methods to copy - * - * @return {undefined} - */ -exports.setup = function( dest, properties, methods ) -{ - // first, set up the public and protected members - exports.setupInherited( dest, properties, methods ); // then add the private parts - doSetup( dest, properties[ 'private' ], methods[ 'private' ] ); + doSetup( obj, properties[ 'private' ], methods[ 'private' ] ); + + return obj; }; diff --git a/test/perf/README b/test/perf/README new file mode 100644 index 0000000..9c95d99 --- /dev/null +++ b/test/perf/README @@ -0,0 +1,8 @@ +This directory contains the performance tests. These tests contain basic +routines that perform a single action and output the result in seconds, with a +basic description of what has been done. The timing is done via the native Date +object to ensure that it can be run both server and client-side. + +It is important that each test performs only a single operation to ensure that +the prior operations have no consequences on the previous, at least when run +server-side by invoking a separate executable for each. diff --git a/test/perf/common.js b/test/perf/common.js new file mode 100644 index 0000000..538d7a7 --- /dev/null +++ b/test/perf/common.js @@ -0,0 +1,99 @@ +/** + * Common performance testing functionality + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + +/** + * Stores start time + * @type {number} + */ +var start = 0; + + +/** + * Includes a module from the lib directory + * + * @param {string} name module name + * + * @return {Object} module exports + */ +exports.require = function( name ) +{ + return require( '../../lib/' + name ); +}; + + +/** + * A simple wrapper to perform testing and output the result + * + * The count is not used to call the function multiple times, because that would + * greatly impact the test results. Instead, you should pass the number of times + * the test was performed in a loop. + * + * @param {function()} test performance test to perform + * @param {number} count number of times the test was performed + * @param {string=} desc test description + * + * @return {undefined} + */ +exports.test = function( test, count, desc ) +{ + exports.start(); + test(); + exports.report( count, desc ); +}; + + +/** + * Starts the timer + * + * @return {undefined} + */ +exports.start = function() +{ + start = ( new Date() ).getTime(); +}; + + +/** + * Outputs the time elapsed, followed by the description (if available) + * + * @param {number} count number of times the test was performed + * @param {string=} desc test description + * + * @return {undefined} + */ +exports.report = function( count, desc ) +{ + count = +count; + desc = desc || ''; + + var end = ( new Date() ).getTime(), + total = ( ( end - start ) / 1000 ).toFixed( 3 ), + pers = ( total / count ).toFixed( 10 ) + ; + + console.log( total + "s (x" + count + " = " + pers + "s each)" + + ( ( desc ) ? ( ': ' + desc ) : '' ) + ); +}; + diff --git a/test/perf/perf-class-define-methods-keyword-private.js b/test/perf/perf-class-define-methods-keyword-private.js new file mode 100644 index 0000000..3c7ecce --- /dev/null +++ b/test/perf/perf-class-define-methods-keyword-private.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few members, + * private keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'private a': function() {}, + 'private b': function() {}, + 'private c': function() {}, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with private members' ); diff --git a/test/perf/perf-class-define-methods-keyword-protected.js b/test/perf/perf-class-define-methods-keyword-protected.js new file mode 100644 index 0000000..611b8f4 --- /dev/null +++ b/test/perf/perf-class-define-methods-keyword-protected.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few members, + * protected keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'protected a': function() {}, + 'protected b': function() {}, + 'protected c': function() {}, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with protected members' ); diff --git a/test/perf/perf-class-define-methods-keyword-public.js b/test/perf/perf-class-define-methods-keyword-public.js new file mode 100644 index 0000000..8820713 --- /dev/null +++ b/test/perf/perf-class-define-methods-keyword-public.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few members, public + * keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'public a': function() {}, + 'public b': function() {}, + 'public c': function() {}, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with public members' ); diff --git a/test/perf/perf-class-define-methods.js b/test/perf/perf-class-define-methods.js new file mode 100644 index 0000000..f51a1c6 --- /dev/null +++ b/test/perf/perf-class-define-methods.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few members, no + * keywords + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + a: function() {}, + b: function() {}, + c: function() {}, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with few methods' ); diff --git a/test/perf/perf-class-define-named.js b/test/perf/perf-class-define-named.js new file mode 100644 index 0000000..16f52e9 --- /dev/null +++ b/test/perf/perf-class-define-named.js @@ -0,0 +1,42 @@ +/** + * Tests amount of time taken to declare 1000 classes + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( 'Foo', {} ); + } + +}, count, 'Declare ' + count + ' empty named classes' ); diff --git a/test/perf/perf-class-define-properties-keyword-private.js b/test/perf/perf-class-define-properties-keyword-private.js new file mode 100644 index 0000000..c3ed765 --- /dev/null +++ b/test/perf/perf-class-define-properties-keyword-private.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few properties, + * private keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'private a': 'foo', + 'private b': 10, + 'private c': false, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with private properties' ); diff --git a/test/perf/perf-class-define-properties-keyword-protected.js b/test/perf/perf-class-define-properties-keyword-protected.js new file mode 100644 index 0000000..07f19a4 --- /dev/null +++ b/test/perf/perf-class-define-properties-keyword-protected.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few properties, + * protected keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'protected a': 'foo', + 'protected b': 10, + 'protected c': false, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with protected properties' ); diff --git a/test/perf/perf-class-define-properties-keyword-public.js b/test/perf/perf-class-define-properties-keyword-public.js new file mode 100644 index 0000000..569ee5e --- /dev/null +++ b/test/perf/perf-class-define-properties-keyword-public.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with a few properties, + * public keyword + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + 'public a': 'foo', + 'public b': 10, + 'public c': false, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with public properties' ); diff --git a/test/perf/perf-class-define-properties.js b/test/perf/perf-class-define-properties.js new file mode 100644 index 0000000..97046d1 --- /dev/null +++ b/test/perf/perf-class-define-properties.js @@ -0,0 +1,47 @@ +/** + * Tests amount of time taken to declare 1000 classes with few properties, no + * keywords + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( { + a: 'foo', + b: 10, + c: false, + } ); + } + +}, count, 'Declare ' + count + ' anonymous classes with few properties' ); diff --git a/test/perf/perf-class-define.js b/test/perf/perf-class-define.js new file mode 100644 index 0000000..ad448db --- /dev/null +++ b/test/perf/perf-class-define.js @@ -0,0 +1,42 @@ +/** + * Tests amount of time taken to declare 1000 classes + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 1000 +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Class( {} ); + } + +}, count, 'Declare ' + count + ' empty anonymous classes' ); diff --git a/test/perf/perf-class-get-property.js b/test/perf/perf-class-get-property.js new file mode 100644 index 0000000..4e3a0ed --- /dev/null +++ b/test/perf/perf-class-get-property.js @@ -0,0 +1,101 @@ +/** + * Tests amount of time taken to declare read properties internally and + * externally + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ), + + // we need many tests for a measurable result + count = 500000 + + // instance of anonymous class + foo = Class( { + 'public pub_bar': 'foo', + 'protected prot_bar': 'bar', + 'private priv_bar': 'baz', + + 'public testInternal': function() + { + var _self = this; + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + val = _self.pub_bar; + } + }, count, 'Read public properties internally' ); + + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + val = _self.prot_bar; + } + }, count, 'Read protected properties internally' ); + + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + val = _self.priv_bar; + } + }, count, 'Read private properties internally' ); + }, + } )() +; + + +common.test( function() +{ + var i = count, + val = null + ; + + while ( i-- ) + { + val = foo.pub_bar; + } + +}, count, 'Read public properties externally' ); + + +// run the same test internally +foo.testInternal(); + diff --git a/test/perf/perf-class-inst-anon-empty.js b/test/perf/perf-class-inst-anon-empty.js new file mode 100644 index 0000000..d737f95 --- /dev/null +++ b/test/perf/perf-class-inst-anon-empty.js @@ -0,0 +1,43 @@ +/** + * Tests amount of time taken to instantiate anonymous classes + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 5000, + Foo = Class( {} ) +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + Foo(); + } + +}, count, 'Instantiate ' + count + ' empty anonymous classes' ); diff --git a/test/perf/perf-class-inst-named-empty.js b/test/perf/perf-class-inst-named-empty.js new file mode 100644 index 0000000..1149909 --- /dev/null +++ b/test/perf/perf-class-inst-named-empty.js @@ -0,0 +1,44 @@ +/** + * Tests amount of time taken to instantiate named classes + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ) + + count = 5000, + Foo = Class( 'Foo', {} ) +; + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + // to be extra confident that V8 or another compiler won't realize this + // is useless and optimize it out + Foo(); + } + +}, count, 'Instantiate ' + count + ' empty named classes' ); diff --git a/test/perf/perf-class-invoke-method.js b/test/perf/perf-class-invoke-method.js new file mode 100644 index 0000000..2a97355 --- /dev/null +++ b/test/perf/perf-class-invoke-method.js @@ -0,0 +1,111 @@ +/** + * Tests amount of time taken to invoke Class methods + * + * The expected results are as follows: + * - Method invocations are expected to be slower than invoking a method on a + * conventional constructor instance. This is because of the method wrapper + * used by ease.js. + * - Public methods externally should be invoked very quickly. They are part + * of the class's prototype and therefore easily accessible. + * - Public methods /internally/ are likely to be invoked slightly more + * slowly. This is because it takes one extra step down the prototype chain + * to access them. The difference should be minute. + * - Protected and private methods internally should be accessed fairly + * quickly since, like public methods externally, they are first on the + * prototype chain. + * - Protected members will be accessed more slowly than private members, + * because they are one step lower on the prototype chain. Future versions + * will remove this performance hit if the Class contains no private + * members. + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ), + + count = 500000, + + // used to ensure v8 doesn't optimize functions away + i = 0, + + // instance of anonymous class + foo = Class( { + 'public pub': function() { i++; }, + 'protected prot': function() { i++; }, + 'private priv': function() { i++; }, + + 'public testInternal': function() + { + var _self = this; + + common.test( function() + { + var i = count; + + while ( i-- ) + { + _self.pub(); + } + }, count, 'Invoke public methods internally' ); + + + common.test( function() + { + var i = count; + + while ( i-- ) + { + _self.prot(); + } + }, count, 'Invoke protected methods internally' ); + + + common.test( function() + { + var i = count; + + while ( i-- ) + { + _self.priv(); + } + }, count, 'Invoke private methods internally' ); + }, + } )() +; + + +common.test( function() +{ + var i = count; + + while ( i-- ) + { + foo.pub(); + } + +}, count, 'Invoke public methods externally' ); + + +// run the same test internally +foo.testInternal(); + diff --git a/test/perf/perf-class-require.js b/test/perf/perf-class-require.js new file mode 100644 index 0000000..c5bd893 --- /dev/null +++ b/test/perf/perf-class-require.js @@ -0,0 +1,33 @@ +/** + * Tests amount of time spent on requiring class module + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ); + +// we run this test once because require() will cache the object in memory +common.test( function() +{ + common.require( 'class' ); +}, 1, 'Require class module' ); + diff --git a/test/perf/perf-class-set-property.js b/test/perf/perf-class-set-property.js new file mode 100644 index 0000000..0221d5a --- /dev/null +++ b/test/perf/perf-class-set-property.js @@ -0,0 +1,101 @@ +/** + * Tests amount of time taken to declare read properties internally and + * externally + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package performance + */ + + +var common = require( __dirname + '/common.js' ), + Class = common.require( 'class' ), + + // we need many tests for a measurable result + count = 500000 + + // instance of anonymous class + foo = Class( { + 'public pub_bar': 'foo', + 'protected prot_bar': 'bar', + 'private priv_bar': 'baz', + + 'public testInternal': function() + { + var _self = this; + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + _self.pub_bar = 'foo'; + } + }, count, 'Write public properties internally' ); + + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + _self.prot_bar = 'foo'; + } + }, count, 'Write protected properties internally' ); + + + common.test( function() + { + var i = count, + val = null + ; + + while ( i-- ) + { + _self.priv_bar = 'foo'; + } + }, count, 'Write private properties internally' ); + }, + } )() +; + + +common.test( function() +{ + var i = count, + val = null + ; + + while ( i-- ) + { + foo.pub_bar = 'foo'; + } + +}, count, 'Write public properties externally' ); + + +// run the same test internally +foo.testInternal(); + diff --git a/test/test-class-visibility.js b/test/test-class-visibility.js index 61bf5c9..5c82bf2 100644 --- a/test/test-class-visibility.js +++ b/test/test-class-visibility.js @@ -73,6 +73,24 @@ var common = require( './common' ), { // override me }, + + + 'public getPrivProp': function() + { + return this.parts; + }, + + + 'public invokePriv': function() + { + return this._priv(); + }, + + + 'private _priv': function() + { + return priv; + }, }), // instance of Foo @@ -80,13 +98,28 @@ var common = require( './common' ), // subtype SubFoo = Foo.extend({ + 'private _pfoo': 'baz', + 'public getSelfOverride': function() { // return this from overridden method return this; }, + + + /** + * We have to override this so that 'this' is not bound to the supertype + */ + 'public getProp': function( name ) + { + // return property, allowing us to break encapsulation for + // protected/private properties (for testing purposes) + return this[ name ]; + }, }), - sub_foo = SubFoo() + sub_foo = SubFoo(), + + sub_sub_foo = SubFoo.extend( {} )() ; @@ -362,3 +395,50 @@ var common = require( './common' ), ); } )(); + +/** + * This one's a particularly nasty bug that snuck up on me. Private members + * should not be accessible to subtypes; that's a given. However, they need to + * be accessible to the parent methods. For example, let's say class Foo + * contains public method bar(), which invokes private method _baz(). This is + * perfectly legal. Then SubFoo extends Foo, but does not override method bar(). + * Invoking method bar() should still be able to invoke private method _baz(), + * because, from the perspective of the parent class, that operation is + * perfectly legal. + * + * The resolution of this bug required a slight system redesign. The short-term + * fix was to declare any needed private members are protected, so that they + * were accessible by the subtype. + */ +( function testParentMethodsCanAccessPrivateMembersOfParent() +{ + // properties + assert.equal( + sub_foo.getPrivProp(), + priv, + "Parent methods should have access to the private properties of the " + + "parent" + ); + + // methods + assert.equal( + sub_foo.invokePriv(), + priv, + "Parent methods should have access to the private methods of the parent" + ); + + // should apply to super-supertypes too + assert.equal( + sub_sub_foo.getPrivProp(), + priv, + "Parent methods should have access to the private properties of the " + + "parent (2)" + ); + assert.equal( + sub_sub_foo.invokePriv(), + priv, + "Parent methods should have access to the private methods of the " + + "parent (2)" + ); +} )(); +