From 87fe30561615f16585cd408e7ab9ed32b5920197 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 30 Aug 2022 15:17:44 +0000 Subject: [PATCH] Add tests for MMTk --- gc.c | 72 ++++++++++++++++++++++++--- test/ruby/test_mmtk.rb | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 test/ruby/test_mmtk.rb diff --git a/gc.c b/gc.c index cf9ab2bd9e6aec..0fb47108357a48 100644 --- a/gc.c +++ b/gc.c @@ -160,6 +160,8 @@ static const char *mmtk_env_plan = NULL; static const char *mmtk_pre_arg_plan = NULL; static const char *mmtk_post_arg_plan = NULL; static const char *mmtk_chosen_plan = NULL; +static size_t mmtk_pre_max_heap_size = 0; +static size_t mmtk_post_max_heap_size = 0; static bool mmtk_max_heap_parse_error = false; static size_t mmtk_max_heap_size = 0; @@ -15463,6 +15465,15 @@ size_t rb_mmtk_heap_limit(void) { } void rb_mmtk_pre_process_opts(int argc, char **argv) { + /* + * Processing these arguments is a mess - we have to process them before + * Ruby is set up, when arguments are normally processed, because we need + * the GC up and running to set up Ruby. We have to kind of rough parsing + * and then re-parse them properly later and compare against our rough + * parsing. We also can't report errors using exceptions. Needs tidying + * up in general, but may always be a bit awkward. + */ + bool enable_rubyopt = true; mmtk_env_plan = getenv("MMTK_PLAN"); @@ -15470,6 +15481,10 @@ void rb_mmtk_pre_process_opts(int argc, char **argv) { mmtk_enable = true; } + if (getenv("THIRD_PARTY_HEAP_LIMIT")) { + mmtk_enable = true; + } + for (int n = 1; n < argc; n++) { if (strcmp(argv[n], "--") == 0) { break; @@ -15493,13 +15508,23 @@ void rb_mmtk_pre_process_opts(int argc, char **argv) { || strcmp(argv[n], "--disable=mmtk") == 0) { mmtk_enable = false; } - else if (strncmp(argv[n], "--mmtk-plan=", strlen("--mmtk-plan=")) == 0) { + else if (strncmp(argv[n], "--mmtk-plan", strlen("--mmtk-plan")) == 0) { mmtk_enable = true; mmtk_pre_arg_plan = argv[n] + strlen("--mmtk-plan="); + if (argv[n][strlen("--mmtk-plan")] != '=' || strlen(mmtk_pre_arg_plan) == 0) { + fputs("[FATAL] --mmtk-plan needs an argument\n", stderr); + exit(EXIT_FAILURE); + } } - else if (strncmp(argv[n], "--mmtk-max-heap=", strlen("--mmtk-max-heap=")) == 0) { + else if (strncmp(argv[n], "--mmtk-max-heap", strlen("--mmtk-max-heap")) == 0) { mmtk_enable = true; - mmtk_max_heap_size = rb_mmtk_parse_heap_limit(argv[n] + strlen("--mmtk-max-heap="), &mmtk_max_heap_parse_error); + char *mmtk_max_heap_size_arg = argv[n] + strlen("--mmtk-max-heap="); + if (argv[n][strlen("--mmtk-max-heap")] != '=' || strlen(mmtk_max_heap_size_arg) == 0) { + fputs("[FATAL] --mmtk-max-heap needs an argument\n", stderr); + exit(EXIT_FAILURE); + } + mmtk_pre_max_heap_size = rb_mmtk_parse_heap_limit(mmtk_max_heap_size_arg, &mmtk_max_heap_parse_error); + mmtk_max_heap_size = mmtk_pre_max_heap_size; } } @@ -15516,12 +15541,41 @@ void rb_mmtk_pre_process_opts(int argc, char **argv) { length++; } - if (strncmp(env_args, "--mmtk-plan=", strlen("--mmtk-plan=")) == 0) { + if (strncmp(env_args, "--mmtk", strlen("--mmtk")) == 0) { + mmtk_enable = true; + } else if (strncmp(env_args, "--enable-mmtk", strlen("--enable-mmtk")) == 0) { mmtk_enable = true; + } else if (strncmp(env_args, "--enable=mmtk", strlen("--enable=mmtk")) == 0) { + mmtk_enable = true; + } + + if (strncmp(env_args, "--mmtk-plan", strlen("--mmtk-plan")) == 0) { + if (env_args[strlen("--mmtk-plan")] != '=') { + fputs("[FATAL] --mmtk-plan needs an argument\n", stderr); + exit(EXIT_FAILURE); + } mmtk_pre_arg_plan = strndup(env_args + strlen("--mmtk-plan="), length - strlen("--mmtk-plan=")); if (mmtk_pre_arg_plan == NULL) { rb_bug("could not allocate space for argument"); } + if (strlen(mmtk_pre_arg_plan) == 0) { + fputs("[FATAL] --mmtk-plan needs an argument\n", stderr); + exit(EXIT_FAILURE); + } + } else if (strncmp(env_args, "--mmtk-max-heap", strlen("--mmtk-max-heap")) == 0) { + if (env_args[strlen("--mmtk-max-heap")] != '=') { + fputs("[FATAL] --mmtk-max-heap needs an argument\n", stderr); + exit(EXIT_FAILURE); + } + char *mmtk_max_heap_size_arg = strndup(env_args + strlen("--mmtk-max-heap="), length - strlen("--mmtk-max-heap=")); + if (mmtk_max_heap_size_arg == NULL) { + rb_bug("could not allocate space for argument"); + } + if (strlen(mmtk_max_heap_size_arg) == 0) { + fputs("[FATAL] --mmtk-max-heap needs an argument\n", stderr); + exit(EXIT_FAILURE); + } + mmtk_pre_max_heap_size = rb_mmtk_parse_heap_limit(mmtk_max_heap_size_arg, &mmtk_max_heap_parse_error); } env_args += length; @@ -15532,7 +15586,7 @@ void rb_mmtk_pre_process_opts(int argc, char **argv) { if (enable_rubyopt && mmtk_env_plan && mmtk_pre_arg_plan && strcmp(mmtk_env_plan, mmtk_pre_arg_plan) != 0) { fputs("[FATAL] MMTK_PLAN and --mmtk-plan do not agree\n", stderr); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } if (enable_rubyopt && mmtk_env_plan) { @@ -15546,8 +15600,6 @@ void rb_mmtk_pre_process_opts(int argc, char **argv) { } } -#define opt_match_noarg(s, l, name) \ - opt_match(s, l, name) && (*(s) ? (rb_warn("argument to --mmtk-" name " is ignored"), 1) : 1) #define opt_match_arg(s, l, name) \ opt_match(s, l, name) && (*(s) ? 1 : (rb_raise(rb_eRuntimeError, "--mmtk-" name " needs an argument"), 0)) @@ -15560,7 +15612,7 @@ void rb_mmtk_post_process_opts(const char *s) { mmtk_post_arg_plan = s + 1; } else if (opt_match_arg(s, l, "max-heap")) { - // no-op + mmtk_post_max_heap_size = rb_mmtk_parse_heap_limit((char *) (s + 1), &mmtk_max_heap_parse_error); } else { rb_raise(rb_eRuntimeError, @@ -15577,6 +15629,10 @@ void rb_mmtk_post_process_opts_finish(bool feature_enable) { rb_raise(rb_eRuntimeError, "--mmtk-plan values disagree"); } + if (mmtk_pre_max_heap_size != 0 && mmtk_post_max_heap_size != 0 && mmtk_pre_max_heap_size != mmtk_post_max_heap_size) { + rb_raise(rb_eRuntimeError, "--mmtk-max-heap values disagree"); + } + if (mmtk_max_heap_parse_error) { rb_raise(rb_eRuntimeError, "--mmtk-max-heap Invalid. Valid values positive integers, with optional KiB, MiB, GiB, TiB suffixes."); diff --git a/test/ruby/test_mmtk.rb b/test/ruby/test_mmtk.rb new file mode 100644 index 00000000000000..3a512bd34d0c09 --- /dev/null +++ b/test/ruby/test_mmtk.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS='test/ruby/test_mmtk.rb' RUN_OPTS="--mmtk" + +require 'test/unit' +require 'envutil' +require 'tmpdir' + +return unless defined?(GC::MMTk.enabled?) && GC::MMTk.enabled? + +class TestMMTk < Test::Unit::TestCase + def test_description + assert_includes(RUBY_DESCRIPTION, '+MMTk') + end + + ENABLE_OPTIONS = [ + ['--mmtk'], + ["--mmtk-plan=#{GC::MMTk.plan_name}"], + ['--mmtk-max-heap=1024000'], + ['--enable-mmtk'], + ['--enable=mmtk'], + + ['--disable-mmtk', '--mmtk'], + ['--disable-mmtk', '--enable-mmtk'], + ['--disable-mmtk', '--enable=mmtk'], + ['--disable-mmtk', "--mmtk-plan=#{GC::MMTk.plan_name}"], + ['--disable-mmtk', '--mmtk-max-heap=1024000'], + + ['--disable=mmtk', '--mmtk'], + ['--disable=mmtk', '--enable-mmtk'], + ['--disable=mmtk', '--enable=mmtk'], + ['--disable=mmtk', "--mmtk-plan=#{GC::MMTk.plan_name}"], + ['--disable=mmtk', '--mmtk-max-heap=1024000'] + ] + + def test_enable + ENABLE_OPTIONS.each do |version_args| + assert_in_out_err(['--version'] + version_args) do |stdout, stderr| + assert_equal(RUBY_DESCRIPTION, stdout.first) + assert_equal([], stderr) + end + end + end + + def test_enable_from_rubyopt + ENABLE_OPTIONS.each do |version_args| + mmtk_child_env = {'RUBYOPT' => version_args.join(' ')} + assert_in_out_err([mmtk_child_env, '--version'], '') do |stdout, stderr| + assert_equal(RUBY_DESCRIPTION, stdout.first) + assert_equal([], stderr) + end + end + end + + def test_invalid_flags + assert_in_out_err('--mmtk-', '', [], /invalid option --mmtk-/) + assert_in_out_err('--mmtkhello', '', [], /invalid option --mmtkhello/) + end + + def test_args + assert_in_out_err('--mmtk-plan', '', [], /--mmtk-plan needs an argument/) + assert_in_out_err('--mmtk-plan=', '', [], /--mmtk-plan needs an argument/) + assert_in_out_err('--mmtk-max-heap', '', [], /--mmtk-max-heap needs an argument/) + assert_in_out_err('--mmtk-max-heap=', '', [], /--mmtk-max-heap needs an argument/) + end + + def test_arg_after_script + Tempfile.create(["test_ignore_after_script", ".rb"]) do |t| + t.puts "p ARGV" + t.close + assert_in_out_err([t.path, '--mmtk'], '', [["--mmtk"].inspect]) + end + end + + def test_mmtk_plan_env_var + assert_in_out_err([{'MMTK_PLAN' => 'NoGC'}, '-e puts GC::MMTk.plan_name'], '', ['NoGC']) + end + + def test_third_party_max_heap_env_var + assert_in_out_err([{'THIRD_PARTY_HEAP_LIMIT' => '1024000'}, '-e p GC.stat(:mmtk_total_bytes)'], '', ['1024000']) + end + + def test_enabled + assert_in_out_err(['-e p GC::MMTk.enabled?'], '', ['false']) + assert_in_out_err(['--mmtk', '-e p GC::MMTk.enabled?'], '', ['true']) + end + + def test_plan_name + assert_in_out_err(['--mmtk-plan=NoGC', '-e puts GC::MMTk.plan_name'], '', ['NoGC']) + assert_in_out_err(['--mmtk-plan=MarkSweep', '-e puts GC::MMTk.plan_name'], '', ['MarkSweep']) + end + + def test_max_heap + assert_in_out_err(['--mmtk-max-heap=1024000', '-e p GC.stat(:mmtk_total_bytes)'], '', ['1024000']) + assert_in_out_err(['--mmtk-max-heap=1000KiB', '-e p GC.stat(:mmtk_total_bytes)'], '', ['1024000']) + assert_in_out_err(['--mmtk-max-heap=1MiB', '-e p GC.stat(:mmtk_total_bytes)'], '', ['1048576']) + end + + def test_gc_stat + assert_equal(GC.stat(:mmtk_free_bytes).class, Integer) + assert_equal(GC.stat(:mmtk_total_bytes).class, Integer) + assert_equal(GC.stat(:mmtk_used_bytes).class, Integer) + assert_equal(GC.stat(:mmtk_starting_heap_address).class, Integer) + assert_equal(GC.stat(:mmtk_last_heap_address).class, Integer) + assert_operator(GC.stat(:mmtk_last_heap_address), :>, GC.stat(:mmtk_starting_heap_address)) + assert_operator(GC.stat(:mmtk_free_bytes), :<=, GC.stat(:mmtk_total_bytes)) + assert_operator(GC.stat(:mmtk_used_bytes), :<=, GC.stat(:mmtk_total_bytes)) + end +end