Skip to content

Add tests for MMTk #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -15463,13 +15465,26 @@ 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");
if (mmtk_env_plan) {
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;
Expand All @@ -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;
}
}

Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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))

Expand All @@ -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,
Expand All @@ -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.");
Expand Down
110 changes: 110 additions & 0 deletions test/ruby/test_mmtk.rb
Original file line number Diff line number Diff line change
@@ -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