# frozen_string_literal: true
#
# purpose:
#  Profile memory usage of each tests.
#
# usage:
#   RUBY_TEST_ALL_PROFILE=[file] make test-all
#
# output:
#   [file] specified by RUBY_TEST_ALL_PROFILE
#   If [file] is 'true', then it is ./test_all_profile
#
# collected information:
#   - ObjectSpace.memsize_of_all
#   - GC.stat
#   - /proc/meminfo     (some fields, if exists)
#   - /proc/self/status (some fields, if exists)
#   - /proc/self/statm  (if exists)
#

require 'objspace'

class MiniTest::Unit::TestCase
  alias orig_run run

  file = ENV['RUBY_TEST_ALL_PROFILE']
  file = 'test-all-profile-result' if file == 'true'
  TEST_ALL_PROFILE_OUT = open(file, 'w')
  TEST_ALL_PROFILE_GC_STAT_HASH = {}
  TEST_ALL_PROFILE_BANNER = ['name']
  TEST_ALL_PROFILE_PROCS  = []

  def self.add *name, &b
    TEST_ALL_PROFILE_BANNER.concat name
    TEST_ALL_PROFILE_PROCS << b
  end

  add 'failed?' do |result, tc|
    result << (tc.passed? ? 0 : 1)
  end

  add 'memsize_of_all' do |result, *|
    result << ObjectSpace.memsize_of_all
  end

  add *GC.stat.keys do |result, *|
    GC.stat(TEST_ALL_PROFILE_GC_STAT_HASH)
    result.concat TEST_ALL_PROFILE_GC_STAT_HASH.values
  end

  def self.add_proc_meminfo file, fields
    return unless FileTest.exist?(file)
    regexp = /(#{fields.join("|")}):\s*(\d+) kB/
    # check = {}; fields.each{|e| check[e] = true}
    add *fields do |result, *|
      text = File.read(file)
      text.scan(regexp){
        # check.delete $1
        result << $2
        ''
      }
      # raise check.inspect unless check.empty?
    end
  end

  add_proc_meminfo '/proc/meminfo', %w(MemTotal MemFree)
  add_proc_meminfo '/proc/self/status', %w(VmPeak VmSize VmHWM VmRSS)

  if FileTest.exist?('/proc/self/statm')
    add *%w(size resident share text lib data dt) do |result, *|
      result.concat File.read('/proc/self/statm').split(/\s+/)
    end
  end

  def memprofile_test_all_result_result
    result = ["#{self.class}\##{self.__name__.to_s.gsub(/\s+/, '')}"]
    TEST_ALL_PROFILE_PROCS.each{|proc|
      proc.call(result, self)
    }
    result.join("\t")
  end

  def run runner
    result = orig_run(runner)
    TEST_ALL_PROFILE_OUT.puts memprofile_test_all_result_result
    TEST_ALL_PROFILE_OUT.flush
    result
  end

  TEST_ALL_PROFILE_OUT.puts TEST_ALL_PROFILE_BANNER.join("\t")
end
