diff --git a/Makefile b/Makefile index d4833a561..d95841000 100644 --- a/Makefile +++ b/Makefile @@ -83,10 +83,12 @@ bundle: | $(BUILD_DIR) # make unit-test # -# This target runs in-browser unit tests with js-test-driver and jasmine unit -# test framework. +# This target runs in-browser unit tests with our test framework and the +# jasmine unit test framework. unit-test: - @cd test/unit/ ; make ; + cd test; \ + $(DEFAULT_PYTHON) test.py --unitTest \ + --browserManifestFile=$(PDF_BROWSERS) # make browser-test # diff --git a/external/jasmine/jasmine-html.js b/external/jasmine/jasmine-html.js new file mode 100644 index 000000000..3de4e8a5f --- /dev/null +++ b/external/jasmine/jasmine-html.js @@ -0,0 +1,676 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = doc.location.search.substring(1).split('&'); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/external/jasmine/jasmine.css b/external/jasmine/jasmine.css new file mode 100644 index 000000000..826e57531 --- /dev/null +++ b/external/jasmine/jasmine.css @@ -0,0 +1,81 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/external/jasmine/jasmine_favicon.png b/external/jasmine/jasmine_favicon.png new file mode 100644 index 000000000..218f3b437 Binary files /dev/null and b/external/jasmine/jasmine_favicon.png differ diff --git a/external/jasmineAdapter/JasmineAdapter.js b/external/jasmineAdapter/JasmineAdapter.js deleted file mode 100644 index 3b0fb2d76..000000000 --- a/external/jasmineAdapter/JasmineAdapter.js +++ /dev/null @@ -1,198 +0,0 @@ -/** - * @fileoverview Jasmine JsTestDriver Adapter. - * @author misko@hevery.com (Misko Hevery) - */ -(function(window) { - var rootDescribes = new Describes(window); - var describePath = []; - rootDescribes.collectMode(); - - var JASMINE_TYPE = 'jasmine test case'; - TestCase('Jasmine Adapter Tests', null, JASMINE_TYPE); - - var jasminePlugin = { - name:'jasmine', - - getTestRunsConfigurationFor: function(testCaseInfos, expressions, testRunsConfiguration) { - for (var i = 0; i < testCaseInfos.length; i++) { - if (testCaseInfos[i].getType() == JASMINE_TYPE) { - testRunsConfiguration.push(new jstestdriver.TestRunConfiguration(testCaseInfos[i], [])); - } - } - return false; - }, - - runTestConfiguration: function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){ - if (testRunConfiguration.getTestCaseInfo().getType() != JASMINE_TYPE) return false; - - var jasmineEnv = jasmine.currentEnv_ = new jasmine.Env(); - rootDescribes.playback(); - var specLog = jstestdriver.console.log_ = []; - var start; - jasmineEnv.specFilter = function(spec) { - return rootDescribes.isExclusive(spec); - }; - jasmineEnv.reporter = { - log: function(str){ - specLog.push(str); - }, - - reportRunnerStarting: function(runner) { }, - - reportSpecStarting: function(spec) { - specLog = jstestdriver.console.log_ = []; - start = new Date().getTime(); - }, - - reportSpecResults: function(spec) { - var suite = spec.suite; - var results = spec.results(); - if (results.skipped) return; - var end = new Date().getTime(); - var messages = []; - var resultItems = results.getItems(); - var state = 'passed'; - for ( var i = 0; i < resultItems.length; i++) { - if (!resultItems[i].passed()) { - state = resultItems[i].message.match(/AssertionError:/) ? 'error' : 'failed'; - messages.push({ - message: resultItems[i].toString(), - name: resultItems[i].trace.name, - stack: formatStack(resultItems[i].trace.stack) - }); - } - } - onTestDone( - new jstestdriver.TestResult( - suite.getFullName(), - spec.description, - state, - jstestdriver.angular.toJson(messages), - specLog.join('\n'), - end - start)); - }, - - reportSuiteResults: function(suite) {}, - - reportRunnerResults: function(runner) { - onTestRunConfigurationComplete(); - } - }; - jasmineEnv.execute(); - return true; - }, - - onTestsFinish: function(){ - jasmine.currentEnv_ = null; - rootDescribes.collectMode(); - } - }; - jstestdriver.pluginRegistrar.register(jasminePlugin); - - function formatStack(stack) { - var lines = (stack||'').split(/\r?\n/); - var frames = []; - for (i = 0; i < lines.length; i++) { - if (!lines[i].match(/\/jasmine[\.-]/)) { - frames.push(lines[i].replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' ')); - } - } - return frames.join('\n'); - } - - function noop(){} - function Describes(window){ - var describes = {}; - var beforeEachs = {}; - var afterEachs = {}; - // Here we store: - // 0: everyone runs - // 1: run everything under ddescribe - // 2: run only iits (ignore ddescribe) - var exclusive = 0; - var collectMode = true; - intercept('describe', describes); - intercept('xdescribe', describes); - intercept('beforeEach', beforeEachs); - intercept('afterEach', afterEachs); - - function intercept(functionName, collection){ - window[functionName] = function(desc, fn){ - if (collectMode) { - collection[desc] = function(){ - jasmine.getEnv()[functionName](desc, fn); - }; - } else { - jasmine.getEnv()[functionName](desc, fn); - } - }; - } - window.ddescribe = function(name, fn){ - if (exclusive < 1) { - exclusive = 1; // run ddescribe only - } - window.describe(name, function(){ - var oldIt = window.it; - window.it = function(name, fn){ - fn.exclusive = 1; // run anything under ddescribe - jasmine.getEnv().it(name, fn); - }; - try { - fn.call(this); - } finally { - window.it = oldIt; - }; - }); - }; - window.iit = function(name, fn){ - exclusive = fn.exclusive = 2; // run only iits - jasmine.getEnv().it(name, fn); - }; - - - this.collectMode = function() { - collectMode = true; - exclusive = 0; // run everything - }; - this.playback = function(){ - collectMode = false; - playback(beforeEachs); - playback(afterEachs); - playback(describes); - - function playback(set) { - for ( var name in set) { - set[name](); - } - } - }; - - this.isExclusive = function(spec) { - if (exclusive) { - var blocks = spec.queue.blocks; - for ( var i = 0; i < blocks.length; i++) { - if (blocks[i].func.exclusive >= exclusive) { - return true; - } - } - return false; - } - return true; - }; - } - -})(window); - -// Patch Jasmine for proper stack traces -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception' - }); - // PATCH - if (e) { - expectationResult.trace = e; - } - this.results_.addResult(expectationResult); -}; - diff --git a/external/jasmineAdapter/MIT.LICENSE b/external/jasmineAdapter/MIT.LICENSE deleted file mode 100644 index f650924e6..000000000 --- a/external/jasmineAdapter/MIT.LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2010 - Misko Hevery - Olmo Maldonado - Christoph Pojer - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/external/jsTestDriver/JsTestDriver-1.3.3d.jar b/external/jsTestDriver/JsTestDriver-1.3.3d.jar deleted file mode 100644 index 9de7cf64a..000000000 Binary files a/external/jsTestDriver/JsTestDriver-1.3.3d.jar and /dev/null differ diff --git a/external/jsTestDriver/LICENSE-2.0.txt b/external/jsTestDriver/LICENSE-2.0.txt deleted file mode 100644 index d64569567..000000000 --- a/external/jsTestDriver/LICENSE-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/make.js b/make.js index bcd7f433d..469d53aa4 100755 --- a/make.js +++ b/make.js @@ -358,8 +358,9 @@ target.chrome = function() { // make test // target.test = function() { - target.browsertest(); - target.unittest(); + target.unittest({}, function() { + target.browsertest(); + }); }; // @@ -367,8 +368,10 @@ target.test = function() { // (Special tests for the Github bot) // target.bottest = function() { - target.browsertest({noreftest: true}); - // target.unittest(); + target.unittest(); + target.browsertest({noreftest: true}, function() { + target.browsertest(); + }); }; // @@ -398,18 +401,22 @@ target.browsertest = function(options) { // // make unittest // -target.unittest = function() { +target.unittest = function(options, callback) { cd(ROOT_DIR); echo(); echo('### Running unit tests'); - if (!which('make')) { - echo('make not found. Skipping unit tests...'); - return; - } + var PDF_BROWSERS = env['PDF_BROWSERS'] || 'resources/browser_manifests/browser_manifest.json'; - cd('test/unit'); - exec('make', {async: true}); + if (!test('-f', 'test/' + PDF_BROWSERS)) { + echo('Browser manifest file test/' + PDF_BROWSERS + ' does not exist.'); + echo('Try copying one of the examples in test/resources/browser_manifests/'); + exit(1); + } + callback = callback || function() {}; + cd('test'); + exec(PYTHON_BIN + ' -u test.py --unitTest --browserManifestFile=' + PDF_BROWSERS, + {async: true}, callback); }; // diff --git a/test/test.py b/test/test.py index 368069aff..30dafe840 100644 --- a/test/test.py +++ b/test/test.py @@ -39,9 +39,13 @@ class TestOptions(OptionParser): default=False) self.add_option("--port", action="store", dest="port", type="int", help="The port the HTTP server should listen on.", default=8080) + self.add_option("--unitTest", action="store_true", dest="unitTest", + help="Run the unit tests.", default=False) self.set_usage(USAGE_EXAMPLE) def verifyOptions(self, options): + if options.reftest and options.unitTest: + self.error("--reftest and --unitTest must not be specified at the same time.") if options.masterMode and options.manifestFile: self.error("--masterMode and --manifestFile must not be specified at the same time.") if not options.manifestFile: @@ -50,6 +54,7 @@ class TestOptions(OptionParser): print "Warning: ignoring browser argument since manifest file was also supplied" if not options.browser and not options.browserManifestFile: print "Starting server on port %s." % options.port + return options def prompt(question): @@ -86,6 +91,13 @@ class State: eqLog = None lastPost = { } +class UnitTestState: + browsers = [ ] + browsersRunning = 0 + lastPost = { } + numErrors = 0 + numRun = 0 + class Result: def __init__(self, snapshot, failure, page): self.snapshot = snapshot @@ -95,8 +107,7 @@ class Result: class TestServer(SocketServer.TCPServer): allow_reuse_address = True -class PDFTestHandler(BaseHTTPRequestHandler): - +class TestHandlerBase(BaseHTTPRequestHandler): # Disable annoying noise by default def log_request(code=0, size=0): if VERBOSE: @@ -110,34 +121,6 @@ class PDFTestHandler(BaseHTTPRequestHandler): with open(path, "rb") as f: self.wfile.write(f.read()) - def sendIndex(self, path, query): - if not path.endswith("/"): - # we need trailing slash - self.send_response(301) - redirectLocation = path + "/" - if query: - redirectLocation += "?" + query - self.send_header("Location", redirectLocation) - self.end_headers() - return - - self.send_response(200) - self.send_header("Content-Type", "text/html") - self.end_headers() - if query == "frame": - self.wfile.write("" + - "") - return - - location = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) - self.wfile.write("

PDFs of " + path + "

\n") - for filename in os.listdir(location): - if filename.lower().endswith('.pdf'): - self.wfile.write("" + - filename + "
\n") - self.wfile.write("") - def do_GET(self): url = urlparse(self.path) # Ignore query string @@ -168,6 +151,70 @@ class PDFTestHandler(BaseHTTPRequestHandler): self.sendFile(path, ext) +class UnitTestHandler(TestHandlerBase): + def sendIndex(self, path, query): + print "send index" + def do_POST(self): + numBytes = int(self.headers['Content-Length']) + + self.send_response(200) + self.send_header('Content-Type', 'text/plain') + self.end_headers() + + url = urlparse(self.path) + result = json.loads(self.rfile.read(numBytes)) + browser = result['browser'] + UnitTestState.lastPost[browser] = int(time.time()) + if url.path == "/tellMeToQuit": + tellAppToQuit(url.path, url.query) + UnitTestState.browsersRunning -= 1 + UnitTestState.lastPost[browser] = None + return + elif url.path == '/info': + print result['message'] + elif url.path == '/submit_task_results': + status, description = result['status'], result['description'] + UnitTestState.numRun += 1 + if status == 'TEST-UNEXPECTED-FAIL': + UnitTestState.numErrors += 1 + message = status + ' | ' + description + ' | in ' + browser + if 'error' in result: + message += ' | ' + result['error'] + print message + else: + print 'Error: uknown action' + url.path + +class PDFTestHandler(TestHandlerBase): + + def sendIndex(self, path, query): + if not path.endswith("/"): + # we need trailing slash + self.send_response(301) + redirectLocation = path + "/" + if query: + redirectLocation += "?" + query + self.send_header("Location", redirectLocation) + self.end_headers() + return + + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + if query == "frame": + self.wfile.write("" + + "") + return + + location = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) + self.wfile.write("

PDFs of " + path + "

\n") + for filename in os.listdir(location): + if filename.lower().endswith('.pdf'): + self.wfile.write("" + + filename + "
\n") + self.wfile.write("") + + def do_POST(self): numBytes = int(self.headers['Content-Length']) @@ -354,6 +401,17 @@ def verifyPDFs(manifestList): error = True return not error +def getTestBrowsers(options): + testBrowsers = [] + if options.browserManifestFile: + testBrowsers = makeBrowserCommands(options.browserManifestFile) + elif options.browser: + testBrowsers = [makeBrowserCommand({"path":options.browser, "name":None})] + + if options.browserManifestFile or options.browser: + assert len(testBrowsers) > 0 + return testBrowsers + def setUp(options): # Only serve files from a pdf.js clone assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') @@ -366,14 +424,7 @@ def setUp(options): assert not os.path.isdir(TMPDIR) - testBrowsers = [] - if options.browserManifestFile: - testBrowsers = makeBrowserCommands(options.browserManifestFile) - elif options.browser: - testBrowsers = [makeBrowserCommand({"path":options.browser, "name":None})] - - if options.browserManifestFile or options.browser: - assert len(testBrowsers) > 0 + testBrowsers = getTestBrowsers(options) with open(options.manifestFile) as mf: manifestList = json.load(mf) @@ -398,13 +449,23 @@ def setUp(options): return testBrowsers -def startBrowsers(browsers, options): +def setUpUnitTests(options): + # Only serve files from a pdf.js clone + assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') + + testBrowsers = getTestBrowsers(options) + + UnitTestState.browsersRunning = len(testBrowsers) + for b in testBrowsers: + UnitTestState.lastPost[b.name] = int(time.time()) + return testBrowsers + +def startBrowsers(browsers, options, path): for b in browsers: b.setup() print 'Launching', b.name host = 'http://%s:%s' % (SERVER_HOST, options.port) - path = '/test/test_slave.html?' - qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + qs = '?browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) qs += '&path=' + b.path b.start(host + path + qs) @@ -576,7 +637,7 @@ def startReftest(browser, options): def runTests(options, browsers): t1 = time.time() try: - startBrowsers(browsers, options) + startBrowsers(browsers, options, '/test/test_slave.html') while not State.done: for b in State.lastPost: if State.remaining[b] > 0 and int(time.time()) - State.lastPost[b] > BROWSER_TIMEOUT: @@ -598,6 +659,30 @@ def runTests(options, browsers): print "\nStarting reftest harness to examine %d eq test failures." % State.numEqFailures startReftest(browsers[0], options) +def runUnitTests(options, browsers): + t1 = time.time() + try: + startBrowsers(browsers, options, '/test/unit/unit_test.html') + while UnitTestState.browsersRunning > 0: + for b in UnitTestState.lastPost: + if UnitTestState.lastPost[b] != None and int(time.time()) - UnitTestState.lastPost[b] > BROWSER_TIMEOUT: + print 'TEST-UNEXPECTED-FAIL | test failed', b, "has not responded in", BROWSER_TIMEOUT, "s" + UnitTestState.lastPost[b] = None + UnitTestState.browsersRunning -= 1 + UnitTestState.numErrors += 1 + time.sleep(1) + print '' + print 'Ran', UnitTestState.numRun, 'tests' + if UnitTestState.numErrors > 0: + print 'OHNOES! Some tests failed!' + print ' ', UnitTestState.numErrors, 'of', UnitTestState.numRun, 'failed' + else: + print 'All unit tests passed.' + finally: + teardownBrowsers(browsers) + t2 = time.time() + print "Unit test Runtime was", int(t2 - t1), "seconds" + def main(): optionParser = TestOptions() options, args = optionParser.parse_args() @@ -605,23 +690,33 @@ def main(): if options == None: sys.exit(1) - httpd = TestServer((SERVER_HOST, options.port), PDFTestHandler) - httpd.masterMode = options.masterMode - httpd_thread = threading.Thread(target=httpd.serve_forever) - httpd_thread.setDaemon(True) - httpd_thread.start() + if options.unitTest: + httpd = TestServer((SERVER_HOST, options.port), UnitTestHandler) + httpd_thread = threading.Thread(target=httpd.serve_forever) + httpd_thread.setDaemon(True) + httpd_thread.start() - browsers = setUp(options) - if len(browsers) > 0: - runTests(options, browsers) + browsers = setUpUnitTests(options) + if len(browsers) > 0: + runUnitTests(options, browsers) else: - # just run the server - print "Running HTTP server. Press Ctrl-C to quit." - try: - while True: - time.sleep(1) - except (KeyboardInterrupt): - print "\nExiting." + httpd = TestServer((SERVER_HOST, options.port), PDFTestHandler) + httpd.masterMode = options.masterMode + httpd_thread = threading.Thread(target=httpd.serve_forever) + httpd_thread.setDaemon(True) + httpd_thread.start() + + browsers = setUp(options) + if len(browsers) > 0: + runTests(options, browsers) + else: + # just run the server + print "Running HTTP server. Press Ctrl-C to quit." + try: + while True: + time.sleep(1) + except (KeyboardInterrupt): + print "\nExiting." if __name__ == '__main__': main() diff --git a/test/unit/Makefile b/test/unit/Makefile deleted file mode 100644 index 811f9155e..000000000 --- a/test/unit/Makefile +++ /dev/null @@ -1,116 +0,0 @@ -# Create temporary profile directory name. -TEMP_PROFILE:=$(shell echo `pwd`)/test_reports/temp_profile - -# These are the Firefox command line arguments. -FIREFOX_ARGS:=-no-remote -profile $(TEMP_PROFILE) - -# These are the Chrome command line arguments. -CHROME_ARGS:=--user-data-dir=$(TEMP_PROFILE) --no-first-run --disable-sync - -# Unit test uses the manifest from ref test to determine which browsers will -# be used for running the unit tests. -MANIFEST:=../resources/browser_manifests/browser_manifest.json - -# This is a helper command to separate multiple browsers to their own lines -# for an easier sed operation. -SPLIT_LINES:=sed 's|,|,@|g' | tr '@' '\n' - -# This is a helper command to join multiple lines together. -JOIN_LINES:=tr -d '\n' - -# Fetch the paths to browsers that are going to be used in testing. -# For OS X the path to the binary needs to be added. -# Add the browser arguments for each browser. -# Create random profile directory for each browser. -BROWSERS_PATHS:=$(shell echo `\ - sed -n 's|.*"path":\(.*\)|\1,|p' $(MANIFEST) | \ - $(JOIN_LINES) \ -`) - -# The browser_manifest.json file has only the app directory for mac browsers. -# The absolute path to the browser binary needs to be used. -BROWSERS_PATHS_WITH_MAC_CORRECTION:=$(shell echo '$(BROWSERS_PATHS)' | \ - $(SPLIT_LINES) | \ - sed 's|\(Aurora\.app\)|\1/Contents/MacOS/firefox-bin|' | \ - sed 's|\(Firefox.*\.app\)|\1/Contents/MacOS/firefox-bin|' | \ - sed 's|\(Google Chrome\.app\)|\1/Contents/MacOS/Google Chrome|' | \ - $(JOIN_LINES) \ -) - -# Replace " with @@@@ so that echoing do not destroy the quotation marks. -QUOTATION_MARK:=\" -SUBSTITUTE_FOR_QUOTATION_MARK:=@@@@ - -# Each of the browser can have their own separate arguments. -BROWSERS_WITH_ARGUMENTS:=$(shell echo '$(BROWSERS_PATHS_WITH_MAC_CORRECTION)' | \ - $(SPLIT_LINES) | \ - sed "s|\(irefox.*\)\($(QUOTATION_MARK)\),|\1;$(FIREFOX_ARGS)\2,|" | \ - sed "s|\(hrome.*\)\($(QUOTATION_MARK)\),|\1;$(CHROME_ARGS)\2,|" | \ - $(JOIN_LINES) \ -) - -# A temporary profile directory is needed for each of the browser. In this way -# a unit test run will not disturb the main browsing session of the user. The -# $RANDOM shell variable is used to generate non-conflicting temporary -# directories. -BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS:=$(shell echo '$(BROWSERS_WITH_ARGUMENTS)' | \ - $(SPLIT_LINES) | \ - sed 's|\(temp_profile\)|\1_$$RANDOM$$RANDOM|' | \ - sed "s|$(QUOTATION_MARK)|$(SUBSTITUTE_FOR_QUOTATION_MARK)|g" | \ - $(JOIN_LINES) \ -) - -# Echo the variable so that the unknown random directories become known. -# Replace @@@@ with " so that jsTestDriver will work properly. -BROWSERS:=$(shell echo "$(BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS)" | \ - sed "s|$(SUBSTITUTE_FOR_QUOTATION_MARK)|$(QUOTATION_MARK)|g" \ -) - -# Get the known random directories for browsers. This information will be used -# to create the profile directories beforehand. Create also the dummy temp -# profile directory so that the mkdir command would not fail for browsers that -# do not need it. -PROFILES:=$(TEMP_PROFILE) $(shell echo '$(BROWSERS)' | \ - $(SPLIT_LINES) | \ - sed -n "s|.*\( $(TEMP_PROFILE)_[0-9]\+\).*|\1|p" | \ - $(JOIN_LINES) \ -) - -# This is the command to invoke the unit test. -PROG:=java \ --Xms512m \ --Xmx1024m \ --jar ../../external/jsTestDriver/JsTestDriver-1.3.3d.jar \ ---config ./jsTestDriver.conf \ ---reset \ ---port 4224 \ ---browser $(BROWSERS) \ ---tests all \ ---testOutput ./test_reports/ - -# This default rule runs the unit tests with the constructed command. -test: - @mkdir -p $(PROFILES) - $(PROG) - @rm -rf $(PROFILES) - -# In case this Makefile needs to be debugged then this rule will provide all -# the information from intermediate steps. -debug: - @echo 'Debug browsers paths: $(BROWSERS_PATHS)' - @echo - @echo 'Debug browsers paths with mac correction: $(BROWSERS_PATHS_WITH_MAC_CORRECTION)' - @echo - @echo 'Debug browsers with arguments: $(BROWSERS_WITH_ARGUMENTS)' - @echo - @echo 'Debug browsers random profile paths: $(BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS)' - @echo - @echo 'Debug browsers: $(BROWSERS)' - @echo - @echo 'Debug profiles: $(PROFILES)' - @echo - @echo 'Command to be run: $(PROG)' - @echo - -.phony:: test - diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 318dbb42a..5b068fbb9 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -6,11 +6,11 @@ describe('api', function() { // TODO run with worker enabled PDFJS.disableWorker = true; - var basicApiUrl = '/basicapi.pdf'; + var basicApiUrl = '../pdfs/basicapi.pdf'; function waitsForPromise(promise) { waitsFor(function() { return promise.isResolved || promise.isRejected; - }, 250); + }, 1000); } function expectAfterPromise(promise, successCallback) { waitsForPromise(promise); @@ -25,8 +25,6 @@ describe('api', function() { describe('PDFJS', function() { describe('getDocument', function() { it('creates pdf doc from URL', function() { - console.log('what is'); - debugger; var promise = PDFJS.getDocument(basicApiUrl); expectAfterPromise(promise, function(data) { expect(true).toEqual(true); diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf deleted file mode 100644 index b0f917b66..000000000 --- a/test/unit/jsTestDriver.conf +++ /dev/null @@ -1,39 +0,0 @@ -server: http://localhost:4224 - -basepath: .. - -load: - - ../external/jasmine/jasmine.js - - ../external/jasmineAdapter/JasmineAdapter.js - - ../src/obj.js - - ../src/core.js - - ../src/util.js - - ../src/api.js - - ../src/canvas.js - - ../src/obj.js - - ../src/function.js - - ../src/charsets.js - - ../src/cidmaps.js - - ../src/colorspace.js - - ../src/crypto.js - - ../src/evaluator.js - - ../src/fonts.js - - ../src/glyphlist.js - - ../src/image.js - - ../src/metrics.js - - ../src/parser.js - - ../src/pattern.js - - ../src/stream.js - - ../src/worker.js - - ../src/bidi.js - - ../src/metadata.js - - ../external/jpgjs/jpg.js - - unit/obj_spec.js - - unit/font_spec.js - - unit/function_spec.js - - unit/crypto_spec.js - - unit/stream_spec.js - - unit/api_spec.js - -gateway: - - {matcher: "*.pdf", server: "http://localhost:8888/test/pdfs/"} diff --git a/test/unit/stream_spec.js b/test/unit/stream_spec.js index 1dab8e42c..984c28a8e 100644 --- a/test/unit/stream_spec.js +++ b/test/unit/stream_spec.js @@ -4,7 +4,21 @@ 'use strict'; describe('stream', function() { - + beforeEach(function() { + this.addMatchers({ + toMatchTypedArray: function(expected) { + var actual = this.actual; + if (actual.length != expected.length) + return false; + for (var i = 0, ii = expected.length; i < ii; i++) { + var a = actual[i], b = expected[i]; + if (a !== b) + return false; + } + return true; + } + }); + }); describe('PredictorStream', function() { it('should decode simple predictor data', function() { var dict = new Dict(); @@ -18,7 +32,7 @@ describe('stream', function() { var predictor = new PredictorStream(input, dict); var result = predictor.getBytes(6); - expect(result).toEqual(new Uint8Array([100, 3, 101, 2, 102, 1])); + expect(result).toMatchTypedArray(new Uint8Array([100, 3, 101, 2, 102, 1])); }); }); }); diff --git a/test/unit/test_reports/.gitignore b/test/unit/test_reports/.gitignore deleted file mode 100644 index 7193eb3d2..000000000 --- a/test/unit/test_reports/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -TEST* -temp* - diff --git a/test/unit/testreporter.js b/test/unit/testreporter.js new file mode 100644 index 000000000..1e43db1fa --- /dev/null +++ b/test/unit/testreporter.js @@ -0,0 +1,71 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +var TestReporter = function(browser, appPath) { + function send(action, json) { + var r = new XMLHttpRequest(); + // (The POST URI is ignored atm.) + r.open('POST', action, true); + r.setRequestHeader('Content-Type', 'application/json'); + r.onreadystatechange = function sendTaskResultOnreadystatechange(e) { + if (r.readyState == 4) { + // Retry until successful + if (r.status !== 200) + send(action, json); + } + }; + json['browser'] = browser; + r.send(JSON.stringify(json)); + } + + function sendInfo(message) { + send('/info', {message: message}); + } + + function sendResult(status, description, error) { + var message = { + status: status, + description: description + }; + if (typeof error !== 'undefined') + message['error'] = error; + send('/submit_task_results', message); + } + + function sendQuitRequest() { + send('/tellMeToQuit?path=' + escape(appPath), {}); + } + + this.now = function() { + return new Date().getTime(); + }; + + this.reportRunnerStarting = function() { + this.runnerStartTime = this.now(); + sendInfo('Started unit tests for ' + browser + '.'); + }; + + this.reportSpecStarting = function() { }; + + this.reportSpecResults = function(spec) { + var results = spec.results(); + if (results.skipped) { + sendResult('TEST-SKIPPED', results.description); + } else if (results.passed()) { + sendResult('TEST-PASSED', results.description); + } else { + var failedMessages = ''; + var items = results.getItems(); + for (var i = 0, ii = items.length; i < ii; i++) + if (!items[i].passed()) + failedMessages += items[i].message + ' '; + sendResult('TEST-UNEXPECTED-FAIL', results.description, failedMessages); + } + }; + + this.reportSuiteResults = function(suite) { }; + + this.reportRunnerResults = function(runner) { + sendQuitRequest(); + }; +}; diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html new file mode 100644 index 000000000..49de9dbde --- /dev/null +++ b/test/unit/unit_test.html @@ -0,0 +1,94 @@ + + + + pdf.js unit test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +