Compare commits

..

No commits in common. "master" and "duck-punch-sprockets" have entirely different histories.

197 changed files with 1288 additions and 6294 deletions

9
.autotest Normal file
View File

@ -0,0 +1,9 @@
Autotest.add_hook(:initialize) do |at|
at.add_mapping(%r{bin/.*}, true) do |filename|
at.files_matching(%r{spec/bin/.*})
end
at.add_mapping(%r{spec/jasmine/.*}, true) do |filename|
at.files_matching(%r{spec/bin/.*})
end
end

11
.gitignore vendored
View File

@ -6,14 +6,3 @@ Makefile
specrunner.moc
specrunner.o
ext/jasmine-webkit-specrunner/jasmine-webkit-specrunner
*.o
moc_*.*
.DS_Store
hydra-runner.log
jhw-test
.jhw-cache/
_site/
jhw.*.html
coverage/
tmp/
cache dir/

View File

@ -1,84 +1,3 @@
## 0.7.2
* Improved finding of CoffeeScript spec line locations
* Improved Runner reporting of which expectations failed
* Initial vendored helper support, paving the way for Sprockets integratioon (maybe!)
* Add 1.9.3-rc1 test support and fixes
* Add console.peek()
## 0.7.1
* Bugfix for missing digest/sha1 import
## 0.7.0
* Major C++ cleanup, now much more modular
* Greatly improved object inspection and printing provided by jsDump and beautify-js
## 0.6.3
* Ensure Rubygems is available before doing version comparison
* Fix other build problems
* Better output for jQuery nodes
## 0.6.2
* Clean up C++ and test running
## 0.6.1
* Ensure YAML is loaded before use
* Make sure specs can't break out of the page they're running in
* Fix compilation on FreeBSD
## 0.6.0
* File and line number information for failing specs
* Try to build the runner if it's missing
* Kill warnings and streamline includes
## 0.5.0
* Run all tests after focused run if `console.log` was used
* Ensure Rake task works outside of Rails
* Ensure focused tests aren't run when CLI called with no files
* Support globs in focused test filters
* Raise exceptions on Rake task failures
* Update to use Jasmine 1.1
## 0.4.2
* Fix Rails 3.1 Railtie so it's included properly
* Fix compilation of runner on Linux
* Run files that are outside of the project's scope
## 0.4.1
* Fix CoffeeScript concatenation bug
* Report CoffeeScript errors better
## 0.4.0
* Change how tests are counted for totals
* Run targeted and full tests in the same runner instance for speed!
* Concatenate adjacent CoffeeScript files before compilation for more speed!
* Ensure files are not required twice
* Break out runner usage from CLI so that it can be resued in Rake tasks and elsewhere
* Add a Rails 3.1 task to precompile all assets with a specific "MD5 hash"
## 0.2.3
* Better messages for JavaScript errors
* `console.pp` added for more in-depth object inspection
## 0.2.2
* Write out a reporting file that can be used for Guard notification
## 0.2.1
* Avoid a Railtie so JHW works outside of Rails
## 0.2.0
* Add a Rake task and a default task for Rails

24
Gemfile
View File

@ -1,4 +1,4 @@
source :rubygems
source "http://rubygems.org"
# Specify your gem's dependencies in jasmine-headless-webkit.gemspec
gemspec
@ -6,25 +6,9 @@ gemspec
gem 'rspec'
gem 'fakefs', :require => nil
gem 'guard'
gem 'guard-rspec'
gem 'guard-shell'
gem 'guard-coffeescript'
gem 'guard-cucumber'
require 'rbconfig'
case RbConfig::CONFIG['host_os']
when /darwin/
when /linux/
gem 'libnotify'
end
gem 'mocha'
gem 'cucumber'
gem 'jquery-rails', '~> 1.0.0'
gem 'ejs'
gem 'guard-jasmine-headless-webkit', :git => 'git://github.com/johnbintz/guard-jasmine-headless-webkit.git'
gem 'growl'
gem 'rake', '0.8.7'
gem 'mocha', '0.9.12'

View File

@ -3,14 +3,8 @@
# watch('file/path') { `command(s)` }
#
guard 'coffeescript', :input => 'vendor/assets/coffeescripts', :output => 'vendor/assets/javascripts'
guard 'shell' do
watch(%r{ext/jasmine-webkit-specrunner/.*\.(cpp|h|pro|pri)}) { |m|
if !m[0]['moc_']
compile
end
}
watch(%r{ext/jasmine-webkit-specrunner/specrunner.cpp}) { compile }
end
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
@ -22,20 +16,10 @@ guard 'rspec', :version => 2, :all_on_start => false do
watch('spec/spec_helper.rb') { "spec" }
end
guard 'cucumber', :cli => '-r features --format pretty' do
watch(%r{^features/.+\.feature$})
watch(%r{^features/support/.+$}) { 'features' }
watch(%r{^features/steps/(.+)_steps\.rb$}) { 'features' }
end
guard 'jasmine-headless-webkit', :all_on_start => false do
watch(%r{^spec/javascripts/.+_spec\.coffee})
watch(%r{^jasmine/(.+)\.coffee$}) { |m| "spec/javascripts/#{m[1]}_spec.coffee" }
end
def compile
system %{cd ext/jasmine-webkit-specrunner && ruby extconf.rb}
end
compile
guard 'coffeescript', :input => 'jasmine'

View File

@ -1,5 +1,3 @@
_This project is dead. You should use [Karma](http://karma-runner.github.io/) instead. I do._
# Jasmine Headless WebKit runner
Run your specs at sonic boom speed! No pesky reload button or page rendering slowdowns!
@ -7,19 +5,6 @@ Run your specs at sonic boom speed! No pesky reload button or page rendering slo
http://johnbintz.github.com/jasmine-headless-webkit/ has the most up-to-date information on using
this project. You can see the source of that site on the gh-pages branch.
## For those who want to hack on the project...
The best way to get everything running that you need for development and testing is
to use Guard:
``` bash
bundle install
bundle exec guard
... build Qt runner ...
... compile CoffeeScript to JS ...
... run RSpec and JHW ...
```
## License
* Copyright (c) 2011 John Bintz

View File

@ -1,5 +1,3 @@
include Rake::DSL if defined?(Rake::DSL)
require 'bundler'
Bundler::GemHelper.install_tasks
@ -7,59 +5,28 @@ require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
$: << File.expand_path('../lib', __FILE__)
require 'jasmine-headless-webkit'
require 'jasmine/headless/task'
Jasmine::Headless::Task.new
PLATFORMS = %w{1.9.2 1.9.3}
def rvm_bundle(command = '')
Bundler.with_clean_env do
system %{bash -c 'unset BUNDLE_BIN_PATH && unset BUNDLE_GEMFILE && rvm #{PLATFORMS.join(',')} do bundle #{command}'}
end
end
class SpecFailure < StandardError; end
class BundleFailure < StandardError; end
namespace :spec do
desc "Run on three Rubies"
task :platforms do
rvm_bundle
rvm_bundle "exec rspec spec"
rvm_bundle "exec cucumber"
raise SpecError.new if $?.exitstatus != 0
current = %x{rvm-prompt v}
fail = false
%w{1.8.7 1.9.2 ree}.each do |version|
puts "Switching to #{version}"
Bundler.with_clean_env do
system %{bash -c 'source ~/.rvm/scripts/rvm ; rvm #{version} ; bundle install ; bundle exec rake spec'}
end
if $?.exitstatus != 0
fail = true
break
end
end
task :default => [ 'spec:platforms', 'jasmine:headless' ]
system %{rvm #{current}}
desc "Build the runner"
task :build_runner do
Dir.chdir 'ext/jasmine-webkit-specrunner' do
system %{ruby extconf.rb}
exit (fail ? 1 : 0)
end
end
desc "Generate vendored JS"
task :generate_js do
require 'sprockets'
source = 'vendor/assets/coffeescripts'
target = 'vendor/assets/javascripts'
env = Sprockets::Environment.new { |s| s.append_path 'vendor/assets/coffeescripts' }
Dir[File.join(File.expand_path(source), '*.coffee')].each do |file|
file_target = file.gsub(source, target).gsub('.coffee', '.js')
puts "#{file} => #{file_target}"
File.open(file_target, 'wb') do |fh|
fh.print env.find_asset(File.expand_path(file)).to_s
end
end
end
task :default => 'spec:platforms'

View File

@ -2,10 +2,87 @@
require 'rubygems'
$: << File.expand_path('../../lib', __FILE__)
def gem_dir
File.expand_path('../..', __FILE__)
end
require 'jasmine-headless-webkit'
$:.unshift(File.join(gem_dir, 'lib'))
require 'benchmark'
require 'yaml'
require 'fileutils'
require 'getoptlong'
require 'jasmine/base'
require 'coffee-script'
require 'rainbow'
Jasmine::Headless::CommandLine.run!
require 'jasmine/cli'
require 'jasmine/files_list'
require 'jasmine/template_writer'
include Jasmine::CLI
if !File.file?(File.join(gem_dir, RUNNER))
puts "The Qt WebKit widget is not compiled! Try re-installing this gem."
exit 1
end
opts = GetoptLong.new(
[ '--colors', '-c', GetoptLong::NO_ARGUMENT ],
[ '--no-colors', GetoptLong::NO_ARGUMENT ],
[ '--keep', GetoptLong::NO_ARGUMENT ],
[ '--report', GetoptLong::REQUIRED_ARGUMENT ],
[ '--jasmine-config', '-j', GetoptLong::REQUIRED_ARGUMENT ],
[ '--no-full-run', GetoptLong::NO_ARGUMENT ]
)
options = {
:colors => false,
:remove_html_file => true,
:jasmine_config => 'spec/javascripts/support/jasmine.yml',
:report => false,
:full_run => true
}
@process_options = lambda { |*args|
opt, arg = args.flatten[0..1]
case opt
when '--colors', '-c'
options[:colors] = true
when '--no-colors', '-nc'
options[:colors] = false
when '--keep'
options[:remove_html_file] = false
when '--report'
options[:report] = arg
when '--jasmine-config', '-j'
options[:jasmine_config] = arg
when '--no-full-run'
options[:full_run] = false
end
}
read_defaults_files!
opts.each(&@process_options)
puts "Running Jasmine specs..."
files_list = Jasmine::FilesList.new(
:config => load_config(options[:jasmine_config]),
:only => ARGV.dup
)
targets = Jasmine::TemplateWriter.write!(files_list)
run_targets = targets.dup
run_targets.pop if !options[:full_run]
system jasmine_command(options, run_targets)
status = $?.exitstatus
if options[:remove_html_file] || (status == 0)
targets.each { |target| FileUtils.rm_f target }
end
exit status

View File

@ -1,2 +0,0 @@
default: -r features

View File

@ -1,5 +0,0 @@
build:
true
install:
true

View File

@ -1,15 +0,0 @@
#include <QtGui>
#include <QtWebKit>
#include <iostream>
#include "Page.h"
Page::Page() : QWebPage() {}
void Page::javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID) {
emit handleError(message, lineNumber, sourceID);
}
void Page::javaScriptAlert(QWebFrame *, const QString &) {}
bool Page::javaScriptConfirm(QWebFrame *, const QString &) { return false; }
bool Page::javaScriptPrompt(QWebFrame *, const QString &, const QString &, QString *) { return false; }

View File

@ -1,20 +0,0 @@
#ifndef JHW_PAGE
#define JHW_PAGE
#include <QtGui>
#include <QtWebKit>
class Page: public QWebPage {
Q_OBJECT
public:
Page();
protected:
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
void javaScriptAlert(QWebFrame *, const QString &);
bool javaScriptConfirm(QWebFrame *, const QString &);
bool javaScriptPrompt(QWebFrame *, const QString &, const QString &, QString *);
signals:
void handleError(const QString & message, int lineNumber, const QString & sourceID);
};
#endif

View File

@ -1,208 +0,0 @@
#include <QtGui>
#include <QtWebKit>
#include <QFile>
#include <QTextStream>
#include <iostream>
#include <sstream>
#include <QQueue>
#include "Runner.h"
#include "Page.h"
using namespace std;
Runner::Runner() : QObject()
, runs(0)
, hasErrors(false)
, _hasSpecFailure(false)
, usedConsole(false)
, isFinished(false)
, useColors(false)
, quiet(false)
{
page.settings()->enablePersistentStorage();
ticker.setInterval(TIMER_TICK);
connect(&ticker, SIGNAL(timeout()), this, SLOT(timerEvent()));
connect(&page, SIGNAL(loadFinished(bool)), this, SLOT(watch(bool)));
connect(&page, SIGNAL(handleError(const QString &, int, const QString &)), this, SLOT(handleError(const QString &, int, const QString &)));
connect(page.mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJHW()));
}
void Runner::addFile(const QString &spec) {
runnerFiles.enqueue(spec);
}
void Runner::go() {
ticker.stop();
page.setPreferredContentsSize(QSize(1024, 600));
addJHW();
loadSpec();
}
void Runner::addJHW() {
page.mainFrame()->addToJavaScriptWindowObject("JHW", this);
}
void Runner::handleError(const QString &message, int lineNumber, const QString &sourceID) {
QString messageEscaped = QString(message);
QString sourceIDEscaped = QString(sourceID);
messageEscaped.replace(QString("\""), QString("\\\""));
sourceIDEscaped.replace(QString("\""), QString("\\\""));
std::stringstream ss;
ss << lineNumber;
QString command("JHW._handleError(\"" + messageEscaped + "\", " + QString(ss.str().c_str()) + ", \"" + sourceIDEscaped + "\"); false;");
page.mainFrame()->evaluateJavaScript(command);
hasErrors = true;
}
void Runner::loadSpec()
{
QVectorIterator<QString> iterator(reportFiles);
while (iterator.hasNext()) {
QFile *outputFile = new QFile(iterator.next());
outputFile->open(QIODevice::WriteOnly);
outputFiles.enqueue(outputFile);
}
QString runnerFile = runnerFiles.dequeue();
page.mainFrame()->load(runnerFile);
ticker.start();
}
void Runner::watch(bool ok) {
if (!ok) {
std::cerr << "Can't load " << qPrintable(page.mainFrame()->url().toString()) << ", the file may be broken." << std::endl;
std::cerr << "Out of curiosity, did your tests try to submit a form and you haven't prevented that?" << std::endl;
std::cerr << "Try running your tests in your browser with the Jasmine server and see what happens." << std::endl;
QApplication::instance()->exit(1);
return;
}
page.mainFrame()->evaluateJavaScript(QString("JHW._setColors(") + (useColors ? QString("true") : QString("false")) + QString("); false;"));
}
void Runner::setColors(bool colors) {
useColors = colors;
}
void Runner::hasUsedConsole() {
usedConsole = true;
}
void Runner::hasError() {
hasErrors = true;
}
void Runner::hasSpecFailure() {
_hasSpecFailure = true;
}
void Runner::setReportFiles(QStack<QString> &files) {
reportFiles = files;
}
void Runner::timerPause() {
ticker.stop();
}
void Runner::timerDone() {
ticker.start();
}
void Runner::ping() {
runs = 0;
}
void Runner::setSeed(QString s) {
seed = s;
}
void Runner::setQuiet(bool q) {
quiet = q;
}
QString Runner::getSeed() {
return seed;
}
bool Runner::isQuiet() {
return quiet;
}
void Runner::print(const QString &fh, const QString &content) {
if (fh == "stdout") {
std::cout << qPrintable(content);
std::cout.flush();
}
if (fh == "stderr") {
std::cerr << qPrintable(content);
std::cerr.flush();
}
if (fh.contains("report")) {
int index = (int)fh.split(":").last().toUInt();
QTextStream ts(outputFiles.at(index));
ts << qPrintable(content);
ts.flush();
}
}
void Runner::finishSuite() {
isFinished = true;
runs = 0;
}
void Runner::timerEvent() {
++runs;
if (hasErrors && runs > 2)
QApplication::instance()->exit(1);
if (isFinished && runs > 2) {
while (!outputFiles.isEmpty()) {
outputFiles.dequeue()->close();
}
int exitCode = 0;
if (_hasSpecFailure || hasErrors) {
exitCode = 1;
} else {
if (usedConsole) {
exitCode = 2;
}
}
bool runAgain = true;
if (runnerFiles.count() == 0) {
runAgain = false;
} else {
if (exitCode == 1) {
runAgain = false;
}
}
if (runAgain) {
isFinished = false;
loadSpec();
} else {
QApplication::instance()->exit(exitCode);
}
}
if (runs > MAX_LOOPS) {
std::cerr << "WARNING: too many runs and the test is still not finished!" << std::endl;
QApplication::instance()->exit(1);
}
}

View File

@ -1,73 +0,0 @@
#ifndef JHW_RUNNER
#define JHW_RUNNER
#include <QtGui>
#include <QtWebKit>
#include <QFile>
#include <QTextStream>
#include <iostream>
#include <fstream>
#include <sstream>
#include <QQueue>
#include <QApplication>
#include "Page.h"
using namespace std;
class Runner: public QObject {
Q_OBJECT
public:
enum { TIMER_TICK = 200, MAX_LOOPS = 50 };
Runner();
void setColors(bool colors);
void setReportFiles(QStack<QString> &files);
void setSeed(QString s);
void setQuiet(bool q);
void addFile(const QString &spec);
void go();
public slots:
void timerPause();
void timerDone();
void hasUsedConsole();
void hasError();
void hasSpecFailure();
bool isQuiet();
QString getSeed();
void print(const QString &fh, const QString &content);
void finishSuite();
void ping();
private slots:
void watch(bool ok);
void addJHW();
void timerEvent();
void handleError(const QString & message, int lineNumber, const QString & sourceID);
private:
Page page;
QTimer ticker;
int runs;
bool hasErrors;
bool _hasSpecFailure;
bool usedConsole;
bool isFinished;
bool useColors;
bool quiet;
QString seed;
QQueue<QString> runnerFiles;
QStack<QString> reportFiles;
void loadSpec();
QQueue<QFile *> outputFiles;
};
#endif

View File

@ -1,8 +0,0 @@
TEMPLATE = app
CONFIG -= app_bundle
QMAKE_INFO_PLIST = Info.plist
QT += network webkit
SOURCES = Page.cpp Runner.cpp
HEADERS = Page.h Runner.h

View File

@ -1,10 +1,2 @@
require 'fileutils'
$: << File.expand_path("../../../lib", __FILE__)
require 'qt/qmake'
system %{make clean}
Qt::Qmake.make!('jasmine-headless-webkit', 'specrunner.pro')
FileUtils.cp File.expand_path('../Makefile.dummy', __FILE__), File.expand_path('../Makefile', __FILE__)
system %{qmake -spec macx-g++}
system %{make}

View File

@ -21,63 +21,355 @@
THE SOFTWARE.
*/
#include "Runner.h"
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
#include <getopt.h>
#endif
#include <QtGui>
#include <QtWebKit>
#include <QFile>
#include <QTextStream>
#include <iostream>
#include <QQueue>
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
#error Use Qt 4.7 or later version
#endif
class HeadlessSpecRunnerPage: public QWebPage
{
Q_OBJECT
signals:
void consoleLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
protected:
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
bool javaScriptConfirm(QWebFrame *frame, const QString &msg);
void javaScriptAlert(QWebFrame *frame, const QString &msg);
};
void HeadlessSpecRunnerPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID)
{
emit consoleLog(message, lineNumber, sourceID);
}
bool HeadlessSpecRunnerPage::javaScriptConfirm(QWebFrame *frame, const QString &msg)
{
emit internalLog("TODO", "jasmine-headless-webkit can't handle confirm() yet! You should mock window.confirm for now. Returning true.");
return true;
}
void HeadlessSpecRunnerPage::javaScriptAlert(QWebFrame *frame, const QString &msg)
{
emit internalLog("alert", msg);
}
class HeadlessSpecRunner: public QObject
{
Q_OBJECT
public:
HeadlessSpecRunner();
void setColors(bool colors);
void reportFile(const QString &file);
void addFile(const QString &spec);
void go();
public slots:
void log(const QString &msg);
void specPassed();
void specFailed();
void printName(const QString &name);
void printResult(const QString &result);
void finishSuite(const QString &duration, const QString &total, const QString& failed);
private slots:
void watch(bool ok);
void errorLog(const QString &msg, int lineNumber, const QString &sourceID);
void internalLog(const QString &note, const QString &msg);
void addJHW();
protected:
bool hasElement(const char *select);
void timerEvent(QTimerEvent *event);
private:
HeadlessSpecRunnerPage m_page;
QBasicTimer m_ticker;
int m_runs;
bool hasErrors;
bool usedConsole;
bool showColors;
bool isFinished;
bool didFail;
bool consoleNotUsedThisRun;
QQueue<QString> runnerFiles;
QString reportFilename;
void red();
void green();
void yellow();
void clear();
void loadSpec();
};
HeadlessSpecRunner::HeadlessSpecRunner()
: QObject()
, m_runs(0)
, hasErrors(false)
, usedConsole(false)
, showColors(false)
, isFinished(false)
, didFail(false)
, consoleNotUsedThisRun(false)
{
m_page.settings()->enablePersistentStorage();
connect(&m_page, SIGNAL(loadFinished(bool)), this, SLOT(watch(bool)));
connect(&m_page, SIGNAL(consoleLog(QString, int, QString)), this, SLOT(errorLog(QString, int, QString)));
connect(&m_page, SIGNAL(internalLog(QString, QString)), this, SLOT(internalLog(QString, QString)));
connect(m_page.mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJHW()));
}
void HeadlessSpecRunner::addFile(const QString &spec)
{
runnerFiles.enqueue(spec);
}
void HeadlessSpecRunner::go()
{
m_ticker.stop();
m_page.setPreferredContentsSize(QSize(1024, 600));
addJHW();
loadSpec();
}
void HeadlessSpecRunner::addJHW()
{
m_page.mainFrame()->addToJavaScriptWindowObject("JHW", this);
}
void HeadlessSpecRunner::loadSpec()
{
m_page.mainFrame()->load(runnerFiles.dequeue());
m_ticker.start(200, this);
}
void HeadlessSpecRunner::watch(bool ok)
{
if (!ok) {
std::cerr << "Can't load " << qPrintable(m_page.mainFrame()->url().toString()) << ", the file may be broken." << std::endl;
std::cerr << "Out of curiosity, did your tests try to submit a form and you haven't prevented that?" << std::endl;
std::cerr << "Try running your tests in your browser with the Jasmine server and see what happens." << std::endl;
QApplication::instance()->exit(1);
return;
}
m_ticker.start(200, this);
}
bool HeadlessSpecRunner::hasElement(const char *select)
{
return !m_page.mainFrame()->findFirstElement(select).isNull();
}
void HeadlessSpecRunner::setColors(bool colors)
{
showColors = colors;
}
void HeadlessSpecRunner::reportFile(const QString &file)
{
reportFilename = file;
}
void HeadlessSpecRunner::red()
{
if (showColors) std::cout << "\033[0;31m";
}
void HeadlessSpecRunner::green()
{
if (showColors) std::cout << "\033[0;32m";
}
void HeadlessSpecRunner::yellow()
{
if (showColors) std::cout << "\033[0;33m";
}
void HeadlessSpecRunner::clear()
{
if (showColors) std::cout << "\033[m";
}
void HeadlessSpecRunner::specPassed()
{
consoleNotUsedThisRun = true;
green();
std::cout << '.';
clear();
fflush(stdout);
}
void HeadlessSpecRunner::specFailed()
{
consoleNotUsedThisRun = true;
didFail = true;
red();
std::cout << 'F';
clear();
fflush(stdout);
}
void HeadlessSpecRunner::errorLog(const QString &msg, int lineNumber, const QString &sourceID)
{
red();
std::cout << "[error] ";
clear();
std::cout << qPrintable(sourceID) << ":" << lineNumber << " : " << qPrintable(msg);
std::cout << std::endl;
hasErrors = true;
m_runs = 0;
m_ticker.start(200, this);
}
void HeadlessSpecRunner::internalLog(const QString &note, const QString &msg) {
red();
std::cout << "[" << qPrintable(note) << "] ";
clear();
std::cout << qPrintable(msg);
std::cout << std::endl;
}
void HeadlessSpecRunner::log(const QString &msg)
{
usedConsole = true;
green();
if (consoleNotUsedThisRun) {
std::cout << std::endl;
consoleNotUsedThisRun = false;
}
std::cout << "[console] ";
clear();
if (msg.contains("\n"))
std::cout << std::endl;
std::cout << qPrintable(msg);
std::cout << std::endl;
}
void HeadlessSpecRunner::printName(const QString &name)
{
std::cout << std::endl << std::endl;
red();
std::cout << qPrintable(name) << std::endl;
clear();
}
void HeadlessSpecRunner::printResult(const QString &result)
{
red();
std::cout << " " << qPrintable(result) << std::endl;
clear();
}
void HeadlessSpecRunner::finishSuite(const QString &duration, const QString &total, const QString& failed)
{
std::cout << std::endl;
if (didFail) {
red();
std::cout << "FAIL: ";
} else {
green();
std::cout << "PASS";
if (hasErrors) {
std::cout << " with JS errors";
}
std::cout << ": ";
}
std::cout << qPrintable(total) << " tests, " << qPrintable(failed) << " failures, " << qPrintable(duration) << " secs.";
clear();
std::cout << std::endl;
if (!reportFilename.isEmpty()) {
QFile reportFH(reportFilename);
if (reportFH.open(QFile::WriteOnly)) {
QTextStream report(&reportFH);
report << qPrintable(total) << "/" << qPrintable(failed) << "/";
report << (usedConsole ? "T" : "F");
report << "/" << qPrintable(duration) << "\n";
reportFH.close();
}
}
isFinished = true;
}
void HeadlessSpecRunner::timerEvent(QTimerEvent *event)
{
++m_runs;
if (event->timerId() != m_ticker.timerId())
return;
if (hasErrors && m_runs > 2)
QApplication::instance()->exit(1);
if (isFinished) {
int exitCode = 0;
if (didFail || hasErrors) {
exitCode = 1;
} else {
if (usedConsole) {
exitCode = 2;
}
}
if ((exitCode == 0 && runnerFiles.count() == 0) || (exitCode != 0)) {
QApplication::instance()->exit(exitCode);
} else {
isFinished = false;
loadSpec();
}
}
if (m_runs > 30) {
std::cout << "WARNING: too many runs and the test is still not finished!" << std::endl;
QApplication::instance()->exit(1);
}
}
#include "specrunner.moc"
int main(int argc, char** argv)
{
bool showColors = false;
bool isQuiet = false;
QString seed;
QStack<QString> reporterFiles;
char *reporter = NULL;
char showColors = false;
int c, index;
while ((c = getopt(argc, argv, "cr:s:q")) != -1) {
while ((c = getopt(argc, argv, "cr:")) != -1) {
switch(c) {
case 'c':
showColors = true;
break;
case 'q':
isQuiet = true;
break;
case 'r':
reporterFiles.push(QString(optarg));
break;
case 's':
seed = QString(optarg);
reporter = optarg;
break;
}
}
if (optind == argc) {
std::cerr << "Run Jasmine's SpecRunner headlessly" << std::endl << std::endl;
std::cerr << " specrunner [-c] [-s seed] [-r report file ...] specrunner.html ..." << std::endl;
std::cerr << " specrunner [-c] [-r <report file>] specrunner.html ..." << std::endl;
return 1;
}
QApplication app(argc, argv);
app.setApplicationName("jasmine-headless-webkit");
Runner runner;
runner.setColors(showColors);
runner.setQuiet(isQuiet);
runner.setReportFiles(reporterFiles);
runner.setSeed(seed);
HeadlessSpecRunner runner;
runner.setColors(true);
runner.reportFile(reporter);
for (index = optind; index < argc; index++) {
runner.addFile(QString::fromLocal8Bit(argv[index]));
}
runner.go();
return app.exec();
}

View File

@ -1,5 +1,7 @@
include(common.pri)
SOURCES += specrunner.cpp
TEMPLATE = app
CONFIG -= app_bundle
TARGET = jasmine-webkit-specrunner
SOURCES = specrunner.cpp
QT += network webkit
QMAKE_INFO_PLIST = Info.plist
QMAKESPEC = macx-gcc

View File

@ -1,7 +0,0 @@
Feature: Bin - Failure
Scenario: Run a failing test
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/failure/failure.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - Files
Scenario: List the files a test suite will use
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml -l`
Then the exit status should be 0
And the output should include "spec/jasmine/success/success.js"
And the output should include "spec/jasmine/success/success_spec.js"

View File

@ -1,18 +0,0 @@
Feature: Bin - Filtered Run - Both Runs
Background:
Given there is no existing "spec/report.txt" file
Scenario: Run one and fail
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_failure/filtered_failure.yml -f File:spec/report.txt ./spec/jasmine/filtered_failure/failure_spec.js`
Then the exit status should be 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage
Scenario: Run both and succeed
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success/success_one_spec.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 2 total, 0 failures, no console usage
Scenario: Run both with console.log
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success_with_console/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success_with_console/success_one_spec.js`
Then the exit status should be 2
And the report file "spec/report.txt" should have 2 total, 0 failures, yes console usage

View File

@ -1,14 +0,0 @@
Feature: Bin - No Full Run
Background:
Given there is no existing "spec/report.txt" file
Scenario: Only run the filtered run
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt --no-full-run ./spec/jasmine/filtered_success/success_one_spec.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failure, no console usage
Scenario: Use a file outside of the normal test run
When I run `bin/jasmine-headless-webkit -j spec/jasmine/filtered_success/filtered_success.yml -f File:spec/report.txt ./spec/jasmine/filtered_success/success_other_file.js`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failure, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - Help
Scenario: Display the Help
Given I have a test suite
When I run `bin/jasmine-headless-webkit -h`
Then I should get help output
And the exit status should be 0

View File

@ -1,8 +0,0 @@
Feature: Bin - Quiet Messages
Scenario: Run a test that would cause a lot of messages to be displayed and silence them all
Given I have a test suite
When I run `bin/jasmine-headless-webkit -q -j spec/jasmine/noisy/noisy.yml`
Then the exit status should be 0
And the output should not include "[Skipping File]"
And the output should not include "You should mock"

View File

@ -1,8 +0,0 @@
Feature: Bin - Runner Out
Scenario: Write out the runner to a specified file
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml --runner-out spec/runner.html`
Then the exit status should be 0
And the file "spec/runner.html" should contain a JHW runner
When I delete the file "spec/runner.html"

View File

@ -1,7 +0,0 @@
Feature: Bin - Two spec files with same basename
Scenario: Run both files
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/two_spec_files_same_basename/jasmine.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 2 total, 0 failures, no console usage

View File

@ -1,20 +0,0 @@
Feature: Bin - Success
Scenario: Run a successful test with long format definition
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit --seed 1234 -j spec/jasmine/success/success.yml --format File --out spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
And the report file "spec/report.txt" should have seed 1234
Scenario: Run a successful test with legacy file reporting
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml --report spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
Scenario: Run a successful test with shortened format definition
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success/success.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage

View File

@ -1,5 +0,0 @@
Feature: Bin - Success with JS Error
Scenario: Succeed
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/success_with_error/success_with_error.yml -f File:spec/report.txt`
Then the exit status should be 1

View File

@ -1,7 +0,0 @@
Feature: Bin - Try to Leave Page
Scenario: Fail on trying to leave the page
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/leave_page/leave_page.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should exist

View File

@ -1,7 +0,0 @@
Feature: Bin - Try to Click A Button
Scenario: Don't leave page when clicking a button
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/click_button/click_button.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 0 total, 0 failures, no console usage

View File

@ -1,11 +0,0 @@
Feature: Two files from source files
Scenario: Files are ordered directly
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/two_files_from_src_files/jasmine.yml -l`
Then the exit status should be 0
And the following files should be loaded in order:
| vendor/vendor-file.js |
| vendor/vendor.js |
| app/app-file.js |
| app/app.js |

View File

@ -1,7 +0,0 @@
Feature: Bin - With CoffeeScript error
Scenario: Fail on CoffeeScript error
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/coffeescript_error/coffeescript_error.yml -f File:spec/report.txt`
Then the exit status should be 1
And the report file "spec/report.txt" should not exist

View File

@ -1,7 +0,0 @@
Feature: Use console.log
Scenario: Run a successful test that uses console.log
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit -j spec/jasmine/console_log/console_log.yml -f File:spec/report.txt`
Then the exit status should be 2
And the report file "spec/report.txt" should have 1 total, 0 failures, yes console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - With Server
Scenario: Run using an HTTP server
Given there is no existing "spec/report.txt" file
When I run `bin/jasmine-headless-webkit --use-server -j spec/jasmine/success/success.yml -f File:spec/report.txt`
Then the exit status should be 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage

View File

@ -1,7 +0,0 @@
Feature: Bin - With window.prompt()
Scenario: Alert the user that window.prompt() needs to be stubbed
Given I have a test suite
When I run `bin/jasmine-headless-webkit -j spec/jasmine/window_prompt/window_prompt.yml`
Then the exit status should be 0
And the output should include "You should mock window.prompt"

View File

@ -1,24 +0,0 @@
Feature: Reporters
In order to allow for multiple types of output
I should be able to
Manage reporters and decide which ones to use
Scenario: Use default reporters
Given I have the default runner options
When I get a runner
And I get a template writer
Then the template should use the "Console" reporter to "stdout"
And the command to run the runner should not include a report file
Scenario: Use a file reporter
Given I have the default runner options
And I have the following reporters:
| Name | File |
| Console | |
| File | file |
When I get a runner
And I get a template writer
Then the template should use the "Console" reporter to "stdout"
And the template should use the "File" reporter to "report:0"
And the command to run the runner should include the report file "file"

View File

@ -1,34 +0,0 @@
Feature: Using the Runner directly
Scenario: Succeed
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/success/success.yml
:reporters:
- [ 'File', 'spec/report.txt' ]
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 0
And the report file "spec/report.txt" should have 1 total, 0 failures, no console usage
Scenario: JavaScript Error
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/success_with_error/success_with_error.yml
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 1
Scenario: Failure
Given I have the following runner options:
"""
:jasmine_config: spec/jasmine/failure/failure.yml
:reporters:
- [ 'File', 'spec/report.txt' ]
"""
When I get a runner
And I run the runner
Then the runner should have an exit status of 1
And the report file "spec/report.txt" should have 1 total, 1 failure, no console usage

View File

@ -1,2 +0,0 @@
Given /^I have a test suite$/ do
end

View File

@ -1,4 +0,0 @@
Given /^there is no existing "([^"]*)" file$/ do |file|
FileUtils.rm_rf file
end

View File

@ -1,4 +0,0 @@
Given /^I have the default runner options$/ do
@options = Jasmine::Headless::Options.new
end

View File

@ -1,10 +0,0 @@
Given /^I have the following reporters:$/ do |table|
@options[:reporters] = []
table.hashes.each do |hash|
reporter = [ hash['Name'] ]
reporter << hash['File'] if !hash['File'].empty?
@options[:reporters] << reporter
end
end

View File

@ -1,3 +0,0 @@
Given /^I have the following runner options:$/ do |string|
@options = YAML.load(string)
end

View File

@ -1,3 +0,0 @@
Then /^the exit status should be (\d+)$/ do |exitstatus|
$?.exitstatus.should == exitstatus.to_i
end

View File

@ -1,4 +0,0 @@
Then /^the file "([^"]*)" should contain a JHW runner$/ do |file|
File.read(file).should include('jasmine.HeadlessReporter')
end

View File

@ -1,10 +0,0 @@
Then /^the following files should be loaded in order:$/ do |table|
files = table.raw.flatten
@output.lines.collect(&:strip).each do |line|
files.shift if line[files.first]
end
files.should be_empty
end

View File

@ -1,4 +0,0 @@
Then /^I should get help output$/ do
@output.should include("Usage:")
end

View File

@ -1,3 +0,0 @@
Then /^the output should include "([^"]*)"$/ do |string|
@output.should include(string)
end

View File

@ -1,4 +0,0 @@
Then /^the output should not include "([^"]*)"$/ do |string|
@output.should_not include(string)
end

View File

@ -1,4 +0,0 @@
Then /^the report file "([^"]*)" should not exist$/ do |file|
File.file?(file).should be_false
end

View File

@ -1,3 +0,0 @@
Then /^the report file "([^"]*)" should exist$/ do |file|
File.file?(file).should be_true
end

View File

@ -1,7 +0,0 @@
Then /^the report file "(.*)" should have (\d+) total, (\d+) failures?, (no|yes) console usage$/ do |file, total, failures, console_usage|
report = Jasmine::Headless::Report.load(file)
report.total.should == total.to_i
report.failed.should == failures.to_i
report.has_used_console?.should == (console_usage == 'yes')
end

View File

@ -1,4 +0,0 @@
Then /^the report file "([^"]*)" should have seed (\d+)$/ do |file, seed|
report = Jasmine::Headless::Report.load(file)
report.seed.should == seed.to_i
end

View File

@ -1,3 +0,0 @@
Then /^the runner should have an exit status of (\d+)$/ do |exit_status|
@result.should == exit_status.to_i
end

View File

@ -1,4 +0,0 @@
Then /^the command to run the runner should include the report file "([^"]*)"$/ do |file|
@runner.jasmine_command.should include("-r #{file}")
end

View File

@ -1,3 +0,0 @@
Then /^the command to run the runner should not include a report file$/ do
@runner.jasmine_command.should_not include('-r')
end

View File

@ -1,6 +0,0 @@
Then /^the template should use the "([^"]*)" reporter to "([^"]*)"$/ do |reporter, target|
output = @template_writer.render
output.should include(%{jasmine.HeadlessReporter.#{reporter}("#{target}")})
end

View File

@ -1,3 +0,0 @@
When /^I delete the file "([^"]*)"$/ do |file|
FileUtils.rm_f(file)
end

View File

@ -1,4 +0,0 @@
When /^I get a runner$/ do
@runner = Jasmine::Headless::Runner.new(@options)
end

View File

@ -1,4 +0,0 @@
When /^I get a template writer$/ do
@template_writer = Jasmine::Headless::TemplateWriter.new(@runner)
end

View File

@ -1,4 +0,0 @@
When /^I run `(.*)`$/ do |command|
@output = `#{command}`
end

View File

@ -1,4 +0,0 @@
When /^I run the runner$/ do
@result = @runner.run
end

View File

@ -1,7 +0,0 @@
require 'jasmine-headless-webkit'
After do
FileUtils.rm_f 'spec/report.txt'
FileUtils.rm_f 'spec/runner.html'
end

View File

@ -1,10 +1,10 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "jasmine/headless/version"
require "jasmine-headless-webkit/version"
Gem::Specification.new do |s|
s.name = "jasmine-headless-webkit"
s.version = Jasmine::Headless::VERSION
s.version = Jasmine::Headless::Webkit::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["John Bintz", "Sencha Inc.", "Pivotal Labs"]
s.email = ["john@coswellproductions.com"]
@ -20,11 +20,7 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_runtime_dependency 'jasmine-core'
s.add_runtime_dependency 'coffee-script'
s.add_runtime_dependency 'rainbow'
s.add_runtime_dependency 'multi_json', '>= 1.2.0'
s.add_runtime_dependency 'sprockets'
s.add_runtime_dependency 'sprockets-vendor_gems'
s.add_dependency 'jasmine'
s.add_dependency 'coffee-script', '>= 2.2'
s.add_dependency 'rainbow'
end

View File

@ -0,0 +1,44 @@
if !jasmine?
throw new Error("jasmine not laoded!")
class HeadlessReporterResult
constructor: (name) ->
@name = name
@results = []
addResult: (message) ->
@results.push(message)
print: ->
JHW.printName(@name)
for result in @results
do (result) =>
JHW.printResult(result)
class jasmine.HeadlessReporter
constructor: ->
@results = []
@failedCount = 0
@length = 0
reportRunnerResults: (runner) ->
for result in @results
do (result) =>
result.print()
JHW.finishSuite((new Date() - @startTime) / 1000.0, @length, @failedCount)
reportRunnerStarting: (runner) ->
@startTime = new Date()
reportSpecResults: (spec) ->
results = spec.results()
@length++
if results.passed()
JHW.specPassed()
else
JHW.specFailed()
@failedCount++
failureResult = new HeadlessReporterResult(spec.getFullName())
for result in results.getItems()
do (result) =>
if result.type == 'expect' and !result.passed_
failureResult.addResult(result.message)
@results.push(failureResult)
reportSpecStarting: (spec) ->
reportSuiteResults: (suite) ->

View File

@ -0,0 +1,78 @@
(function() {
var HeadlessReporterResult;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
if (!(typeof jasmine !== "undefined" && jasmine !== null)) {
throw new Error("jasmine not laoded!");
}
HeadlessReporterResult = (function() {
function HeadlessReporterResult(name) {
this.name = name;
this.results = [];
}
HeadlessReporterResult.prototype.addResult = function(message) {
return this.results.push(message);
};
HeadlessReporterResult.prototype.print = function() {
var result, _i, _len, _ref, _results;
JHW.printName(this.name);
_ref = this.results;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
_results.push(__bind(function(result) {
return JHW.printResult(result);
}, this)(result));
}
return _results;
};
return HeadlessReporterResult;
})();
jasmine.HeadlessReporter = (function() {
function HeadlessReporter() {
this.results = [];
this.failedCount = 0;
this.length = 0;
}
HeadlessReporter.prototype.reportRunnerResults = function(runner) {
var result, _fn, _i, _len, _ref;
_ref = this.results;
_fn = __bind(function(result) {
return result.print();
}, this);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
_fn(result);
}
return JHW.finishSuite((new Date() - this.startTime) / 1000.0, this.length, this.failedCount);
};
HeadlessReporter.prototype.reportRunnerStarting = function(runner) {
return this.startTime = new Date();
};
HeadlessReporter.prototype.reportSpecResults = function(spec) {
var failureResult, result, results, _fn, _i, _len, _ref;
results = spec.results();
this.length++;
if (results.passed()) {
return JHW.specPassed();
} else {
JHW.specFailed();
this.failedCount++;
failureResult = new HeadlessReporterResult(spec.getFullName());
_ref = results.getItems();
_fn = __bind(function(result) {
if (result.type === 'expect' && !result.passed_) {
return failureResult.addResult(result.message);
}
}, this);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
_fn(result);
}
return this.results.push(failureResult);
}
};
HeadlessReporter.prototype.reportSpecStarting = function(spec) {};
HeadlessReporter.prototype.reportSuiteResults = function(suite) {};
return HeadlessReporter;
})();
}).call(this);

View File

@ -1,3 +1,9 @@
require 'jasmine/headless'
require 'jasmine/headless/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
module Jasmine
module Headless
module Webkit
end
end
end
require 'jasmine/headless/railtie' if defined?(Rails)

View File

@ -0,0 +1,7 @@
module Jasmine
module Headless
module Webkit
VERSION = "0.3.0"
end
end
end

54
lib/jasmine/cli.rb Normal file
View File

@ -0,0 +1,54 @@
module Jasmine
module CLI
DEFAULTS = {
'spec_files' => [ '**/*[sS]pec.js' ],
'helpers' => [ 'helpers/**/*.js' ],
'spec_dir' => 'spec/javascripts',
'src_dir' => nil,
'stylesheets' => [],
'src_files' => []
}
RUNNER = 'ext/jasmine-webkit-specrunner/jasmine-webkit-specrunner'
DEFAULTS_FILE = '.jasmine-headless-webkit'
GLOBAL_DEFAULTS_FILE = File.expand_path("~/#{DEFAULTS_FILE}")
def load_config(file)
process_jasmine_config(YAML.load_file(file))
end
def process_jasmine_config(overrides = {})
DEFAULTS.merge(overrides)
end
def read_defaults_files!
[ GLOBAL_DEFAULTS_FILE, DEFAULTS_FILE ].each do |file|
if File.file?(file)
File.readlines(file).collect { |line| line.strip.split(' ', 2) }.each(&@process_options)
end
end
end
def jasmine_html_template(files)
end
def runner_path
@runner_path ||= File.join(gem_dir, RUNNER)
end
def jasmine_command(options, targets)
[
runner_path,
options[:colors] ? '-c' : nil,
options[:report] ? "-r #{options[:report]}" : nil,
*targets
].join(" ")
end
private
def read_config_file(file)
end
end
end

119
lib/jasmine/files_list.rb Normal file
View File

@ -0,0 +1,119 @@
require 'jasmine'
module Jasmine
class FilesList
attr_reader :files, :filtered_files
DEFAULT_FILES = [
File.join(Jasmine.root, "lib/jasmine.js"),
File.join(Jasmine.root, "lib/jasmine-html.js"),
File.expand_path('../../../jasmine/jasmine.headless-reporter.js', __FILE__)
]
def initialize(options = {})
@options = options
@files = DEFAULT_FILES.dup
@filtered_files = @files.dup
use_config! if config?
@code_for_file = {}
end
def use_spec?(file)
spec_filter.empty? || spec_filter.include?(file)
end
def filtered?
files != filtered_files
end
def files_to_html
to_html(files)
end
def filtered_files_to_html
to_html(filtered_files)
end
private
def to_html(files)
coffeescript_run = []
files.collect { |file|
next @code_for_file[file] if @code_for_file[file]
coffeescript_run << file if (ext = File.extname(file)) == '.coffee'
output = []
if (files.last == file or ext != '.coffee') and !coffeescript_run.empty?
output << ensure_coffeescript_run!(coffeescript_run)
end
if ext != '.coffee'
output << case File.extname(file)
when '.js'
%{<script type="text/javascript" src="#{file}"></script>}
when '.css'
%{<link rel="stylesheet" href="#{file}" type="text/css" />}
end
end
@code_for_file[file] = output if output.length == 1
output
}.flatten.reject(&:empty?)
end
def ensure_coffeescript_run!(files)
data = StringIO.new
files.each { |file| data << File.read(file) }
data.rewind
%{<script type="text/javascript">#{CoffeeScript.compile(data)}</script>}
rescue CoffeeScript::CompilationError => e
files.each do |file|
begin
CoffeeScript.compile(fh = File.open(file))
rescue CoffeeScript::CompilationError => ne
puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), ne.message.to_s.color(:white) ]
exit 1
ensure
fh.close
end
end
ensure
files.clear
end
def spec_filter
@options[:only] || []
end
def use_config!
@filtered_files = @files.dup
data = @options[:config].dup
[ [ 'src_files', 'src_dir' ], [ 'stylesheets', 'src_dir' ], [ 'helpers', 'spec_dir' ], [ 'spec_files', 'spec_dir' ] ].each do |searches, root|
if data[searches]
data[searches].collect do |search|
path = search
path = File.join(data[root], path) if data[root]
found_files = Dir[path] - @files
@files += found_files
if searches == 'spec_files'
found_files = found_files.find_all { |file| use_spec?(file) }
end
@filtered_files += found_files
end
end
end
end
def config?
@options[:config]
end
end
end

View File

@ -1,61 +0,0 @@
require 'pathname'
module Jasmine
module Headless
EXCLUDED_FORMATS = %w{less sass scss erb str}
autoload :CommandLine, 'jasmine/headless/command_line'
autoload :CoffeeScriptCache, 'jasmine/headless/coffee_script_cache'
autoload :SpecFileAnalyzer, 'jasmine/headless/spec_file_analyzer'
autoload :CacheableAction, 'jasmine/headless/cacheable_action'
autoload :VERSION, 'jasmine/headless/version'
autoload :Runner, 'jasmine/headless/runner'
autoload :Options, 'jasmine/headless/options'
autoload :Task, 'jasmine/headless/task'
autoload :FilesList, 'jasmine/headless/files_list'
autoload :UniqueAssetList, 'jasmine/headless/unique_asset_list'
autoload :TemplateWriter, 'jasmine/headless/template_writer'
autoload :FileChecker, 'jasmine/headless/file_checker'
autoload :CoffeeTemplate, 'jasmine/headless/coffee_template'
autoload :JSTemplate, 'jasmine/headless/js_template'
autoload :JSTTemplate, 'jasmine/headless/jst_template'
autoload :CSSTemplate, 'jasmine/headless/css_template'
autoload :NilTemplate, 'jasmine/headless/nil_template'
autoload :Report, 'jasmine/headless/report'
autoload :ReportMessage, 'jasmine/headless/report_message'
class << self
def root
@root ||= Pathname(File.expand_path('../../..', __FILE__))
end
def warn(message)
output.puts message if show_warnings?
end
def show_warnings=(show)
@show_warnings = show
end
def show_warnings?
@show_warnings = true if @show_warnings.nil?
@show_warnings
end
def output
$stdout
end
end
end
end
require 'jasmine/headless/errors'

View File

@ -1,85 +0,0 @@
module Jasmine::Headless
class CacheableAction
class << self
def enabled=(bool)
@enabled = bool
end
def enabled?
@enabled = true if @enabled == nil
@enabled
end
def cache_type
raise ArgumentError.new("No cache type defined for #{self.name}") if @cache_type == nil
@cache_type
end
def cache_type=(type)
@cache_type = type
end
def cache_dir=(dir)
@cache_dir = dir
end
def cache_dir
@cache_dir ||= '.jhw-cache'
end
def for(file)
new(file).handle
end
end
attr_reader :file
def initialize(file)
@file = file
end
def handle
if CacheableAction.enabled?
if fresh?
unserialize(File.read(cache_file))
else
result = action
FileUtils.mkdir_p File.split(cache_file).first
File.open(cache_file, 'wb') { |fh| fh.print serialize(result) }
result
end
else
action
end
end
def cache_file
@cache_file ||= File.expand_path(relative_cache_file) + '.js'
end
def relative_cache_file
File.join(self.class.cache_dir, self.class.cache_type, file.gsub(Dir.pwd + '/', ''))
end
def fresh?
cached? && (File.mtime(file) < File.mtime(cache_file))
end
def cached?
File.exist?(cache_file)
end
def action
raise StandardError.new("Override action")
end
def serialize(data)
data
end
def unserialize(data)
data
end
end
end

View File

@ -1,20 +0,0 @@
require 'coffee_script'
require 'digest/sha1'
require 'fileutils'
module Jasmine
module Headless
class CoffeeScriptCache < CacheableAction
class << self
def cache_type
"coffee_script"
end
end
def action
CoffeeScript.compile(File.read(file))
end
end
end
end

View File

@ -1,36 +0,0 @@
require 'tilt/template'
require 'rainbow'
module Jasmine::Headless
class CoffeeTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
begin
cache = Jasmine::Headless::CoffeeScriptCache.new(file)
source = cache.handle
if cache.cached?
%{<script type="text/javascript" src="#{cache.cache_file}"></script>
<script type="text/javascript">window.CSTF['#{File.split(cache.cache_file).last}'] = '#{file}';</script>}
else
%{<script type="text/javascript">#{source}</script>}
end
rescue CoffeeScript::CompilationError => ne
puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), "#{ne.message}".color(:white) ]
raise ne
rescue StandardError => e
puts "[%s] Error in compiling file: %s" % [ 'coffeescript'.color(:red), file.color(:yellow) ]
raise e
end
end
end
end

View File

@ -1,31 +0,0 @@
module Jasmine::Headless
class CommandLine
class << self
def run!
require 'coffee-script'
require 'rainbow'
begin
options = Options.from_command_line
runner = Runner.new(options)
if options[:do_list]
FilesList.reset!
files_list = FilesList.new(:config => runner.jasmine_config)
files_list.files.each { |file| puts file }
else
exit runner.run
end
rescue CoffeeScript::CompilationError
exit 1
rescue StandardError => e
$stderr.puts "[%s] %s (%s)" % [ "jasmine-headless-webkit".color(:red), e.message.color(:white), e.class.name.color(:yellow) ]
$stderr.puts e.backtrace.collect { |line| " #{line}" }.join("\n")
exit 1
end
end
end
end
end

View File

@ -1,19 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class CSSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'text/css'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
file ? %{<link rel="stylesheet" href="#{file}" type="text/css" />} : data
end
end
end

View File

@ -1,16 +0,0 @@
module Jasmine
module Headless
class NoRunnerError < StandardError
def message
"The jasmine-headless-webkit specrunner (jasmine-webkit-specrunner) could not be found! Try reinstalling the gem."
end
end
class TestFailure < StandardError; end
class ConsoleLogUsage < StandardError ; end
class JasmineConfigNotFound < Errno::ENOENT ; end
class InvalidReport < StandardError ; end
end
end

View File

@ -1,25 +0,0 @@
module Jasmine::Headless::FileChecker
def excluded_formats
::Jasmine::Headless::EXCLUDED_FORMATS
end
def bad_format?(file)
return if file.nil?
excluded_formats.any? do |format|
file[%r{\.#{format}(\.|$)}]
end
end
def alert_bad_format(file)
Jasmine::Headless.warn("[%s] %s: %s" % [ 'Skipping File'.color(:red), file.color(:yellow), "unsupported format".color(:white) ])
end
def alert_if_bad_format?(file)
if result = bad_format?(file)
alert_bad_format(file)
end
result
end
end

View File

@ -1,332 +0,0 @@
require 'jasmine-core'
require 'time'
require 'multi_json'
require 'set'
require 'sprockets'
require 'sprockets/engines'
require 'sprockets-vendor_gems'
module Jasmine::Headless
class FilesList
include FileChecker
class << self
def asset_paths
@asset_paths ||= Sprockets.find_gem_vendor_paths(:for => 'javascripts')
end
def reset!
@asset_paths = nil
@registered_engines = {}
# register haml-sprockets and handlebars_assets if it's available...
%w{haml-sprockets handlebars_assets}.each do |library|
begin
require library
rescue LoadError
end
end
if ENV['JHW_ENV']
begin
require 'bundler'
Bundler.require(ENV['JHW_ENV'].to_sym)
rescue LoadError
end
end
@sprockets_environment = nil
end
def registered_engines
@registered_engines ||= {}
end
def register_engine(file_extension, template_class)
registered_engines[file_extension] = template_class
end
def register_engines!
registered_engines.each do |file_extension, template_class|
Sprockets.register_engine file_extension, template_class
end
end
def default_files
%w{jasmine.js jasmine-html jasmine.css jasmine-extensions
intense headless_reporter_result jasmine.HeadlessReporter
jasmine.HeadlessReporter.ConsoleBase
jsDump beautify-html}
end
def extension_filter
extensions = (%w{.js .css} + Sprockets.engine_extensions)
%r{(#{extensions.join('|')})$}
end
end
PLEASE_WAIT_IM_WORKING_TIME = 2
attr_reader :options, :required_files, :potential_files_to_filter
def initialize(options = {})
@options = options
Kernel.srand(options[:seed]) if options[:seed]
@required_files = UniqueAssetList.new
@potential_files_to_filter = []
register_engines!
load_initial_assets
use_config if config?
end
def register_engines!
begin
require spec_helper
rescue LoadError
end
self.class.register_engines!
end
def load_initial_assets
self.class.default_files.each do |file|
begin
add_path(file)
rescue InvalidUniqueAsset => e
raise StandardError.new("Not an asset: #{file}")
end
end
(options[:reporters] || []).each do |reporter, identifier, file|
add_path("jasmine.HeadlessReporter.#{reporter}")
end
end
def files
required_files.flatten.collect { |asset| asset.pathname.to_s }.uniq
end
def spec_files
filter_for_requested_specs(
files.find_all { |file| spec_dir.any? { |dir| file[dir] } }
)
end
def filtered_files
filter_for_requested_specs(files)
end
def search_paths
return @search_paths if @search_paths
@search_paths = [ Jasmine::Core.path, Jasmine::Headless.root.join('vendor/assets/javascripts').to_s ]
@search_paths += self.class.asset_paths
@search_paths += src_dir.collect { |dir| File.expand_path(dir) }
@search_paths += asset_paths.collect { |dir| File.expand_path(dir) }
@search_paths += spec_dir.collect { |dir| File.expand_path(dir) }
@search_paths.uniq!
@search_paths
end
def sprockets_environment
return @sprockets_environment if @sprockets_environment
@sprockets_environment = Sprockets::Environment.new
search_paths.each { |path| @sprockets_environment.append_path(path) }
@sprockets_environment.unregister_postprocessor('application/javascript', Sprockets::SafetyColons)
# ...and unregister ones we don't want/need
@sprockets_environment.instance_eval do
EXCLUDED_FORMATS.each do |extension|
register_engine ".#{extension}", Jasmine::Headless::NilTemplate
end
register_engine '.coffee', Jasmine::Headless::CoffeeTemplate
register_engine '.js', Jasmine::Headless::JSTemplate
register_engine '.css', Jasmine::Headless::CSSTemplate
register_engine '.jst', Jasmine::Headless::JSTTemplate
end
@sprockets_environment
end
def has_spec_outside_scope?
if is_outside_scope = !spec_filter.empty?
is_outside_scope = spec_dir.any? do |dir|
spec_file_searches.any? do |search|
!spec_files.any? do |file|
target = File.join(dir, search)
File.fnmatch?(target, file) || File.fnmatch?(target.gsub(%{^**/}, ''), file)
end
end
end
end
is_outside_scope
end
def filtered?
files != filtered_files
end
def files_to_html
to_html(files)
end
def filtered_files_to_html
to_html(filtered_files)
end
def spec_file_line_numbers
@spec_file_line_numbers ||= Hash[spec_files.collect { |file|
if File.exist?(file)
if !(lines = Jasmine::Headless::SpecFileAnalyzer.for(file)).empty?
[ file, lines ]
end
else
nil
end
}.compact]
end
private
def to_html(files)
alert_time = Time.now + PLEASE_WAIT_IM_WORKING_TIME
files.collect do |file|
if alert_time && alert_time < Time.now
puts "Rebuilding cache, please wait..."
alert_time = nil
end
sprockets_environment.find_asset(file, :bundle => false).body
end.compact.reject(&:empty?)
end
def spec_filter
@spec_filter ||= (@options[:only] && @options[:only].collect { |path| expanded_dir(path) }.flatten) || []
end
SEARCH_ROOTS = {
'src_files' => 'src_dir',
'stylesheets' => 'src_dir',
'helpers' => 'spec_dir',
'spec_files' => 'spec_dir'
}
def use_config
@config = @options[:config].dup
@searches = {}
@potential_files_to_filter = []
%w{src_files stylesheets helpers spec_files}.each do |type|
if data = @config[type]
add_files(@searches[type] = data.flatten, type, send(SEARCH_ROOTS[type]))
end
end
end
def add_files(patterns, type, dirs)
patterns.each do |pattern|
dirs.collect { |dir| expanded_dir(File.join(dir, pattern)) }.each do |files|
files.sort! { |a, b| Kernel.rand(3) - 1 } if type == 'spec_files'
files.each do |path|
add_path(path, type)
end
end
end
if type == 'spec_files'
spec_filter.each { |path| add_path(path, type) }
end
end
def config?
@options[:config]
end
def expanded_dir(path)
file_list = Dir.glob(path).sort
file_list.find_all { |file|
file[extension_filter] && !alert_if_bad_format?(file)
}.collect {
|file| File.expand_path(file)
}.find_all {
|path| File.file?(path)
}
end
def extension_filter
self.class.extension_filter
end
def add_path(path, type = nil)
asset = sprockets_environment.find_asset(path)
@required_files << asset
if type == 'spec_files'
@potential_files_to_filter << path
end
end
def src_dir
@src_dir ||= config_dir_or_pwd('src_dir') + asset_paths
end
def spec_dir
@spec_dir ||= config_dir_or_pwd('spec_dir')
end
def asset_paths
@asset_paths ||= config_dir('asset_paths')
end
def spec_file_searches
@searches['spec_files']
end
def config_dir_or_pwd(dir)
if (found = config_dir(dir)).empty?
found = [ Dir.pwd ]
end
found
end
def config_dir(dir)
[ @options[:config] && @options[:config][dir] ].flatten.compact.collect { |dir| File.expand_path(dir) }
end
def filter_for_requested_specs(files)
files.find_all do |file|
if potential_files_to_filter.include?(file)
spec_filter.empty? || spec_filter.any? { |pattern| File.fnmatch?(pattern, file) }
else
true
end
end
end
def spec_helper
File.join(spec_dir, "helpers", "spec_helper")
end
end
end
module Jasmine::Headless
extend self
def register_engine(file_extension, template_class)
Jasmine::Headless::FilesList.register_engine(file_extension, template_class)
end
end

View File

@ -1,23 +0,0 @@
require 'tilt/template'
module Jasmine::Headless
class JSTemplate < Tilt::Template
include Jasmine::Headless::FileChecker
self.default_mime_type = 'application/javascript'
def prepare ; end
def evaluate(scope, locals, &block)
if bad_format?(file)
alert_bad_format(file)
return ''
end
if data[%r{^<script type="text/javascript"}]
data
else
file ? %{<script type="text/javascript" src="#{file}"></script>} : data
end
end
end
end

View File

@ -1,15 +0,0 @@
require 'sprockets/jst_processor'
module Jasmine::Headless
class JSTTemplate < Sprockets::JstProcessor
include Jasmine::Headless::FileChecker
def evaluate(*args)
if bad_format?(file)
alert_bad_format(file)
return ''
end
%{<script type="text/javascript">#{super}</script>}
end
end
end

View File

@ -1,14 +0,0 @@
require 'tilt/template'
require 'rainbow'
module Jasmine::Headless
class NilTemplate < Tilt::Template
def prepare ; end
def evaluate(scope, locals, &block)
return ''
end
end
end

View File

@ -1,206 +0,0 @@
require 'forwardable'
require 'getoptlong'
module Jasmine
module Headless
class Options
extend Forwardable
def_delegators :@options, :[], :[]=
DEFAULT_OPTIONS = {
:colors => false,
:remove_html_file => true,
:runner_output_filename => false,
:jasmine_config => 'spec/javascripts/support/jasmine.yml',
:do_list => false,
:full_run => true,
:enable_cache => true,
:files => [],
:reporters => [ [ 'Console' ] ],
:quiet => false,
:use_server => false,
:server_port => nil
}
DEFAULTS_FILE = File.join(Dir.pwd, '.jasmine-headless-webkit')
GLOBAL_DEFAULTS_FILE = File.expand_path('~/.jasmine-headless-webkit')
REPORT_DEPRECATED_MESSAGE = "--report is deprecated. Use --format HeadlessFileReporter --out <filename>"
def self.from_command_line
options = new
options.process_command_line_args
options[:files] = ARGV
options
end
def initialize(opts = {})
@options = DEFAULT_OPTIONS.dup
srand
@options[:seed] = rand(10000)
read_defaults_files
opts.each { |k, v| @options[k] = v if v }
end
def process_option(*args)
opt, arg = args.flatten[0..1]
case opt
when '--colors', '-c'
@options[:colors] = true
when '--no-colors', '-nc'
@options[:colors] = false
when '--cache'
@options[:enable_cache] = true
when '--no-cache'
@options[:enable_cache] = false
when '--keep'
@options[:remove_html_file] = false
when '--report'
warn REPORT_DEPRECATED_MESSAGE
add_reporter('File', arg)
add_reporter('Console')
when '--runner-out'
@options[:runner_output_filename] = arg
when '--jasmine-config', '-j'
@options[:jasmine_config] = arg
when '--no-full-run'
@options[:full_run] = false
when '--list', '-l'
@options[:do_list] = true
when '--quiet', '-q'
@options[:quiet] = true
when '--seed'
@options[:seed] = arg.to_i
when '--format', '-f'
add_reporter(arg)
when '--use-server'
@options[:use_server] = true
when '--server-port'
@options[:server_port] = arg.to_i
when '--out'
add_reporter_file(arg)
when '-h', '--help'
print_help
exit
end
end
def read_defaults_files
[ GLOBAL_DEFAULTS_FILE, DEFAULTS_FILE ].each do |file|
if File.file?(file)
File.readlines(file).collect { |line| line.strip.split(' ', 2) }.each { |*args| process_option(*args) }
end
end
end
def process_command_line_args
command_line_args = GetoptLong.new(
[ '--colors', '-c', GetoptLong::NO_ARGUMENT ],
[ '--no-colors', GetoptLong::NO_ARGUMENT ],
[ '--cache', GetoptLong::NO_ARGUMENT ],
[ '--no-cache', GetoptLong::NO_ARGUMENT ],
[ '--keep', GetoptLong::NO_ARGUMENT ],
[ '--runner-out', GetoptLong::REQUIRED_ARGUMENT ],
[ '--report', GetoptLong::REQUIRED_ARGUMENT ],
[ '--jasmine-config', '-j', GetoptLong::REQUIRED_ARGUMENT ],
[ '--no-full-run', GetoptLong::NO_ARGUMENT ],
[ '--list', '-l', GetoptLong::NO_ARGUMENT ],
[ '--seed', GetoptLong::REQUIRED_ARGUMENT ],
[ '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ],
[ '--out', GetoptLong::REQUIRED_ARGUMENT ],
[ '--use-server', GetoptLong::NO_ARGUMENT ],
[ '--server-port', GetoptLong::REQUIRED_ARGUMENT ],
[ '-h', '--help', GetoptLong::NO_ARGUMENT ],
[ '-q', '--quiet', GetoptLong::NO_ARGUMENT ]
)
command_line_args.each { |*args| process_option(*args) }
end
def reporters
file_index = 0
@options[:reporters].collect do |reporter, file|
output = [ reporter ]
if file
output << "report:#{file_index}"
output << file
file_index += 1
else
output << "stdout"
end
output
end
end
def file_reporters
reporters.find_all { |reporter| reporter[1]["report:"] }
end
private
def add_reporter(name, file = nil)
if !@added_reporter
@options[:reporters] = []
@added_reporter = true
end
if (parts = name.split(':')).length == 2
name, file = parts
end
@options[:reporters] << [ name ]
add_reporter_file(file) if file
end
def add_reporter_file(file)
@options[:reporters].last << file
end
def print_help
options = [
[ '-c, --colors', 'Enable colors (default: disabled)' ],
[ '-nc, --no-colors', 'Disable colors' ],
[ '--cache', 'Enable cache (default: enabled)' ],
[ '--no-cache', 'Disable cache' ],
[ '--keep', 'Keep runner files on failure' ],
[ '--runner-out <filename>', 'Write runner to specified filename' ],
[ '-j, --jasmine-config <config file>', 'Jasmine Yaml config to use' ],
[ '--no-full-run', 'Do not perform a full spec run after a successful targeted spec run' ],
[ '--use-server', 'Load tests from an HTTP server instead of from filesystem' ],
[ '-l, --list', 'List files in the order they will be required' ],
[ '--seed <seed>', 'Random order seed for spec file ordering' ],
[ '-f, --format <reporter<:filename>>', 'Specify an output reporter and possibly output filename' ],
[ '--out <filename>', 'Specify output filename for last defined reporter' ],
[ '-q, --quiet', "Silence most non-test related warnings" ],
[ '-h, --help', "You're looking at it" ]
]
longest_length = options.collect(&:first).collect(&:length).max
puts <<-HELP
Usage: #{$0} [ options ] [ spec files ]
Options:
#{options.collect { |option, description| " #{option.ljust(longest_length)} #{description}" }.join("\n")}
Available reporters:
Console Write out spec results to the console in a progress format (default)
Verbose Write out spec results to the console in a verbose format
File Write spec results in jasmine-headless-webkit ReportFile format
Tap Write spec results in TAP format
Add reporters to the jasmine.HeadlessReporter object to access them
(ex: jasmine.HeadlessReporter.Teamcity for the Teamcity reporter)
HELP
end
end
end
end

View File

@ -1,3 +1,25 @@
require 'jasmine/headless/task'
module Digest
class JasmineTest
def self.file(file)
new
end
def file(file)
self
end
def hexdigest
'test'
end
def update(prefix)
self
end
end
end
module Jasmine
module Headless
class Railtie < Rails::Railtie
@ -5,6 +27,15 @@ module Jasmine
Jasmine::Headless::Task.new do |t|
t.colors = true
end
if Rails.version >= "3.1.0"
desc 'Force generate static assets without an MD5 hash, all assets end with -test.<ext>'
task 'assets:precompile:for_testing' => :environment do
Rails.application.assets.digest_class = Digest::JasmineTest
Rake::Task['assets:precompile'].invoke
end
end
end
end
end

View File

@ -1,81 +0,0 @@
require 'forwardable'
module Jasmine::Headless
class Report
extend Forwardable
def_delegators :report, :length, :[]
def_delegators :last_total, :total, :failed, :time
class << self
def load(file)
new(file).process
end
end
attr_reader :file, :report
def initialize(file)
@file = file
end
def process
last_message = nil
@report = File.readlines(file).collect do |line|
type, *parts = line.split('||', -1)
if !(report_klass = report_class_for(type))
if last_message.kind_of?(Jasmine::Headless::ReportMessage::Console)
last_message.message << "\n"
last_message.message << line.strip
end
else
parts.last.strip!
last_message = report_klass.new_from_parts(parts)
end
end
self
end
def has_used_console?
@report.any? { |entry| entry.kind_of?(Jasmine::Headless::ReportMessage::Console) }
end
def has_failed_on?(statement)
@report.any? { |entry|
if entry.kind_of?(Jasmine::Headless::ReportMessage::Fail)
entry.statement == statement
end
}
end
def valid?
last_total != nil
end
def failed_files
@report.find_all { |entry|
entry.kind_of?(Jasmine::Headless::ReportMessage::Fail)
}.collect(&:filename).uniq.compact
end
def seed
if seed = report.find { |entry| entry.respond_to?(:seed) }
seed.seed
end
end
private
def last_total
@report.reverse.find { |entry| entry.respond_to?(:total) }
end
def report_class_for(type)
if constant = ReportMessage.constants.find { |k| k.to_s.downcase == type.downcase }
ReportMessage.const_get(constant)
end
end
end
end

View File

@ -1,12 +0,0 @@
module Jasmine::Headless
module ReportMessage
autoload :Spec, 'jasmine/headless/report_message/spec'
autoload :Pass, 'jasmine/headless/report_message/pass'
autoload :Fail, 'jasmine/headless/report_message/fail'
autoload :Console, 'jasmine/headless/report_message/console'
autoload :Error, 'jasmine/headless/report_message/error'
autoload :Total, 'jasmine/headless/report_message/total'
autoload :Seed, 'jasmine/headless/report_message/seed'
end
end

View File

@ -1,18 +0,0 @@
module Jasmine::Headless::ReportMessage
class Console
def self.new_from_parts(parts)
new(parts.first)
end
attr_reader :message
def initialize(message)
@message = message
end
def ==(other)
self.message == other.message
end
end
end

View File

@ -1,20 +0,0 @@
module Jasmine::Headless::ReportMessage
class Error
class << self
def new_from_parts(parts)
new(*parts)
end
end
attr_reader :message, :file_info
def initialize(message, file_info)
@message, @file_info = message, file_info
end
def ==(other)
self.message == other.message && self.file_info == other.file_info
end
end
end

View File

@ -1,5 +0,0 @@
module Jasmine::Headless::ReportMessage
class Fail < Spec
end
end

View File

@ -1,5 +0,0 @@
module Jasmine::Headless::ReportMessage
class Pass < Spec
end
end

View File

@ -1,14 +0,0 @@
module Jasmine::Headless::ReportMessage
class Seed
def self.new_from_parts(parts)
new(parts.first)
end
attr_reader :seed
def initialize(seed)
@seed = seed.to_i
end
end
end

View File

@ -1,28 +0,0 @@
module Jasmine::Headless::ReportMessage
class Spec
def self.new_from_parts(parts)
file_info = parts.pop
new(parts.join(' '), file_info)
end
attr_reader :statement, :file_info
def initialize(statement, file_info)
@statement, @file_info = statement, file_info
end
def ==(other)
self.statement == other.statement && self.file_info == other.file_info
end
def filename
if name = file_info.split(":").first
name
else
nil
end
end
end
end

View File

@ -1,31 +0,0 @@
module Jasmine::Headless::ReportMessage
class Total
class << self
def new_from_parts(parts)
new(*parts)
end
end
attr_reader :total, :failed, :time, :has_js_error
def initialize(total, failed, time, has_js_error)
@total, @failed, @time = total.to_i, failed.to_i, time.to_f
@has_js_error = case has_js_error
when String
has_js_error == "T"
else
has_js_error
end
end
def ==(other)
other &&
self.total == other.total &&
self.failed == other.failed &&
self.time == other.time &&
self.has_js_error == other.has_js_error
end
end
end

View File

@ -1,261 +0,0 @@
require 'fileutils'
require 'coffee-script'
require 'rainbow'
require 'yaml'
require 'erb'
require 'sprockets'
module Jasmine
module Headless
class IndexHandler
class << self
attr_accessor :index
end
def initialize(app)
@app = app
end
def call(env)
if env['PATH_INFO'] == '/'
return [ 302, { 'Location' => self.class.index }, [ 'Redirecting...' ] ]
end
@app.call(env)
end
end
class Runner
JASMINE_DEFAULTS = {
'spec_files' => [ '**/*[sS]pec.js' ],
'helpers' => [ 'helpers/**/*.js' ],
'spec_dir' => 'spec/javascripts',
'src_dir' => nil,
'stylesheets' => [],
'src_files' => [],
'backtrace' => []
}
RUNNER_DIR = File.expand_path('../../../../ext/jasmine-webkit-specrunner', __FILE__)
RUNNER = File.join(RUNNER_DIR, 'jasmine-webkit-specrunner')
attr_reader :options
def self.run(options = {})
new(options).run
end
def self.server_port
return @server_port if @server_port
require 'socket'
count = 100
begin
port = select_server_port
socket = TCPSocket.new(server_interface, port)
socket.close
count -= 1
raise "Could not create server port after 100 attempts!" if count == 0
rescue Errno::ECONNREFUSED
@server_port = port
break
ensure
begin
socket.close if socket
rescue IOError
end
end while true
@server_port
end
def self.server_port=(port)
@server_port = port
end
def self.select_server_port
21000 + rand(10000)
end
def self.server_interface
'127.0.0.1'
end
def self.server_uri
"http://#{server_interface}:#{server_port}"
end
def self.server_spec_path
self.server_uri + '/__JHW__/'
end
def self.ensure_server(options)
return if @server
require 'webrick'
require 'thread'
require 'rack'
require 'net/http'
port = server_port
@server = Thread.new do
Jasmine::Headless.warn "Powering up!"
app = Rack::Builder.new do
use IndexHandler
map '/__JHW__' do
run Rack::File.new(Dir.pwd)
end
map '/' do
run Rack::File.new('/')
end
end
Rack::Handler::WEBrick.run(
app,
:Port => port,
:Logger => Logger.new(StringIO.new),
:AccessLog => [
[ StringIO.new, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
[ StringIO.new, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
]
)
end
while true do
begin
Net::HTTP.get(URI(server_uri))
break
rescue Errno::ECONNREFUSED => e
end
sleep 0.1
end
end
def initialize(options)
options = Options.new(options) if !options.kind_of?(Options)
@options = options
end
def template_writer
@template_writer ||= TemplateWriter.new(self)
end
def jasmine_config
return @jasmine_config if @jasmine_config
@jasmine_config = JASMINE_DEFAULTS.dup
jasmine_config_data.each do |key, value|
@jasmine_config[key] = value if value
end
@jasmine_config
end
def jasmine_command(*targets)
command = [ RUNNER ]
command << "-s #{options[:seed]}"
command << '-c' if options[:colors]
command << '-q' if options[:quiet]
options.file_reporters.each do |reporter, identifier, file|
command << "-r #{file}"
end
command += targets
command.compact.join(' ')
end
def run
Jasmine::Headless::CacheableAction.enabled = @options[:enable_cache]
Jasmine::Headless.show_warnings = !@options[:quiet]
FilesList.reset!
self.class.server_port = options[:server_port]
@_targets = template_writer.write
run_targets = absolute_run_targets(@_targets.dup)
if run_targets.length == 2
if (!@options[:full_run] && files_list.filtered?) || files_list.has_spec_outside_scope?
run_targets.pop
end
end
runner = lambda { system jasmine_command(run_targets) }
if options[:use_server]
wrap_in_server(run_targets, &runner)
else
runner.call
end
@_status = $?.exitstatus
ensure
if @_targets && !runner_filename && (@options[:remove_html_file] || (@_status == 0))
@_targets.each { |target| FileUtils.rm_f target }
end
end
def absolute_run_targets(targets)
targets.flatten.collect do |target|
if options[:use_server]
target = self.class.server_spec_path + target
else
target = "file://" + File.expand_path(target)
end
target
end
end
def runner_filename
options[:runner_output_filename] || begin
if (runner_output = jasmine_config['runner_output']) && !runner_output.empty?
runner_output
else
false
end
end
end
def files_list
@files_list ||= Jasmine::Headless::FilesList.new(
:config => jasmine_config,
:only => options[:files],
:seed => options[:seed],
:reporters => options.reporters
)
end
def wrap_in_server(run_targets)
self.class.ensure_server(options)
IndexHandler.index = run_targets.last
Jasmine::Headless.warn "HTTP powered specs! Located at #{run_targets.join(' ')}"
yield
end
private
def jasmine_config_data
raise JasmineConfigNotFound.new("Jasmine config not found. I tried #{@options[:jasmine_config]}.") if !File.file?(@options[:jasmine_config])
YAML.load(ERB.new(File.read(@options[:jasmine_config])).result(binding))
end
end
end
end

View File

@ -1,44 +0,0 @@
require 'multi_json'
module Jasmine::Headless
class SpecFileAnalyzer < CacheableAction
class << self
def cache_type
"spec_file_analysis"
end
end
def action
line_numbers = {}
data = File.read(file)
if data.respond_to?(:encode)
data.encode!('US-ASCII', 'UTF-8', :invalid => :replace, :undef => :replace)
else
require 'iconv'
ic = Iconv.new('UTF-8//IGNORE', 'US-ASCII')
data = ic.iconv(File.read(file) + ' ')[0..-2]
end
data.force_encoding('US-ASCII') if data.respond_to?(:force_encoding)
data.lines.each_with_index.each { |line, index|
if description = line[%r{(describe|context|it)[( ]*(["'])(.*)\2}, 3]
(line_numbers[description] ||= []) << (index + 1)
end
}
line_numbers
end
def serialize(data)
MultiJson.dump(data)
end
def unserialize(data)
MultiJson.load(data)
end
end
end

View File

@ -1,8 +1,6 @@
module Jasmine
module Headless
class Task
include Rake::DSL if defined?(Rake::DSL)
attr_accessor :colors, :keep_on_error, :jasmine_config
def initialize(name = 'jasmine:headless')
@ -13,20 +11,8 @@ module Jasmine
yield self if block_given?
desc 'Run Jasmine specs headlessly'
task(name) { run_rake_task }
end
private
def run_rake_task
case Jasmine::Headless::Runner.run(
:colors => colors,
:remove_html_file => !@keep_on_error,
:jasmine_config => @jasmine_config
)
when 1
raise Jasmine::Headless::TestFailure
when 2
raise Jasmine::Headless::ConsoleLogUsage
task name do
system %{jasmine-headless-webkit #{@colors ? "-c" : "--no-colors"} #{@keep_on_error ? "--keep" : ""} #{@jasmine_config ? "-j #{@jasmine_config}" : ""}}
end
end
end

View File

@ -1,65 +0,0 @@
require 'multi_json'
require 'erb'
require 'tempfile'
require 'forwardable'
module Jasmine::Headless
class TemplateWriter
attr_reader :runner
extend Forwardable
def_delegators :runner, :files_list, :options
def_delegators :options, :reporters
def initialize(runner)
@runner = runner
end
def write
output = [
[ all_tests_filename, files_list.files_to_html ]
]
output.unshift([filtered_tests_filename, files_list.filtered_files_to_html ]) if files_list.filtered?
output.each do |name, files|
template = template_for(files)
File.open(name, 'wb') { |fh| fh.print template }
end
output.collect(&:first)
end
def all_tests_filename
runner.runner_filename || "jhw.#{$$}.html"
end
def filtered_tests_filename
all_tests_filename.gsub(%r{\.html$}, '.filter.html')
end
def render
template_for(all_files)
end
def all_files
files_list.files_to_html
end
def jhw_reporters
reporters.collect do |reporter, output|
%{jasmine.getEnv().addReporter(new jasmine.HeadlessReporter.#{reporter}("#{output}"));}
end.join("\n")
end
private
def template_for(files)
spec_lines = files_list.spec_file_line_numbers
ERB.new(Jasmine::Headless.root.join('skel/template.html.erb').read).result(binding)
end
end
end

View File

@ -1,16 +0,0 @@
module Jasmine::Headless
class UniqueAssetList < ::Array
def <<(asset)
raise InvalidUniqueAsset.new("Not an asset: #{asset.inspect}") if !asset.respond_to?(:logical_path)
super if !self.any? { |other| asset.pathname == other.pathname }
end
def flatten
self.collect(&:to_a).flatten
end
end
class InvalidUniqueAsset < StandardError ; end
end

View File

@ -1,5 +0,0 @@
module Jasmine
module Headless
VERSION = "0.9.0.rc.2"
end
end

View File

@ -0,0 +1,50 @@
require 'jasmine/files_list'
module Jasmine
class TemplateWriter
class << self
def write!(files_list)
output = [
[ "specrunner.#{$$}.html", files_list.files_to_html ]
]
output.unshift([ "specrunner.#{$$}.filter.html", files_list.filtered_files_to_html ]) if files_list.filtered?
output.each do |name, files|
File.open(name, 'w') { |fh| fh.print template_for(files) }
end
output.collect(&:first)
end
private
def template_for(files)
<<-HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Test Runner</title>
<script type="text/javascript">
window.console = { log: function(data) {
JHW.log(JSON.stringify(data));
}, pp: function(data) {
JHW.log(jasmine ? jasmine.pp(data) : JSON.stringify(data));
} };
</script>
#{files.join("\n")}
</head>
<body>
<script type="text/javascript">
jasmine.getEnv().addReporter(new jasmine.HeadlessReporter());
jasmine.getEnv().execute();
</script>
</body>
</html>
HTML
end
end
end
end

View File

@ -1,155 +0,0 @@
require 'rbconfig'
require 'rubygems'
require 'rubygems/version'
module Qt
class NotInstalledError < StandardError; end
class Qmake
class << self
QMAKES = %w{qmake-qt4 qmake}
def installed?
path != nil
end
def make_installed?
make_path != nil
end
def command(project_file = nil)
spec = (case platform
when :linux
"linux-g++"
when :freebsd
"freebsd-g++"
when :mac_os_x
"macx-g++"
end)
command = "#{path} #{envs} -spec #{spec}"
command << " #{project_file}" if project_file
command
end
def make!(name, project_file = nil)
@name = name
check_make!
check_qmake!
system command(project_file)
system %{make}
end
#
# We need integration tests for these!
#
def path
@path ||= best_qmake
end
def make_path
get_exe_path('gmake') || get_exe_path('make')
end
def platform
case RbConfig::CONFIG['host_os']
when /linux/
:linux
when /freebsd/i
:freebsd
when /darwin/
:mac_os_x
end
end
def qt_version_of(qmake_path)
Gem::Version.new(%x{#{qmake_path} -v}.lines.to_a[1][%r{Using Qt version ([^ ]+) },1])
end
def best_qmake
if qmake_path = QMAKES.collect do |path|
result = nil
if qmake_path = get_exe_path(path)
if (qt_version = qt_version_of(qmake_path)) >= Gem::Version.create('4.7')
result = [ qmake_path, qt_version ]
end
end
result
end.compact.sort { |a, b| b.last <=> a.last }.first
qmake_path.first
else
nil
end
end
private
def envs
%w{QMAKE_CC QMAKE_CXX}.collect do |env|
if ENV[env]
"#{env}=#{ENV[env]}"
end
end.compact.join(" ")
end
def get_exe_path(command)
path = %x{which #{command}}.strip
path = nil if path == ''
path
end
def check_make!
if !make_installed?
install_method = (
case platform
when :linux
%{sudo apt-get install make or sudo yum install make}
when :freebsd
%{install /usr/ports/devel/gmake}
when :mac_os_x
%{Install XCode, and/or sudo port install make}
end
)
$stderr.puts <<-MSG
make is not installed. You'll need to install it to build #{@name}.
#{install_method} should do it for you.
MSG
raise NotInstalledError
end
end
def check_qmake!
if !installed?
install_method = (
case platform
when :linux
<<-MSG
sudo apt-get install libqt4-dev qt4-qmake on Debian-based systems, or downloading
Nokia's prebuilt binary at http://qt.nokia.com/downloads/
MSG
when :freebsd
<<-MSG
Install /usr/ports/www/qt4-webkit and /usr/ports/devel/qmake4.
MSG
MSG
when :mac_os_x
<<-MSG
sudo port install qt4-mac (for the patient) or downloading Nokia's pre-built binary
at http://qt.nokia.com/downloads/
MSG
end
).strip
$stderr.puts <<-MSG
qmake is not installed or is not the right version (#{@name} needs Qt 4.7 or above).
You'll need to install it to build #{@name}.
#{install_method} should do it for you.
MSG
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More