diff --git a/lib/benchmark_runner.rb b/lib/benchmark_runner.rb index a19846b1..4cc4a886 100644 --- a/lib/benchmark_runner.rb +++ b/lib/benchmark_runner.rb @@ -80,4 +80,21 @@ def setarch_prefix prefix end + + # Checked system - error or return info if the command fails + def check_call(command, env: {}, raise_error: true, quiet: false) + puts("+ #{command}") unless quiet + + result = {} + + result[:success] = system(env, command) + result[:status] = $? + + unless result[:success] + puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}" + raise RuntimeError.new if raise_error + end + + result + end end diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb new file mode 100644 index 00000000..0ac01688 --- /dev/null +++ b/lib/cpu_config.rb @@ -0,0 +1,161 @@ +require_relative 'benchmark_runner' + +# Manages CPU frequency and turbo boost configuration for benchmark consistency +class CPUConfig + class << self + # Configure CPU for benchmarking: disable frequency scaling and verify settings + def configure_for_benchmarking(turbo:) + build.configure_for_benchmarking(turbo: turbo) + end + + def build + if File.exist?(IntelCPUConfig::PSTATE_DIR) + IntelCPUConfig.new + elsif File.exist?(AMDCPUConfig::BOOST_PATH) + AMDCPUConfig.new + else + NullCPUConfig.new + end + end + end + + def configure_for_benchmarking(turbo:) + disable_frequency_scaling(turbo: turbo) + check_pstate(turbo: turbo) + end + + private + + def disable_frequency_scaling(turbo:) + disable_turbo_boost unless turbo || turbo_disabled? + maximize_frequency unless frequency_maximized? + end + + def turbo_disabled? + # Override in subclasses + false + end + + def frequency_maximized? + # Override in subclasses + false + end + + def disable_turbo_boost + # Override in subclasses + end + + def maximize_frequency + # Override in subclasses + end + + def check_pstate(turbo:) + # Override in subclasses + end +end + +# Intel CPU configuration +class IntelCPUConfig < CPUConfig + PSTATE_DIR = '/sys/devices/system/cpu/intel_pstate' + NO_TURBO_PATH = "#{PSTATE_DIR}/no_turbo" + MIN_PERF_PCT_PATH = "#{PSTATE_DIR}/min_perf_pct" + TURBO_DISABLED_VALUE = '1' + FREQUENCY_MAXIMIZED_VALUE = '100' + + private + + def disable_turbo_boost + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > #{NO_TURBO_PATH}'", quiet: true) } + end + + def maximize_frequency + # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'") + end + + def turbo_disabled? + @turbo_disabled ||= File.exist?(NO_TURBO_PATH) && + File.read(NO_TURBO_PATH).strip == TURBO_DISABLED_VALUE + end + + def frequency_maximized? + @frequency_maximized ||= File.exist?(MIN_PERF_PCT_PATH) && + File.read(MIN_PERF_PCT_PATH).strip == FREQUENCY_MAXIMIZED_VALUE + end + + def check_pstate(turbo:) + unless turbo || turbo_disabled? + puts("You forgot to disable turbo:") + puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'") + exit(-1) + end + + unless frequency_maximized? + puts("You forgot to set the min perf percentage to 100:") + puts(" sudo sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'") + exit(-1) + end + end +end + +# AMD CPU configuration +class AMDCPUConfig < CPUConfig + CPUFREQ_DIR = '/sys/devices/system/cpu/cpufreq' + BOOST_PATH = "#{CPUFREQ_DIR}/boost" + SCALING_GOVERNOR_GLOB = '/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor' + TURBO_DISABLED_VALUE = '0' + TURBO_ENABLED_VALUE = '1' + PERFORMANCE_GOVERNOR = 'performance' + + private + + def disable_turbo_boost + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_ENABLED_VALUE} > #{BOOST_PATH}'", quiet: true) } + end + + def maximize_frequency + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") + end + + def turbo_disabled? + @turbo_disabled ||= File.exist?(BOOST_PATH) && + File.read(BOOST_PATH).strip == TURBO_DISABLED_VALUE + end + + def frequency_maximized? + @frequency_maximized ||= Dir.glob(SCALING_GOVERNOR_GLOB).all? do |governor| + File.read(governor).strip == PERFORMANCE_GOVERNOR + end + end + + def check_pstate(turbo:) + unless turbo || turbo_disabled? + puts("You forgot to disable boost:") + puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'") + exit(-1) + end + + unless frequency_maximized? + puts("You forgot to set the performance governor:") + puts(" sudo cpupower frequency-set -g #{PERFORMANCE_GOVERNOR}") + exit(-1) + end + end +end + +# Null object for unsupported CPUs +class NullCPUConfig < CPUConfig + private + + def disable_frequency_scaling(turbo:) + # Do nothing + end + + def check_pstate(turbo:) + # Do nothing + end +end diff --git a/run_benchmarks.rb b/run_benchmarks.rb index 4bc60915..55d32693 100755 --- a/run_benchmarks.rb +++ b/run_benchmarks.rb @@ -11,101 +11,16 @@ require 'etc' require 'yaml' require_relative 'misc/stats' +require_relative 'lib/cpu_config' require_relative 'lib/benchmark_runner' require_relative 'lib/table_formatter' require_relative 'lib/benchmark_filter' -# Checked system - error or return info if the command fails -def check_call(command, env: {}, raise_error: true, quiet: false) - puts("+ #{command}") unless quiet - - result = {} - - result[:success] = system(env, command) - result[:status] = $? - - unless result[:success] - puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}" - raise RuntimeError.new if raise_error - end - - result -end - -def check_output(*command) - IO.popen(*command, &:read) -end - def have_yjit?(ruby) - ruby_version = check_output("#{ruby} -v --yjit", err: File::NULL).strip + ruby_version = `#{ruby} -v --yjit 2> #{File::NULL}`.strip ruby_version.downcase.include?("yjit") end -# Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. -def set_bench_config(turbo:) - # sudo requires the flag '-S' in order to take input from stdin - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless intel_no_turbo? || turbo - check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } - end - # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless amd_no_boost? || turbo - check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } - end - check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? - end -end - -def check_pstate(turbo:) - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless turbo || intel_no_turbo? - puts("You forgot to disable turbo:") - puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - exit(-1) - end - - unless intel_perf_100pct? - puts("You forgot to set the min perf percentage to 100:") - puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") - exit(-1) - end - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless turbo || amd_no_boost? - puts("You forgot to disable boost:") - puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - exit(-1) - end - - unless performance_governor? - puts("You forgot to set the performance governor:") - puts(" sudo cpupower frequency-set -g performance") - exit(-1) - end - end -end - -def intel_no_turbo? - File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' -end - -def intel_perf_100pct? - File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' -end - -def amd_no_boost? - File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' -end - -def performance_governor? - Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| - File.read(governor).strip == 'performance' - end -end - def mean(values) Stats.new(values).mean end @@ -132,10 +47,6 @@ def sort_benchmarks(bench_names) BenchmarkRunner.sort_benchmarks(bench_names, benchmarks_metadata) end -def setarch_prefix - BenchmarkRunner.setarch_prefix -end - # Run all the benchmarks and record execution times def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_path:, harness:, pre_init:, no_pinning:) bench_data = {} @@ -193,7 +104,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat # Set up the benchmarking command cmd = [] if BenchmarkRunner.os == :linux - cmd += setarch_prefix + cmd += BenchmarkRunner.setarch_prefix # Pin the process to one given core to improve caching and reduce variance on CRuby # Other Rubies need to use multiple cores, e.g., for JIT threads @@ -230,7 +141,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat end # Do the benchmarking - result = check_call(cmd.shelljoin, env: env, raise_error: false) + result = BenchmarkRunner.check_call(cmd.shelljoin, env: env, raise_error: false) if result[:success] bench_data[bench_name] = JSON.parse(File.read(result_json_path)).tap do |json| @@ -387,18 +298,14 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat end end -# Disable CPU frequency scaling -set_bench_config(turbo: args.turbo) - -# Check pstate status -check_pstate(turbo: args.turbo) +CPUConfig.configure_for_benchmarking(turbo: args.turbo) # Create the output directory FileUtils.mkdir_p(args.out_path) ruby_descriptions = {} args.executables.each do |name, executable| - ruby_descriptions[name] = check_output([*executable, "-v"]).chomp + ruby_descriptions[name] = `#{executable.shelljoin} -v`.chomp end # Benchmark with and without YJIT diff --git a/test/benchmark_runner_test.rb b/test/benchmark_runner_test.rb index 1633c141..e44c2c0f 100644 --- a/test/benchmark_runner_test.rb +++ b/test/benchmark_runner_test.rb @@ -170,6 +170,82 @@ end end + describe '.check_call' do + it 'runs a successful command and returns success status' do + result = nil + + capture_io do + result = BenchmarkRunner.check_call('true') + end + + assert_equal true, result[:success] + assert_equal 0, result[:status].exitstatus + end + + it 'prints the command by default' do + output = capture_io do + BenchmarkRunner.check_call('true') + end + + assert_includes output[0], '+ true' + end + + it 'suppresses output when quiet is true' do + output = capture_io do + BenchmarkRunner.check_call('true', quiet: true) + end + + assert_equal '', output[0] + end + + it 'raises error by default when command fails' do + output = capture_io do + assert_raises(RuntimeError) do + BenchmarkRunner.check_call('false') + end + end + + assert_includes output[0], 'Command "false" failed' + end + + it 'does not raise error when raise_error is false' do + output = capture_io do + result = BenchmarkRunner.check_call('false', raise_error: false) + + assert_equal false, result[:success] + assert_equal 1, result[:status].exitstatus + end + + assert_includes output[0], 'Command "false" failed' + end + + it 'passes environment variables to the command' do + Dir.mktmpdir do |dir| + output_file = File.join(dir, 'test_output.txt') + output = capture_io do + result = BenchmarkRunner.check_call("sh -c 'echo $TEST_VAR > #{output_file}'", env: { 'TEST_VAR' => 'hello' }) + assert_equal true, result[:success] + end + + # Command should be printed + assert_includes output[0], "+ sh -c 'echo $TEST_VAR" + # Environment variable should be written to file + assert_equal "hello\n", File.read(output_file) + end + end + + it 'includes exit code and directory in error message' do + output = capture_io do + result = BenchmarkRunner.check_call('sh -c "exit 42"', raise_error: false) + assert_equal false, result[:success] + assert_equal 42, result[:status].exitstatus + end + + assert_includes output[0], 'exit code 42' + assert_includes output[0], "directory #{Dir.pwd}" + end + end + describe '.setarch_prefix' do it 'returns an array' do result = BenchmarkRunner.setarch_prefix diff --git a/test/cpu_config_test.rb b/test/cpu_config_test.rb new file mode 100644 index 00000000..f59a28e9 --- /dev/null +++ b/test/cpu_config_test.rb @@ -0,0 +1,365 @@ +require_relative 'test_helper' +require_relative '../lib/cpu_config' +require_relative '../lib/benchmark_runner' + +describe CPUConfig do + describe '.build' do + it 'returns IntelCPUConfig when Intel pstate files exist' do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + config = CPUConfig.build + assert_instance_of IntelCPUConfig, config + end + end + + it 'returns AMDCPUConfig when AMD cpufreq files exist' do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + config = CPUConfig.build + assert_instance_of AMDCPUConfig, config + end + end + + it 'returns NullCPUConfig when no CPU files exist' do + File.stub :exist?, false do + config = CPUConfig.build + assert_instance_of NullCPUConfig, config + end + end + end +end + +describe NullCPUConfig do + describe '#configure_for_benchmarking' do + it 'does nothing when CPU frequency files do not exist' do + call_count = 0 + at_exit_called = false + exit_called = false + + File.stub :exist?, false do + cpu_config = NullCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when files don't exist" + refute at_exit_called, "Should not call at_exit when CPU frequency files don't exist" + refute exit_called, "Should not exit when files don't exist" + end + end + end + end + end + end +end + +describe IntelCPUConfig do + describe '#configure_for_benchmarking' do + it 'does not call commands or exit when Intel CPU is already properly configured with turbo disabled' do + call_count = 0 + at_exit_called = false + exit_called = false + + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = IntelCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "1\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when Intel CPU is properly configured" + refute at_exit_called, "Should not call at_exit when Intel CPU already configured" + refute exit_called, "Should not exit when Intel CPU is properly configured" + end + end + end + end + end + end + + it 'does not call commands or exit when Intel CPU allows turbo and min_perf is 100%' do + call_count = 0 + at_exit_called = false + exit_called = false + + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = IntelCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "0\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: true) + end + assert_equal 0, call_count, "Should not call check_call when turbo is true and min_perf is correct" + refute at_exit_called, "Should not call at_exit when turbo is true and CPU already configured" + refute exit_called, "Should not exit when turbo is true and performance is correct" + end + end + end + end + end + end + + it 'configures Intel CPU and registers cleanup when turbo needs to be disabled' do + call_count = 0 + at_exit_called = false + at_exit_block = nil + exit_called = false + read_count = 0 + + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = IntelCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + read_count += 1 + # First read checks if turbo is disabled (for disable_frequency_scaling) + # Second read checks if turbo is disabled (for check_pstate) + # After the first check_call, we simulate that turbo is now disabled + read_count <= 1 ? "0\n" : "1\n" + elsif path.include?('min_perf_pct') + # After check_call sets it, simulate it's now 100% + "100\n" + end + } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: false) + end + assert_operator call_count, :>, 0, "Should call check_call to configure Intel CPU" + assert at_exit_called, "Should register at_exit handler to restore CPU settings" + assert_instance_of Proc, at_exit_block, "at_exit should be called with a block" + refute exit_called, "Should not exit when Intel CPU gets properly configured" + end + end + end + end + end + + # Verify at_exit block restores Intel turbo settings + cleanup_commands = [] + BenchmarkRunner.stub :check_call, ->(cmd, **opts) { cleanup_commands << { cmd: cmd, opts: opts } } do + at_exit_block.call + end + + assert_equal 1, cleanup_commands.length, "at_exit block should call check_call once" + assert_equal "sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", cleanup_commands[0][:cmd] + assert_equal({ quiet: true }, cleanup_commands[0][:opts]) + end + + it 'exits when Intel turbo is not disabled and turbo flag is false' do + exit_code = nil + output = capture_io do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = IntelCPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "0\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + cpu_config.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to disable turbo" + assert_includes output[0], "sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'" + end + + it 'exits when Intel min perf is not 100%' do + exit_code = nil + output = capture_io do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = IntelCPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "1\n" + elsif path.include?('min_perf_pct') + "50\n" + end + } do + cpu_config.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to set the min perf percentage to 100" + assert_includes output[0], "sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'" + end + end +end + +describe AMDCPUConfig do + describe '#configure_for_benchmarking' do + it 'does not call commands or exit when AMD CPU is already properly configured with turbo disabled' do + call_count = 0 + at_exit_called = false + exit_called = false + + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = AMDCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :read, ->(path) { + if path.include?('boost') + "0\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when AMD CPU is properly configured" + refute at_exit_called, "Should not call at_exit when AMD CPU already configured" + refute exit_called, "Should not exit when AMD CPU is properly configured" + end + end + end + end + end + end + end + + it 'configures AMD CPU and registers cleanup when boost needs to be disabled' do + call_count = 0 + at_exit_called = false + at_exit_block = nil + exit_called = false + read_count = 0 + + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = AMDCPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :read, lambda { |path| + if path.include?('boost') + read_count += 1 + # First read checks if boost is disabled, second read is after we disable it + read_count == 1 ? "1\n" : "0\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + capture_io do + cpu_config.configure_for_benchmarking(turbo: false) + end + assert_operator call_count, :>, 0, "Should call check_call to configure AMD CPU" + assert at_exit_called, "Should register at_exit handler to restore CPU settings" + assert_instance_of Proc, at_exit_block, "at_exit should be called with a block" + refute exit_called, "Should not exit when AMD CPU gets properly configured" + end + end + end + end + end + end + + # Verify at_exit block restores AMD boost settings + cleanup_commands = [] + BenchmarkRunner.stub :check_call, ->(cmd, **opts) { cleanup_commands << { cmd: cmd, opts: opts } } do + at_exit_block.call + end + + assert_equal 1, cleanup_commands.length, "at_exit block should call check_call once" + assert_equal "sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", cleanup_commands[0][:cmd] + assert_equal({ quiet: true }, cleanup_commands[0][:opts]) + end + + it 'exits when AMD boost is not disabled and turbo flag is false' do + exit_code = nil + output = capture_io do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = AMDCPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :read, ->(path) { + if path.include?('boost') + "1\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + cpu_config.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to disable boost" + assert_includes output[0], "sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'" + end + + it 'exits when AMD performance governor is not set' do + exit_code = nil + output = capture_io do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = AMDCPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :read, lambda { |path| + if path.include?('boost') + "0\n" + else + "powersave\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + cpu_config.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to set the performance governor" + assert_includes output[0], "sudo cpupower frequency-set -g performance" + end + end +end