]> git.parisson.com Git - pdf.js.git/commitdiff
Use test.py for unit tests too.
authorBrendan Dahl <brendan.dahl@gmail.com>
Thu, 19 Apr 2012 19:32:24 +0000 (12:32 -0700)
committerBrendan Dahl <brendan.dahl@gmail.com>
Thu, 19 Apr 2012 19:32:24 +0000 (12:32 -0700)
17 files changed:
Makefile
external/jasmine/jasmine-html.js [new file with mode: 0644]
external/jasmine/jasmine.css [new file with mode: 0644]
external/jasmine/jasmine_favicon.png [new file with mode: 0644]
external/jasmineAdapter/JasmineAdapter.js [deleted file]
external/jasmineAdapter/MIT.LICENSE [deleted file]
external/jsTestDriver/JsTestDriver-1.3.3d.jar [deleted file]
external/jsTestDriver/LICENSE-2.0.txt [deleted file]
make.js
test/test.py
test/unit/Makefile [deleted file]
test/unit/api_spec.js
test/unit/jsTestDriver.conf [deleted file]
test/unit/stream_spec.js
test/unit/test_reports/.gitignore [deleted file]
test/unit/testreporter.js [new file with mode: 0644]
test/unit/unit_test.html [new file with mode: 0644]

index d4833a561961e3d78476739b53615174d76af693..d958410002c670e6d81abaa496c5b72c7b031169 100644 (file)
--- 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 (file)
index 0000000..3de4e8a
--- /dev/null
@@ -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 (file)
index 0000000..826e575
--- /dev/null
@@ -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 (file)
index 0000000..218f3b4
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 (file)
index 3b0fb2d..0000000
+++ /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 (file)
index f650924..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-Copyright (c) 2010
-  Misko Hevery <misko@hevery.com>
-  Olmo Maldonado <me@ibolmo.com>
-  Christoph Pojer <christoph.pojer@gmail.com>
-
-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 (file)
index 9de7cf6..0000000
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 (file)
index d645695..0000000
+++ /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 bcd7f433d5e2f923e8aa4c98334d61917a23b944..469d53aa4074147f07e3ca3c1c713f047d1d39a9 100755 (executable)
--- 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);
 };
 
 //
index 368069aff24af153da62cec2c00a2e12a192e534..30dafe84070ea898c56934c8c5babebb7cae5b9c 100644 (file)
@@ -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("<html><frameset cols=*,200><frame name=pdf>" +
-            "<frame src='" + path + "'></frameset></html>")
-          return
-
-        location = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path))
-        self.wfile.write("<html><body><h1>PDFs of " + path + "</h1>\n")
-        for filename in os.listdir(location):
-          if filename.lower().endswith('.pdf'):
-            self.wfile.write("<a href='/web/viewer.html?file=" +
-              urllib.quote_plus(path + filename, '/') + "' target=pdf>" +
-              filename + "</a><br>\n")
-        self.wfile.write("</body></html>")
-
     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("<html><frameset cols=*,200><frame name=pdf>" +
+            "<frame src='" + path + "'></frameset></html>")
+          return
+
+        location = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path))
+        self.wfile.write("<html><body><h1>PDFs of " + path + "</h1>\n")
+        for filename in os.listdir(location):
+          if filename.lower().endswith('.pdf'):
+            self.wfile.write("<a href='/web/viewer.html?file=" +
+              urllib.quote_plus(path + filename, '/') + "' target=pdf>" +
+              filename + "</a><br>\n")
+        self.wfile.write("</body></html>")
+
+
     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 (file)
index 811f915..0000000
+++ /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
-
index 318dbb42a278c4b5f9d6754fef154c8cc372a879..5b068fbb9232cc568e5f408dbdcf32f74f952d3f 100644 (file)
@@ -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 (file)
index b0f917b..0000000
+++ /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/"}
index 1dab8e42cfa06b614676c7f4ba4b9830a397116e..984c28a8eca6f23f0362cbd399996ba8f2f5c28d 100644 (file)
@@ -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 (file)
index 7193eb3..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-TEST*
-temp*
-
diff --git a/test/unit/testreporter.js b/test/unit/testreporter.js
new file mode 100644 (file)
index 0000000..1e43db1
--- /dev/null
@@ -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 (file)
index 0000000..49de9db
--- /dev/null
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>pdf.js unit test</title>
+
+  <link rel="shortcut icon" type="image/png" href="../../external/jasmine/jasmine_favicon.png">
+  <link rel="stylesheet" type="text/css" href="../../external/jasmine/jasmine.css">
+
+  <script type="text/javascript" src="../../external/jasmine/jasmine.js"></script>
+  <script type="text/javascript" src="../../external/jasmine/jasmine-html.js"></script>
+  <script type="text/javascript" src="testreporter.js"></script>
+
+  <!-- include source files here... -->
+  <script type="text/javascript" src="../../src/core.js"></script>
+  <script type="text/javascript" src="../../src/api.js"></script>
+  <script type="text/javascript" src="../../src/util.js"></script>
+  <script type="text/javascript" src="../../src/canvas.js"></script>
+  <script type="text/javascript" src="../../src/obj.js"></script>
+  <script type="text/javascript" src="../../src/function.js"></script>
+  <script type="text/javascript" src="../../src/charsets.js"></script>
+  <script type="text/javascript" src="../../src/cidmaps.js"></script>
+  <script type="text/javascript" src="../../src/colorspace.js"></script>
+  <script type="text/javascript" src="../../src/crypto.js"></script>
+  <script type="text/javascript" src="../../src/evaluator.js"></script>
+  <script type="text/javascript" src="../../src/fonts.js"></script>
+  <script type="text/javascript" src="../../src/glyphlist.js"></script>
+  <script type="text/javascript" src="../../src/image.js"></script>
+  <script type="text/javascript" src="../../src/metrics.js"></script>
+  <script type="text/javascript" src="../../src/parser.js"></script>
+  <script type="text/javascript" src="../../src/pattern.js"></script>
+  <script type="text/javascript" src="../../src/stream.js"></script>
+  <script type="text/javascript" src="../../src/worker.js"></script>
+  <script type="text/javascript" src="../../src/metadata.js"></script>
+  <script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
+  <script type="text/javascript">PDFJS.workerSrc = '../../src/worker_loader.js';</script>
+  
+  <!-- include spec files here... -->
+  <script type="text/javascript" src="obj_spec.js"></script>
+  <script type="text/javascript" src="font_spec.js"></script>
+  <script type="text/javascript" src="function_spec.js"></script>
+  <script type="text/javascript" src="crypto_spec.js"></script>
+  <script type="text/javascript" src="stream_spec.js"></script>
+  <script type="text/javascript" src="api_spec.js"></script>
+  <script type="text/javascript">
+    'use strict';
+
+    (function pdfJsUnitTest() {
+      function queryParams() {
+        var qs = window.location.search.substring(1);
+        var kvs = qs.split('&');
+        var params = { };
+        for (var i = 0; i < kvs.length; ++i) {
+          var kv = kvs[i].split('=');
+          params[unescape(kv[0])] = unescape(kv[1]);
+        }
+        return params;
+      }
+
+      var jasmineEnv = jasmine.getEnv();
+      jasmineEnv.updateInterval = 1000;
+
+      var trivialReporter = new jasmine.TrivialReporter();
+
+      jasmineEnv.addReporter(trivialReporter);
+
+      var params = queryParams();
+      if (params['browser']) {
+        var testReporter = new TestReporter(params['browser'], params['path']);
+        jasmineEnv.addReporter(testReporter);
+      }
+
+      jasmineEnv.specFilter = function pdfJsUnitTestSpecFilter(spec) {
+        return trivialReporter.specFilter(spec);
+      };
+
+      var currentWindowOnload = window.onload;
+
+      window.onload = function pdfJsUnitTestOnload() {
+        if (currentWindowOnload) {
+          currentWindowOnload();
+        }
+        execJasmine();
+      };
+
+      function execJasmine() {
+        jasmineEnv.execute();
+      }
+    })();
+  </script>
+</head>
+<body>
+</body>
+</html>
+