From 2bbce5264bb4b0750954781ffd3174893e0ed252 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Fri, 24 Mar 2017 17:33:40 -0700 Subject: [PATCH] qunit: Remove experimental CompletenessTest feature The CompletenessTest was my attempt at measuring a basic code coverage using run-time inspection instead of static instrumentation. Originally added in 540419a82e5a5 (2010; MediaWiki 1.17). It was never finished, remained fairly buggy and disabled by default. It is also no longer used anywhere. Bug: T155194 Change-Id: I26e7466426dddb43596f402e31005a89060c1b96 --- resources/Resources.php | 5 - .../jquery/jquery.qunit.completenessTest.js | 306 ------------------ tests/qunit/data/testrunner.js | 52 +-- 3 files changed, 2 insertions(+), 361 deletions(-) delete mode 100644 resources/src/jquery/jquery.qunit.completenessTest.js diff --git a/resources/Resources.php b/resources/Resources.php index a8764728c1..631386a629 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -308,11 +308,6 @@ return [ 'styles' => 'resources/lib/qunitjs/qunit.css', 'targets' => [ 'desktop', 'mobile' ], ], - 'jquery.qunit.completenessTest' => [ - 'scripts' => 'resources/src/jquery/jquery.qunit.completenessTest.js', - 'dependencies' => 'jquery.qunit', - 'targets' => [ 'desktop', 'mobile' ], - ], 'jquery.spinner' => [ 'scripts' => 'resources/src/jquery/jquery.spinner.js', 'styles' => 'resources/src/jquery/jquery.spinner.css', diff --git a/resources/src/jquery/jquery.qunit.completenessTest.js b/resources/src/jquery/jquery.qunit.completenessTest.js deleted file mode 100644 index 0aaa4ff39d..0000000000 --- a/resources/src/jquery/jquery.qunit.completenessTest.js +++ /dev/null @@ -1,306 +0,0 @@ -/** - * jQuery QUnit CompletenessTest 0.4 - * - * Tests the completeness of test suites for object oriented javascript - * libraries. Written to be used in environments with jQuery and QUnit. - * Requires jQuery 1.7.2 or higher. - * - * Built for and tested with: - * - Chrome 19 - * - Firefox 4 - * - Safari 5 - * - * @author Timo Tijhof, 2011-2012 - */ -/* eslint-env qunit */ -( function ( mw, $ ) { - 'use strict'; - - var util, - hasOwn = Object.prototype.hasOwnProperty, - log = ( window.console && window.console.log ) ? - function () { return window.console.log.apply( window.console, arguments ); } : - function () {}; - - // Simplified version of a few jQuery methods, except that they don't - // call other jQuery methods. Required to be able to run the CompletenessTest - // on jQuery itself as well. - util = { - keys: Object.keys || function ( object ) { - var key, keys = []; - for ( key in object ) { - if ( hasOwn.call( object, key ) ) { - keys.push( key ); - } - } - return keys; - }, - each: function ( object, callback ) { - var name; - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - }, - // $.type and $.isEmptyObject are safe as is, they don't call - // other $.* methods. Still need to be derefenced into `util` - // since the CompletenessTest will overload them with spies. - type: $.type, - isEmptyObject: $.isEmptyObject - }; - - /** - * CompletenessTest - * - * @constructor - * @example - * var myTester = new CompletenessTest( myLib ); - * @param {Object} masterVariable The root variable that contains all object - * members. CompletenessTest will recursively traverse objects and keep track - * of all methods. - * @param {Function} [ignoreFn] Optionally pass a function to filter out certain - * methods. Example: You may want to filter out instances of jQuery or some - * other constructor. Otherwise "missingTests" will include all methods that - * were not called from that instance. - */ - function CompletenessTest( masterVariable, ignoreFn ) { - var warn, - that = this; - - // Keep track in these objects. Keyed by strings with the - // method names (ie. 'my.foo', 'my.bar', etc.) values are boolean true. - this.injectionTracker = {}; - this.methodCallTracker = {}; - this.missingTests = {}; - - this.ignoreFn = ignoreFn === undefined ? function () { return false; } : ignoreFn; - - // Lazy limit in case something weird happends (like recurse (part of) ourself). - this.lazyLimit = 2000; - this.lazyCounter = 0; - - // Bind begin and end to QUnit. - QUnit.begin( function () { - // Suppress warnings (e.g. deprecation notices for accessing the properties) - warn = mw.log.warn; - mw.log.warn = $.noop; - - that.walkTheObject( masterVariable, null, masterVariable, [] ); - log( 'CompletenessTest/walkTheObject', that ); - - // Restore warnings - mw.log.warn = warn; - warn = undefined; - } ); - - QUnit.done( function () { - var toolbar, testResults, cntTotal, cntCalled, cntMissing; - - that.populateMissingTests(); - log( 'CompletenessTest/populateMissingTests', that ); - - cntTotal = util.keys( that.injectionTracker ).length; - cntCalled = util.keys( that.methodCallTracker ).length; - cntMissing = util.keys( that.missingTests ).length; - - function makeTestResults( blob, title, style ) { - var elOutputWrapper, elTitle, elContainer, elList, elFoot; - - elTitle = document.createElement( 'strong' ); - elTitle.textContent = title || 'Values'; - - elList = document.createElement( 'ul' ); - util.each( blob, function ( key ) { - var elItem = document.createElement( 'li' ); - elItem.textContent = key; - elList.appendChild( elItem ); - } ); - - elFoot = document.createElement( 'p' ); - elFoot.innerHTML = '— CompletenessTest'; - - elContainer = document.createElement( 'div' ); - elContainer.appendChild( elTitle ); - elContainer.appendChild( elList ); - elContainer.appendChild( elFoot ); - - elOutputWrapper = document.getElementById( 'qunit-completenesstest' ); - if ( !elOutputWrapper ) { - elOutputWrapper = document.createElement( 'div' ); - elOutputWrapper.id = 'qunit-completenesstest'; - } - elOutputWrapper.appendChild( elContainer ); - - util.each( style, function ( key, value ) { - elOutputWrapper.style[ key ] = value; - } ); - return elOutputWrapper; - } - - if ( cntMissing === 0 ) { - // Good - testResults = makeTestResults( - {}, - 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. No missing tests!', - { - backgroundColor: '#D2E0E6', - color: '#366097', - paddingTop: '1em', - paddingRight: '1em', - paddingBottom: '1em', - paddingLeft: '1em' - } - ); - } else { - // Bad - testResults = makeTestResults( - that.missingTests, - 'Detected calls to ' + cntCalled + '/' + cntTotal + ' methods. ' + cntMissing + ' methods not covered:', - { - backgroundColor: '#EE5757', - color: 'black', - paddingTop: '1em', - paddingRight: '1em', - paddingBottom: '1em', - paddingLeft: '1em' - } - ); - } - - toolbar = document.getElementById( 'qunit-testrunner-toolbar' ); - if ( toolbar ) { - toolbar.insertBefore( testResults, toolbar.firstChild ); - } - } ); - - return this; - } - - /* Public methods */ - CompletenessTest.fn = CompletenessTest.prototype = { - - /** - * CompletenessTest.fn.walkTheObject - * - * This function recursively walks through the given object, calling itself as it goes. - * Depending on the action it either injects our listener into the methods, or - * reads from our tracker and records which methods have not been called by the test suite. - * - * @param {Mixed} currObj The variable to check (initially an object, - * further down it could be anything). - * @param {string|null} currName Name of the given object member (Initially this is null). - * @param {Object} masterVariable Throughout our interation, always keep track of the master/root. - * Initially this is the same as currVar. - * @param {Array} parentPathArray Array of names that indicate our breadcrumb path starting at - * masterVariable. Not including currName. - */ - walkTheObject: function ( currObj, currName, masterVariable, parentPathArray ) { - var key, currVal, type, - ct = this, - currPathArray = parentPathArray; - - if ( currName ) { - currPathArray.push( currName ); - currVal = currObj[ currName ]; - } else { - currName = '(root)'; - currVal = currObj; - } - - type = util.type( currVal ); - - // Hard ignores - if ( this.ignoreFn( currVal, this, currPathArray ) ) { - return; - } - - // Handle the lazy limit - this.lazyCounter++; - if ( this.lazyCounter > this.lazyLimit ) { - log( 'CompletenessTest.fn.walkTheObject> Limit reached: ' + this.lazyCounter, currPathArray ); - return; - } - - // Functions - if ( type === 'function' ) { - // Don't put a spy in constructor functions as it messes with - // instanceof etc. - if ( !currVal.prototype || util.isEmptyObject( currVal.prototype ) ) { - this.injectionTracker[ currPathArray.join( '.' ) ] = true; - this.injectCheck( currObj, currName, function () { - ct.methodCallTracker[ currPathArray.join( '.' ) ] = true; - } ); - } - } - - // Recursively. After all, this is the *completeness* test - // This also traverses static properties and the prototype of a constructor - if ( type === 'object' || type === 'function' ) { - for ( key in currVal ) { - if ( hasOwn.call( currVal, key ) ) { - this.walkTheObject( currVal, key, masterVariable, currPathArray.slice() ); - } - } - } - }, - - populateMissingTests: function () { - var ct = this; - util.each( ct.injectionTracker, function ( key ) { - ct.hasTest( key ); - } ); - }, - - /** - * CompletenessTest.fn.hasTest - * - * Checks if the given method name (ie. 'my.foo.bar') - * was called during the test suite (as far as the tracker knows). - * If not it adds it to missingTests. - * - * @param {string} fnName - * @return {boolean} - */ - hasTest: function ( fnName ) { - if ( !( fnName in this.methodCallTracker ) ) { - this.missingTests[ fnName ] = true; - return false; - } - return true; - }, - - /** - * CompletenessTest.fn.injectCheck - * - * Injects a function (such as a spy that updates methodCallTracker when - * it's called) inside another function. - * - * @param {Object} obj The object into which `injectFn` will be inserted - * @param {Array} key The key by which `injectFn` will be known in `obj`; if this already - * exists, a wrapper will first call `injectFn` and then the original `obj[key]` function. - * @param {Function} injectFn The function to insert - */ - injectCheck: function ( obj, key, injectFn ) { - var spy, - val = obj[ key ]; - - spy = function () { - injectFn(); - return val.apply( this, arguments ); - }; - - // Make the spy inherit from the original so that its static methods are also - // visible in the spy (e.g. when we inject a check into mw.log, mw.log.warn - // must remain accessible). - spy.__proto__ = val; - - // Objects are by reference, members (unless objects) are not. - obj[ key ] = spy; - } - }; - - /* Expose */ - window.CompletenessTest = CompletenessTest; - -}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js index cef5c15e4f..b0118afbec 100644 --- a/tests/qunit/data/testrunner.js +++ b/tests/qunit/data/testrunner.js @@ -1,8 +1,8 @@ -/* global CompletenessTest, sinon */ +/* global sinon */ ( function ( $, mw, QUnit ) { 'use strict'; - var mwTestIgnore, addons; + var addons; /** * Add bogus to url to prevent IE crazy caching @@ -37,17 +37,6 @@ value: 'true' } ); - /** - * CompletenessTest - * - * Adds toggle checkbox to header - */ - QUnit.config.urlConfig.push( { - id: 'completenesstest', - label: 'Run CompletenessTest', - tooltip: 'Run the completeness test' - } ); - /** * SinonJS * @@ -125,43 +114,6 @@ }; }() ); - // Initiate when enabled - if ( QUnit.urlParams.completenesstest ) { - - // Return true to ignore - mwTestIgnore = function ( val, tester ) { - - // Don't record methods of the properties of constructors, - // to avoid getting into a loop (prototype.constructor.prototype..). - // Since we're therefor skipping any injection for - // "new mw.Foo()", manually set it to true here. - if ( val instanceof mw.Map ) { - tester.methodCallTracker.Map = true; - return true; - } - if ( val instanceof mw.Title ) { - tester.methodCallTracker.Title = true; - return true; - } - - // Don't record methods of the properties of a jQuery object - if ( val instanceof $ ) { - return true; - } - - // Don't iterate over the module registry (the 'script' references would - // be listed as untested methods otherwise) - if ( val === mw.loader.moduleRegistry ) { - return true; - } - - return false; - }; - - // eslint-disable-next-line no-new - new CompletenessTest( mw, mwTestIgnore ); - } - /** * Reset mw.config and others to a fresh copy of the live config for each test(), * and restore it back to the live one afterwards. -- 2.20.1