1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
# frozen_string_literal: true
require "fileutils"
require "logger"
require "test_prof/logging"
require "test_prof/utils"
# Add an alias for Process.clock_gettime "reserved" for TestProf
# (in case some other tool would like to patch it)
module ::Process
class << self
# Already patched by Timecop
if method_defined?(:clock_gettime_without_mock)
alias_method :clock_gettime_for_test_prof, :clock_gettime_without_mock
else
alias_method :clock_gettime_for_test_prof, :clock_gettime
def singleton_method_added(method_name)
return super unless method_name == :clock_gettime_without_mock
define_method(:clock_gettime_for_test_prof) { |*args| clock_gettime_without_mock(*args) }
end
end
end
end
# Main TestProf module
#
# Contains configuration and common methods
# Ruby applications tests profiling tools.
#
# Contains tools to analyze factories usage, integrate with Ruby profilers,
# profile your examples using ActiveSupport notifications (if any) and
# statically analyze your code with custom RuboCop cops.
#
# Example usage:
#
# require 'test_prof'
#
# # Activate a tool by providing environment variable, e.g.
# TEST_RUBY_PROF=1 rspec ...
#
# # or manually in your code
# TestProf::RubyProf.run
#
# See other modules for more examples.
module TestProf
class << self
include Logging
def config
@config ||= Configuration.new
end
def configure
yield config
end
# Returns true if we're inside RSpec
def rspec?
defined?(RSpec::Core)
end
# Returns true if we're inside Minitest
def minitest?
defined?(Minitest)
end
# Returns true if Spring is used and not disabled
def spring?
# See https://github.com/rails/spring/blob/577cf01f232bb6dbd0ade7df2df2ac209697e741/lib/spring/binstub.rb
disabled = ENV["DISABLE_SPRING"]
defined?(::Spring::Application) && (disabled.nil? || disabled.empty? || disabled == "0")
end
def dry_run?
rspec? && ::RSpec.configuration.dry_run?
end
# Returns the current process time
def now
Process.clock_gettime_for_test_prof(Process::CLOCK_MONOTONIC)
end
# Require gem and shows a custom
# message if it fails to load
def require(gem_name, msg = nil)
Kernel.require gem_name
block_given? ? yield : true
rescue LoadError
log(:error, msg) if msg
false
end
# Run block only if provided env var is present and
# equal to the provided value (if any).
# Contains workaround for applications using Spring.
def activate(env_var, val = nil)
if spring?
notify_spring_detected
::Spring.after_fork do
activate!(env_var, val) do
notify_spring_activate env_var
yield
end
end
else
activate!(env_var, val) { yield }
end
end
# Return absolute path to asset
def asset_path(filename)
::File.expand_path(filename, ::File.join(::File.dirname(__FILE__), "..", "..", "assets"))
end
# Return a path to store artifact
def artifact_path(filename)
create_artifact_dir
with_timestamps(
::File.join(
config.output_dir,
with_report_suffix(
filename
)
)
)
end
def create_artifact_dir
FileUtils.mkdir_p(config.output_dir)[0]
end
private
def activate!(env_var, val)
yield if ENV[env_var] && (val.nil? || val === ENV[env_var])
end
def with_timestamps(path)
return path unless config.timestamps?
timestamps = "-#{now.to_i}"
"#{path.sub(/\.\w+$/, "")}#{timestamps}#{::File.extname(path)}"
end
def with_report_suffix(path)
return path if config.report_suffix.nil?
"#{path.sub(/\.\w+$/, "")}-#{config.report_suffix}#{::File.extname(path)}"
end
def notify_spring_detected
return if instance_variable_defined?(:@spring_notified)
log :info, "Spring detected"
@spring_notified = true
end
def notify_spring_activate(env_var)
log :info, "Activating #{env_var} with `Spring.after_fork`"
end
end
# TestProf configuration
class Configuration
attr_accessor :output, # IO to write logs
:color, # Whether to colorize output or not
:output_dir, # Directory to store artifacts
:timestamps, # Whether to use timestamped names for artifacts,
:report_suffix # Custom suffix for reports/artifacts
def initialize
@output = $stdout
@color = true
@output_dir = "tmp/test_prof"
@timestamps = false
@report_suffix = ENV["TEST_PROF_REPORT"]
end
def color?
color == true && output.is_a?(IO) && output.tty?
end
def timestamps?
timestamps == true
end
def logger
@logger ||= Logger.new(output, formatter: Logging::Formatter.new)
end
end
end
|