Chromium Code Reviews| Index: client/testing/dartest/dartest.dart |
| =================================================================== |
| --- client/testing/dartest/dartest.dart (revision 0) |
| +++ client/testing/dartest/dartest.dart (revision 0) |
| @@ -0,0 +1,496 @@ |
| +// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +#library('dartest'); |
| + |
| +#import('dart:dom'); |
| +#import('../unittest/unittest_dartest.dart'); |
| + |
| +#source('css.dart'); |
| + |
| +/** DARTest provides a library to run tests in the app. */ |
| +class DARTest{ |
| + Map<String, HTMLElement> _inAppElements, _fullAppElements, _appElements; |
| + DOMWindow _runnerWindow; |
| + |
| + DARTest() { |
| + _runnerWindow = window; |
| + dartestLogger = _log; |
| + _inAppElements = new HashMap<String, HTMLElement>(); |
| + _appElements = _inAppElements; |
| + DARTestCss.inject(document, true); |
| + } |
| + |
| + void run() { |
| + _renderMain(); |
| + _createResultsTable(); |
| + } |
| + |
| + void _log(String message) { |
| + _runnerWindow.console.log(message); |
| + } |
| + |
| + void _addChildToElem(String key, HTMLElement child) { |
| + if(_appElements[key] != null) { |
| + _appElements[key].appendChild(child); |
| + } |
| + } |
| + |
| + /** Create the results table after loading tests. */ |
| + void _createResultsTable() { |
| + _log('Creating results table'); |
| + HTMLTableElement table = _runnerWindow.document.createElement('table'); |
| + table.className = 'dt-results'; |
| + HTMLTableSectionElement head = _runnerWindow.document.createElement('thead'); |
| + head.innerHTML = '<tr><th>ID <th>Description <th>Result'; |
| + table.appendChild(head); |
| + |
| + HTMLTableSectionElement body = _runnerWindow.document.createElement('tbody'); |
| + body.id = 'dt-results-body'; |
| + tests.forEach((final t) { |
| + HTMLTableRowElement testDetailRow = _runnerWindow.document.createElement('tr'); |
| + testDetailRow.id = 'dt-test-${t.id}'; |
| + _addTestDetails(t, testDetailRow); |
| + body.appendChild(testDetailRow); |
| + |
| + HTMLTableRowElement testMessageRow = _runnerWindow.document.createElement('tr'); |
| + testMessageRow.id = 'dt-detail-${t.id}'; |
| + testMessageRow.className = 'dt-hide'; |
| + body.appendChild(testMessageRow); |
| + }); |
| + |
| + table.appendChild(body); |
| + _addChildToElem('testBody', table); |
| + } |
| + |
| + /** Update the results table for test. */ |
| + void _updateResultsTable(TestCase t, DOMWindow domWin) { |
| + HTMLTableRowElement row = domWin.document.getElementById('dt-test-${t.id}'); |
| + row.className = 'dt-result-row'; |
| + row.innerHTML = ''; // Remove all children as we will re-populate them |
| + _addTestDetails(t, row); |
| + |
| + HTMLTableRowElement details = |
| + domWin.document.getElementById('dt-detail-${t.id}'); |
| + details.appendChild(_getTestStats(t, domWin)); |
| + |
| + row.addEventListener('click', (Event e) { |
| + if(details.className == 'dt-hide') { |
| + details.className = ''; |
| + } else { |
| + details.className = 'dt-hide'; |
| + } |
| + }, true); |
| + } |
| + |
| + /** Escape HTML special chars. */ |
| + String _escape(String str) { |
| + str = str.replaceAll('&','&'); |
| + str = str.replaceAll('<','<'); |
| + str = str.replaceAll('>','>'); |
| + str = str.replaceAll('"','"'); |
| + str = str.replaceAll("'",'''); |
| + str = str.replaceAll('/','/'); |
| + return str; |
| + } |
| + |
| + /** Get test results as table cells. */ |
| + void _addTestDetails(TestCase t, HTMLTableRowElement row) { |
| + |
|
pdr
2011/12/16 17:00:50
Extra space
shauvik
2011/12/16 17:57:06
Done.
|
| + HTMLTableCellElement testId = _runnerWindow.document.createElement('td'); |
| + testId.textContent = t.id; |
| + row.appendChild(testId); |
| + |
| + HTMLTableCellElement testDesc = _runnerWindow.document.createElement('td'); |
| + testDesc.textContent = t.description; |
| + row.appendChild(testDesc); |
| + |
| + HTMLTableCellElement testResult = _runnerWindow.document.createElement('td'); |
| + String result = (t.result == null) ? 'none' : _escape(t.result); |
| + testResult.className = 'dt-$result'; |
| + testResult.title = '${_escape(t.message)}'; |
| + testResult.textContent = result.toUpperCase(); |
| + row.appendChild(testResult); |
| + } |
| + |
| + HTMLTableCellElement _getTestStats(TestCase t, DOMWindow domWin) { |
| + HTMLTableCellElement tableCell = domWin.document.createElement('td'); |
| + tableCell.colSpan = 3; |
| + |
| + if(t.message != '') { |
| + HTMLSpanElement messageSpan = domWin.document.createElement('span'); |
| + messageSpan.textContent = t.message; |
| + tableCell.appendChild(messageSpan); |
| + tableCell.appendChild(domWin.document.createElement('br')); |
| + } |
| + if(t.stackTrace != null) { |
| + HTMLPreElement stackTacePre = domWin.document.createElement('pre'); |
| + stackTacePre.textContent = t.stackTrace; |
| + } |
| + |
| + HTMLSpanElement durationSpan = domWin.document.createElement('span'); |
| + durationSpan.textContent = 'took ${_printDuration(t.runningTime)}'; |
| + tableCell.appendChild(durationSpan); |
| + |
| + return tableCell; |
| + } |
| + |
| + /** Update the UI after running test. */ |
| + void _updateDARTestUI(TestCase test) { |
| + _updateResultsTable(test, window); |
| + if(_runnerWindow != window) { |
| + _updateResultsTable(test, _runnerWindow); |
| + } |
| + |
| + if(test.result != null) { |
| + _log(' Result: ${test.result.toUpperCase()} ${test.message}'); |
| + } |
| + if(test.runningTime != null) { |
| + _log(' took ${_printDuration(test.runningTime)}'); |
| + } |
| + _updateStatusProgress(_appElements); |
| + if(_runnerWindow != window) { |
| + _updateStatusProgress(_inAppElements); |
| + } |
| + } |
| + |
| + void _updateStatusProgress(Map<String, HTMLElement> elements) { |
| + // Update progressbar |
| + var pPass = |
| + ((testsRun - testsFailed - testsErrors) / tests.length) * 100; |
| + elements['green'].setAttribute('style', 'width:$pPass%'); |
| + var pFailed = pPass + (testsFailed / tests.length) * 100; |
| + elements['red'].setAttribute('style', 'width:$pFailed%'); |
| + var pErrors = pFailed + (testsErrors / tests.length) * 100; |
| + elements['orange'].setAttribute('style', 'width:$pErrors%'); |
| + |
| + // Update status |
| + elements['testsRunElem'].textContent = testsRun.toString(); |
| + elements['testsFailedElem'].textContent = testsFailed.toString(); |
| + elements['testsErrorsElem'].textContent = testsErrors.toString(); |
| + } |
| + |
| + String _printDuration(Duration timeDuration) { |
| + StringBuffer out = new StringBuffer(); |
| + if(timeDuration.inDays > 0) { |
| + out.add('${timeDuration.inDays} days '); |
| + } |
| + if(timeDuration.inHours > 0) { |
| + out.add('${timeDuration.inHours} hrs '); |
| + } |
| + if(timeDuration.inMinutes > 0) { |
| + out.add('${timeDuration.inMinutes} mins '); |
| + } |
| + if(timeDuration.inSeconds > 0) { |
| + out.add('${timeDuration.inSeconds} s '); |
| + } |
| + if(timeDuration.inMilliseconds > 0 || out.length == 0) { |
| + out.add('${timeDuration.inMilliseconds} ms'); |
| + } |
| + return out.toString(); |
| + } |
| + |
| + /** Populates the floating div with controls and toolbar. */ |
| + HTMLDivElement _renderMain() { |
| + HTMLDivElement containerDiv = _runnerWindow.document.createElement('div'); |
| + containerDiv.className = 'dt-container'; |
| + _appElements['containerDiv'] = containerDiv; |
| + |
| + // Add the test controls |
| + HTMLDivElement mainElem = _runnerWindow.document.createElement('div'); |
| + mainElem.className = 'dt-main'; |
| + _appElements['mainElem'] = mainElem; |
| + |
| + _showTestControls(); |
| + |
| + // Create header to hold window controls |
| + if(_runnerWindow == window) { |
| + HTMLDivElement headDiv = _runnerWindow.document.createElement('div'); |
| + headDiv.className = 'dt-header'; |
| + headDiv.innerHTML = '<b>DARTest: In-App View</b>'; |
|
pdr
2011/12/16 17:00:50
I think you should use font-weight: bold in your C
|
| + HTMLImageElement close = _runnerWindow.document.createElement('img'); |
| + close.className = 'dt-header-close'; |
| + close.addEventListener('click', (Event) { |
| + containerDiv.className = 'dt-hide'; |
| + }, true); |
| + HTMLImageElement pop = _runnerWindow.document.createElement('img'); |
| + pop.className = 'dt-header-pop'; |
| + pop.addEventListener('click', (Event) => dartestMaximize(), true); |
| + HTMLImageElement minMax = _runnerWindow.document.createElement('img'); |
| + minMax.className = 'dt-header-min'; |
| + minMax.addEventListener('click', (Event) { |
| + if (mainElem.classList.contains('dt-hide')) { |
| + mainElem.classList.remove('dt-hide'); |
| + mainElem.classList.add('dt-show'); |
| + minMax.className = 'dt-header-min'; |
| + } else { |
| + if (mainElem.classList.contains('dt-show')) { |
| + mainElem.classList.remove('dt-show'); |
| + } |
| + mainElem.classList.add('dt-hide'); |
| + minMax.className = 'dt-header-max'; |
| + } |
| + }, true); |
| + headDiv.appendChild(close); |
| + headDiv.appendChild(pop); |
| + headDiv.appendChild(minMax); |
| + |
| + containerDiv.appendChild(headDiv); |
| + } |
| + |
| + HTMLDivElement tabDiv = _runnerWindow.document.createElement('div'); |
| + tabDiv.className = 'dt-tab'; |
| + HTMLUListElement tabList = _runnerWindow.document.createElement('ul'); |
| + HTMLLIElement testingTab = _runnerWindow.document.createElement('li'); |
| + HTMLLIElement coverageTab = _runnerWindow.document.createElement('li'); |
| + testingTab.className = 'dt-tab-selected'; |
| + testingTab.textContent = 'Testing'; |
| + testingTab.addEventListener('click', (Event) { |
| + _showTestControls(); |
| + _changeTabs(testingTab, coverageTab); |
| + }, true); |
| + tabList.appendChild(testingTab); |
| + coverageTab.textContent = 'Coverage'; |
| + coverageTab.addEventListener('click', (Event) { |
| + _showCoverageControls(); |
| + _changeTabs(coverageTab, testingTab); |
| + }, true); |
| + tabList.appendChild(coverageTab); |
| + tabDiv.appendChild(tabList); |
| + containerDiv.appendChild(tabDiv); |
| + |
| + if(_runnerWindow != window) { |
| + HTMLDivElement popIn = _runnerWindow.document.createElement('div'); |
| + popIn.className = 'dt-minimize'; |
| + popIn.innerHTML = 'Pop In ⇲'; |
| + popIn.addEventListener('click', (Event) => dartestMinimize(), true); |
| + containerDiv.appendChild(popIn); |
| + } |
| + |
| + containerDiv.appendChild(mainElem); |
| + _runnerWindow.document.body.appendChild(containerDiv); |
| + } |
| + |
| + void _changeTabs(HTMLLIElement clickedTab, HTMLLIElement oldTab) { |
| + oldTab.className = ''; |
| + clickedTab.className = 'dt-tab-selected'; |
| + } |
| + |
| + void _showTestControls() { |
| + HTMLDivElement testBody = _appElements['testBody']; |
| + if(testBody == null) { |
| + testBody = _runnerWindow.document.createElement('div'); |
| + _appElements['testBody'] = testBody; |
| + |
| + // Create a toolbar to hold action buttons |
| + HTMLDivElement toolDiv = _runnerWindow.document.createElement('div'); |
| + toolDiv.className = 'dt-toolbar'; |
| + HTMLButtonElement runBtn = _runnerWindow.document.createElement('button'); |
| + runBtn.innerHTML = '►'; |
| + runBtn.title = 'Run Tests'; |
| + runBtn.className = 'dt-button dt-run'; |
| + runBtn.addEventListener('click', (Event) { |
| + _log('Running tests'); |
| + updateUI = _updateDARTestUI; |
| + runDartests(); |
| + }, true); |
| + toolDiv.appendChild(runBtn); |
| + HTMLButtonElement exportBtn = |
| + _runnerWindow.document.createElement('button'); |
| + exportBtn.innerHTML = '↷'; |
| + exportBtn.title = 'Export Results'; |
| + exportBtn.className = 'dt-button dt-run'; |
| + exportBtn.addEventListener('click', (Event e) { |
| + _log('Exporting results'); |
| + exportTestResults(); |
| + }, true); |
| + toolDiv.appendChild(exportBtn); |
| + testBody.appendChild(toolDiv); |
| + |
| + // Create a datalist element for showing test status |
| + HTMLDListElement statList = _runnerWindow.document.createElement('dl'); |
| + statList.className = 'dt-status'; |
| + HTMLElement runsDt = _runnerWindow.document.createElement('dt'); |
| + runsDt.textContent = 'Runs:'; |
| + statList.appendChild(runsDt); |
| + HTMLElement testsRunElem = _runnerWindow.document.createElement('dd'); |
| + _appElements['testsRunElem'] = testsRunElem; |
| + testsRunElem.textContent = testsRun.toString(); |
| + statList.appendChild(testsRunElem); |
| + |
| + HTMLElement failDt = _runnerWindow.document.createElement('dt'); |
| + failDt.textContent = 'Failed:'; |
| + statList.appendChild(failDt); |
| + HTMLElement testsFailedElem = _runnerWindow.document.createElement('dd'); |
| + _appElements['testsFailedElem'] = testsFailedElem; |
| + testsFailedElem.textContent = testsFailed.toString(); |
| + statList.appendChild(testsFailedElem); |
| + |
| + HTMLElement errDt = _runnerWindow.document.createElement('dt'); |
| + errDt.textContent = 'Errors:'; |
| + statList.appendChild(errDt); |
| + HTMLElement testsErrorsElem = _runnerWindow.document.createElement('dd'); |
| + _appElements['testsErrorsElem'] = testsErrorsElem; |
| + testsErrorsElem.textContent = testsErrors.toString(); |
| + statList.appendChild(testsErrorsElem); |
| + testBody.appendChild(statList); |
| + |
| + // Create progressbar and add red, green, orange bars |
| + HTMLDivElement progressDiv = _runnerWindow.document.createElement('div'); |
| + progressDiv.className = 'dt-progressbar'; |
| + progressDiv.innerHTML = "<span style='width:100%'></span>"; |
| + |
| + HTMLSpanElement orange = _runnerWindow.document.createElement('span'); |
| + _appElements['orange'] = orange; |
| + orange.className = 'orange'; |
| + progressDiv.appendChild(orange); |
| + |
| + HTMLSpanElement red = _runnerWindow.document.createElement('span'); |
| + _appElements['red'] = red; |
| + red.className = 'red'; |
| + progressDiv.appendChild(red); |
| + |
| + HTMLSpanElement green = _runnerWindow.document.createElement('span'); |
| + _appElements['green'] = green; |
| + green.className = 'green'; |
| + |
| + progressDiv.appendChild(green); |
| + testBody.appendChild(progressDiv); |
| + |
| + HTMLDivElement hiddenElem = _runnerWindow.document.createElement('div'); |
| + hiddenElem.className = 'dt-hide'; |
| + hiddenElem.innerHTML = |
| + "<a id='dt-export' download='test_results.csv' href='#' />"; |
| + testBody.appendChild(hiddenElem); |
| + |
| + _addChildToElem('mainElem', testBody); |
| + } |
| + |
| + // Show hide divs |
| + _show('testBody'); |
| + _hide('coverageBody'); |
| + } |
| + |
| + void _showCoverageControls() { |
| + HTMLDivElement coverageBody = _appElements['coverageBody']; |
| + if(coverageBody == null) { |
| + coverageBody = _runnerWindow.document.createElement('div'); |
| + _appElements['coverageBody'] = coverageBody; |
| + |
| + HTMLPreElement covPreElem = _runnerWindow.document.createElement('pre'); |
| + _appElements['covPreElem'] = covPreElem; |
| + coverageBody.appendChild(covPreElem); |
| + |
| + HTMLTableElement covTable = _runnerWindow.document.createElement('table'); |
| + covTable.className = 'dt-results'; |
| + HTMLTableSectionElement head = |
| + _runnerWindow.document.createElement('thead'); |
| + head.innerHTML = '<tr><th>Unit <th>Function <th>Statement <th>Branch'; |
| + covTable.appendChild(head); |
| + HTMLTableSectionElement covTableBody = |
| + _runnerWindow.document.createElement('tbody'); |
| + _appElements['covTableBody'] = covTableBody; |
| + covTableBody.id = 'dt-results-body'; |
| + covTable.appendChild(covTableBody); |
|
pdr
2011/12/16 17:00:50
indentation
shauvik
2011/12/16 17:57:06
Done.
|
| + coverageBody.appendChild(covTable); |
| + |
| + _addChildToElem('mainElem', coverageBody); |
| + } |
| + _show('coverageBody'); |
| + _hide('testBody'); |
| + |
| + _appElements['covPreElem'].textContent = getCoverageSummary(); |
| + _appElements['covTableBody'].innerHTML = getCoverageDetails(); |
| + } |
| + |
| + void _show(String toShow) { |
| + HTMLElement show = _appElements[toShow]; |
| + if(show != null) { |
| + if(show.classList.contains('dt-hide')) { |
| + show.classList.remove('dt-hide'); |
| + } |
| + show.classList.add('dt-show'); |
| + } |
| + } |
| + |
| + void _hide(String toHide) { |
| + HTMLElement hide = _appElements[toHide]; |
| + if(hide != null) { |
| + if(hide.classList.contains('dt-show')) { |
| + hide.classList.remove('dt-show'); |
| + } |
| + hide.classList.add('dt-hide'); |
| + } |
| + } |
| + |
| + void dartestMaximize() { |
| + _hide('containerDiv'); |
| + _runnerWindow = window.open('', 'dartest-window', 'width=600,height=750'); |
|
pdr
2011/12/16 17:00:50
Optional nit: I like moving these kinds of static
shauvik
2011/12/16 17:57:06
Done.
|
| + _runnerWindow.document.title = 'Dartest'; |
| + _fullAppElements = new HashMap<String, HTMLElement>(); |
| + _appElements = _fullAppElements; |
| + DARTestCss.inject(_runnerWindow.document, false); |
| + run(); |
| + if(testsRun > 0) { |
| + populateResults(); |
| + } |
| + } |
| + |
| + void populateResults() { |
| + tests.forEach((final t) => _updateDARTestUI(t)); |
| + } |
| + |
| + void dartestMinimize() { |
| + _runnerWindow.close(); |
| + _runnerWindow = window; |
| + _appElements = _inAppElements; |
| + _show('containerDiv'); |
| + } |
| + |
| + void exportTestResults() { |
| + String csvData = getTestResultsCsv(); |
| + _log(csvData); |
| + HTMLAnchorElement exportLink = |
| + _runnerWindow.document.getElementById('dt-export'); |
| + |
| + /** Bug: Can't instantiate WebKitBlobBuilder |
| + * If this bug is fixed, we can remove the urlencode and lpad function. |
| + WebKitBlobBuilder bb = new WebKitBlobBuilder(); |
| + bb.append(csvData); |
| + Blob blob = bb.getBlob('text/plain;charset=${document.characterSet}'); |
| + exportLink.href = window.webkitURL.createObjectURL(blob); |
| + **/ |
| + |
| + exportLink.href = 'data:text/csv,'+urlencode(csvData); |
| + |
| + MouseEvent ev = document.createEvent("MouseEvents"); |
| + ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0 |
| + , false, false, false, false, 0, null); |
| + exportLink.dispatchEvent(ev); |
| + |
| + } |
| + |
|
pdr
2011/12/16 17:00:50
Make these private and static
shauvik
2011/12/16 17:57:06
Done.
|
| + String urlencode(String s) { |
| + StringBuffer out = new StringBuffer(); |
| + for(int i = 0; i < s.length; i++) { |
| + int cc = s.charCodeAt(i); |
| + if((cc >= 48 && cc <= 57) || (cc >= 65 && cc <= 90) || |
| + (cc >= 97 && cc <= 122)) { |
| + out.add(s[i]); |
| + } else { |
| + out.add('%${lpad(cc.toRadixString(16),2).toUpperCase()}'); |
| + } |
| + } |
| + return out.toString(); |
| + } |
| + |
| + String lpad(String s, int n) { |
| + if(s.length < n) { |
| + for(int i = 0; i < n - s.length; i++) { |
| + s = '0'+s; |
| + } |
| + } |
| + return s; |
| + } |
| +} |