cybernetlab/ensure_it

View on GitHub
lib/tasks/ensure_it_benchmark.thor

Summary

Maintainability
Test Coverage
require 'benchmark'

if ENV['USE_REFINES'] == 'true'
  ENSURE_IT_REFINED = true
end

module EnsureIt
  class Benchmark < Thor
    OBJECTS = [
      nil, true, false, 0, 0.1, '1/3'.to_r, 'test', :test, Object, Class, ->{}
    ]

    class_option :smart, aliases: '-s', desc: 'use smart errors'
    class_option :count, aliases: '-n', desc: 'number of tests', default: 10000
    class_option :profile, aliases: '-p', desc: 'profile'

    desc 'symbol', 'runs benchmarks for ensure_symbol'
    def symbol
      run_benchmark :symbol, ensure_proc: ->(x) { x.ensure_symbol } do |x|
        x = x.to_sym if x.is_a?(String)
        x.is_a?(Symbol) ? x : nil
      end
    end

    desc 'symbol!', 'runs benchmarks for ensure_symbol!'
    def symbol!
      run_benchmark :symbol!, ensure_proc: ->(x) { x.ensure_symbol! } do |x|
        x = x.to_sym if x.is_a?(String)
        raise ArgumentError unless x.is_a?(Symbol)
      end
    end

    desc 'string', 'runs benchmarks for ensure_string'
    def string
      run_benchmark :string, ensure_proc: ->(x) { x.ensure_string } do |x|
        x = x.to_s if x.is_a?(Symbol)
        x.is_a?(String) ? x : nil
      end
    end

    desc 'string!', 'runs benchmarks for ensure_string!'
    def string!
      run_benchmark :string!, ensure_proc: ->(x) { x.ensure_string! } do |x|
        x = x.to_s if x.is_a?(Symbol)
        raise ArgumentError unless x.is_a?(String)
      end
    end

    desc 'integer', 'runs benchmarks for ensure_integer'
    def integer
      run_benchmark :integer, ensure_proc: ->(x) { x.ensure_integer } do |x|
        if x.is_a?(Integer)
          x
        elsif x.is_a?(Float) || x.is_a?(Rational)
          x.round
        elsif x.is_a?(String)
          case x
          when ::EnsureIt::INT_REGEXP then x.to_i
          when ::EnsureIt::HEX_REGEXP then x[2..-1].to_i(16)
          when ::EnsureIt::BIN_REGEXP then x[2..-1].to_i(2)
          else nil
          end
        else
          nil
        end
      end
    end

    desc 'integer!', 'runs benchmarks for ensure_integer!'
    def integer!
      run_benchmark :integer!, ensure_proc: ->(x) { x.ensure_integer! } do |x|
        if x.is_a?(Integer)
          x
        elsif x.is_a?(Float) || x.is_a?(Rational)
          x.round
        elsif x.is_a?(String)
          case x
          when ::EnsureIt::INT_REGEXP then x.to_i
          when ::EnsureIt::HEX_REGEXP then x[2..-1].to_i(16)
          when ::EnsureIt::BIN_REGEXP then x[2..-1].to_i(2)
          else raise ArgumentError
          end
        else
          raise ArgumentError
        end
      end
    end

    desc 'float', 'runs benchmarks for ensure_float'
    def float
      run_benchmark :float, ensure_proc: ->(x) { x.ensure_float } do |x|
        if x.is_a?(Float)
          x
        elsif x.is_a?(Numeric) ||
              x.is_a?(String) && ::EnsureIt::FLOAT_REGEXP =~ x
          x.to_f
        else
          nil
        end
      end
    end

    desc 'float!', 'runs benchmarks for ensure_float!'
    def float!
      run_benchmark :float!, ensure_proc: ->(x) { x.ensure_float! } do |x|
        if x.is_a?(Float)
          x
        elsif x.is_a?(Numeric) ||
              x.is_a?(String) && ::EnsureIt::FLOAT_REGEXP =~ x
          x.to_f
        else
          raise ArgumentError
        end
      end
    end

    desc 'non_bang', 'runs all non-bang benchmarks'
    def non_bang
      invoke(:symbol)
      invoke(:string)
      invoke(:integer)
      invoke(:float)
    end

    desc 'bang', 'runs all bang benchmarks'
    def bang
      invoke(:symbol!)
      invoke(:string!)
      invoke(:integer!)
      invoke(:float!)
    end

    desc 'all', 'runs all benchmarks'
    def all
      invoke(:non_bang)
      invoke(:bang)
    end

    no_commands do
      protected

      def run_benchmark(task_name, ensure_proc: proc {}, &standard_proc)
        load_ensure_it
        ensure_it, standard = [], []
        start_task(task_name)
        ::Benchmark.benchmark do |x|
          start_profile(task_name)
          ensure_it = x.report('ensure_it:    ') do
            OBJECTS.each do |obj|
              count.times { ensure_proc.call(obj) rescue ::EnsureIt::Error }
            end
          end
          standard = x.report('standard way: ') do
            OBJECTS.each do |obj|
              count.times { standard_proc.call(obj) rescue ArgumentError }
            end
          end
          end_profile(task_name)
        end
        end_task(task_name)
        [ensure_it, standard]
      end

      def start_task(task_name)
        text = "Starting benchmarks for #ensure_#{task_name} "
        if ENSURE_IT_REFINED == true
          text << ' with refined version of EnsureIt.'
        else
          text << ' with monkey-patched version of EnsureIt.'
        end
        text << " Errors: #{::EnsureIt.config.errors}."
        text << " Ruby version: #{RUBY_VERSION}"
        say text, :green
      end

      def end_task(task_name); end

      def start_profile(task_name)
        RubyProf.start if profile?
      end

      def end_profile(task_name)
        if profile?
          result = RubyProf.stop
          result.eliminate_methods!([
            /\ABenchmark/,
            /Thor::(?!Sandbox::EnsureIt::Benchmark#run_benchmark)/,
            /Integer#times/, /Struct::Tms/, /<Module::Process>/,
            /<Class::Time>/, /Time/
          ])
          file = File.join(profile_path, "ensure_#{task_name}.dot")
          unless Dir.exist?(File.dirname(file))
            FileUtils.mkpath File.dirname(file)
          end
          printer = RubyProf::DotPrinter.new(result)
          File.open(file, 'w') { |f| printer.print(f, min_percent: 0) }
          file = File.join(profile_path, "ensure_#{task_name}.txt")
          printer = RubyProf::GraphPrinter.new(result)
          File.open(file, 'w') { |f| printer.print(f, min_percent: 0) }
        end
      end

      def count
        n = options['count'].to_i
        n <= 0 ? 10000 : n
      end

      def refined?
        options['refined'] == 'refined' || options['refined'] == 'true'
      end

      def profile?
        options.key?('profile')
      end

      def profile_path
        if options['profile'] == 'profile' || options['profile'] == 'true'
          File.join(Dir.pwd, 'tmp')
        else
          File.expand_path(File.join('..', 'tmp'), __FILE__)
        end
      end

      def errors
        if options['smart'] == 'smart' || options['smart'] == true
          :smart
        else
          :standard
        end
      end

      def load_ensure_it
        lib = File.expand_path(File.join('..', '..'), __FILE__)
        $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
        require(refined? ? 'ensure_it_refined' : 'ensure_it')
        ::EnsureIt.configure do |config|
          config.errors = errors
        end
        require 'ruby-prof' if profile?
      end
    end
  end
end