From fa27ee715a5244fed2d783bb9a10677ac71785c2 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Thu, 1 Sep 2011 09:38:53 -0400 Subject: [PATCH] enable coffeescript cache, step 1 of a big speedup --- .gitignore | 1 + Gemfile | 1 + lib/jasmine-headless-webkit.rb | 3 +- lib/jasmine/files_list.rb | 56 +++-------- lib/jasmine/headless/coffee_script_cache.rb | 66 +++++++++++++ lib/jasmine/headless/options.rb | 7 ++ lib/jasmine/headless/runner.rb | 2 + spec/lib/jasmine/files_list_spec.rb | 96 +++++-------------- .../headless/coffee_script_cache_spec.rb | 87 +++++++++++++++++ spec/spec_helper.rb | 56 ++++++----- 10 files changed, 238 insertions(+), 137 deletions(-) create mode 100644 lib/jasmine/headless/coffee_script_cache.rb create mode 100644 spec/lib/jasmine/headless/coffee_script_cache_spec.rb diff --git a/.gitignore b/.gitignore index 801dd0a..b523b09 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ moc_*.* .DS_Store hydra-runner.log jhw-test +.jhw-cache/ diff --git a/Gemfile b/Gemfile index fc8fa1e..bfcf6b6 100644 --- a/Gemfile +++ b/Gemfile @@ -14,3 +14,4 @@ gem 'rake', '0.8.7' gem 'mocha', '0.9.12' gem 'guard-jasmine-headless-webkit' gem 'facter' + diff --git a/lib/jasmine-headless-webkit.rb b/lib/jasmine-headless-webkit.rb index d5b1fb7..0ca38a4 100644 --- a/lib/jasmine-headless-webkit.rb +++ b/lib/jasmine-headless-webkit.rb @@ -1,7 +1,6 @@ module Jasmine module Headless - module Webkit - end + autoload :CoffeeScriptCache, 'jasmine/headless/coffee_script_cache' end end diff --git a/lib/jasmine/files_list.rb b/lib/jasmine/files_list.rb index 0b4db1b..21ed3ce 100644 --- a/lib/jasmine/files_list.rb +++ b/lib/jasmine/files_list.rb @@ -1,3 +1,4 @@ +require 'jasmine/headless/coffee_script_cache' require 'jasmine-core' require 'iconv' @@ -66,53 +67,26 @@ module Jasmine private def to_html(files) - coffeescript_run = [] - files.collect { |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' - %{} - when '.css' - %{} + case File.extname(file) + when '.coffee' + begin + %{} + rescue CoffeeScript::CompilationError => ne + puts "[%s] %s: %s" % [ 'coffeescript'.color(:red), file.color(:yellow), ne.message.to_s.color(:white) ] + raise ne + rescue StandardError => e + puts "[%s] Error in compiling one of the followng: %s" % [ 'coffeescript'.color(:red), files.join(' ').color(:yellow) ] + raise e end + when '.js' + %{} + when '.css' + %{} end - - output }.flatten.reject(&:empty?) end - def ensure_coffeescript_run!(files) - data = StringIO.new - files.each { |file| data << File.read(file) << "\n" } - data.rewind - - %{} - 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) ] - raise ne - ensure - fh.close - end - end - rescue StandardError => e - puts "[%s] Error in compiling one of the followng: %s" % [ 'coffeescript'.color(:red), files.join(' ').color(:yellow) ] - raise e - ensure - files.clear - end - def spec_filter @spec_filter ||= (@options[:only] ? @options[:only].collect { |path| Dir[path] }.flatten : []) end diff --git a/lib/jasmine/headless/coffee_script_cache.rb b/lib/jasmine/headless/coffee_script_cache.rb new file mode 100644 index 0000000..5a1b103 --- /dev/null +++ b/lib/jasmine/headless/coffee_script_cache.rb @@ -0,0 +1,66 @@ +require 'coffee_script' +require 'digest/sha1' +require 'fileutils' + +module Jasmine + module Headless + class CoffeeScriptCache + class << self + def enabled=(bool) + @enabled = bool + end + + def enabled? + @enabled = true if @enabled == nil + @enabled + 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 self.class.enabled? + if fresh? + File.read(cache_file) + else + result = compile + FileUtils.mkdir_p self.class.cache_dir + File.open(cache_file, 'wb') { |fh| fh.print result } + result + end + else + compile + end + end + + def cache_file + @cache_file ||= File.join(self.class.cache_dir, Digest::SHA1.hexdigest(file)) + end + + def fresh? + File.exist?(cache_file) && (File.mtime(file) < File.mtime(cache_file)) + end + + def compile + CoffeeScript.compile(File.read(file)) + end + end + end +end + diff --git a/lib/jasmine/headless/options.rb b/lib/jasmine/headless/options.rb index 7afbd63..fff6302 100644 --- a/lib/jasmine/headless/options.rb +++ b/lib/jasmine/headless/options.rb @@ -15,6 +15,7 @@ module Jasmine :report => false, :do_list => false, :full_run => true, + :enable_cache => true, :files => [] } @@ -42,6 +43,10 @@ module Jasmine @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' @@ -67,6 +72,8 @@ module Jasmine command_line_args = GetoptLong.new( [ '--colors', '-c', GetoptLong::NO_ARGUMENT ], [ '--no-colors', GetoptLong::NO_ARGUMENT ], + [ '--cache', GetoptLong::NO_ARGUMENT ], + [ '--no-t stcache', GetoptLong::NO_ARGUMENT ], [ '--keep', GetoptLong::NO_ARGUMENT ], [ '--report', GetoptLong::REQUIRED_ARGUMENT ], [ '--jasmine-config', '-j', GetoptLong::REQUIRED_ARGUMENT ], diff --git a/lib/jasmine/headless/runner.rb b/lib/jasmine/headless/runner.rb index f3e4343..7f7613b 100644 --- a/lib/jasmine/headless/runner.rb +++ b/lib/jasmine/headless/runner.rb @@ -61,6 +61,8 @@ module Jasmine end def run + Jasmine::Headless::CoffeeScriptCache.enabled = @options[:enable_cache] + files_list = Jasmine::FilesList.new( :config => jasmine_config, :only => @options[:files] diff --git a/spec/lib/jasmine/files_list_spec.rb b/spec/lib/jasmine/files_list_spec.rb index 26cd34a..9daaade 100644 --- a/spec/lib/jasmine/files_list_spec.rb +++ b/spec/lib/jasmine/files_list_spec.rb @@ -147,83 +147,37 @@ describe Jasmine::FilesList do describe '#.*files_to_html' do include FakeFS::SpecHelpers - context 'one coffeescript file' do - before do - files_list.instance_variable_set(:@files, [ - 'test.js', - 'test.coffee', - 'test.css' - ]) + before do + files_list.instance_variable_set(:@files, [ + 'test.js', + 'test.coffee', + 'test.css' + ]) - files_list.instance_variable_set(:@filtered_files, [ - 'test.js', - 'test.coffee' - ]) + files_list.instance_variable_set(:@filtered_files, [ + 'test.js', + 'test.coffee' + ]) - File.open('test.coffee', 'w') { |fh| fh.print "first" } + Jasmine::Headless::CoffeeScriptCache.stubs(:for).with('test.coffee').returns("i compiled") + end - CoffeeScript.stubs(:compile).with() { |field| field.read == "first\n" }.returns("i compiled") - end - - context '#files_to_html' do - it "should create the right HTML" do - files_list.files_to_html.should == [ - %{}, - %{}, - %{} - ] - end - end - - context '#filtered_files_to_html' do - it "should create the right HTML" do - files_list.filtered_files_to_html.should == [ - %{}, - %{} - ] - end + context '#files_to_html' do + it "should create the right HTML" do + files_list.files_to_html.should == [ + %{}, + %{}, + %{} + ] end end - context 'two coffeescript files' do - before do - files_list.instance_variable_set(:@files, [ - 'test.js', - 'test.coffee', - 'test2.coffee', - 'test.css' - ]) - - files_list.instance_variable_set(:@filtered_files, [ - 'test.js', - 'test.coffee' - ]) - - File.open('test.coffee', 'w') { |fh| fh.print "first" } - File.open('test2.coffee', 'w') { |fh| fh.print "second" } - end - - context '#files_to_html' do - it "should create the right HTML" do - CoffeeScript.stubs(:compile).with() { |field| field.read == "first\nsecond\n" }.returns("i compiled") - - files_list.files_to_html.should == [ - %{}, - %{}, - %{} - ] - end - end - - context '#filtered_files_to_html' do - it "should create the right HTML" do - CoffeeScript.stubs(:compile).with() { |field| field.read == "first\n" }.returns("i compiled") - - files_list.filtered_files_to_html.should == [ - %{}, - %{} - ] - end + context '#filtered_files_to_html' do + it "should create the right HTML" do + files_list.filtered_files_to_html.should == [ + %{}, + %{} + ] end end end diff --git a/spec/lib/jasmine/headless/coffee_script_cache_spec.rb b/spec/lib/jasmine/headless/coffee_script_cache_spec.rb new file mode 100644 index 0000000..9629a00 --- /dev/null +++ b/spec/lib/jasmine/headless/coffee_script_cache_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Jasmine::Headless::CoffeeScriptCache do + include FakeFS::SpecHelpers + + let(:file) { 'file.coffee' } + let(:data) { 'data' } + let(:compiled) { 'compiled' } + + before do + File.open(file, 'wb') { |fh| fh.print(data) } + described_class.cache_dir = cache_dir + end + + let(:coffee_script_compiles!) do + CoffeeScript.expects(:compile).with(data).returns(compiled) + end + + let(:cache_dir) { 'cache' } + let(:cache_file) { File.join(cache_dir, Digest::SHA1.hexdigest(file)) } + let(:cache_file_data) { File.read(cache_file) } + + describe '.for' do + context 'cache disabled' do + before do + described_class.enabled = false + p described_class.enabled? + end + + it 'should compile' do + coffee_script_compiles! + described_class.for(file).should == compiled + cache_file.should_not be_a_file + end + end + + context 'cache enabled' do + before do + described_class.enabled = true + FileUtils.mkdir_p(cache_dir) + + File.stubs(:mtime).with(file).returns(Time.at(10)) + File.stubs(:mtime).with(cache_file).returns(Time.at(cache_file_mtime)) + end + + context 'cache empty' do + let(:cache_file_mtime) { 0 } + + it 'should compile' do + coffee_script_compiles! + described_class.for(file).should == compiled + + cache_file_data.should == compiled + end + end + + context 'cache fresh' do + let(:cache_file_mtime) { 15 } + + before do + File.open(cache_file, 'wb') { |fh| fh.print compiled } + end + + it 'should not compile' do + coffee_script_compiles!.never + + described_class.for(file).should == compiled + + cache_file_data.should == compiled + end + end + + context 'cache stale' do + let(:cache_file_mtime) { 5 } + + it 'should compile' do + coffee_script_compiles! + + described_class.for(file).should == compiled + + cache_file_data.should == compiled + end + end + end + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1f9e555..0fcd858 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'jasmine-headless-webkit' +require 'fakefs/spec_helpers' + RSpec.configure do |c| c.mock_with :mocha end @@ -10,32 +13,39 @@ if !File.file?(specrunner) end end -RSpec::Matchers.define :be_a_report_containing do |total, fails, used_console| - match do |filename| - parts(filename).length.should == 4 - parts[0].should == total.to_s - parts[1].should == fails.to_s - parts[2].should == (used_console ? "T" : "F") - true +module RSpec::Matchers + define :be_a_report_containing do |total, fails, used_console| + match do |filename| + parts(filename).length.should == 4 + parts[0].should == total.to_s + parts[1].should == fails.to_s + parts[2].should == (used_console ? "T" : "F") + true + end + + failure_message_for_should do |filename| + parts(filename) + "expected #{filename} to be a report containing (#{total}, #{fails}, #{used_console.inspect}), instead it contained (#{parts[0]}, #{parts[1]}, #{(parts[2] == "T").inspect})" + end + + def parts(filename = nil) + @parts ||= File.readlines(filename).first.strip.split('/') + end end - failure_message_for_should do |filename| - parts(filename) - "expected #{filename} to be a report containing (#{total}, #{fails}, #{used_console.inspect}), instead it contained (#{parts[0]}, #{parts[1]}, #{(parts[2] == "T").inspect})" + define :contain_a_failing_spec do |*parts| + match do |filename| + report(filename).include?(parts.join("||")).should be_true + end + + def report(filename) + @report ||= File.readlines(filename)[1..-1].collect(&:strip) + end end - def parts(filename = nil) - @parts ||= File.readlines(filename).first.strip.split('/') + define :be_a_file do + match do |file| + File.file?(file) + end end end - -RSpec::Matchers.define :contain_a_failing_spec do |*parts| - match do |filename| - report(filename).include?(parts.join("||")).should be_true - end - - def report(filename) - @report ||= File.readlines(filename)[1..-1].collect(&:strip) - end -end -