| <!doctype html> | |
| <html> | |
| <head> | |
| <style type='text/css'> | |
| table, tr, td | |
| { | |
| border-spacing: 1px; | |
| padding: 0px; | |
| } | |
| td.textColumn | |
| { | |
| white-space: nowrap; | |
| font-family: Courier, monospace; | |
| } | |
| #directories, #codeviewer | |
| { | |
| overflow: scroll; | |
| background-color: white; | |
| position: absolute; | |
| width: 50%; | |
| height: 100%; | |
| } | |
| #directories | |
| { | |
| left: 0%; | |
| } | |
| #codeviewer | |
| { | |
| left: 50%; | |
| } | |
| ul | |
| { | |
| padding: 1px 0px 1px 8px; | |
| margin: 0px; | |
| list-style-type: none; | |
| } | |
| li.file | |
| { | |
| /* 8px is to match the width of downArrow and rightArrow because files don't have an image before them. */ | |
| padding: 0px 0px 0px 8px; | |
| } | |
| div.graphsContainer | |
| { | |
| right: 0px; | |
| width: 300px; | |
| position: absolute; | |
| display: inline-block; | |
| } | |
| div.codeCoverage | |
| { | |
| right: 0px; | |
| width: 150px; | |
| position: absolute; | |
| display: inline-block; | |
| } | |
| div.branchCoverage | |
| { | |
| right: 150px; | |
| width: 150px; | |
| position: absolute; | |
| display: inline-block; | |
| } | |
| </style> | |
| <script type='text/javascript'> | |
| // This is the contents of the images left of directories. | |
| var downArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYHFioBcCRLNAAAAJhJREFUGNOFzrEKwjAURuHTJHUspekDZGmhdlawCEqoUDK19O1cnX0dFxcnQXyIOAhKSsUzXr4Lf3Q8nf3leuNXy8IgBmeRYh5IAYOziDxLsdv1LNo3K/IsRQAcdhu0TgKgdUJnG4A3ipVk7NoAjV2LkvKLAKrSUBcGgLowVKX5PASTe2eJF4re2XCcn3R/PKcnH3nvPX96AbbuOGLOrLT1AAAAAElFTkSuQmCC'; | |
| var rightArrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYHFiojpUQK0AAAAKZJREFUGNNjmL1k/f+379//xweYlXXMGo6dvcDw/x8jg7y0OAMzMzMDOmBiYGBg+PP7P8OO/ccYOqctZLh2+x52RTDw9t0nhtlLNjDMWbqB4d2HD9gVwcDVW/cYumcsZXj56i0DAwMDAws2RdqqCgwBHo4MIiKCmIqEBPgYgrycGbTVFVE0sTAwMDCwMjMyONqYMbjYWjCwsmL6jmH24rX/37zBH04AHAxpneY+98AAAAAASUVORK5CYII='; | |
| function getHeatBackgroundColor(hits, maxHits) | |
| { | |
| if (hits === -1) | |
| return 'white'; // Non-code lines are white. | |
| else if (hits === 0) | |
| return 'orange'; // Unexecuted lines are orange. | |
| else { | |
| // Executed lines are between red and green. | |
| var relativeHeat = Math.floor(hits / maxHits * 255); | |
| return 'rgb(' + relativeHeat + ',' + (255 - relativeHeat) + ', 0)'; | |
| } | |
| } | |
| function getCoverageBackgroundColor(coverage) | |
| { | |
| var value = Math.floor(coverage * 255); | |
| return 'rgb(' + (255 - value) + ',' + value + ', 0)'; | |
| } | |
| function expandClicked(event) | |
| { | |
| var children = this.parentNode.lastChild; | |
| if (children.style.display === '') { | |
| children.style.display = 'none'; | |
| this.src = rightArrow; | |
| } else { | |
| children.style.display = ''; | |
| this.src = downArrow; | |
| } | |
| } | |
| function processFile(fileData, contents) | |
| { | |
| var lines = contents.split('\n'); | |
| var hits = new Array(); | |
| var branchesNumerator = new Array(); | |
| var branchesDenominator = new Array(); | |
| for (var i = 0; i < lines.length; i++) { | |
| hits[i] = -1; | |
| branchesNumerator[i] = -1; | |
| branchesDenominator[i] = -1; | |
| } | |
| for (var i = 0; i < fileData.hitLines.length; i++) | |
| hits[fileData.hitLines[i] - 1] = fileData.hits[i]; | |
| for (var i = 0; i < fileData.branchLines.length; i++) { | |
| branchesNumerator[fileData.branchLines[i] - 1] = fileData.branchesTaken[i]; | |
| branchesDenominator[fileData.branchLines[i] - 1] = fileData.branchesPossible[i]; | |
| } | |
| var table = document.createElement('table'); | |
| for (var i = 0; i < lines.length; i++) { | |
| var row = document.createElement('tr'); | |
| var branchesColumn = document.createElement('td'); | |
| if (branchesNumerator[i] != -1) | |
| branchesColumn.appendChild(document.createTextNode('(' + branchesNumerator[i] + '/' + branchesDenominator[i] + ')')); | |
| var hitsColumn = document.createElement('td'); | |
| if (hits[i] != -1) | |
| hitsColumn.appendChild(document.createTextNode(hits[i])); | |
| var textColumn = document.createElement('td'); | |
| textColumn.style.background = getHeatBackgroundColor(hits[i], fileData.maxHeat); | |
| textColumn.style.className = 'textColumn'; | |
| textColumn.appendChild(document.createTextNode(lines[i])); | |
| row.appendChild(branchesColumn); | |
| row.appendChild(hitsColumn); | |
| row.appendChild(textColumn); | |
| table.appendChild(row); | |
| } | |
| return table; | |
| } | |
| function fileClicked(event) | |
| { | |
| var xhr = new XMLHttpRequest(); | |
| xhr.onreadystatechange = function() | |
| { | |
| if (xhr.readyState === XMLHttpRequest.DONE) { | |
| var codeviewer = document.getElementById('codeviewer'); | |
| codeviewer.replaceChild(processFile(xhr.fileData, xhr.responseText), codeviewer.firstChild); | |
| } | |
| } | |
| xhr.fileData = event.target.fileData; | |
| xhr.open('GET', '../../' + event.target.fileData.filename.substring(1), true); | |
| xhr.send(); | |
| event.stopPropagation(); | |
| } | |
| function makeGraphs(dirOrFile) | |
| { | |
| var codeCoverage = document.createElement('div'); | |
| codeCoverage.className = 'codeCoverage'; | |
| var codeCoveragePercent = dirOrFile.totalLines ? Math.floor(dirOrFile.totalHitLines / dirOrFile.totalLines * 100) + '%' : '-'; | |
| var codeCoverageText = codeCoveragePercent + ' (' + dirOrFile.totalHitLines + '/' + dirOrFile.totalLines + ')'; | |
| codeCoverage.appendChild(document.createTextNode(codeCoverageText)); | |
| codeCoverage.style.backgroundColor = getCoverageBackgroundColor(dirOrFile.coverage); | |
| var branchCoverage = document.createElement('div'); | |
| branchCoverage.className = 'branchCoverage'; | |
| var branchCoveragePercent = dirOrFile.totalBranchesPossible ? Math.floor(dirOrFile.totalBranchesTaken / dirOrFile.totalBranchesPossible * 100) + '%' : '-'; | |
| branchCoverage.appendChild(document.createTextNode(branchCoveragePercent + ' (' + dirOrFile.totalBranchesTaken + '/' + dirOrFile.totalBranchesPossible + ')')); | |
| branchCoverage.style.backgroundColor = getCoverageBackgroundColor(dirOrFile.branchCoverage); | |
| var graphsContainer = document.createElement('div'); | |
| graphsContainer.className = 'graphsContainer'; | |
| graphsContainer.appendChild(codeCoverage); | |
| graphsContainer.appendChild(branchCoverage); | |
| return graphsContainer; | |
| } | |
| function makeFileListItem(fileData, filename) | |
| { | |
| var li = document.createElement('li'); | |
| li.className = 'file'; | |
| var a = document.createElement('a'); | |
| a.appendChild(document.createTextNode(filename)); | |
| a.href = '#'; | |
| a.addEventListener('click', fileClicked.bind(a)); | |
| a.fileData = fileData; | |
| li.appendChild(a); | |
| li.appendChild(makeGraphs(fileData)); | |
| return li; | |
| } | |
| function makeDirectoryListItem(dir, dirName) | |
| { | |
| var li = document.createElement('li'); | |
| var children = document.createElement('ul'); | |
| // Recursively add all sorted subdirectories and files. | |
| var fileNames = dir.files ? Object.keys(dir.files).sort() : []; | |
| var subdirNames = dir.subdirs ? Object.keys(dir.subdirs).sort() : []; | |
| for (var i = 0; i < subdirNames.length; i++) { | |
| var subdir = subdirNames[i]; | |
| children.appendChild(makeDirectoryListItem(dir.subdirs[subdir], subdir)); | |
| } | |
| for (var i = 0; i < fileNames.length; i++) { | |
| var file = fileNames[i]; | |
| children.appendChild(makeFileListItem(dir.files[file], file, dir.maxHeat, dir.totalHeat)); | |
| } | |
| var img = document.createElement('img'); | |
| img.addEventListener('click', expandClicked.bind(img)); | |
| // These four directories are expanded by default. | |
| if (dirName === '' || dirName === 'Source' || dirName === 'Tools' || dirName === 'WebKitBuild') { | |
| img.src = downArrow; | |
| children.style.display = ''; | |
| } else { | |
| img.src = rightArrow; | |
| children.style.display = 'none'; | |
| } | |
| li.appendChild(img); | |
| li.appendChild(document.createTextNode(dirName)); | |
| li.appendChild(makeGraphs(dir)); | |
| li.appendChild(children); | |
| return li; | |
| } | |
| // Collect total coverage for a directory and its subdirectories. | |
| function collectDirectoryTotals(directory) | |
| { | |
| directory.totalBranchesPossible = 0; | |
| directory.totalBranchesTaken = 0; | |
| directory.totalHitLines = 0; | |
| directory.totalLines = 0; | |
| directory.totalHeat = 0; | |
| directory.maxHeat = 0; | |
| if (directory.subdirs) { | |
| for (var subdirName in directory.subdirs) { | |
| var subdir = directory.subdirs[subdirName]; | |
| collectDirectoryTotals(subdir); | |
| directory.totalBranchesPossible += subdir.totalBranchesPossible; | |
| directory.totalBranchesTaken += subdir.totalBranchesTaken; | |
| directory.totalHitLines += subdir.totalHitLines; | |
| directory.totalLines += subdir.totalLines; | |
| directory.totalHeat += subdir.totalHeat; | |
| directory.maxHeat = Math.max(directory.maxHeat, subdir.maxHeat); | |
| } | |
| } | |
| if (directory.files) { | |
| for (var fileName in directory.files) { | |
| var file = directory.files[fileName]; | |
| file.totalBranchesPossible = 0; | |
| file.totalBranchesTaken = 0; | |
| file.totalHitLines = 0; | |
| file.totalLines = file.hitLines.length; | |
| file.totalHeat = 0; | |
| for (var i = 0; i < file.branchesPossible.length; i++) { | |
| file.totalBranchesPossible += file.branchesPossible[i]; | |
| file.totalBranchesTaken += file.branchesTaken[i]; | |
| } | |
| for (var i = 0; i < file.hits.length; i++) { | |
| file.totalHeat += file.hits[i]; | |
| if (file.hits[i]) | |
| file.totalHitLines++; | |
| } | |
| directory.totalBranchesPossible += file.totalBranchesPossible; | |
| directory.totalBranchesTaken += file.totalBranchesTaken; | |
| directory.totalHitLines += file.totalHitLines; | |
| directory.totalLines += file.totalLines; | |
| directory.totalHeat += file.totalHeat; | |
| directory.maxHeat = Math.max(directory.maxHeat, file.maxHeat); | |
| } | |
| } | |
| directory.coverage = directory.totalHitLines / directory.totalLines; | |
| directory.branchCoverage = directory.totalBranchesPossible ? directory.totalBranchesTaken / directory.totalBranchesPossible : 1; | |
| } | |
| function addFileToDirectory(filename, filedata, directory) | |
| { | |
| var slashIndex = filename.indexOf('/', 1); | |
| if (slashIndex === -1) { | |
| if (!directory.files) | |
| directory.files = {}; | |
| directory.files[filename.substring(1)] = filedata; | |
| } else { | |
| if (!directory.subdirs) | |
| directory.subdirs = {}; | |
| var subdirName = filename.substring(1, slashIndex); | |
| if (!directory.subdirs[subdirName]) | |
| directory.subdirs[subdirName] = {}; | |
| addFileToDirectory(filename.substring(slashIndex), filedata, directory.subdirs[subdirName]); | |
| } | |
| } | |
| function updateReport(data) | |
| { | |
| var rootDirectory = {}; | |
| for (var i = 0; i < data.length; i++) | |
| addFileToDirectory(data[i].filename, data[i], rootDirectory); | |
| collectDirectoryTotals(rootDirectory); | |
| var report = document.createElement('div'); | |
| var codeCoverageHeader = document.createElement('div'); | |
| codeCoverageHeader.className = 'codeCoverage'; | |
| codeCoverageHeader.appendChild(document.createTextNode('Code Coverage')); | |
| var branchCoverageHeader = document.createElement('div'); | |
| branchCoverageHeader.className = 'branchCoverage'; | |
| branchCoverageHeader.appendChild(document.createTextNode('Branch Coverage')); | |
| var ul = document.createElement('ul'); | |
| ul.appendChild(makeDirectoryListItem(rootDirectory, '')); | |
| report.appendChild(codeCoverageHeader); | |
| report.appendChild(branchCoverageHeader); | |
| report.appendChild(document.createTextNode('Directories')); | |
| report.appendChild(ul); | |
| var directories = document.getElementById('directories'); | |
| directories.replaceChild(report, directories.firstChild); | |
| } | |
| function bodyLoaded() | |
| { | |
| updateReport(JSON.parse(document.getElementById('json').textContent)); | |
| } | |
| </script> | |
| </head> | |
| <body onload='bodyLoaded();'> | |
| <div id='directories'> | |
| loading data... | |
| </div> | |
| <div id='codeviewer'> | |
| </div> | |
| <script id='json' type='application/json'>%CoverageDataJSON%</script> | |
| </body> | |
| </html> |