From ee59d7ed0f0a70832a65ccce6e43bbcaef58865d Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Mon, 7 Aug 2017 18:36:54 -0700 Subject: [PATCH 1/6] Initial stab at @pleath's suggestion of only adding args to InitLocalClosure op Assertion on GlobOpt.cpp:5187 fails when running reproduction case; more work necessary. --- lib/Backend/GlobOpt.cpp | 6 ++++++ lib/Backend/GlobOpt.h | 7 ++++++- lib/Backend/GlobOptFields.cpp | 17 +++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index 2d4d1645ec3..aef16f692d8 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -360,6 +360,7 @@ GlobOpt::ForwardPass() this->byteCodeConstantValueNumbersBv = JitAnew(this->alloc, BVSparse, this->alloc); this->tempBv = JitAnew(this->alloc, BVSparse, this->alloc); this->prePassCopyPropSym = JitAnew(this->alloc, BVSparse, this->alloc); + this->slotSyms = JitAnew(this->alloc, BVSparse, this->alloc); this->byteCodeUses = nullptr; this->propertySymUse = nullptr; @@ -5180,6 +5181,11 @@ GlobOpt::ValueNumberDst(IR::Instr **pInstr, Value *src1Val, Value *src2Val) case Js::OpCode::Typeof: return this->NewGenericValue(ValueType::String, dst); break; + case Js::OpCode::InitLocalClosure: + Assert(instr->GetDst()); + Assert(instr->GetDst()->IsSymOpnd()); + this->slotSyms->Set(instr->GetDst()->AsSymOpnd()->m_sym->m_id); + break; } #ifdef ENABLE_SIMDJS diff --git a/lib/Backend/GlobOpt.h b/lib/Backend/GlobOpt.h index 9f8d4cb7e37..ac4c061c58e 100644 --- a/lib/Backend/GlobOpt.h +++ b/lib/Backend/GlobOpt.h @@ -414,6 +414,11 @@ class GlobOpt BVSparse * objectTypeSyms; BVSparse * prePassCopyPropSym; // Symbols that were copy prop'd during loop prepass + // Symbols that refer to slots in the stack frame. We still use currentBlock->liveFields to tell us + // which of these slots are live; this bit-vector just identifies which entries in liveFields represent + // slots, so we can zero them all out quickly. + BVSparse * slotSyms; + PropertySym * propertySymUse; BVSparse * lengthEquivBv; @@ -837,7 +842,7 @@ class GlobOpt void ProcessFieldKills(IR::Instr * instr); void KillLiveFields(StackSym * stackSym, BVSparse * bv); void KillLiveFields(PropertySym * propertySym, BVSparse * bv); - void KillLiveFields(BVSparse *const propertyEquivSet, BVSparse *const bv) const; + void KillLiveFields(BVSparse *const fieldsToKill, BVSparse *const bv) const; void KillLiveElems(IR::IndirOpnd * indirOpnd, BVSparse * bv, bool inGlobOpt, Func *func); void KillAllFields(BVSparse * bv); void SetAnyPropertyMayBeWrittenTo(); diff --git a/lib/Backend/GlobOptFields.cpp b/lib/Backend/GlobOptFields.cpp index 00f11ac7326..8591fb9ce40 100644 --- a/lib/Backend/GlobOptFields.cpp +++ b/lib/Backend/GlobOptFields.cpp @@ -332,19 +332,19 @@ GlobOpt::KillLiveFields(PropertySym * propertySym, BVSparse * KillLiveFields(propertySym->m_propertyEquivSet, bv); } -void GlobOpt::KillLiveFields(BVSparse *const propertyEquivSet, BVSparse *const bv) const +void GlobOpt::KillLiveFields(BVSparse *const fieldsToKill, BVSparse *const bv) const { Assert(bv); - if (propertyEquivSet) + if (fieldsToKill) { - bv->Minus(propertyEquivSet); + bv->Minus(fieldsToKill); if (this->IsLoopPrePass()) { for (Loop * loop = this->rootLoopPrePass; loop != nullptr; loop = loop->parent) { - loop->fieldKilled->Or(propertyEquivSet); + loop->fieldKilled->Or(fieldsToKill); } } } @@ -564,6 +564,15 @@ GlobOpt::ProcessFieldKills(IR::Instr *instr, BVSparse *bv, bo } break; + case Js::OpCode::LdHeapArguments: + case Js::OpCode::LdLetHeapArguments: + case Js::OpCode::LdHeapArgsCached: + case Js::OpCode::LdLetHeapArgsCached: + if (inGlobOpt) { + this->KillLiveFields(this->slotSyms, bv); + } + break; + default: if (instr->UsesAllFields()) { From 923bc8e6f25048248dcc1803def67a6ce6e86c75 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Mon, 7 Aug 2017 19:18:08 -0700 Subject: [PATCH 2/6] Update alternative suggestion to load all of InitLocalClosure's arg's properties Fixes the assertion failure, at least on the bug's repro case. --- lib/Backend/GlobOpt.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index aef16f692d8..06fdb1ef3a8 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -5183,8 +5183,16 @@ GlobOpt::ValueNumberDst(IR::Instr **pInstr, Value *src1Val, Value *src2Val) break; case Js::OpCode::InitLocalClosure: Assert(instr->GetDst()); - Assert(instr->GetDst()->IsSymOpnd()); - this->slotSyms->Set(instr->GetDst()->AsSymOpnd()->m_sym->m_id); + Assert(instr->GetDst()->IsRegOpnd()); + IR::RegOpnd *regOpnd = instr->GetDst()->AsRegOpnd(); + StackSym *opndStackSym = regOpnd->m_sym; + Assert(opndStackSym != nullptr); + ObjectSymInfo *objectSymInfo = opndStackSym->m_objectInfo; + Assert(objectSymInfo != nullptr); + for (PropertySym *localVarSlotList = objectSymInfo->m_propertySymList; localVarSlotList; localVarSlotList = localVarSlotList->m_nextInStackSymList) + { + this->slotSyms->Set(localVarSlotList->m_id); + } break; } From f32245f05db7b35ee4691d025c834d98e626ef57 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Tue, 8 Aug 2017 15:15:09 -0700 Subject: [PATCH 3/6] Add unit test for stack-alloc closure copy-prop bug --- .../copy-prop-stack-slot-test-framework.js | 3068 +++++++++++++++++ test/Closures/copy-prop-stack-slot.baseline | 3 + test/Closures/copy-prop-stack-slot.js | 21 + test/Closures/rlexe.xml | 6 + 4 files changed, 3098 insertions(+) create mode 100644 test/Closures/copy-prop-stack-slot-test-framework.js create mode 100644 test/Closures/copy-prop-stack-slot.baseline create mode 100644 test/Closures/copy-prop-stack-slot.js diff --git a/test/Closures/copy-prop-stack-slot-test-framework.js b/test/Closures/copy-prop-stack-slot-test-framework.js new file mode 100644 index 00000000000..3df41abacf2 --- /dev/null +++ b/test/Closures/copy-prop-stack-slot-test-framework.js @@ -0,0 +1,3068 @@ +(function (window) { + /////////////////////////////////////////////////////////////////////////////// + // Utils object for helper functions + /////////////////////////////////////////////////////////////////////////////// + var Utils = {}; + + //Doc Mode + Utils.IE7STANDARDSMODE = 7; + Utils.IE8STANDARDSMODE = 8; + Utils.IE9STANDARDSMODE = 9; + Utils.IE10STANDARDSMODE = 10; + Utils.IE11STANDARDSMODE = 11; + Utils.IE12STANDARDSMODE = 12; + + //IE Architectures + Utils.X86MODE = "x86"; + Utils.X64MODE = "x64"; + Utils.ARMMODE = "arm"; + + Utils.getHOSTMode = function () { + var documentMode = this.IE11STANDARDSMODE; // Making default to be IE11 standards mode + //See if document.documentMode is defined + try { + if ((document) && (document.documentMode)) { + documentMode = document.documentMode; + } + } catch (ex) { + //We are not running in IE, need to put logic of versioning for other hosts later + } + if (documentMode < 8) + return this.IE7STANDARDSMODE; + else if (documentMode == 8) + return this.IE8STANDARDSMODE; + else if (documentMode == 9) + return this.IE9STANDARDSMODE; + else if (documentMode == 10) + return this.IE10STANDARDSMODE; + else if (documentMode == 11 && typeof Math.hypot === "undefined") //hack for now as we run version:6 switch to emulate Doc mode 12 + return this.IE11STANDARDSMODE; + else + return this.IE12STANDARDSMODE; + } + + var activeXInfo = { + "WScript.Shell": { + "clsid": "72C24DD5-D70A-438b-8A42-98424B88AFB8", + "type": "application/x-shellscript" + }, + "Loader42.Logger": { + "clsid": "7CDFA963-4058-45AE-9061-D34614D83472", + "type": "application/vns.ms-loader42loggerax" + }, + "Scripting.FileSystemObject": { + "clsid": "0D43FE01-F093-11cf-8940-00A0C9054228", + "type": "application/x-filesystemobject" + }, + "MutatorsHost.MutatorsActiveX": { + "clsid": "ffafec7c-214c-4aab-b929-caa646777649", + "type": "application/x-mutatorsactivex" + }, + "apgloballib.apglobal": { + "clsid": "CBFCDDC2-CDB1-3270-B9C5-FBE31729B041", + "type": "application/x-apgloballib" + }, + "Logger.Status": { + "clsid": "712B2344-7A71-4242-B21C-AA728108EDD8", + "type": "application/x-loggerstatus" + }, + "WTTLogger.Logger": { + "clsid": "FFC4E997-D97B-45DF-AD22-44C4B2DEDD2B", + "type": "application/x-wttlogger" + } + }; + + function createActiveXObject(progId) { + var activeX; + try { + activeX = new ActiveXObject(progId); + } + catch (ex) { + // Can't create ActiveXObject element in Edge mode, append Object element instead + activeX = document.createElement("object"); + activeX.setAttribute('id', progId); + activeX.setAttribute('type', activeXInfo[progId]["type"]); + document.body.appendChild(activeX); + } + return activeX; + } + + Utils.IEMatchesArch = function () { + if (this.getProcessorArchitecture() != this.X64MODE) { + return true; + } + if (this.runWow64()) { + return this.getIEArchitecture() == this.X86MODE; + } + else { + return this.getIEArchitecture() == this.X64MODE; + } + } + + Utils.getProcessorArchitecture = function () { + try { + var GetEnvVariable = function (variable) { + var wscriptShell = createActiveXObject("WScript.Shell"); + var wshEnvironment = wscriptShell.Environment("System"); + return wshEnvironment(variable); + } + var result = GetEnvVariable("PROCESSOR_ARCHITECTURE"); + if (result != null) { + result = result.toUpperCase(); + } + if (result === "AMD64") { + return this.X64MODE; + } + else if (result === "X86") { + return this.X86MODE; + } + else if (result === "ARM") { + return this.ARMMODE; + } + } + catch (e) { + throw new Error("[getProcessorArchitecture] Failed to retrieve environment variable. " + e.Message); + } + } + + Utils.getIEArchitecture = function () { + if (typeof navigator === 'undefined') { + return this.X64MODE; + } + + //get the user agent and split the strings and identify t he IE mode that we are running + var mode = navigator.userAgent; + var arr_mode = mode.toLowerCase().split(";"); + + for (var i = 0; i < arr_mode.length; i++) { + if (arr_mode[i] === " wow64") { + return this.X86MODE; + } + else if (arr_mode[i] === " x64") { + return this.X64MODE; + } + else if (arr_mode[i] === " arm") { + for (var j = i + 1; j < arr_mode.length; j++) { + if (arr_mode[j] === " virtual)") + return X86MODE; + } + return this.ARMMODE; + } + } + //default to x86 + return this.X86MODE; + } + + Utils.runWow64 = function () { + try { + var GetEnvVariable = function (variable) { + var wscriptShell = createActiveXObject("WScript.Shell"); + var wshEnvironment = wscriptShell.Environment("Process"); + return wshEnvironment(variable); + } + var result = GetEnvVariable("NightlyWow64"); + if (result === "1") { + return true; + } + else { + return false; + } + } + catch (e) { + throw new Error("[runWow64] Failed to retrieve environment variable. " + e.Message); + } + } + + //Get the host of script engine + Utils.WWAHOST = "WWA"; + Utils.IEHOST = "Internet Explorer"; + Utils.getHOSTType = function () { + //navigator.appName will return the host name, e.g. "WWAHost/1.0" or "Microsoft Internet Explorer" + if (typeof navigator != 'undefined' && navigator.appName.indexOf(this.WWAHOST) >= 0) { + return this.WWAHOST; + } + return this.IEHOST; + } + + //Get localized error message + Utils.getLocalizedError = function (ID, _default, substitution) { + if (_default) { + return _default; + } + if (substitution == undefined) { + var localizedString = apGlobalObj.apGetLocalizedString(ID); + } else { + var localizedString = apGlobalObj.apGetLocalizedStringWithSubstitution(ID, substitution); + } + return localizedString; + } + + // read "MaxInterpretCount" environment variable to know how many time to run test suite + Utils.getMaxInterpretCount = function () { + try { + var GetEnvVariable = function (variable) { + var wscriptShell = createActiveXObject("WScript.Shell"); + var wshEnvironment = wscriptShell.Environment("Process"); + return wshEnvironment(variable); + } + var result = GetEnvVariable("MaxInterpretCount"); + if (result === "") + return 0; + else + return Math.floor(result); + } + catch (e) { + return 0; + } + } + + /////////////////////////////////////////////////////////////////////////////// + // WTTLogger: + // Logs directly to WTT. + /////////////////////////////////////////////////////////////////////////////// + var loggers = []; + function WTTLogger() { + var WTT_TRACE_LVL_MSG = 0x10000000; + var WTT_TRACE_LVL_USER = 0x20000000; + + var WTT_TRACE_LVL_ERR = 0x01000000; + var WTT_TRACE_LVL_ASSERT = 0x02000000; + var WTT_TRACE_LVL_INVALID_PARAM = 0x04000000; + var WTT_TRACE_LVL_BUG = 0x08000000; + + var WTT_TRACE_LVL_BREAK = 0x00100000; + var WTT_TRACE_LVL_WARN = 0x00200000; + + var WTT_TRACE_LVL_FUNCTION_ENTRY = 0x00010000; + var WTT_TRACE_LVL_FUNCTION_EXIT = 0x00020000; + var WTT_TRACE_LVL_CONTEXT_ENTRY = 0x00040000; + var WTT_TRACE_LVL_CONTEXT_EXIT = 0x00080000; + + var WTT_TRACE_LVL_START_TEST = 0x0100; + var WTT_TRACE_LVL_END_TEST = 0x0200; + var WTT_TRACE_LVL_TCMINFO = 0x0400; + var WTT_TRACE_LVL_MACHINEINFO = 0x0800; + + var WTT_TRACE_LVL_ROLLUP = 0x0010; + + var WTT_ERROR_TYPE_HRESULT = 0x1; + var WTT_ERROR_TYPE_NTSTATUS = 0x2; + var WTT_ERROR_TYPE_WIN32 = 0x4; + var WTT_ERROR_TYPE_BOOL = 0x8; + + var WTT_ERROR_LIST_EXPECTED = 0x1; + var WTT_ERROR_LIST_BREAKON = 0x2; + + var WTT_TC_RESULT_PASS = 0x1; + var WTT_TC_RESULT_FAIL = 0x2; + var WTT_TC_RESULT_BLOCKED = 0x3; + var WTT_TC_RESULT_WARN = 0x4; + var WTT_TC_RESULT_SKIPPED = 0x5; + + var WTT_BUG_TYPE_BLOCKING = 0x1; + var WTT_BUG_TYPE_NONBLOCKING = 0x2; + + var loggerObj = createActiveXObject("WTTLogger.Logger"); + var logHandle; + var wwahostTimeout = 1000; + var maxMsgSize = 3000; //actually 3901, but do we really even need 3000 in wtt + + this.start = function (filename, priority) { + logHandle = loggerObj.CreateLogDevice(null); + loggerObj.Trace(WTT_TRACE_LVL_MSG, logHandle, "Running " + filename); + } + + this.testStart = function (test) { + loggerObj.StartTest(test.desc, logHandle); + } + + this.verify = function (test, passed, act, exp, msg) { + if (!passed) + loggerObj.Trace(WTT_TRACE_LVL_MSG, logHandle, "Failed - Act: " + act + ", Exp: " + exp + ":" + msg); + } + + this.pass = function (test) { + loggerObj.EndTest(test.desc, WTT_TC_RESULT_PASS, "", logHandle); + } + + this.fail = function (test) { + loggerObj.EndTest(test.desc, WTT_TC_RESULT_FAIL, "", logHandle); + } + + this.error = function (test, error) { + loggerObj.Trace(WTT_TRACE_LVL_MSG, logHandle, "Error: " + error.name + " - " + error.description); + loggerObj.EndTest(test.desc, WTT_TC_RESULT_FAIL, "", logHandle); + } + + this.end = function () { + loggerObj.CloseLogDevice("", logHandle); + //If we're running WTTLogger in a IE environment, we are probably running tests in wwahost, + //so we should close the window after we finish in order to write logs and rollup. The timeout + //is to ensure the rest of glue shutdown happens + //if(typeof document !== "undefined" && Utils.getHOSTType() == Utils.WWAHOST) + // setTimeout(function() { window.close(); }, wwahostTimeout); + } + + this.comment = function (str) { + var msg = str.toString().substring(0, maxMsgSize); + loggerObj.Trace(WTT_TRACE_LVL_MSG, logHandle, msg); + } + + this.bug = function (number, msg, db) { + db = db || "Windows 8 Bugs"; + loggerObj.Trace(WTT_TRACE_LVL_BUG, logHandle, db, number, WTT_BUG_TYPE_BLOCKING); + this.comment(msg); + } + } + + WTTLogger.shouldEnable = function () { + try { + var test = createActiveXObject("WTTLogger.Logger"); + return true; + } catch (e) { + return false; + } + } + + loggers.push(WTTLogger); + /////////////////////////////////////////////////////////////////////////////// + // HTMLLogger: + // Provides HTML output. Must be run in a browser to function. + /////////////////////////////////////////////////////////////////////////////// + + function HTMLLogger() { + var resultsContainer; + var testProgress = []; + var results = []; + var domReady = false; + var summaryTable; + var passes = 0; + var failures = 0; + var failureList = []; + var executingTest = false; + var done = false; + var loggerInstance = this; + var filename; + var priority; + var verifies = 0; + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", whenDomReady, false); + } else { + setTimeout(waitForDomReady, 1); + } + + function waitForDomReady() { + // Otherwise use the oft-used doScroll hack which, according to MSDN, will always thrown an + // error unless the document is ready. + try { + document.documentElement.doScroll("left"); + } + catch (e) { + // We're not ready yet. Check again in a bit. + setTimeout(waitForDomReady, 1); + return; + } + whenDomReady(); + } + + function whenDomReady() { + domReady = true; + insertStyles(); + setupMutators(); + createSummaryTable(); + createCheckBox(); + setupTable(); + printResults(); + } + + // Inserts a stylesheet to the DOM and fills it with our styles. + function insertStyles() { + var css = [ + "body{font-family:'Segoe UI';font-size:1em}", + "table{border-collapse:collapse;padding:0;margin:0}", + "table td,table th{padding:3px}", + "table tr.test_result td.result{border:1px solid #000;border-right:none}", + "table tr.test_result td.details{border:1px solid #000;border-left:none}", + "table td.result{font-size:2em;font-family:'Segoe UI Semibold';text-align:center;padding:3px 20px}", + "table tr.pass td.result{background-color:#c3d69b}", + "table tr.fail td.result{background-color:#d99694}", + "table tr.error td.result{background-color:#d99694}", + "table tr.bug td.result{background-color:#ffefbb}", + "table tr.comment td.result, table tr.comment td.details {font-size:1em;text-align:left;}", + "table td.details{padding:0px}", + "table td.details table{width:100%;height:100%;}", + "table tr.pass td.details th{background-color:#c3d69b}", + "table tr.fail td.details th{background-color:#d99694}", + "table tr.error td.details th{background-color:#d99694}", + "table tr.hidden {display:none;}", + "table td.details tr.detail_pass{background-color:#ebf1dd}", + "table td.details tr.detail_fail{background-color:#f2dcdb}", + "table td.details tr.detail_comment{background-color:#f2ffe9;}", + "table td.details tr.detail_comment td{padding: 10px 20px;}", + "table td.details tr.detail_comment td.detail_result{font-family:'Segoe UI Semibold';}", + "table td.details tr.detail_bug{background-color:#ffefbb;}", + "table td.details tr.detail_bug td{padding: 10px 20px;}", + "table td.details tr.detail_bug td.detail_result{font-family:'Segoe UI Semibold';}", + "table td.details th{font-size:1.1em;font-weight:normal;font-family:'Segoe UI Semibold';text-align:left}", + "table td.details table td{padding:3px 20px}", + "table td.details table td.error_message{background-color:#d99694;padding:3px}", + "table td.details td.detail_message{width:95%}", + "table td.details td.detail_result{width:5%}", + "#results_summary{margin-bottom:1em;font-size:1.5em;}", + "div.mutators { float: right }", + "#results_summary td{padding:0 20px;}"]; + + // Create a style dom element + var style = document.createElement('style'); + style.type = 'text/css'; + style.rel = 'stylesheet'; + style.media = 'screen'; + + // Append the element to the page's header + document.getElementsByTagName("head")[0].appendChild(style); + + // Get a stylesheet object for the element we just inserted + var stylesheet = document.styleSheets[document.styleSheets.length - 1]; + + // Add the styles to the sheet. If there is a cssText property, we can just join together + // all of the rules and add them at once. Otherwise, add them one at a time. + if (typeof stylesheet.cssText == "string") + document.styleSheets[document.styleSheets.length - 1].cssText = css.join(''); + else + for (var i = 0; i < css.length; i++) + stylesheet.insertRule(css[i], i); + } + + //Replace angle brackets for the text + function replaceAngleBrackets(text) { + if (text == undefined) + return ""; + if (text.replace == undefined) + return text; + return text.replace(/&/g, '&') + .replace(//g, '>') + .replace(/\n/g, '
') + .replace(/\s/g, ' '); + } + + // Sets up the master table that will hold the results. + function setupTable() { + var masterTable = document.createElement("table"); + var body = document.createElement("tbody"); + masterTable.appendChild(body); + document.body.appendChild(masterTable); + + resultsContainer = body; + } + + // Add a dropdown box to select mutators to apply if we have the mutators ActiveX. + function setupMutators() { + function applyMutator(e) { + // Undefined in IE < 8. + if (typeof e === "undefined") + e = window.event; + + var target = (typeof e.target === "undefined") ? e.srcElement : e.target; + + if (target.value === "") + runner.mutators = []; + else + runner.mutators = [target.value]; + + // Clear logger and reset. + loggerInstance.clear(); + Run(); + } + + try { + if (typeof mutator === "undefined") { + // assigns to global mutator + mutator = createActiveXObject("MutatorsHost.MutatorsActiveX"); + } + } catch (e) { + return + } + + var mutators = mutator.GetMutatorsList().split(","); + + // Create a select box and fill it with options for each mutator. + var select = document.createElement("select"); + select.appendChild(document.createElement("option")); + for (var i = 0; i < mutators.length; i++) { + option = document.createElement("option"); + option.innerHTML = mutators[i]; + option.value = mutators[i]; + select.appendChild(option); + } + + select.onchange = applyMutator; + + // Put the box in a label, put the label in a div, and put the div in the document body. + var container = document.createElement("div"); + var label = document.createElement("label"); + label.innerHTML = "Mutate With: "; + label.appendChild(select); + container.appendChild(label); + container.setAttribute("class", "mutators"); + + document.body.appendChild(container); + } + // Adds a result to the results array.. + function addResult(type, result, test, testProgress, error) { + results.push({ + type: type, + result: result, + test: test, + testProgress: testProgress, + error: error + }); + + if (domReady) + printResults(); + } + + // Goes through all the results and prints them to the table. This results array + // fills up when the DOM is not ready yet. + // + function printResults() { + while (results.length > 0) { + var result = results.shift(); + if (result.type === "comment" || result.type === "bug") + addRow(result); + else + addResultRow(result.result, result.test, result.testProgress, result.error); + } + } + //Add a comment or bug to the status table + function addRow(result) { + var comment = result.result; + var row = resultsContainer.insertRow(-1); + var res = row.insertCell(-1); + var details = row.insertCell(-1); + + res.innerHTML = replaceAngleBrackets(result.type); + details.innerHTML = replaceAngleBrackets(comment); + + row.className = result.type.toLowerCase(); + res.className = "result"; + } + // Adds a result to the status table. + function addResultRow(result, test, testProgress, error) { + var row = resultsContainer.insertRow(-1); + var res = row.insertCell(-1); + var details = row.insertCell(-1); + + res.innerHTML = result.toUpperCase(); + + row.className = "test_result " + result; + res.className = "result"; + details.className = "details"; + + var detailsTable = constructDetailsTable(test, testProgress, error); + details.appendChild(detailsTable); + } + + // Constructs and returns the table that holds all the test progress that were + // run. + function constructDetailsTable(test, testProgress, error) { + var table = document.createElement('table'); + var body, row, cell, text, msgCell; + + // Create the row with the test description + body = document.createElement("tbody"); + row = document.createElement("tr"); + cell = document.createElement("th"); + cell.innerHTML = replaceAngleBrackets(test.id + ": " + test.desc); + cell.setAttribute("colSpan", "2"); + row.appendChild(cell); + body.appendChild(row); + + // Create an error row if there's an error. + if (typeof error != "undefined") { + row = document.createElement("tr"); + cell = document.createElement("td"); + cell.innerHTML = error.name + ": " + error.message; + cell.setAttribute("colSpan", "2"); + cell.className = "error_message"; + row.appendChild(cell); + body.appendChild(row); + } + + + // Loop through all the test progress adding rows for each. + var assertionResult; + for (var i = 0; i < testProgress.length; i++) { + if (testProgress[i].type === 'comment') + assertionResult = "comment"; + else if (testProgress[i].type === 'bug') + assertionResult = "bug"; + else if (testProgress[i].passed) + assertionResult = "pass"; + else + assertionResult = "fail"; + + row = document.createElement("tr"); + row.className = "detail_" + assertionResult; + + if (testProgress[i].passed) + row.className += " hidden"; + cell = document.createElement("td"); + cell.className = "detail_result"; + msgCell = document.createElement("td"); + msgCell.className = "detail_message"; + + cell.innerHTML = replaceAngleBrackets(assertionResult); + msgCell.innerHTML = replaceAngleBrackets(testProgress[i].message); + + if (testProgress[i].type !== 'comment' && testProgress[i].type !== 'bug') { + if (!testProgress[i].passed) { + msgCell.innerHTML += "
Expected: " + testProgress[i].expected; + msgCell.innerHTML += "
Actual: " + testProgress[i].actual; + } + } + row.appendChild(cell); + row.appendChild(msgCell); + body.appendChild(row); + } + + table.appendChild(body); + return table; + } + + function createSummaryTable() { + var table = document.createElement("table"); + table.id = "results_summary"; + var row = table.insertRow(-1); + + var cell = row.insertCell(-1); + cell.id = "testName"; + + cell = row.insertCell(-1); + cell.id = "priority"; + + cell = row.insertCell(-1); + cell.id = "passes"; + + cell = row.insertCell(-1); + cell.id = "failures"; + + cell = row.insertCell(-1); + cell.id = "total"; + + var rowForFailure = table.insertRow(-1); + var cellForFailure = rowForFailure.insertCell(-1); + cellForFailure.id = "FailedCases"; + + document.body.appendChild(table); + + if (done) + populateSummaryTable(); + } + + function populateSummaryTable() { + if (!domReady) + return; // we'll come back later if we need at the end of createSummaryTable. + + document.getElementById("testName").innerHTML = "TEST: " + filename; + document.getElementById("priority").innerHTML = "PRIORITY: " + priority; + document.getElementById("passes").innerHTML = "PASS: " + passes; + document.getElementById("failures").innerHTML = "FAIL: " + failures; + document.getElementById("total").innerHTML = "TOTAL: " + (passes + failures); + + if (failures > 0) + document.getElementById("FailedCases").innerHTML = "Failed Cases: " + failureList.toString(); + + if ((passes + failures) === 0) { + document.write("

No tests were run! There's probably a SyntaxError in the test file.

") + } + + // Added for XBox team to parse out pass/fail from page title + document.title = + 'PASS:' + passes + + ';FAIL:' + failures + + ';TOTAL:' + (passes + failures); + } + function createCheckBox() { + var box = document.createElement("input"); + box.type = "checkbox"; + box.checked = false; + box.id = "displayCheckbox"; + + var label = document.createElement('label'); + label.innerHTML = "Display passed test result" + + box.onclick = function () { + var trList = document.getElementsByTagName("tr"); + for (var x = 0; x < trList.length; x++) { + if (this.checked) { + //Remove hidden in class name + trList[x].className = trList[x].className.replace("hidden", ""); + } + else { + //Add hidden to detail_pass + trList[x].className = trList[x].className.replace("detail_pass", "detail_pass hidden"); + } + } + } + + document.body.appendChild(box); + document.body.appendChild(label); + } + function internalComment(type, str) { + if (executingTest) + testProgress.push({ type: type, message: str }); + else + addResult(type, str); + } + + this.start = function (file, pri) { + filename = file; + priority = pri; + } + + this.testStart = function (test) { + testProgress = []; // track assertions and comments from tests. + executingTest = true; + } + + this.verify = function (test, passed, act, exp, msg) { + verifies++; + if (verifies < 1000 || !passed) { + testProgress.push({ type: 'assertion', passed: passed, actual: act, expected: exp, message: msg }); + } + } + + this.pass = function (test) { + passes++; + addResult('result', 'pass', test, testProgress); + + executingTest = false; + } + + this.fail = function (test) { + failures++; + failureList.push(test.id); + addResult('result', 'fail', test, testProgress); + + executingTest = false; + } + + this.error = function (test, error) { + failures++; + // Throw the error so we can debug in browser. + setTimeout(function () { throw error }, 0); + addResult('result', 'error', test, testProgress, error); + + executingTest = false; + } + + this.end = function () { + done = true; + populateSummaryTable(); + } + + this.comment = function (str) { + internalComment('comment', str); + }; + + this.bug = function (number, msg) { + msg = msg || ""; + internalComment('bug', "Bug: " + number + ": " + msg); + }; + + this.clear = function () { + if (typeof resultsContainer == "undefined") + return; + + passes = 0; + failures = 0; + failureList = []; + var children = resultsContainer.childNodes; + for (var i = children.length - 1; i >= 0; i--) { + resultsContainer.removeChild(children[i]); + } + } + } + + HTMLLogger.shouldEnable = function () { + return typeof document !== "undefined" + } + + loggers.push(HTMLLogger); + + /////////////////////////////////////////////////////////////////////////////// + // WScriptLogger: + // Log to the console using WScript. + //////////////////////////////////////////////////////////////////////////////// + function WScriptLogger() { + var passCount = 0; + var failCount = 0; + var verifications = []; + var verbose = false; + + // Initialize the projection related stuff in JsHost + if (Utils.getHOSTType() == Utils.WWAHOST) { + WScript.InitializeProjection(); + } + + this.start = function (filename, priority) { + if (!verbose) { + return; + } + + if (priority === "all") { + WScript.Echo(filename); + } else { + WScript.Echo(filename + " Priority " + priority); + } + } + + this.testStart = function (test) { + verifications = []; + } + + this.verify = function (test, passed, act, exp, msg) { + //print(`test:${test} passed:${passed} act:${act} exp:${exp} msg:${msg}`) + if (passed) { + if (verbose) { + verifications.push("\tPass: " + msg + "\n\t\t Actual: " + act + "\n"); + } + } else { + verifications.push("\n\tFail: " + msg + "\n\t\tExpected: " + exp + "\n\t\t Actual: " + act + "\n"); + } + } + + this.pass = function (test) { + passCount++; + + if (verbose) { + WScript.Echo("PASS\t" + test.id + ": " + test.desc); + + //Needed for baselines + WScript.Echo(verifications.join("\n")); + } + } + + this.fail = function (test) { + failCount++; + WScript.Echo(""); + WScript.Echo(""); + WScript.Echo("FAIL\t" + test.id + ": " + test.desc); + WScript.Echo(""); + WScript.Echo(verifications.join("\n")); + WScript.Echo(""); + WScript.Echo(""); + } + + this.error = function (test, error) { + failCount++; + WScript.Echo(""); + WScript.Echo(""); + WScript.Echo("FAIL\t" + test.id + ": " + test.desc); + WScript.Echo("\n\tError: " + error.name + " - " + error.description); + if (verifications.length > 0) { + WScript.Echo(""); + WScript.Echo(verifications.join("\n")); + WScript.Echo(""); + } + WScript.Echo(""); + WScript.Echo(""); + } + + this.end = function () { + if (verbose) { + WScript.Echo(""); + WScript.Echo("Passed: " + passCount); + WScript.Echo("Failed: " + failCount); + if ((passCount + failCount) === 0) { + WScript.Echo(""); + WScript.Echo("No tests were run! There's probably a SyntaxError in the test file.") + } + } else { + if (failCount === 0) { + WScript.Echo('pass'); + } else { + WScript.Echo('fail'); + } + } + } + + this.comment = function (str) { + if (verbose) { + verifications.push("\tComment: " + str); + } + }; + } + + + WScriptLogger.shouldEnable = function () { + return typeof WScript !== "undefined" && typeof WScript.Echo !== "undefined" && typeof window.location === "undefined"; + } + + loggers.push(WScriptLogger); + + + // A simple logger object exposed on the window, giving the user access to the comment method. + // Simply publishes a comment event. + var windowLogger = {}; + windowLogger.comment = function (str) { + runner.publish('comment', str); + } + + /////////////////////////////////////////////////////////////////////////////// + // NightlyLogger: + // Provides Logging for automated nightly runs using apgloblib. Must be run in a browser to + // function. + /////////////////////////////////////////////////////////////////////////////// + + function NightlyLogger() { + var apPlatform; + var runCount; + + function VBS_apglobal_init() { + apPlatform = apGlobalObj.apGetPlatform(); + runCount = 0; + } + + function apInitTest(stTestName) { + if (window.runsExecuted == 0) { // In MaxInterprentCount case do it only on first iteration + apGlobalObj.apInitTest(stTestName); + } + } + + function apInitScenario(stScenarioName) { + runCount++; + apGlobalObj.apInitScenario(stScenarioName); + } + + function apLogFailInfo(stMessage, stExpected, stActual, stBugNum) { + if (stBugNum == null) stBugNum = ""; // and may Fajen suffer for it + apGlobalObj.apLogFailInfo("" + stMessage, "" + stExpected, "" + stActual, "" + stBugNum); + } + + function apEndTest() { + if (window.runsExecuted >= window.runsToExecute - 1) { // In MaxInterprentCount case do it only on last iteration + if (runCount === 0) { + apLogFailInfo("Zero tests were run! Must be a SyntaxError in the test file...", true, false, ''); + } + apGlobalObj.apEndTest(); + } + } + + function apErrorTrap() { + apGlobalObj.apLogFailInfo("call to apErrorTrap found!", "", "", ""); + } + + this.start = function (filename) { + VBS_apglobal_init() + apInitTest(filename) + } + + this.testStart = function (test) { + apInitScenario(test.id + " : " + test.desc) + } + + this.verify = function (test, passed, act, exp, msg) { + if (!passed) + apLogFailInfo(msg, exp, act, ''); + } + + this.pass = function (test) { } + + this.fail = function (test) { } + + this.error = function (test, error) { + apLogFailInfo("Exception thrown : " + error.name + "Message: " + error.message, true, false, ''); + } + + this.end = function () { + apEndTest(); + } + + this.comment = function (msg) { } + } + + NightlyLogger.shouldEnable = function () { + try { + var GetEnvVariable = function (variable) { + var wscriptShell = createActiveXObject("WScript.Shell"); + var wshEnvironment = wscriptShell.Environment("Process"); + return wshEnvironment(variable); + } + var result = GetEnvVariable("JScript_NightlyRun"); + if (result === "") + return false; + else + return true; + } + catch (e) { + return false; + } + } + + loggers.push(NightlyLogger); + + /////////////////////////////////////////////////////////////////////////////// + // Loader42Logger: + // Provides Logging for automations runs with Loader42. Must be run in a browser to function. + /////////////////////////////////////////////////////////////////////////////// + + function Loader42Logger() { + // Provides logging back to Loader42 (and therefore WTT and Blackbeard) + var filename = "File Name Not Specified"; + var Loader42Log = { + // define some constants for reporting success/failure of a test + FAIL: 0, + PASS: 1, + BLOCKED: 2, + Logger: null, + TestsComplete: false, + Debug: false, + + // Initialize the ActiveX logger object + InitLogger: function () { + if (Loader42Log.Logger === null) { + try { + Loader42Log.Logger = createActiveXObject("Loader42.Logger"); + } + catch (ex) { + // Can't create ActiveXObject element in Edge mode, append Object element instead + Loader42Log.Logger = document.createElement("object"); + Loader42Log.Logger.setAttribute('id', 'logger'); + Loader42Log.Logger.setAttribute('type', 'application/vns.ms-loader42loggerax'); + document.body.appendChild(Loader42Log.Logger); + } + try { + // Cleanup logger in case where HTMLLoader is not in use + // but Loader42 is present. + if (Loader42Log.Logger.loaderName !== "HTMLLoader") { + Loader42Log.Logger = null; + } + } + catch (ex) { + Loader42Log.Logger = null; + } + } + }, + + // Called at the start of every test case. Argument is the name of the test + // case. + TestStart: function (name) { + Loader42Log.InitLogger(); + + if (Loader42Log.Logger) { + Loader42Log.Logger.LogTestStart(name); + } + else if (Loader42Log.Debug) { + alert("TestStart(): " + name); + } + + }, + + // Called at the end of every test case. Name should match what is used in + // TestStart(). Result should be one of the constants + // Logger42Log.FAIL, + // Logger42Log.PASS, + // Logger42Log.BLOCKED + // Message is a simple descriptive error, mostly useful in explaining why + // a test failed or was blocked. + TestFinish: function (name, result, message) { + Loader42Log.InitLogger(); + + if (Loader42Log.Logger) { + Loader42Log.Logger.LogTestEnd(name, result, message); + } + else if (Loader42Log.Debug) { + var status; + if (result === Loader42Log.PASS) { + status = "PASS"; + } + else if (result === Loader42Log.FAIL) { + status = "FAIL"; + } + else if (result === Loader42Log.BLOCKED) { + status = "BLOCKED"; + } + else { + status = "UNKNOWN"; + } + + alert("TestFinish(): " + name + "\n\n" + status + ": " + message); + } + }, + + // Logs a message on behalf of the function. Used for tracing/debugging. + TestComment: function (comment) { + Loader42Log.InitLogger(); + if (Loader42Log.Logger) { + Loader42Log.Logger.LogComment(comment); + } + + }, + + // Signaled at the end of the test, when the page wishes to close the + // browser. Should only be called once. + AllTestsComplete: function () { + Loader42Log.InitLogger(); + + if (Loader42Log.TestsComplete) { + Loader42Log.TestStart("LogException"); + Loader42Log.TestComment("AllTestsComplete() called twice."); + Loader42Log.TestFinish("LogException", Loader42Log.PASS, "AllTestsComplete() called twice."); + } + else { + Loader42Log.TestsComplete = true; + if (Loader42Log.Logger) { + // Since success/failure is really determined by if there are + // any failures in TestFinish, we don't have to really worry + // about logging success or failure here. + Loader42Log.Logger.LogResult(true, "AllTestsComplete"); + } + else if (Loader42Log.Debug) { + alert("AllTestsComplete"); + } + } + + } + }; + + //Added for Loader42- True for the first Scenario + var firsttest = true; + var prevScenario = ""; + var loggermessage = ""; + var loggerresult = 1; + + function apInitTest(stTestName) { + if (window.runsExecuted == 0) { // In MaxInterprentCount case do it only on first iteration + filename = stTestName; + //Added for Loader42 - InitLogger will initialize the logger + Loader42Log.InitLogger(); + } + } + + function apInitScenario(stScenarioName) { + //Added for Loader42-If this is the first scenario do not call Test Fininsh + if (firsttest === true) { + Loader42Log.TestStart(stScenarioName); + firsttest = false; //Done with the first test + } + else { + //Added for Loader42- if the Scenario Succeeded log the result as passed + if (loggerresult === 1) { + Loader42Log.TestComment(""); + Loader42Log.TestComment("File Name " + filename); + Loader42Log.TestComment("Result PASSED"); + Loader42Log.TestComment("Scenario " + prevScenario); + Loader42Log.TestComment(""); + } + //TestFinish the previous scenario + Loader42Log.TestFinish(prevScenario, loggerresult, loggermessage); + + //Test start a New Scenario + Loader42Log.TestStart(stScenarioName); + + //Added for Loader42-Reset the Variable back to the initial value for the New Scenario + loggermessage = ""; //Clear tge loggermessage + loggerresult = 1; //Set Default to Success + } + prevScenario = stScenarioName; + } + + function apLogFailInfo(stMessage, stExpected, stActual, stBugNum) { + if (stBugNum == null) stBugNum = ""; // and may Fajen suffer for it + + //Added for Loader42 - Store the result in a global variable with all the values + Loader42Log.TestComment(""); + Loader42Log.TestComment("File Name " + filename); + Loader42Log.TestComment("Result FAILED"); + Loader42Log.TestComment("Scenario " + stMessage); + Loader42Log.TestComment("EXPECTED Result " + stExpected); + Loader42Log.TestComment("ACTUAL Result " + stActual); + Loader42Log.TestComment(""); + loggerresult = 0; //this indicates failiure + } + + function apEndTest() { + if (window.runsExecuted >= window.runsToExecute - 1) { // In MaxInterprentCount case do it only on last iteration + //Added for Loader42- Finish the last test that was started + if (loggerresult === 1) { + Loader42Log.TestComment(""); + Loader42Log.TestComment("File Name " + filename); + Loader42Log.TestComment("Result PASSED"); + Loader42Log.TestComment("Scenario " + prevScenario); + Loader42Log.TestComment(""); + } + //Finishing the last Scenario + Loader42Log.TestFinish(prevScenario, loggerresult, loggermessage); + + //Added for Loader42 - - called once to finish the test + Loader42Log.TestComment("Ending Test" + filename); + Loader42Log.AllTestsComplete(); + } + } + + + this.start = function (filename) { + apInitTest(filename) + } + + this.testStart = function (test) { + apInitScenario(test.desc) + } + + this.verify = function (test, passed, act, exp, msg) { + if (!passed) + apLogFailInfo(msg, exp, act, ''); + } + + this.pass = function (test) { } + + this.fail = function (test) { } + + this.error = function (test, error) { + apLogFailInfo("Exception thrown : " + error.name + "Message: " + error.message, true, false, ''); + } + + this.end = function () { + apEndTest(); + } + + this.comment = function (msg) { + Loader42Log.TestComment(msg); + } + } + + Loader42Logger.shouldEnable = function () { + try { + var Logger = createActiveXObject("Loader42.Logger"); + return true; + } + catch (e) { + return false; + } + + return false; + } + + loggers.push(Loader42Logger); + + /////////////////////////////////////////////////////////////////////////////// + // StressLogger: + // Provides logging support via the DrummerLogger ActiveX object to log and report to the Drummer test harness. It also uses the HTML logger to output the results in IE. + /////////////////////////////////////////////////////////////////////////////// + + function StressLogger() { + var htmlLogger = new HTMLLogger(); + var htmlLoggerRegistered = false; + var logger = new ActiveXObject("DrummerLogger.Logger"); + var that = this; + var id; + + window.onerror = function () { + this.end(); + } + + //logger.Config.NumberIterations -> Contains the number of iterations asked to perform + this.CurrentTestGroupIteration = 1; + + function getRunId() { + id = window.location.href.substring(window.location.href.indexOf("stressrun=") + 10) + } + + this.start = function (fileName) { + getRunId(); + logger.Initialize(id); + that.Config = logger.Config; + logger.Comment("Test File: " + fileName); + logger.Comment("Runtime Iterations Requested: " + that.Config.RuntimeIterations); + logger.Comment("Testcase Iterations Requested: " + that.Config.TestcaseIterations); + logger.Comment("Collection Frequency Requested: " + that.Config.CollectionFrequency); + if (that.Config.OutputHTML && !htmlLoggerRegistered) { + runner.subscribe(htmlLogger); + htmlLoggerRegistered = true; + } + } + + this.testStart = function (test) { + + logger.InitScenario(test.id + ": " + test.desc); + } + + this.verify = function (test, passed, act, exp, msg) { + logger.Comment("Passed: " + passed + "; Actual: " + act + "; Expected: " + exp + "; Message: " + msg); + } + + this.pass = function (test) { + logger.EndScenario("pass", test.desc); + } + + this.fail = function (test) { + logger.EndScenario("fail", test.desc); + } + + this.error = function (test, error) { + logger.Error(error.message); + } + + this.end = function () { + logger.Comment("TestGroupIteration Executed: " + (that.CurrentTestGroupIteration - 1)); + logger.Finished(); + } + + this.comment = function (msg) { + logger.Comment(msg); + } + + this.CollectMetrics = function () { + logger.CollectMetrics(); + } + } + + StressLogger.shouldEnable = function () { + try { + var logger = new ActiveXObject("DrummerLogger.Logger"); + var str = window.location.href.substring(window.location.href.indexOf("?") + 1); + if (str.indexOf("stressrun") > -1) + return true; + else + return false; + } + catch (e) { + return false; + } + } + + loggers.push(StressLogger); + + /////////////////////////////////////////////////////////////////////////////// + // End Loggers + /////////////////////////////////////////////////////////////////////////////// + //scheduler in charge of giving us tasks + var scheduler = null; + var runner = (function () { + //holds onto objects that wish to be notified of pub/sub events + var subscribers = []; + //callbacks that get run before any tests execute + var globalSetups = []; + //callbacks that get run after all tests execute + var globalTeardowns = []; + //how many start calls we are waiting for + var waitCount = 0; + //current handle returned from setTimeout + var waitHandle = null; + //valid states + //init - initial state + // => running + // => error + //running - running test + // => waiting + // => ending + // => error + //waiting - waiting for async op + // => waiting + // => resuming + // => timeout + // => error + //resuming - resuming from async op + // => error + // => running + //timeout - timed out during async op + // => resuming + // => error + //ending - final state + //error - error in the test + // => running + // => ending + //current state of the runner + var currentState = "init"; + + //name of the currently executing file/suite. This is set via + //setting Loader42_FileName on the global scope + var fileName = null; + //priority to run for filtering tests. Valid values are "all" or a number + var priority = "all"; + //default timeout for async tasks + var DEFAULT_TIMEOUT = 10000; + //flag for the main run loop + var ended = false; + //object holding valid states and the action to take each iteration while running in them + var states = { + init: function () { + if (window.runsExecuted == 0) { // In MaxInterprentCount case do it only on first iteration + runner.publish('start', fileName, priority); + } + scheduler.prepend(globalSetups); + scheduler.push(globalTeardowns); + transitionTo("running"); + }, + resuming: function () { + transitionTo("running"); + }, + running: function () { + var task = scheduler.next(); + if (task) { + try { + task(); + } catch (ex) { + runner.publish('error', currentTest, ex); + transitionTo("error"); + } + } else { + transitionTo('ending'); + } + }, + ending: function () { + if (++window.runsExecuted < window.runsToExecute) { + transitionTo("restarting"); + } + else { + runner.publish("end", fileName, priority); + ended = true; + } + }, + restarting: function () { + tasks = ranTasks; + ranTasks = []; + transitionTo("init"); + }, + waiting: function () { }, + timeout: function () { + if (waitHandle === null) + return; + waitHandle = null; + currentTest.error = new Error("Timed out before runner.start() being called"); + testPassed = false; + waitCount = 0; + transitionTo("resuming"); + }, + error: function () { + ended = true; + } + }; + + // Subscribes to an event named prop for each property of obj, with the callback being the + // value. + function subscribeObject(obj) { + for (var prop in obj) + if (typeof obj[prop] === "function") + subscribeToEvent(prop, obj[prop]); + } + + // Subscribes to an event of a given name with the given callback. + function subscribeToEvent(e, callback) { + if (typeof subscribers[e] === "undefined") + subscribers[e] = []; + + subscribers[e].push(callback); + } + + //transitions the runner to a new state and validates that the transition is allowed + //parameters: + // * newState: string representing the new state to transition to + function transitionTo(newState) { + function transitionAssert() { + var res = false; + for (var i = 0; i < arguments.length; i++) + if (arguments[i] === newState) + res = true; + runnerAssert(res, "invalid state transition: " + currentState + "->" + newState) + } + switch (currentState) { + case "init": + transitionAssert("running", "error"); + break; + case "running": + transitionAssert("waiting", "ending", "error"); + break; + case "waiting": + transitionAssert("resuming", "waiting", "error", "timeout"); + break; + case "timeout": + transitionAssert("resuming", "error"); + break; + case "resuming": + transitionAssert("running", "error"); + break; + case "error": + transitionAssert("running", "ending"); + break; + case "ending": + transitionAssert("restarting", "error"); + break; + case "restarting": + transitionAssert("init", "error"); + break; + default: + runnerAssert(false, "Invalid state"); + break; + } + currentState = newState; + } + + //internal check to validate assumptions at given points in the code + //along the lines of the C ASSERT macro or C# asserts + //parameters: + // * bool - boolean value that is assumed to be true + // * msg - message to publish if the bool is false + //returns: + // the bool passed in (for use in conditionals or assignment) + function runnerAssert(bool, msg) { + if (!bool) { + runner.publish('comment', "Internal failure: " + msg); + transitionTo("error"); + currentTest.error = new Error("Internal failure: " + msg); + } + return bool; + } + + //main runloop for the runner. Loops continuously unless state + //is waiting or the test is ended. Each iteration, it runs the action + //associated with the current state + function runTasks() { + while (!ended) { + states[currentState](); + if (currentState === "waiting") + return; + } + } + + //wraps globalSetup or globalTeardown operations in a try catch block to allow logging + function wrapGlobalOperation(type, task) { + return function () { + try { + task(); + } catch (e) { + runner.publish('comment', "Error during " + type + " - " + e.name + ": " + e.description); + runner.publish('end', fileName, priority); + transitionTo("error"); + } + } + } + + + // Runner object is responsible for controlling the execution of tests. It is exposed on the global + // object to external code can call any method on runner. + return { + // Subscribe to an event by passing an eventName and a callback function. Subscribe to + // multiple events by passing an object with keys that correspond to the event names you + // want to subscribe to. + subscribe: function () { + if (arguments.length == 1) + subscribeObject(arguments[0]); + else + subscribeToEvent(arguments[0], arguments[1]); + }, + + // Publish an event. Calls all subscribed callbacks with the provided arguments. + publish: function (e) { + var args = Array.prototype.slice.call(arguments, 1); + + if (typeof subscribers[e] !== "undefined") + for (var i = 0; i < subscribers[e].length; i++) + subscribers[e][i].apply(undefined, args); + }, + + mutators: [], + + + //Runs list of tests once. + //parameters: + // * testFileName - name of the test file/suite + run: function (testFileName) { + runnerAssert(scheduler !== null, "no scheduler set. This only happens if setScheduler is not called with a scheduler"); + if (ended) { // We are running it again + window.runsExecuted = 0; + ended = false; + tasks = ranTasks; + ranTasks = []; + currentState = "init"; + } + runnerAssert(currentState === "init", "run called while runner in bad state. Probably means multiple calls to run were made"); + if (typeof testFileName === "undefined") { + try { + testFileName = Loader42_FileName; // Loader42_FileName may be undefined. + } catch (e) { + //noop + } finally { + testFileName = testFileName || "No filename given"; + } + } + + fileName = testFileName; + runTasks(); + }, + + //Decrements the wait counter and instructs the runner to resume running after a wait call + // if the counter is 0. If the timeout has fired, this is a noop. + //parameters: + // * testWaitHandle - waithandle corresponding to the return value from start + start: function (testWaitHandle) { + if (typeof testWaitHandle !== "undefined" && testWaitHandle !== waitHandle) + return; + + waitCount--; + if (waitCount === 0) { + clearTimeout && clearTimeout(testWaitHandle); + transitionTo("resuming"); + waitHandle = null; + runTasks(); + } + }, + + //Increments the wait counter and instructs the runner to wait for an async op to finish + //if the counter is > 0. If count is passed, the wait counter is incremented by that amount. + //parameters: + // * timeout - time (in milliseconds) to wait before timing out. Defaults to DEFAULT_TIMEOUT + // * count - amount to increment counter by. Defaults to 1 + //returns: + // * the waitHandle produced by setTimeout + wait: function (timeout, count) { + count = count || 1; + timeout = timeout || DEFAULT_TIMEOUT; + waitCount += count; + if (waitCount > 0) { + transitionTo("waiting"); + waitHandle = setTimeout && setTimeout(function () { + transitionTo("timeout"); + runTasks(); + }, timeout); + return waitHandle; + } + }, + + // Create a new test object with the properties specified in obj. + addTest: function (obj) { + var test = new TestCase(); + + for (var prop in obj) { + test[prop] = obj[prop] + } + + test.AddTest(); + }, + + globalSetup: function (callback) { + globalSetups.push(wrapGlobalOperation("global setup", callback)); + }, + + globalTeardown: function (callback) { + globalTeardowns.push(wrapGlobalOperation("global teardown", callback)); + }, + + //sets the scheduler for the runner. Note that this doesn't (currently) unregister the old scheduler. + //As part of registering the scheduler, the 'schedule' and 'prepend' events are registered for pub/sub + //parameters: + // * sched - new scheduler object + setScheduler: function (sched) { + scheduler = sched; + runner.subscribe("schedule", sched.schedule); + } + }; + })(); + + for (var i = 0; i < loggers.length; i++) + if (loggers[i].shouldEnable()) + runner.subscribe(new loggers[i]()); + + // holds the list of tasks that need to be scheduled. Running is currently destructive to this list + var tasks = []; + // holds the list of tasks that have been run already, so that tasks[] list can be restored after the run + var ranTasks = []; + window.runsExecuted = 0; + window.runsToExecute = Utils.getMaxInterpretCount() + 1; + + // populate and return default scheduler object + var defaultScheduler = (function () { + return { + //adds an item to the tail of the list. If the item has a tasks property that is a function + //the return value of that function is used instead of this item + //parameters: + // * item - item to schedule + schedule: function (item) { + var items; + if (item.tasks && typeof item.tasks === "function") + items = item.tasks(); + else + items = [item]; + for (var i = 0; i < items.length; i++) + tasks.push(items[i]); + }, + //prepends an item or array to the list. If the item is an array, it is concatenated onto + //the front of the list (i.e. single level flatten). Otherwise it is just put into the list + //parameters: + // * obj - item to put onto the front of the list + prepend: function (obj) { + var type = hoozit(obj); + if (type === "array") { + for (var i = obj.length - 1; i >= 0; i--) { + tasks.unshift(obj[i]); + } + } else { + tasks.unshift(obj); + } + }, + //appends an item or array to the list. If the item is an array, it is concatenated onto + //the end of the list (i.e. single level flatten). Otherwise it is just put into the list + //parameters: + // * obj - item to put onto the end of the list + push: function (obj) { + var type = hoozit(obj); + if (type === "array") { + for (var i = 0; i < obj.length; i++) { + tasks.push(obj[i]); + } + } else { + tasks.push(obj); + } + }, + //returns the next task to run + //returns: + //the next task to run or null if there are no more tasks + next: function () { + var task = null; + if (tasks.length > 0) { + task = tasks.shift(); + ranTasks.push(task); + } + + return task; + } + }; + })(); + runner.setScheduler(defaultScheduler); + + /////////////////////////////////////////////////////////////////////////////// + // Test Runner + /////////////////////////////////////////////////////////////////////////////// + var currentTest; + var testPassed; + + // A test case object + var TestCase = function () { + //close over this for the tasks method + var testCase = this; + + // Flag to execute the test in global scope + this.useGlobalThis = false; + this.baselineFile = false; + this.baselineHandler = 0; + this.baselineCounter = 0; + this.pass = true; + + this.AddTest = function () { + // Function to add use test case to testCases list + if ((this.id === undefined) || (this.id === "")) { + runner.publish('comment', "Test case id is not valid "); + } else if (this.desc === undefined || this.desc === "") { + runner.publish('comment', "Test case description not specified"); + } else if (this.preReq && (!(typeof (this.preReq) === "function"))) { + runner.publish('comment', "Invalid preReq function"); + } else if ((this.test === undefined) || (!(typeof (this.test) === "function"))) { + runner.publish('comment', "Invalid test function"); + } else { + runner.publish('schedule', this); + } + }; + + //splits the test work into setup, test and cleanup. This allows the runner to pause + //between the test task and the cleanup task in order to wait for async operations + //returns: + // - an array of functions + this.tasks = function () { + function setup() { + testPassed = true; + currentTest = testCase; + + runner.publish('testStart', currentTest); + } + + function test() { + // if (!Utils.IEMatchesArch()) { + // throw new Error("IE architecture setting does not match Processor architecture and run settings: " + Utils.getIEArchitecture()); + // } + if (!currentTest.preReq || currentTest.preReq()) { + try { + if (currentTest.useGlobalThis) { + testFunc = currentTest.test; + testFunc(); + } else { + currentTest.test(); + } + } catch (e) { + currentTest.error = e; + } + } else { + runner.publish('comment', "Test prereq returned false. Skipping"); + } + } + + function cleanup() { + if (currentTest.error !== undefined) { + runner.publish('error', currentTest, currentTest.error); + } else if (testPassed) { + runner.publish('pass', currentTest); + } else { + runner.publish('fail', currentTest); + } + runner.publish('testEnd', currentTest); + } + + return [setup, test, cleanup]; + } + } + + /* + * Baseline test cases + */ + + function addEscapeCharacter(txt) { + txt = txt.replace(/\\/g, "\\\\"); + txt = txt.replace(/\"/g, "\\\""); + txt = txt.replace(/\'/g, "\\\'"); + txt = txt.replace(/\r/g, "\\r"); + txt = txt.replace(/\n/g, "\\n"); + + return txt; + } + + //Testcase that will use baseline support + function BaselineTestCase() { + //close over this for the tasks method + var testCase = this; + + // Flag to execute the test in global scope + this.useGlobalThis = false; + this.baselineFile = false; + this.baselineHandler = 0; + this.baselineCounter = 0; + this.pass = true; + + this.AddTest = function () { + // Function to add use test case to testCases list + if ((this.id === undefined) || (this.id === "")) { + runner.publish('comment', "Test case id is not valid "); + } else if (this.desc === undefined || this.desc === "") { + runner.publish('comment', "Test case description not specified"); + } else if (this.preReq && (!(typeof (this.preReq) === "function"))) { + runner.publish('comment', "Invalid preReq function"); + } else if ((this.test === undefined) || (!(typeof (this.test) === "function"))) { + runner.publish('comment', "Invalid test function"); + } else { + runner.publish('schedule', this); + } + }; + + //splits the test work into setup, test and cleanup. This allows the runner to pause + //between the test task and the cleanup task in order to wait for async operations + //returns: + // - an array of functions + this.tasks = function () { + function setup() { + testPassed = true; + currentTest = testCase; + BaselineTestCase.startBaseline(); + runner.publish('testStart', currentTest); + } + + function test() { + if (!currentTest.preReq || currentTest.preReq()) { + try { + if (currentTest.useGlobalThis) { + testFunc = currentTest.test; + testFunc(); + } else { + currentTest.test(); + } + } catch (e) { + currentTest.error = e; + } + } else { + runner.publish('comment', "Test prereq returned false. Skipping"); + } + } + + function cleanup() { + if (currentTest.error !== undefined) { + runner.publish('error', currentTest, currentTest.error); + } else if (testPassed) { + runner.publish('pass', currentTest); + } else { + runner.publish('fail', currentTest); + } + BaselineTestCase.stopBaseline(); + runner.publish('testEnd', currentTest); + } + + return [setup, test, cleanup]; + } + } + + //Static variables for BaselineTestCase + BaselineTestCase.baselineFileExt = ".baseline.js"; + BaselineTestCase.editMode = false; + BaselineTestCase.useGlobalBaseline = false; + BaselineTestCase.fileName = null; + BaselineTestCase.fileHandler = null; + BaselineTestCase.elementIndexInFile = 0; + BaselineTestCase.currentTestcaseID = 0; + + //Static methods for BaselineTestCase + BaselineTestCase.constructBLFileName = function (testCaseID) { + var filePath = window.location.href; + + var fileName = filePath.substring(filePath.lastIndexOf("/") + 1); + fileName = fileName.substring(0, fileName.lastIndexOf(".")); + + + if (typeof (testCaseID) == "undefined") { + fileName = fileName + "." + getHOSTMode() + BaselineTestCase.baselineFileExt; + } + else { + testCaseID = String(testCaseID).replace(/[^a-zA-Z 0-9]+/g, "_"); + fileName = fileName + testCaseID + "." + getHOSTMode() + BaselineTestCase.baselineFileExt; + } + + var baselineFilePath = filePath.substring(0, filePath.lastIndexOf("/") + 1) + fileName; + + //redirecting the baseline file path to js directory when we find EzeHtmlTestFiles directory structure + baselineFilePath = baselineFilePath.replace(/EzeHtmlTestFiles/, "EzeJSTestFiles"); + + // mutator local run will fail in baseline testcases if don't reset this counters + BaselineTestCase.elementIndexInFile = 0; + BaselineTestCase.currentTestcaseID = 0; + + //loadjsfile(baselineFilePath); + return baselineFilePath; + } + + BaselineTestCase.openBaseline = function (fileName) { + var objFSO = createActiveXObject("Scripting.FileSystemObject"); + + if (fileName.indexOf("file:///") > -1) fileName = fileName.substring(8); + + BaselineTestCase.fileHandler = objFSO.CreateTextFile(fileName, true); + BaselineTestCase.fileHandler.WriteLine("var baselineExpArr = ["); + } + + BaselineTestCase.closeBaseline = function () { + if (BaselineTestCase.editMode) { + BaselineTestCase.fileHandler.WriteLine(); + BaselineTestCase.fileHandler.WriteLine("];"); + BaselineTestCase.fileHandler.Close(); + } + else { + //If there is more elements in the baseline file has not been compared, fail the test. + if (typeof (baselineExpArr) != "undefined") { + if (BaselineTestCase.elementIndexInFile < baselineExpArr.length) { + fail("There is more elements in baseline file than expected. Total element in baselinefile=" + baselineExpArr.length + ", expected=" + BaselineTestCase.elementIndexInFile); + } + } + if (!BaselineTestCase.useGlobalBaseline) { + BaselineTestCase.elementIndexInFile = 0; + BaselineTestCase.fileHandler = 0; + } + } + } + + BaselineTestCase.verify = function (act, msg) { + if (BaselineTestCase.editMode) { + if (BaselineTestCase.elementIndexInFile > 0) { + BaselineTestCase.fileHandler.WriteLine(","); + } + var escapedAct = act; + if (act instanceof String || typeof act == "string") { + escapedAct = addEscapeCharacter(act); + } + + if (escapedAct != null) //don't add quotes to null values + { + BaselineTestCase.fileHandler.Write("\"" + escapedAct + "\""); + } + else { + BaselineTestCase.fileHandler.Write("null"); + } + BaselineTestCase.elementIndexInFile++; + } + else { + if (BaselineTestCase.elementIndexInFile >= baselineExpArr.length) { + fail("Test is expecting more elements in baseline file.Expecting " + (BaselineTestCase.elementIndexInFile + 1) + ", actual = " + baselineExpArr.length); + } + else { + var exp = baselineExpArr[BaselineTestCase.elementIndexInFile++]; + if (act != null && act.toString) + verify(act.toString(), exp, msg); + else + verify(act, exp, msg); + } + } + } + + BaselineTestCase.loadBaseline = function (fileName) { + var loadSuccess = true; + var oXmlHttp = new XMLHttpRequest(); + oXmlHttp.OnReadyStateChange = function () { + if (oXmlHttp.readyState == 4) { + //XmlHttpRequest will return 0 if we request a file from local disk + //Return value 200 on succees only if we request a file from web server + if (oXmlHttp.status == 200 || window.location.href.indexOf("http") == -1) { + if (fileName != null) { + if (document.getElementById("blScriptTag") == null) { + var header = document.getElementsByTagName('HEAD').item(0); + var oScript = document.createElement("script"); + oScript.language = "javascript"; + oScript.type = "text/javascript"; + oScript.defer = true; + oScript.id = "blScriptTag"; + oScript.text = oXmlHttp.responseText; + header.appendChild(oScript); + } + else { + //Get response text and strip the "var baselineExpArr =" then assign the new value to baselineExpArr + var a = oXmlHttp.responseText; + baselineExpArr = eval(a.substring(a.indexOf("=") + 1, a.length - 2)); + } + } + } + else { + loadSuccess = false; + } + } + } + oXmlHttp.open('GET', fileName, false); + oXmlHttp.send(null); + + if (!loadSuccess) throw new Error('XML request error for file ' + fileName + ': ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')'); + } + + BaselineTestCase.startBaseline = function () { + if (BaselineTestCase.useGlobalBaseline) + return; + + //Set baseline file name based on the fileName from Run method and test id + //This block will be executed we are not using global baseline + if (currentTest instanceof BaselineTestCase) { + + //Create baseline file if baselineEditMode is true, otherwise load the js file + if (BaselineTestCase.editMode) { + BaselineTestCase.openBaseline(BaselineTestCase.constructBLFileName(currentTest.id)); + } + else { + BaselineTestCase.loadBaseline(BaselineTestCase.constructBLFileName(currentTest.id)); + } + } + } + + BaselineTestCase.stopBaseline = function () { + if (BaselineTestCase.useGlobalBaseline) + return; + + if (currentTest instanceof BaselineTestCase) { + //Close the baseline file + BaselineTestCase.closeBaseline(); + } + + BaselineTestCase.currentTestcaseID++; + } + + BaselineTestCase.startGlobalBaseline = function () { + if (!BaselineTestCase.useGlobalBaseline) + return; + + try { + //Open baseline file if we use global baseline (one baseline for all test) + if (BaselineTestCase.editMode) { + BaselineTestCase.openBaseline(BaselineTestCase.constructBLFileName()); + } + + //load the global baseline file + else { + BaselineTestCase.loadBaseline(BaselineTestCase.constructBLFileName()); + } + } + catch (e) { + fail("Exception occured on opening baseline file with message: " + e.message); + } + } + + BaselineTestCase.stopGlobalBaseline = function () { + if (!BaselineTestCase.useGlobalBaseline) + return; + + try { + //Close the baseline file (only if we use global baseline file) + BaselineTestCase.closeBaseline(); + } + catch (e) { + fail("Exception occured on closing baseline file with message: " + e.message); + } + } + + // Subscribe to various events to set up baseline. + runner.subscribe('start', BaselineTestCase.startGlobalBaseline); + runner.subscribe('end', BaselineTestCase.stopGlobalBaseline); + + /* + * This object facilitates testing cross-context scenarios. Create a new instance first to getcd + * started. See http://devdiv/sites/bpt/team/eze/Eze%20Wiki/Cross%20Context%20Test%20Framework.aspx + * for documentation. + */ + var CrossContextTest = function () { + var cct = this; + cct.testIframe = true; + cct.testWindow = (Utils.getHOSTType() == Utils.WWAHOST) ? false : true; + + var waitingForReady = false; + var readyCallbacks = []; + var childFunctions = []; + var childProperties = {}; + + /* + * Wait for the document to be ready for DOM updates. + * This should be updated to use DOMContentReady when that is supported. + */ + function onReady(callback) { + // Check if we're already ready, and if so, just call the callback. + if (document.readyState == "complete") { + callback.call(); + return; + } + + // Otherwise, push to our ready list and start waiting. + readyCallbacks.push(callback); + + // If we're not already waiting, wait for document ready and call callbacks. + if (!waitingForReady) { + waitingForReady = true; + waitForReady(); + } + } + + /* + * Wait until doScroll doesn't throw an exception, and then run all the callbacks. + */ + function waitForReady() { + // Otherwise use the oft-used doScroll hack which, according to MSDN, will always thrown an + // error unless the document is ready. + try { + document.documentElement.doScroll("left"); + + } + catch (e) { + // We're not ready yet. Check again in a bit. + setTimeout(waitForReady, 1); + return; + } + + // Assume we're ready, so call all the callbacks. + for (var i = 0; i < readyCallbacks.length; i++) { + readyCallbacks[i].call(); + } + } + + /* + * Sets any properties on the newly opened window. + * be careful -- this only reference obj from parent context + */ + function setChildWindowProperties(win) { + for (var prop in childProperties) { + win[prop] = childProperties[prop]; + } + } + + /* + * Writes the HTML to the child window by appending HTML tag. Used for WWA + * * parentWindow, that refers to either window.parent or window.opener, depending on if this is a + * popup window or an iframe, + * Append html, head, body and script content to iframe/popup window + */ + + function AppendChildHtml(doc) { + + function createChildTag(tagName, attributes) { + tag = doc.createElement(tagName); + for (var k in attributes) { + tag.setAttribute(k, attributes[k]); + } + return tag; + } + + doc.open(); + doc.write(" "); + doc.close(); + + var head = doc.getElementsByTagName("head")[0]; + + var meta1 = createChildTag('meta', { + 'http-equiv': '"X-UA-Compatible"', + 'content': '"IE=' + document.documentMode + '"' + } + ); + head.appendChild(meta1); + + // add the body content + if (typeof cct.childContent !== 'undefined') { + var body = doc.getElementsByTagName("body")[0]; + body.innerHTML = cct.childContent; + } + + // this function will be sent to child, be called when the script load complete + function __init__(sender) { + parentWindow = window.parent == window ? window.opener : window.parent; + if (sender.readyState == "complete") { + sender.onreadystatechange = null; + parentWindow.childReady.ready(); // notify parent to start test + } + } + + // init the script + var script = createChildTag('script', { + type: 'text/javascript', + onreadystatechange: "iamready(this)" + } + ); + script.text += __init__.toString() + "\n" + "var iamready = __init__;\n"; + // write the childFunctions + for (var i = 0; i < childFunctions.length; i++) { + script.text += childFunctions[i].toString() + "\n"; + } + head.appendChild(script); + } + + /* + * Writes html content to doc. Used for IE + * * parentWindow, that refers to either window.parent or window.opener, depending on if this is a + * popup window or an iframe, + */ + + function WriteChildHtml(doc) { + doc.open(); + doc.write("\n\n"); + doc.write("\n"); + doc.write("\n"); + doc.write("var parentWindow = window.parent == window ? window.opener : window.parent;\n"); + + //// function waitForReady will sent to child, to determin if child is ready + function waitForReady() { + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", whenDomReady, false); + } else { + setTimeout(waitForDomReady, 1); + } + function waitForDomReady() { + // Otherwise use the oft-used doScroll hack which, according to MSDN, will always thrown an + // error unless the document is ready. + try { + document.documentElement.doScroll("left"); + } + catch (e) { + // We're not ready yet. Check again in a bit. + setTimeout(waitForDomReady, 1); + return; + } + whenDomReady(); + } + function whenDomReady() { + parentWindow.childReady.ready(); + } + } + ////// + + doc.write(waitForReady.toString() + "\n"); + doc.write("waitForReady();\n"); + + for (var i = 0; i < childFunctions.length; i++) { + doc.write(childFunctions[i].toString() + "\n"); + } + + doc.write("\n \n\n"); + + if (cct.childContent !== undefined) doc.write(cct.childContent) + + doc.write(""); + doc.close(); + } + + var writeChildContent = (Utils.getHOSTType() == Utils.WWAHOST) ? AppendChildHtml : WriteChildHtml; + + // Set either of these to control the content of the child windows. + this.childContent = undefined; + + /* + * If we have Loader42Log, use that to log comments, otherwise use a nop. This is merely a + * default and can be overridden by users should they need custom logging. + */ + if (typeof window.logger !== 'undefined') { + this.comment = logger.comment; + } else { + this.comment = new Function(); + } + + /* + * Add a function to the list of functions to append to the child windows. + * NOTE: While when you define these functions they look like closures, they are not used as such. + * The toString representation is appended to the new windows, destroying any scope you might have + * set up. + */ + this.addChildFunction = function (func) { + childFunctions.push(func); + } + + /* + * Add a property to the list of properties to give to the child windows. + */ + this.addChildProperty = function (name, value) { + childProperties[name] = value; + } + + function ChildReady(waitHandle) { + this.waitHandle = waitHandle; + this.ready = function () { + runner.start(this.waitHandle); + } + } + + var prepareIframe = function () { + cct.comment("Starting iframe tests"); + cct.frame = document.createElement('iframe'); + document.body.appendChild(cct.frame); + var waitHandle = runner.wait(); + window.childReady = new ChildReady(waitHandle); + writeChildContent(cct.frame.contentDocument ? cct.frame.contentDocument : cct.frame.contentWindow.document); + } + + var runIframe = function () { + if (typeof BaselineTestCase !== 'undefined' && typeof mutator !== 'undefined') { + // save the check point for window.open + cct.baselineTCElementIndexInFile = BaselineTestCase.elementIndexInFile; + } + setChildWindowProperties(cct.frame.contentWindow); + try { + cct.callback.call(undefined, cct.frame.contentWindow); + } + catch (e) { + currentTest.error = e; + } + } + + var cleanupIframe = function () { + window.childReady = undefined; + // Remove the iframe when we're done so it doesn't interfere with any other tests. + document.body.removeChild(cct.frame); + } + + var preparePopWin = function () { + cct.comment("Starting new window tests"); + cct.win = window.open(); + var waitHandle = runner.wait(); + window.childReady = new ChildReady(waitHandle); + writeChildContent(cct.win.document); + } + + var runPopWin = function () { + if (typeof BaselineTestCase !== 'undefined' && typeof mutator !== 'undefined') { + // save the check point for window.open + BaselineTestCase.elementIndexInFile = cct.baselineTCElementIndexInFile; + } + setChildWindowProperties(cct.win); + try { + cct.callback.call(undefined, cct.win); + } + catch (e) { + currentTest.error = e; + } + } + + var cleanupPopWin = function () { + window.childReady = undefined; + cct.win.close(); + window.parentWaitHandle = undefined; + + // make sure the popup window is closed and then move on + var waitHandle = runner.wait(); + setTimeout(function () { + try { + if (!cct.win.closed) { + setTimeout(arguments.callee, 10); + return; + } + } catch (e) { } + runner.start(waitHandle); + }, 10); + } + + /* + * Run a test. Do any verification in the callback. The callback will be called with the new + * window as the first parameter. If you want to wait for the document to be ready, pass true + * for the second parameter. You should note that this will currently break our test framework + * as nothing in the test function body can be asyncrhonous. When a test is asynchronous, the + * test will pass (with no verify statements executed) while the verifies wait in the + * background for the document to become ready. + */ + this.test = function (callback, waitForReady) { + + var run = function () { + + cct.callback = callback; + + if (cct.testWindow) { + // Test with window.open + scheduler.prepend([preparePopWin, runPopWin, cleanupPopWin]); + } + + if (cct.testIframe) { + // Test with iframe. + scheduler.prepend([prepareIframe, runIframe, cleanupIframe]); + } + }; + + if (waitForReady) onReady(run); + else run(); + } + } + + //Mutator support + var mutator; + + // env will be the empty string if the environment variable is not present. + var env = ""; + try { + env = (createActiveXObject("WScript.Shell")).Environment("Process")("JScript_Mutators"); + + if (env.length > 0) + runner.mutators = env.split(","); + } catch (e) { } + + + (function () { + var originalTest; + + runner.subscribe('testStart', function (test) { + if (runner.mutators.length === 0) + return; + + try { + if (typeof mutator === "undefined") + mutator = createActiveXObject("MutatorsHost.MutatorsActiveX"); + + runner.publish('comment', "Mutating with " + runner.mutators.join(",")); + + originalTest = test.test; + originalTestString = originalTest.toString(); + + // Parse out just the function body to mutate. + testBody = originalTestString.substring(originalTestString.indexOf("{") + 1, + originalTestString.lastIndexOf("}")); + + newTestString = mutator.Mutate(runner.mutators.join(","), testBody); + + // Create a new function with the mutated function body. Note that this destroys the + // lexical environment of the original test function, so test code that relies on + // identifiers declared outside of the function body but not in global scope will fail. + test.test = new Function(newTestString); + + runner.publish('comment', "
" + test.test.toString() + "
"); + } catch (e) { } + }) + + /* Restore the original test object after the test has finished. + */ + runner.subscribe('testEnd', function (test) { + if (runner.mutators.length === 0) + return; + + test.test = originalTest; + }) + + })() + + /**************************************************************************** + //Function for stress testing that determines if the test group should loop again + //Currently not working as scheduler does not call this function. + //Need to change the stressLogger to StressLogger.shouldEnable() + ****************************************************************************/ + function ContinueTestGroupIteration() { + + if (typeof stressLogger !== "undefined") { + stressLogger.CurrentTestGroupIteration++; + + if (stressLogger.Config.RuntimeIterations < stressLogger.CurrentTestGroupIteration) { + return false; + } + + return true; + } + + return false; + } + + /////////////////////////////////////////////////////////////////////////////// + // Verification functions. + /////////////////////////////////////////////////////////////////////////////// + //the wrapping function is really just to allow folding in editors + var verify = (function () { + function verify(act, exp, msg) { + var result = equiv(act, exp); + testPassed = testPassed && result; + runner.publish('verify', currentTest, result, act, exp, msg); + } + verify.equal = verify; + verify.notEqual = function (act, exp, msg) { + var result = !equiv(act, exp); + testPassed = testPassed && result; + runner.publish('verify', currentTest, result, act, "not " + exp, msg); + }; + + verify.strictEqual = function (act, exp, msg) { + var result = act === exp; + testPassed = testPassed && result; + runner.publish('verify', currentTest, result, act, exp, msg); + } + + verify.notStrictEqual = function (act, exp, msg) { + var result = act !== exp; + testPassed = testPassed && result; + runner.publish('verify', currentTest, result, act, "not " + exp, msg); + } + + verify.noException = function (callback, msg) { + try { + var res = callback(); + verify.equal(res, res, msg); + } catch (e) { + verify.equal(res, "no exception", msg); + } + }; + + verify.exception = function (callback, exception, msg) { + try { + var res = callback(); + verify.equal(res, "exception", msg); + } catch (e) { + verify.instanceOf(e, exception) + } + }; + + verify.defined = function (obj, msg) { + return verify.notStrictEqual(obj, undefined, msg + " should be defined"); + }; + + verify.notDefined = function (obj, msg) { + return verify.strictEqual(obj, undefined, msg + " should not be defined"); + }; + + verify.typeOf = function (obj, expected) { + return verify.equal(typeof obj, expected, "typeof " + obj); + }; + + verify.instanceOf = function (obj, expected) { + return verify.equal(obj instanceof expected, true, "instanceof " + obj); + }; + + return verify; + })(); + + function fail(msg) { + verify(true, false, msg); + } + + function assert(condition, msg) { + verify(condition, true, msg); + } + + /////////////////////////////////////////////////////////////////////////////// + // Below here is code to support the old test case format. These old test + // cases call a series of ap* functions directly. The object below basically + // proxies calls to ap* functions to the appropriate logger. Because the old + // test case format doesn't instantiate a test case object, the code below + // creates a stub to pass to the loggers. + /////////////////////////////////////////////////////////////////////////////// + + function OldGlueProxy() { + var test; + var testStarted = false; + var scenarioStarted = false; + var scenarioPassed = true; + + function finishScenario() { + if (scenarioPassed) + runner.publish('pass', test); + else + runner.publish('fail', test); + } + + this.apInitTest = function (name) { + if (window.runsExecuted == 0) { // In MaxInterprentCount case do it only on first iteration + if (testStarted) + runner.publish('end'); + + testStarted = true; + + if (typeof Loader42_FileName == 'undefined') + runner.publish('start', name); + else + runner.publish('start', Loader42_FileName); + } + } + + this.apInitScenario = function (name) { + if (scenarioStarted) + finishScenario(); + + var id = /^\d+/.exec(name); + if (id === null) + id = ""; + + test = { id: id, desc: name, test: Function() }; + + runner.publish('testStart', test); + scenarioStarted = true; + scenarioPassed = true; + } + + this.apEndScenario = function () { + finishScenario(); + scenarioStarted = false; + } + + this.apLogFailInfo = function (msg, exp, act) { + runner.publish('verify', test, false, act, exp, msg); + scenarioPassed = false; + } + + this.apEndTest = function () { + if (window.runsExecuted >= window.runsToExecute - 1) { // In MaxInterprentCount case do it only on last iteration + if (scenarioStarted) + finishScenario(); + runner.publish('end'); + testStarted = false; + } + } + + this.apWriteDebug = function (msg) { + runner.publish('comment', msg); + } + + this.VBS_apglobal_init = function () { + try { + window.apGlobalObj = createActiveXObject("apgloballib.apglobal"); + } catch (e) { + // Can't create ActiveX, so make a stub object with the same methods. + window.apGlobalObj = { + apInitTest: apInitTest, + apInitScenario: apInitScenario, + apLogFailInfo: apLogFailInfo, + apEndScenario: apEndScenario, + apEndTest: apEndTest, + apGetLangExt: function (Lcid) { + var LangExt = ""; + switch (apGlobalObj.apPrimaryLang(Lcid)) { + case 0x9: + LangExt = "EN"; // LANG_ENGLISH + break; + case 0xC: + LangExt = "FR"; // LANG_FRENCH + break; + case 0xA: + LangExt = "ES"; // LANG_SPANISH + break; + case 0x7: + LangExt = "DE"; // LANG_GERMAN + break; + case 0x10: + LangExt = "IT"; // LANG_ITALIAN + break; + case 0x16: + LangExt = "PT"; // LANG_PORTUGUESE + break; + case 0x1D: + LangExt = "SV"; // LANG_SWEDISH + break; + case 0x14: + LangExt = "NO"; // LANG_NORWEGIAN + break; + } + if (LangExt == "") { //Hack for DBCS + switch (Lcid) { + case 0x411: + LangExt = "JP"; // LANG_JAPANESE + break; + case 0x412: + LangExt = "KO"; // LANG_KOREAN + break; + case 0x404: + LangExt = "CHT"; // Chinese + break; + case 0x804: + LangExt = "CHS"; // PRC + break; + } + } + return LangExt; + }, + apPrimaryLang: function (Lcid) { + return Lcid & 0x3FF; + }, + apGetLocFormatDate: function (Lcid, Fmt) { + var strToken = ""; + switch (Fmt) // am not sure whenstr.toUpper will be supported.. + { + case "LONG DATE": + case "long date": + strToken = "datelong"; + break; + case "MEDIUM DATE": + case "medium date": + strToken = "datemed"; + break; + case "SHORT DATE": + case "short date": + strToken = "dateshort"; + break; + case "GENERAL DATE": + case "general date": + strToken = "gendate"; + break; + case "LONG TIME": + case "long time": + strToken = "timelong"; + break; + case "MEDIUM TIME": + case "medium time": + strToken = "timemed"; + break; + case "SHORT TIME": + case "short time": + strToken = "timeshort"; + break; + } + return apGlobalObj.apGetToken(Lcid, "fmt_named_" + strToken, "OLB"); + }, + apGetToken: function (Lcid, strTokenName, strFileName) { + // not needed right now.. + var strToken = ""; + return (strToken == "") ? "[[Token Not Found]]" : strToken; + }, + apGetLocInfo: function (Lcid, lctype, ProjectOverride, Flags) { }, + apGetOSVer: function () { + //not needed + return "NT;"; + }, + apGetPathName: function () { + //not needed + apGlobalObj.apLogFailInfo("FAILURE: TC requested function apGetPathName", "", "", ""); + apGlobalObj.apEndTest(); + return ""; + }, + apGetPlatform: function () { + //not needed + return "NT;"; + }, + apGetVolumeName: function (OS) { + //not needed + return "NT;"; + }, + LangHost: function () { + // return GetUserDefaultLCID(); + return 1033; + }, + apGetLocalizedString: function (resID) { + return "No localized string returned"; + }, + apGetLocalizedStringWithSubstitution: function (resID, stringSubstitution) { + return "No localized string returned"; + } + } + } + window.apPlatform = apGlobalObj.apGetPlatform(); + } + + this.apGetLocale = function () { + return apGlobalObj.LangHost(); + } + + } + + /////////////////////////////////////////////////////////////////////////////// + // Object Comparison functions. + /////////////////////////////////////////////////////////////////////////////// + + // Determine what is o. + function hoozit(o) { + if (typeof o === "undefined") { + return "undefined"; + } else if (o === null) { + return "null"; + } else if (o.constructor === String) { + return "string"; + + } else if (o.constructor === Boolean) { + return "boolean"; + + } else if (o.constructor === Number) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + // consider: typeof [] === object + } else if (o instanceof Array) { + return "array"; + + // consider: typeof new Date() === object + } else if (o instanceof Date) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (o instanceof RegExp) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (o instanceof Function) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + // Test for equality any JavaScript type. + // Discussions and reference: http://philrathe.com/articles/equiv + // Test suites: http://philrathe.com/tests/equiv + // Author: Philippe Rath? + var equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if (!(hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if (!innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if (a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if (!innerEquiv(a[i], b[i])) { + eq = false; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties, bProperties); + } + }; + } (); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)); + }; + + return innerEquiv; + + } (); + + /////////////////////////////////////////////////////////////////////////////// + // + // Assign globals to the global object. + // These are underscore prefixed because they are called by functions of the same name outside the + // framework closure. This is done because window.verify will not be overwritten by a + // function verify() {} in global scope, which many old tests depend on. + // + /////////////////////////////////////////////////////////////////////////////// + window._verify = verify; + window._assert = assert; + window._fail = fail; + window.getLocalizedError = Utils.getLocalizedError; + + window.Run = runner.run; + window.runner = runner; + window.TestCase = TestCase; + window.BaselineTestCase = BaselineTestCase; + window.CrossContextTest = CrossContextTest; + window.logger = windowLogger; + // globals for versioning + window.IE7STANDARDSMODE = Utils.IE7STANDARDSMODE; + window.IE8STANDARDSMODE = Utils.IE8STANDARDSMODE; + window.IE9STANDARDSMODE = Utils.IE9STANDARDSMODE; + window.IE10STANDARDSMODE = Utils.IE10STANDARDSMODE; + window.IE11STANDARDSMODE = Utils.IE11STANDARDSMODE; + window.IE12STANDARDSMODE = Utils.IE12STANDARDSMODE; + window.getHOSTMode = Utils.getHOSTMode; + + window.X86MODE = Utils.X86MODE; + window.X64MODE = Utils.X64MODE; + window.ARMMODE = Utils.ARMMODE; + window.getIEArchitecture = Utils.getIEArchitecture; + + window.Utils = Utils; + + // Function returns true/false based on equality, same as verify, but excludes logging. + Utils.equiv = equiv; + + + + // An object to hold various implementational details of our engine that our test cases rely upon + window.Chakra = { + // The amount of properties that have to be declared to transition object from path to + // dictionary type. + PathToDictionaryTransitionThreshold: 16 + } + + // if this is called, set up globals required for old test cases. + window.VBS_apglobal_init = function () { + // Old test cases require conditional compilation in a few cases, so turn it on. + //@cc_on + var proxy = new OldGlueProxy(); + window.apInitTest = proxy.apInitTest; + window.apInitScenario = proxy.apInitScenario; + window.apEndScenario = proxy.apEndScenario; + window.apLogFailInfo = proxy.apLogFailInfo; + window.apEndTest = proxy.apEndTest; + window.apWriteDebug = proxy.apWriteDebug; + window.VBS_apglobal_init = proxy.VBS_apglobal_init; + window.apGetLocale = proxy.apGetLocale; + proxy.VBS_apglobal_init(); + } + + // It used to be defined via 'function VBS_apglobal_init()' + // 'window.apGlobalObj' has to be defined + if (typeof window.apGlobalObj == "undefined") { + try { + // Define the object + window.apGlobalObj = createActiveXObject("apgloballib.apglobal"); + } catch (e) { + // Cannot instantiate ActiveXObject + window.apGlobalObj = null; + } + } + +})(this); + +// Default version of these verification functions simply call the framework version of them. +verify = _verify; +assert = _assert; +fail = _fail; + +// Disable simple print from doing anything +VBS_apglobal_init(); +function ScriptEngineMajorVersion() { return 5; } +function ScriptEngineMinorVersion() { return 8; } +apGlobalObj.osSetIntlValue = function() { return 0; } +apGlobalObj.osGetIntlValue = function() { return 0; } diff --git a/test/Closures/copy-prop-stack-slot.baseline b/test/Closures/copy-prop-stack-slot.baseline new file mode 100644 index 00000000000..1230a3d60ac --- /dev/null +++ b/test/Closures/copy-prop-stack-slot.baseline @@ -0,0 +1,3 @@ +12131415 +0123 +pass diff --git a/test/Closures/copy-prop-stack-slot.js b/test/Closures/copy-prop-stack-slot.js new file mode 100644 index 00000000000..6e02281ecb0 --- /dev/null +++ b/test/Closures/copy-prop-stack-slot.js @@ -0,0 +1,21 @@ +WScript.LoadScriptFile('copy-prop-stack-slot-test-framework.js'); +var tc=new TestCase(); +tc.id="38"; +tc.desc="Enumerate arguments"; +tc.test=function(){ + var actualContent = ""; + var actualIndex = ""; + + function testArgument(){ + for(var a in arguments){ + actualContent += arguments[a]; + actualIndex += a; + } + } + + testArgument(12,13,14,15); + WScript.Echo(actualContent); + WScript.Echo(actualIndex); +} +tc.AddTest(); +Run(); diff --git a/test/Closures/rlexe.xml b/test/Closures/rlexe.xml index 0123de5c6f3..7afd75afb8e 100644 --- a/test/Closures/rlexe.xml +++ b/test/Closures/rlexe.xml @@ -175,4 +175,10 @@ BugFix + + + copy-prop-stack-slot.js + copy-prop-stack-slot.baseline + + From 129ebe0e1c90b99b39d73ab17cacda7d8842f7c1 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Tue, 8 Aug 2017 16:55:42 -0700 Subject: [PATCH 4/6] Add copyright header to test files --- test/Closures/copy-prop-stack-slot-test-framework.js | 7 ++++++- test/Closures/copy-prop-stack-slot.js | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/Closures/copy-prop-stack-slot-test-framework.js b/test/Closures/copy-prop-stack-slot-test-framework.js index 3df41abacf2..57bdab464dd 100644 --- a/test/Closures/copy-prop-stack-slot-test-framework.js +++ b/test/Closures/copy-prop-stack-slot-test-framework.js @@ -1,4 +1,9 @@ -(function (window) { +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +(function (window) { /////////////////////////////////////////////////////////////////////////////// // Utils object for helper functions /////////////////////////////////////////////////////////////////////////////// diff --git a/test/Closures/copy-prop-stack-slot.js b/test/Closures/copy-prop-stack-slot.js index 6e02281ecb0..f866d9f1e69 100644 --- a/test/Closures/copy-prop-stack-slot.js +++ b/test/Closures/copy-prop-stack-slot.js @@ -1,4 +1,9 @@ -WScript.LoadScriptFile('copy-prop-stack-slot-test-framework.js'); +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile('copy-prop-stack-slot-test-framework.js'); var tc=new TestCase(); tc.id="38"; tc.desc="Enumerate arguments"; From fd03ee7d52fcd5aa5f685c97194a67e5519998bb Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Tue, 8 Aug 2017 17:12:24 -0700 Subject: [PATCH 5/6] Clean up whitespace, particularly leading tabs --- lib/Backend/GlobOpt.cpp | 2 +- .../copy-prop-stack-slot-test-framework.js | 4 ++-- test/Closures/copy-prop-stack-slot.js | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index 06fdb1ef3a8..ce805079dc0 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -360,7 +360,7 @@ GlobOpt::ForwardPass() this->byteCodeConstantValueNumbersBv = JitAnew(this->alloc, BVSparse, this->alloc); this->tempBv = JitAnew(this->alloc, BVSparse, this->alloc); this->prePassCopyPropSym = JitAnew(this->alloc, BVSparse, this->alloc); - this->slotSyms = JitAnew(this->alloc, BVSparse, this->alloc); + this->slotSyms = JitAnew(this->alloc, BVSparse, this->alloc); this->byteCodeUses = nullptr; this->propertySymUse = nullptr; diff --git a/test/Closures/copy-prop-stack-slot-test-framework.js b/test/Closures/copy-prop-stack-slot-test-framework.js index 57bdab464dd..28ffb566869 100644 --- a/test/Closures/copy-prop-stack-slot-test-framework.js +++ b/test/Closures/copy-prop-stack-slot-test-framework.js @@ -682,8 +682,8 @@ if ((passes + failures) === 0) { document.write("

No tests were run! There's probably a SyntaxError in the test file.

") } - - // Added for XBox team to parse out pass/fail from page title + + // Added for XBox team to parse out pass/fail from page title document.title = 'PASS:' + passes + ';FAIL:' + failures + diff --git a/test/Closures/copy-prop-stack-slot.js b/test/Closures/copy-prop-stack-slot.js index f866d9f1e69..736bdf8f3be 100644 --- a/test/Closures/copy-prop-stack-slot.js +++ b/test/Closures/copy-prop-stack-slot.js @@ -7,20 +7,20 @@ WScript.LoadScriptFile('copy-prop-stack-slot-test-framework.js'); var tc=new TestCase(); tc.id="38"; tc.desc="Enumerate arguments"; -tc.test=function(){ +tc.test=function(){ var actualContent = ""; - var actualIndex = ""; + var actualIndex = ""; - function testArgument(){ - for(var a in arguments){ + function testArgument() { + for (var a in arguments) { actualContent += arguments[a]; - actualIndex += a; - } - } + actualIndex += a; + } + } - testArgument(12,13,14,15); - WScript.Echo(actualContent); - WScript.Echo(actualIndex); + testArgument(12,13,14,15); + WScript.Echo(actualContent); + WScript.Echo(actualIndex); } tc.AddTest(); Run(); From e050ec27c3138df3e6faab8082a9bc4157eda7e5 Mon Sep 17 00:00:00 2001 From: Richard Cobbe Date: Mon, 14 Aug 2017 16:57:35 -0700 Subject: [PATCH 6/6] Remove superfluous break statement --- lib/Backend/GlobOpt.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index ce805079dc0..823e1acf44c 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -5180,7 +5180,6 @@ GlobOpt::ValueNumberDst(IR::Instr **pInstr, Value *src1Val, Value *src2Val) case Js::OpCode::Typeof: return this->NewGenericValue(ValueType::String, dst); - break; case Js::OpCode::InitLocalClosure: Assert(instr->GetDst()); Assert(instr->GetDst()->IsRegOpnd());