<commit_before>require File.dirname(__FILE__) + '/spec_helper.rb'

class Item
  def initialize(i)
    @i, @rand = i, rand
  end

  class MiddleMatcher
    def ===(item)
      raise "#{item.inspect} is not an Item" unless item.is_a?(Item)
      (0.25..0.75) === item.instance_variable_get(:@rand)
    end
  end

  def work
    sleep @rand * 0.008
  end

  def value
    work; @rand
  end

  def check?
    value < 0.5
  end

  def touch_n_value(*args)
    touch(*args); value
  end

  def touch_n_check?(*args)
    touch(*args); check?
  end
end

class ValueItem < Item
  def initialize(i, value)
    super(i)
    @value = value
  end

  def value
    work; @value
  end

  def check?
    !!value
  end
end

def describe_enum_method(method, &block)
  @enum_methods ||= Enumerable.instance_methods.map(&:to_s)
  if @enum_methods.include?(method)
    describe(method, &block)
  else
    it "should not be defined" do
      exception_regexp = /^undefined method `#{Regexp.escape(method)}' for #<InThreads:0x[0-9a-f]+>$/
      proc{ enum.in_threads.send(method) }.should raise_error(NoMethodError, exception_regexp)
    end
  end
end

describe "in_threads" do
  let(:enum){ 30.times.map{ |i| Item.new(i) } }
  let(:speed_coef){ 0.666 } # small coefficient, should be more if sleep time coefficient is bigger

  def measure
    start = Time.now
    yield
    Time.now - start
  end

  # it "should define all Enumerable methods" do
  #   (Enumerable.instance_methods - 10.times.in_threads.class.instance_methods).should == []
  # end

  describe "in_threads" do
    it "should not change existing instance" do
      threaded = enum.in_threads(10)
      proc{ threaded.in_threads(20) }.should_not change(threaded, :thread_count)
    end

    it "should create new instance with different title when called on WithProgress" do
      threaded = enum.in_threads(10)
      tthreaded = threaded.in_threads(20)
      threaded.thread_count.should == 10
      tthreaded.thread_count.should == 20
      tthreaded.class.should == threaded.class
      tthreaded.object_id.should_not == threaded.object_id
      tthreaded.enumerable.should == threaded.enumerable
    end
  end

  describe "each" do
    it "should return same enum after running" do
      enum.in_threads.each(&:value).should == enum
    end

    it "should execute block for each element" do
      enum.each{ |o| o.should_receive(:touch).once }
      enum.in_threads.each(&:touch_n_value)
    end

    it "should run faster with threads" do
      measure{ enum.in_threads.each(&:work) }.should < measure{ enum.each(&:work) } * speed_coef
    end

    it "should run faster with more threads" do
      measure{ enum.in_threads(10).each(&:work) }.should < measure{ enum.in_threads(2).each(&:work) } * speed_coef
    end

    it "should return same enum without block" do
      enum.in_threads.each.to_a.should == enum.each.to_a
    end
  end

  %w[each_with_index enum_with_index].each do |method|
    describe_enum_method method do
      let(:runner){ proc{ |o, i| o.value } }

      it "should return same result with threads" do
        enum.in_threads.send(method, &runner).should == enum.send(method, &runner)
      end

      it "should fire same objects" do<commit_msg>should run in specified number of threads<commit_after>require File.dirname(__FILE__) + '/spec_helper.rb'

class Item
  def initialize(i)
    @i, @rand = i, rand
  end

  class MiddleMatcher
    def ===(item)
      raise "#{item.inspect} is not an Item" unless item.is_a?(Item)
      (0.25..0.75) === item.instance_variable_get(:@rand)
    end
  end

  def work
    sleep @rand * 0.008
  end

  def value
    work; @rand
  end

  def check?
    value < 0.5
  end

  def touch_n_value(*args)
    touch(*args); value
  end

  def touch_n_check?(*args)
    touch(*args); check?
  end
end

class ValueItem < Item
  def initialize(i, value)
    super(i)
    @value = value
  end

  def value
    work; @value
  end

  def check?
    !!value
  end
end

def describe_enum_method(method, &block)
  @enum_methods ||= Enumerable.instance_methods.map(&:to_s)
  if @enum_methods.include?(method)
    describe(method, &block)
  else
    it "should not be defined" do
      exception_regexp = /^undefined method `#{Regexp.escape(method)}' for #<InThreads:0x[0-9a-f]+>$/
      proc{ enum.in_threads.send(method) }.should raise_error(NoMethodError, exception_regexp)
    end
  end
end

describe "in_threads" do
  let(:enum){ 30.times.map{ |i| Item.new(i) } }
  let(:speed_coef){ 0.666 } # small coefficient, should be more if sleep time coefficient is bigger

  def measure
    start = Time.now
    yield
    Time.now - start
  end

  # it "should define all Enumerable methods" do
  #   (Enumerable.instance_methods - 10.times.in_threads.class.instance_methods).should == []
  # end

  describe "in_threads" do
    it "should not change existing instance" do
      threaded = enum.in_threads(10)
      proc{ threaded.in_threads(20) }.should_not change(threaded, :thread_count)
    end

    it "should create new instance with different title when called on WithProgress" do
      threaded = enum.in_threads(10)
      tthreaded = threaded.in_threads(20)
      threaded.thread_count.should == 10
      tthreaded.thread_count.should == 20
      tthreaded.class.should == threaded.class
      tthreaded.object_id.should_not == threaded.object_id
      tthreaded.enumerable.should == threaded.enumerable
    end
  end

  describe "each" do
    it "should return same enum after running" do
      enum.in_threads.each(&:value).should == enum
    end

    it "should execute block for each element" do
      enum.each{ |o| o.should_receive(:touch).once }
      enum.in_threads.each(&:touch_n_value)
    end

    it "should run faster with threads" do
      measure{ enum.in_threads.each(&:work) }.should < measure{ enum.each(&:work) } * speed_coef
    end

    it "should run faster with more threads" do
      measure{ enum.in_threads(10).each(&:work) }.should < measure{ enum.in_threads(2).each(&:work) } * speed_coef
    end

    it "should return same enum without block" do
      enum.in_threads.each.to_a.should == enum.each.to_a
    end

    it "should run in specified number of threads" do
      enum = 100.times.map{ |i| Item.new(i) }

      @thread_count = 0
      @mutex = Mutex.new
      enum.in_threads(13).each do |o|
        @mutex.synchronize do
          @thread_count += 1
          @thread_count.should <= 13
        end
        o.work
        @mutex.synchronize do
          @thread_count -= 1
        end
      end
      @thread_count.should == 0
    end
  end

  %w[each_with_index enum_with_index].each do |method|
    describe_enum_method method do
      let(:runner){ proc{ |o, i| o.value } }

      it "should return same result with threads" do
        enum.in_threads.send(method, &runner).should == enum.send(method, &runner)
      end

      it "should fire same objects" do
{'fraction_non_alphanumeric': 0.11006517016654598, 'fraction_numerical': 0.012599565532223026, 'mean_word_length': 2.9283276450511946, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 5, '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': '479690', 'n_tokens_mistral': 2375, 'n_tokens_neox': 2260, 'n_words': 620}