diff --git a/bin/flowerbox b/bin/flowerbox index bbc3d0d..115b49c 100755 --- a/bin/flowerbox +++ b/bin/flowerbox @@ -1,7 +1,6 @@ #!/usr/bin/env ruby require 'flowerbox' -require 'flowerbox-delivery' require 'thor' class Flowerbox::CLI < Thor @@ -9,23 +8,7 @@ class Flowerbox::CLI < Thor method_options :pwd => :string def test(dir) Dir.chdir(pwd) do - load File.join(dir, 'spec_helper.rb') - - require 'tilt/coffee' - - Tilt::CoffeeScriptTemplate.default_bare = Flowerbox.bare_coffeescript - - sprockets = Flowerbox::Delivery::SprocketsHandler.new(:asset_paths => [ Flowerbox.path.join("lib/assets/javascripts"), dir, Flowerbox.asset_paths ].flatten) - - Flowerbox.test_environment.inject_into(sprockets) - - Flowerbox.spec_patterns.each do |pattern| - Dir[File.join(dir, pattern)].each do |file| - sprockets.add(file.gsub(dir + '/', '')) - end - end - - exit Flowerbox.runner_environment.run(sprockets) + exit Flowerbox.run(dir) end end diff --git a/flowerbox.gemspec b/flowerbox.gemspec index 3752461..8b02816 100644 --- a/flowerbox.gemspec +++ b/flowerbox.gemspec @@ -17,8 +17,8 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'cucumber' gem.add_development_dependency 'rspec' - gem.add_development_dependency 'jasmine-core' + gem.add_dependency 'jasmine-core' gem.add_dependency 'flowerbox-delivery' gem.add_dependency 'thor' gem.add_dependency 'selenium-webdriver' diff --git a/lib/assets/javascripts/flowerbox.js.coffee b/lib/assets/javascripts/flowerbox.js.coffee new file mode 100644 index 0000000..0c5aa89 --- /dev/null +++ b/lib/assets/javascripts/flowerbox.js.coffee @@ -0,0 +1,7 @@ +Flowerbox = + baseUrl: '/' + contact: (url, data...) -> + xhr = new XMLHttpRequest() + xhr.open("POST", Flowerbox.baseUrl + url, false) + xhr.send(JSON.stringify(data)) + diff --git a/lib/assets/javascripts/flowerbox/jasmine.js.coffee b/lib/assets/javascripts/flowerbox/jasmine.js.coffee new file mode 100644 index 0000000..4af33f2 --- /dev/null +++ b/lib/assets/javascripts/flowerbox/jasmine.js.coffee @@ -0,0 +1,27 @@ +#= require flowerbox/jasmine/reporter + +getSplitName = (parts) -> + parts.push(String(@description).replace(/[\n\r]/g, ' ')) + parts + +jasmine.Suite.prototype.getSuiteSplitName = -> + this.getSplitName(if @parentSuite then @parentSuite.getSuiteSplitName() else []) + +jasmine.Spec.prototype.getSpecSplitName = -> + this.getSplitName(@suite.getSuiteSplitName()) + +jasmine.Suite.prototype.getSplitName = getSplitName +jasmine.Spec.prototype.getSplitName = getSplitName + +jasmine.Spec.prototype.addMatcherResult = (result) -> + for method in jasmine.Spec.beforeAddMatcherResult() + method.apply(result, [ this ]) + + @results_.addResult(result) + +jasmine.Spec.beforeAddMatcherResult = -> + @_beforeAddMatcherResult ||= [] + +jasmine.Spec.beforeAddMatcherResult().push (spec) -> + @splitName = spec.getSpecSplitName() + diff --git a/lib/assets/javascripts/flowerbox/jasmine/node.js.coffee b/lib/assets/javascripts/flowerbox/jasmine/node.js.coffee index 6f30198..ac57c90 100644 --- a/lib/assets/javascripts/flowerbox/jasmine/node.js.coffee +++ b/lib/assets/javascripts/flowerbox/jasmine/node.js.coffee @@ -1,9 +1,11 @@ -class jasmine.SimpleNodeReporter - reportRunnerResults: (runner) -> - console.log(runner.results().totalCount + '/' + runner.results().failedCount) +jasmine.Spec.beforeAddMatcherResult().push -> + if !@passed_ + Error.prepareStackTrace_ = Error.prepareStackTrace + Error.prepareStackTrace = (err, stack) -> stack - if runner.results().failedCount == 0 - process.exit(0) - else - process.exit(1) + errorInfo = new Error().stack[3] + + @trace = { stack: [ "#{errorInfo.getFileName()}:#{errorInfo.getLineNumber()}" ] } + + Error.prepareStackTrace = Error.prepareStackTrace_ diff --git a/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee b/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee new file mode 100644 index 0000000..c658d44 --- /dev/null +++ b/lib/assets/javascripts/flowerbox/jasmine/reporter.js.coffee @@ -0,0 +1,18 @@ +class jasmine.FlowerboxReporter + reportRunnerStarting: (runner) -> + @time = (new Date()).getTime() + + Flowerbox.contact("starting") + reportSpecStarting: (spec) -> + Flowerbox.contact("start_test", spec.description) + reportSpecResults: (spec) -> + failures = [] + + for result in spec.results().getItems() + if result.type == 'expect' && !result.passed_ + failures.push(result) + + Flowerbox.contact("finish_test", spec.description, failures) + reportRunnerResults: (runner) -> + Flowerbox.contact("results", (new Date().getTime()) - @time) + diff --git a/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee b/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee index dd860ff..a3ee9de 100644 --- a/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee +++ b/lib/assets/javascripts/flowerbox/jasmine/selenium.js.coffee @@ -1,3 +1,15 @@ -class jasmine.SimpleSeleniumReporter - reportRunnerResults: (runner) -> - Flowerbox.contact("results", runner.results().totalCount + '/' + runner.results().failedCount) +jasmine.Spec.beforeAddMatcherResult().push -> + if !@passed_ + @trace = { stack: [] } + try + lol.wut + catch e + if e.stack + file = switch Flowerbox.environment + when 'firefox' + e.stack.split("\n")[3].replace(/^[^@]*@/, '') + when 'chrome' + e.stack.split("\n")[4].replace(/^.*\((.*)\)$/, '$1').replace(/:[^:]+$/, '') + + @trace = { stack: [ file.replace(/^.*__F__/, '') ] } + diff --git a/lib/flowerbox.rb b/lib/flowerbox.rb index b7b026e..8a983bb 100644 --- a/lib/flowerbox.rb +++ b/lib/flowerbox.rb @@ -1,12 +1,23 @@ require "flowerbox/version" +require 'flowerbox-delivery' module Flowerbox + autoload :Runner, 'flowerbox/runner' + module Runner autoload :Node, 'flowerbox/runner/node' autoload :Selenium, 'flowerbox/runner/selenium' + autoload :Firefox, 'flowerbox/runner/firefox' + autoload :Chrome, 'flowerbox/runner/chrome' autoload :Base, 'flowerbox/runner/base' end + autoload :TestEnvironment, 'flowerbox/test_environment' + + module TestEnvironment + autoload :Jasmine, 'flowerbox/test_environment/jasmine' + end + autoload :Rack, 'flowerbox/rack' class << self @@ -19,11 +30,11 @@ module Flowerbox end def test_with(what) - require "flowerbox/test_environment/#{what}" + self.test_environment = Flowerbox::TestEnvironment.for(what) end - def run_with(what) - require "flowerbox/runner/#{what}" + def run_with(*whats) + self.runner_environment = whats.collect { |what| Flowerbox::Runner.for(what) } end def path @@ -34,6 +45,64 @@ module Flowerbox def configure yield self + + if spec_patterns.empty? + spec_patterns << "**/*_spec*" + spec_patterns << "*/*_spec*" + end + end + + def bare_coffeescript + @bare_coffeescript ||= true + end + + def run(dir) + load File.join(dir, 'spec_helper.rb') + + require 'coffee_script' + require 'tilt/coffee' + + Tilt::CoffeeScriptTemplate.default_bare = Flowerbox.bare_coffeescript + + result = Flowerbox.runner_environment.collect do |env| + env.run(build_sprockets_for(dir)) + end + + result.max + end + + def build_sprockets_for(dir) + sprockets = Flowerbox::Delivery::SprocketsHandler.new( + :asset_paths => [ + Flowerbox.path.join("lib/assets/javascripts"), + Flowerbox.path.join("vendor/assets/javascripts"), + dir, + Flowerbox.asset_paths + ].flatten + ) + + sprockets.add('flowerbox') + sprockets.add('json2') + + Flowerbox.test_environment.inject_into(sprockets) + + spec_files_for(dir).each { |file| sprockets.add(file) } + + sprockets + end + + def spec_files_for(dir) + return @spec_files if @spec_files + + @spec_files = [] + + Flowerbox.spec_patterns.each do |pattern| + Dir[File.join(dir, pattern)].each do |file| + @spec_files << file.gsub(dir + '/', '') + end + end + + @spec_files end end end diff --git a/lib/flowerbox/rack.rb b/lib/flowerbox/rack.rb index b866cd6..80bdb64 100644 --- a/lib/flowerbox/rack.rb +++ b/lib/flowerbox/rack.rb @@ -1,4 +1,5 @@ require 'sinatra' +require 'json' module Flowerbox class Rack < Sinatra::Base @@ -10,12 +11,32 @@ module Flowerbox self.class.runner end - post '/results' do - runner.results = request.body.string + def data + JSON.parse(request.body.string) end - post '/log' do - runner.log(request.body.string) + def self.empty_post(*args, &block) + post(*args) do + instance_eval(&block) + + "" + end + end + + empty_post '/results' do + runner.finish!(data.flatten.first) + end + + empty_post '/start_test' do + runner.tests << data.flatten + end + + empty_post '/finish_test' do + runner.add_failures(data.flatten[1..-1]) + end + + empty_post '/log' do + runner.log(data.first) end get %r{^/__F__(/.*)$} do |file| diff --git a/lib/flowerbox/runner.rb b/lib/flowerbox/runner.rb new file mode 100644 index 0000000..c764c09 --- /dev/null +++ b/lib/flowerbox/runner.rb @@ -0,0 +1,10 @@ +module Flowerbox + module Runner + class << self + def for(env) + self.const_get(self.constants.find { |c| c.to_s.downcase.to_s == env.to_s }).new + end + end + end +end + diff --git a/lib/flowerbox/runner/base.rb b/lib/flowerbox/runner/base.rb index a581b1a..27adaa4 100644 --- a/lib/flowerbox/runner/base.rb +++ b/lib/flowerbox/runner/base.rb @@ -3,8 +3,47 @@ module Flowerbox class Base attr_reader :sprockets + attr_accessor :time, :results + def run(sprockets) @sprockets = sprockets + + puts "Flowerbox running your #{Flowerbox.test_environment.name} tests on #{name}..." + + server.start + + yield + + server.stop + + puts + + failures.each do |failure_set| + if failure_set.first['splitName'] + puts failure_set.first['splitName'].join(' ') + end + + failure_set.each do |failure| + case failure['trace']['stack'] + when String + # exception + puts failure['trace']['stack'] + else + # failed test + puts %{#{failure['message']} (#{failure['trace']['stack'].first})} + end + end + + puts + end + + puts "#{total_count} tests, #{failure_count} failures, #{time.to_f / 1000} sec" + + if failures.length == 0 + $?.exitstatus + else + 1 + end end def type @@ -12,7 +51,7 @@ module Flowerbox end def start_test_environment - Flowerbox.test_environment.start_for(type) + Flowerbox.test_environment.start_for(self) end def server @@ -23,6 +62,48 @@ module Flowerbox @server end + + def log(message) + puts message + end + + def tests + @tests ||= [] + end + + def failures + @failures ||= [] + end + + def add_failures(test_failures) + if test_failures.length == 0 + print '.' + else + print 'F' + end + + $stdout.flush + + failures << test_failures + end + + def total_count + @tests.length + end + + def failure_count + @failures.length + end + + def finish!(time) + @time = time + + @finished = true + end + + def finished? + @finished + end end end end diff --git a/lib/flowerbox/runner/chrome.rb b/lib/flowerbox/runner/chrome.rb new file mode 100644 index 0000000..293277f --- /dev/null +++ b/lib/flowerbox/runner/chrome.rb @@ -0,0 +1,11 @@ +class Flowerbox::Runner::Chrome < Flowerbox::Runner::Selenium + def name + "Chrome" + end + + def browser + :chrome + end +end + + diff --git a/lib/flowerbox/runner/firefox.rb b/lib/flowerbox/runner/firefox.rb new file mode 100644 index 0000000..7622428 --- /dev/null +++ b/lib/flowerbox/runner/firefox.rb @@ -0,0 +1,10 @@ +class Flowerbox::Runner::Firefox < Flowerbox::Runner::Selenium + def name + "Firefox" + end + + def browser + :firefox + end +end + diff --git a/lib/flowerbox/runner/node.rb b/lib/flowerbox/runner/node.rb index 52984b4..0ab8a55 100644 --- a/lib/flowerbox/runner/node.rb +++ b/lib/flowerbox/runner/node.rb @@ -5,21 +5,25 @@ require 'json' module Flowerbox module Runner class Node < Base + def name + "Node.js" + end + + def type + :node + end + def run(sprockets) - super + super do + begin + file = File.join(Dir.pwd, ".node-tmp.#{Time.now.to_i}.js") + File.open(file, 'wb') { |fh| fh.print template } - file = File.join(Dir.pwd, ".node-tmp.#{Time.now.to_i}.js") - File.open(file, 'wb') { |fh| fh.print template.tap { |o| puts o } } - - server.start - - system %{node #{file}} - - server.stop - - $?.exitstatus - ensure - File.unlink(file) if file + system %{node #{file}} + ensure + File.unlink(file) if file + end + end end def template @@ -29,7 +33,8 @@ module Flowerbox var fs = require('fs'), vm = require('vm'), http = require('http'), - jsdom = require('jsdom'); + jsdom = require('jsdom'), + xhr = require('xmlhttprequest') // expand the sandbox a bit var context = function() {}; @@ -38,6 +43,7 @@ for (method in global) { context[method] = global[method]; } jsdom.env( "", [], function(errors, window) { context.window = window; + context.XMLHttpRequest = xhr.XMLHttpRequest; var files = #{sprockets.files.to_json}; var fileRunner = function() { @@ -65,6 +71,10 @@ jsdom.env( if (!context[thing]) { context[thing] = window[thing] } } + if (context.Flowerbox) { + context.Flowerbox.baseUrl = "http://localhost:#{server.port}/"; + } + fileRunner(); }); }); diff --git a/lib/flowerbox/runner/selenium.rb b/lib/flowerbox/runner/selenium.rb index 4b4a119..4934c86 100644 --- a/lib/flowerbox/runner/selenium.rb +++ b/lib/flowerbox/runner/selenium.rb @@ -3,36 +3,33 @@ require 'selenium-webdriver' module Flowerbox module Runner class Selenium < Base - attr_accessor :browser, :results + MAX_COUNT = 30 + + def name + raise StandardError.new("Override me") + end + + def type + :selenium + end def run(sprockets) - super + super do + begin + selenium = ::Selenium::WebDriver.for(browser) - selenium = ::Selenium::WebDriver.for :firefox + selenium.navigate.to "http://localhost:#{server.port}/" - Rack.runner = self + @count = 0 - server.start - - selenium.navigate.to "http://localhost:#{server.port}/" - - 1.upto(30) do - if results - break - else - sleep 1 + while @count < MAX_COUNT && !finished? + @count += 1 + sleep 0.1 + end + ensure + selenium.quit if selenium end end - - if results - puts results - - exit (results.split('/').last.to_i == 0) ? 0 : 1 - else - exit 1 - end - ensure - selenium.quit if selenium end def log(msg) @@ -47,14 +44,6 @@ module Flowerbox Flowerbox - Selenium Runner @@ -77,8 +68,13 @@ HTML def template_files sprockets.files.collect { |file| %{} } end + + def add_failures(data) + super + + @count = 0 + end end end end -Flowerbox.runner_environment = Flowerbox::Runner::Selenium.new diff --git a/lib/flowerbox/test_environment.rb b/lib/flowerbox/test_environment.rb new file mode 100644 index 0000000..112bac8 --- /dev/null +++ b/lib/flowerbox/test_environment.rb @@ -0,0 +1,10 @@ +module Flowerbox + module TestEnvironment + class << self + def for(env) + self.const_get(self.constants.find { |c| c.to_s.downcase.to_s == env.to_s }).new + end + end + end +end + diff --git a/lib/flowerbox/test_environment/jasmine.rb b/lib/flowerbox/test_environment/jasmine.rb index 349140c..0ab1839 100644 --- a/lib/flowerbox/test_environment/jasmine.rb +++ b/lib/flowerbox/test_environment/jasmine.rb @@ -3,6 +3,10 @@ require 'jasmine-core' module Flowerbox module TestEnvironment class Jasmine + def name + self.class.name.split("::").last + end + def inject_into(sprockets) @sprockets = sprockets @@ -13,9 +17,10 @@ module Flowerbox end def start_for(runner) - @sprockets.add("flowerbox/jasmine/#{runner}") + @sprockets.add("flowerbox/jasmine") + @sprockets.add("flowerbox/jasmine/#{runner.type}") - case runner + case runner.type when :node <<-JS var jasmine = context.jasmine; @@ -25,8 +30,7 @@ jasmine.getEnv().execute(); JS when :selenium <<-JS -jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); -jasmine.getEnv().addReporter(new jasmine.SimpleSeleniumReporter()); +jasmine.getEnv().addReporter(new jasmine.FlowerboxReporter()); jasmine.getEnv().execute(); JS end @@ -37,11 +41,9 @@ JS end def reporters - @reporters ||= [] + @reporters ||= [ 'FlowerboxReporter' ] end end end end -Flowerbox.test_environment = Flowerbox::TestEnvironment::Jasmine.new - diff --git a/spec/javascripts/test_spec.js b/spec/javascripts/test_spec.js index 82ad0d3..629a4ce 100644 --- a/spec/javascripts/test_spec.js +++ b/spec/javascripts/test_spec.js @@ -1,9 +1,6 @@ describe("cats", function() { it("should hiss", function() { - $("body").append("