<commit_before>#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../test_helper'
require 'sass/plugin'
require 'fileutils'

class SassPluginTest < Test::Unit::TestCase
  @@templates = %w{
    complex script parent_ref import alt
    subdir/subdir subdir/nested_subdir/nested_subdir
    options
  }

  def setup
    FileUtils.mkdir tempfile_loc
    FileUtils.mkdir tempfile_loc(nil,"more_")
    set_plugin_opts
    Sass::Plugin.update_stylesheets
  end

  def teardown
    clean_up_sassc
    FileUtils.rm_r tempfile_loc
    FileUtils.rm_r tempfile_loc(nil,"more_")
  end

  @@templates.each do |name|
    define_method("test_template_renders_correctly (#{name})") do
      assert_renders_correctly(name)
    end
  end

  def test_no_update
    File.delete(tempfile_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_update_needed_when_modified
    sleep 1
    FileUtils.touch(template_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_update_needed_when_dependency_modified
    sleep 1
    FileUtils.touch(template_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('import', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_full_exception_handling
    File.delete(tempfile_loc('bork1'))
    Sass::Plugin.update_stylesheets
    File.open(tempfile_loc('bork1')) do |file|
      assert_equal(<<CSS.strip, file.read.split("\n")[0...6].join("\n"))
/*
Syntax error: Undefined variable: "!bork".
        on line 2 of #{template_loc('bork1')}

1: bork
2:   :bork= !bork
CSS
    end
    File.delete(tempfile_loc('bork1'))
  end

  def test_nonfull_exception_handling
    old_full_exception = Sass::Plugin.options[:full_exception]
    Sass::Plugin.options[:full_exception] = false

    File.delete(tempfile_loc('bork1'))
    assert_raise(Sass::SyntaxError) {Sass::Plugin.update_stylesheets}
  ensure
    Sass::Plugin.options[:full_exception] = old_full_exception
  end
  
  def test_two_template_directories
    set_plugin_opts :template_location => {
      template_loc => tempfile_loc,
      template_loc(nil,'more_') => tempfile_loc(nil,'more_')
    }
    Sass::Plugin.update_stylesheets
    ['more1', 'more_import'].each { |name| assert_renders_correctly(name, :prefix => 'more_') }
  end

  def test_two_template_directories_with_line_annotations
    set_plugin_opts :line_comments => true,
                    :style => :nested,
                    :template_location => {
                      template_loc => tempfile_loc,
                      template_loc(nil,'more_') => tempfile_loc(nil,'more_')
                    }
    Sass::Plugin.update_stylesheets
    assert_renders_correctly('more1_with_line_comments', 'more1', :prefix => 'more_')
  end

  def test_merb_update
    begin
      require 'merb'
    rescue LoadError
      puts "\nmerb couldn't be loaded, skipping a test"
      return
    end
    
    require 'sass/plugin/merb'
    if defined?(MerbHandler)
      MerbHandler.send(:define_method, :process_without_sass) { |*args| }
    else
      Merb::Rack::Application.send(:define_method, :call_without_sass) { |*args| }
    end

    set_plugin_opts

    File.delete(tempfile_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    
    if defined?(MerbHandler)
      MerbHandler.new('.').process nil, nil
    else
      Merb::Rack::Application.new.call(::Rack::MockRequest.env_for('/'))
    end

    assert_stylesheet_updated 'basic'
  end

  def test_doesnt_render_partials
    assert !File.exists?(tempfile_loc('_partial'))
  end

  ## Regression

  def test_cached_dependencies_update
    FileUtils.mv(template_loc("basic"), template_loc("basic", "more_"))
    set_plugin_opts :load_paths => [result_loc, template_loc(nil, "more_")]

    sleep 1
    FileUtils.touch(template_loc("basic", "more_"))
    assert Sass::Plugin.stylesheet_needs_update?("import", template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_renders_correctly("import")
  ensure
    FileUtils.mv(template_loc("basic", "more_"), template_loc("basic"))
  end

 private

  def assert_renders_correctly(*arguments)
    options = arguments.last.is_a?(Hash) ? arguments.pop : {}
    prefix = options[:prefix]
    result_name = arguments.shift
    tempfile_name = arguments.shift || result_name
    expected_lines = File.read(result_loc(result_name, prefix)).split("\n")
    actual_lines = File.read(tempfile_loc(tempfile_name, prefix)).split("\n")

    if actual_lines.first == "/*" && expected_lines.first != "/*"
      assert(false, actual_lines[0..actual_lines.enum_with_index.find {|l, i| l == "*/"}.last].join("\n"))
    end

    expected_lines.zip(actual_lines).each_with_index do |pair, line|
      message = "template: #{result_name}\nline:     #{line + 1}"
      assert_equal(pair.first, pair.last, message)
    end
    if expected_lines.size < actual_lines.size
      assert(false, "#{actual_lines.size - expected_lines.size} Trailing lines found in #{tempfile_name}.css: #{actual_lines[expected_lines.size..-1].join('\n')}")
    end
  end

  def assert_stylesheet_updated(name)
    assert !Sass::Plugin.stylesheet_needs_update?(name, template_loc, tempfile_loc)

    # Make sure it isn't an exception
    expected_lines = File.read(result_loc(name)).split("\n")
    actual_lines = File.read(tempfile_loc(name)).split("\n")
    if actual_lines.first == "/*" && expected_lines.first != "/*"
      assert(false, actual_lines[0..actual_lines.enum_with_index.find {|l, i| l == "*/"}.last].join("\n"))
    end
  end

  def template_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}templates/#{name}.sass"
    else
      absolutize "#{prefix}templates"
    end
  end

  def tempfile_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}tmp/#{name}.css"
    else
      absolutize "#{prefix}tmp"
    end
  end

  def result_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}results/#{name}.css"
    else
      absolutize "#{prefix}results"
    end
  end

  def absolutize(file)
    "#{File.dirname(__FILE__)}/#{file}"
  end

  def set_plugin_opts(overrides = {})
    Sass::Plugin.options = {
      :template_location => template_loc,
      :css_location => tempfile_loc,
      :style => :compact,
      :load_paths => [result_loc],
      :always_update => true,
    }.merge(overrides)
  end

  def wait_a_tick
    time = Time.now
    loop {break if Time.now.sec != time.sec}
  end
end

module Sass::Plugin
  class << self
    public :stylesheet_needs_update?
  end
end

class Sass::Engine
  alias_method :old_render, :render

  def render
    raise "bork bork bork!" if @template[0] == "{bork now!}"
    old_render
  end
end
<commit_msg>[Sass] Add a few tests for plugin callbacks.<commit_after>#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../test_helper'
require 'sass/plugin'
require 'fileutils'

class SassPluginTest < Test::Unit::TestCase
  @@templates = %w{
    complex script parent_ref import alt
    subdir/subdir subdir/nested_subdir/nested_subdir
    options
  }

  def setup
    FileUtils.mkdir tempfile_loc
    FileUtils.mkdir tempfile_loc(nil,"more_")
    set_plugin_opts
    Sass::Plugin.update_stylesheets
  end

  def teardown
    clean_up_sassc
    clear_callbacks
    FileUtils.rm_r tempfile_loc
    FileUtils.rm_r tempfile_loc(nil,"more_")
  end

  @@templates.each do |name|
    define_method("test_template_renders_correctly (#{name})") do
      assert_renders_correctly(name)
    end
  end

  def test_no_update
    File.delete(tempfile_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_update_needed_when_modified
    sleep 1
    FileUtils.touch(template_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_update_needed_when_dependency_modified
    sleep 1
    FileUtils.touch(template_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('import', template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_stylesheet_updated 'basic'
  end

  def test_full_exception_handling
    File.delete(tempfile_loc('bork1'))
    Sass::Plugin.update_stylesheets
    File.open(tempfile_loc('bork1')) do |file|
      assert_equal(<<CSS.strip, file.read.split("\n")[0...6].join("\n"))
/*
Syntax error: Undefined variable: "!bork".
        on line 2 of #{template_loc('bork1')}

1: bork
2:   :bork= !bork
CSS
    end
    File.delete(tempfile_loc('bork1'))
  end

  def test_nonfull_exception_handling
    old_full_exception = Sass::Plugin.options[:full_exception]
    Sass::Plugin.options[:full_exception] = false

    File.delete(tempfile_loc('bork1'))
    assert_raise(Sass::SyntaxError) {Sass::Plugin.update_stylesheets}
  ensure
    Sass::Plugin.options[:full_exception] = old_full_exception
  end
  
  def test_two_template_directories
    set_plugin_opts :template_location => {
      template_loc => tempfile_loc,
      template_loc(nil,'more_') => tempfile_loc(nil,'more_')
    }
    Sass::Plugin.update_stylesheets
    ['more1', 'more_import'].each { |name| assert_renders_correctly(name, :prefix => 'more_') }
  end

  def test_two_template_directories_with_line_annotations
    set_plugin_opts :line_comments => true,
                    :style => :nested,
                    :template_location => {
                      template_loc => tempfile_loc,
                      template_loc(nil,'more_') => tempfile_loc(nil,'more_')
                    }
    Sass::Plugin.update_stylesheets
    assert_renders_correctly('more1_with_line_comments', 'more1', :prefix => 'more_')
  end

  def test_merb_update
    begin
      require 'merb'
    rescue LoadError
      puts "\nmerb couldn't be loaded, skipping a test"
      return
    end
    
    require 'sass/plugin/merb'
    if defined?(MerbHandler)
      MerbHandler.send(:define_method, :process_without_sass) { |*args| }
    else
      Merb::Rack::Application.send(:define_method, :call_without_sass) { |*args| }
    end

    set_plugin_opts

    File.delete(tempfile_loc('basic'))
    assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
    
    if defined?(MerbHandler)
      MerbHandler.new('.').process nil, nil
    else
      Merb::Rack::Application.new.call(::Rack::MockRequest.env_for('/'))
    end

    assert_stylesheet_updated 'basic'
  end

  def test_doesnt_render_partials
    assert !File.exists?(tempfile_loc('_partial'))
  end

  # Callbacks

  def test_updating_stylesheets_callback
    # Should run even when there's nothing to update
    assert_callback :updating_stylesheets, []
  end

  def test_updating_stylesheets_callback_with_individual_files
    files = [[template_loc("basic"), tempfile_loc("basic")]]
    assert_callback(:updating_stylesheets, files) {Sass::Plugin.update_stylesheets(files)}
  end

  def test_updating_stylesheets_callback_with_never_update
    Sass::Plugin.options[:never_update] = true
    assert_no_callback :updating_stylesheets
  end

  def test_updating_stylesheet_callback_for_updated_template
    Sass::Plugin.options[:always_update] = false
    sleep 1
    FileUtils.touch(template_loc("basic"))
    assert_no_callback :updating_stylesheet, template_loc("complex"), tempfile_loc("complex") do
      assert_callbacks(
        [:updating_stylesheet, template_loc("basic"), tempfile_loc("basic")],
        [:updating_stylesheet, template_loc("import"), tempfile_loc("import")])
    end
  end

  def test_updating_stylesheet_callback_for_fresh_template
    Sass::Plugin.options[:always_update] = false
    assert_no_callback :updating_stylesheet
  end

  def test_updating_stylesheet_callback_for_error_template
    Sass::Plugin.options[:always_update] = false
    FileUtils.touch(template_loc("bork1"))
    assert_no_callback :updating_stylesheet
  end

  ## Regression

  def test_cached_dependencies_update
    FileUtils.mv(template_loc("basic"), template_loc("basic", "more_"))
    set_plugin_opts :load_paths => [result_loc, template_loc(nil, "more_")]

    sleep 1
    FileUtils.touch(template_loc("basic", "more_"))
    assert Sass::Plugin.stylesheet_needs_update?("import", template_loc, tempfile_loc)
    Sass::Plugin.update_stylesheets
    assert_renders_correctly("import")
  ensure
    FileUtils.mv(template_loc("basic", "more_"), template_loc("basic"))
  end

 private

  def assert_renders_correctly(*arguments)
    options = arguments.last.is_a?(Hash) ? arguments.pop : {}
    prefix = options[:prefix]
    result_name = arguments.shift
    tempfile_name = arguments.shift || result_name
    expected_lines = File.read(result_loc(result_name, prefix)).split("\n")
    actual_lines = File.read(tempfile_loc(tempfile_name, prefix)).split("\n")

    if actual_lines.first == "/*" && expected_lines.first != "/*"
      assert(false, actual_lines[0..actual_lines.enum_with_index.find {|l, i| l == "*/"}.last].join("\n"))
    end

    expected_lines.zip(actual_lines).each_with_index do |pair, line|
      message = "template: #{result_name}\nline:     #{line + 1}"
      assert_equal(pair.first, pair.last, message)
    end
    if expected_lines.size < actual_lines.size
      assert(false, "#{actual_lines.size - expected_lines.size} Trailing lines found in #{tempfile_name}.css: #{actual_lines[expected_lines.size..-1].join('\n')}")
    end
  end

  def assert_stylesheet_updated(name)
    assert !Sass::Plugin.stylesheet_needs_update?(name, template_loc, tempfile_loc)

    # Make sure it isn't an exception
    expected_lines = File.read(result_loc(name)).split("\n")
    actual_lines = File.read(tempfile_loc(name)).split("\n")
    if actual_lines.first == "/*" && expected_lines.first != "/*"
      assert(false, actual_lines[0..actual_lines.enum_with_index.find {|l, i| l == "*/"}.last].join("\n"))
    end
  end

  def assert_callback(name, *expected_args)
    run = false
    Sass::Plugin.send("on_#{name}") do |*a|
      run = true if expected_args == a
    end

    if block_given?
      yield
    else
      Sass::Plugin.update_stylesheets
    end

    assert run, "Expected #{name} callback to be run with arguments:\n  #{expected_args.inspect}"
  end

  def assert_no_callback(name, *unexpected_args)
    Sass::Plugin.send("on_#{name}") do |*a|
      next unless unexpected_args.empty? || a == unexpected_args

      msg = "Expected #{name} callback not to be run"
      if !unexpected_args.empty?
        msg << " with arguments #{unexpected_args.inspect}"
      elsif !a.empty?
        msg << ",\n  was run with arguments #{a.inspect}"
      end

      flunk msg
    end

    if block_given?
      yield
    else
      Sass::Plugin.update_stylesheets
    end
  end

  def assert_callbacks(*args)
    return Sass::Plugin.update_stylesheets if args.empty?
    assert_callback(*args.pop) {assert_callbacks(*args)}
  end

  def clear_callbacks
    Sass::Plugin.instance_variable_set('@_sass_callbacks', {})
  end

  def template_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}templates/#{name}.sass"
    else
      absolutize "#{prefix}templates"
    end
  end

  def tempfile_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}tmp/#{name}.css"
    else
      absolutize "#{prefix}tmp"
    end
  end

  def result_loc(name = nil, prefix = nil)
    if name
      absolutize "#{prefix}results/#{name}.css"
    else
      absolutize "#{prefix}results"
    end
  end

  def absolutize(file)
    "#{File.dirname(__FILE__)}/#{file}"
  end

  def set_plugin_opts(overrides = {})
    Sass::Plugin.options = {
      :template_location => template_loc,
      :css_location => tempfile_loc,
      :style => :compact,
      :load_paths => [result_loc],
      :always_update => true,
    }.merge(overrides)
  end

  def wait_a_tick
    time = Time.now
    loop {break if Time.now.sec != time.sec}
  end
end

module Sass::Plugin
  class << self
    public :stylesheet_needs_update?
  end
end

class Sass::Engine
  alias_method :old_render, :render

  def render
    raise "bork bork bork!" if @template[0] == "{bork now!}"
    old_render
  end
end

{'fraction_non_alphanumeric': 0.12042074718897353, 'fraction_numerical': 0.002659896022246403, 'mean_word_length': 3.793682990437554, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5903156', 'n_tokens_mistral': 5531, 'n_tokens_neox': 5402, 'n_words': 1000}