Skip to content
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
11 changes: 11 additions & 0 deletions class.c
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,7 @@ rb_prepend_module(VALUE klass, VALUE module)
if (subs_v) {
struct rb_subclasses *subs = (struct rb_subclasses *)subs_v;
VALUE *entries = rb_imemo_subclasses_entries(subs_v);
VALUE new_origins = 0;
for (uint32_t i = 0; i < subs->count; i++) {
const VALUE subclass = entries[i];
if (!subclass) continue;
Expand All @@ -1900,10 +1901,20 @@ rb_prepend_module(VALUE klass, VALUE module)
RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(subclass));
RCLASS_WRITE_ORIGIN(subclass, origin);
RICLASS_SET_ORIGIN_SHARED_MTBL(origin);
if (!new_origins) new_origins = rb_ary_hidden_new(1);
rb_ary_push(new_origins, origin);
}
include_modules_at(subclass, subclass, module, FALSE);
}
}
/* Register after the loop. Registering during it would visit the
* new iclass and prepend module into it a second time. */
if (new_origins) {
for (long i = 0; i < RARRAY_LEN(new_origins); i++) {
rb_module_add_to_subclasses_list(klass, RARRAY_AREF(new_origins, i));
}
}
RB_GC_GUARD(new_origins);
}
}
}
Expand Down
263 changes: 225 additions & 38 deletions ext/coverage/coverage.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ static int current_mode;
static VALUE cme2counter = Qnil;
static VALUE me_set = Qnil;

/*
* Method coverage result template:
* method_tmpl_by_path: { path => { key => me or [me, ...] } }
* Each peek dups the per-path template and replaces the values with the
* call counts. me_set and cme2counter are append-only and iterate in
* insertion order, so only the entries after the ones already seen are added.
*/
static VALUE method_tmpl_by_path = Qnil;
static long method_tmpl_n_me_set = 0;
static long method_tmpl_n_cme2counter = 0;

static void
method_template_reset(void)
{
method_tmpl_by_path = Qnil;
method_tmpl_n_me_set = 0;
method_tmpl_n_cme2counter = 0;
}

/*
* call-seq: Coverage.supported?(mode) -> true or false
*
Expand Down Expand Up @@ -120,6 +139,7 @@ rb_coverage_setup(int argc, VALUE *argv, VALUE klass)
cme2counter = Qnil;
me_set = Qnil;
}
method_template_reset();

coverages = rb_get_coverages();
if (!RTEST(coverages)) {
Expand Down Expand Up @@ -186,11 +206,16 @@ struct branch_coverage_result_builder
int id;
VALUE result;
VALUE children;
VALUE counters;
};

/*
* Branch coverage result template, cached in branches[2]:
* { base_key => { target_key => counter_index } }
* Each peek dups it and replaces the indexes with the counters, which avoids
* hashing the array keys again and again.
*/
static int
branch_coverage_ii(VALUE _key, VALUE branch, VALUE v)
branch_template_ii(VALUE _key, VALUE branch, VALUE v)
{
struct branch_coverage_result_builder *b = (struct branch_coverage_result_builder *) v;

Expand All @@ -199,14 +224,16 @@ branch_coverage_ii(VALUE _key, VALUE branch, VALUE v)
VALUE target_first_column = RARRAY_AREF(branch, 2);
VALUE target_last_lineno = RARRAY_AREF(branch, 3);
VALUE target_last_column = RARRAY_AREF(branch, 4);
long counter_idx = FIX2LONG(RARRAY_AREF(branch, 5));
rb_hash_aset(b->children, rb_ary_new_from_args(6, target_label, LONG2FIX(b->id++), target_first_lineno, target_first_column, target_last_lineno, target_last_column), RARRAY_AREF(b->counters, counter_idx));
VALUE counter_idx = RARRAY_AREF(branch, 5);
VALUE key = rb_ary_new_from_args(6, target_label, LONG2FIX(b->id++), target_first_lineno, target_first_column, target_last_lineno, target_last_column);
rb_ary_freeze(key);
rb_hash_aset(b->children, key, counter_idx);

return ST_CONTINUE;
}

static int
branch_coverage_i(VALUE _key, VALUE branch_base, VALUE v)
branch_template_i(VALUE _key, VALUE branch_base, VALUE v)
{
struct branch_coverage_result_builder *b = (struct branch_coverage_result_builder *) v;

Expand All @@ -217,52 +244,211 @@ branch_coverage_i(VALUE _key, VALUE branch_base, VALUE v)
VALUE base_last_column = RARRAY_AREF(branch_base, 4);
VALUE branches = RARRAY_AREF(branch_base, 5);
VALUE children = rb_hash_new();
rb_hash_aset(b->result, rb_ary_new_from_args(6, base_type, LONG2FIX(b->id++), base_first_lineno, base_first_column, base_last_lineno, base_last_column), children);
VALUE key = rb_ary_new_from_args(6, base_type, LONG2FIX(b->id++), base_first_lineno, base_first_column, base_last_lineno, base_last_column);
rb_ary_freeze(key);
rb_hash_aset(b->result, key, children);
b->children = children;
rb_hash_foreach(branches, branch_coverage_ii, v);
rb_hash_foreach(branches, branch_template_ii, v);

return ST_CONTINUE;
}

/* returns [template, nbases, ntargets] */
static VALUE
branch_coverage(VALUE branches)
branch_template(VALUE branches)
{
VALUE structure = RARRAY_AREF(branches, 0);
VALUE counters = RARRAY_AREF(branches, 1);
long nbases = RHASH_SIZE(structure);
long ntargets = RARRAY_LEN(counters);
VALUE cache = RARRAY_LEN(branches) > 2 ? RARRAY_AREF(branches, 2) : Qnil;

if (!NIL_P(cache) &&
FIX2LONG(RARRAY_AREF(cache, 1)) == nbases &&
FIX2LONG(RARRAY_AREF(cache, 2)) == ntargets) {
return RARRAY_AREF(cache, 0);
}
else {
struct branch_coverage_result_builder b;
b.id = 0;
b.result = rb_hash_new();
rb_hash_foreach(structure, branch_template_i, (VALUE)&b);
cache = rb_ary_hidden_new(3);
rb_ary_push(cache, b.result);
rb_ary_push(cache, LONG2FIX(nbases));
rb_ary_push(cache, LONG2FIX(ntargets));
rb_ary_store(branches, 2, cache);
return b.result;
}
}

static int
branch_fill_check(st_data_t key, st_data_t value, st_data_t argp, int error)
{
return ST_REPLACE;
}

/* children: {target_key => counter_index} -> {target_key => counter} */
static int
branch_fill_counter(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
{
VALUE counters = (VALUE)argp;
*value = (st_data_t)RARRAY_AREF(counters, FIX2LONG((VALUE)*value));
return ST_CONTINUE;
}

struct branch_coverage_result_builder b;
b.id = 0;
b.result = rb_hash_new();
b.counters = RARRAY_AREF(branches, 1);
struct branch_fill_arg
{
VALUE result;
VALUE counters;
};

rb_hash_foreach(structure, branch_coverage_i, (VALUE)&b);
/* result: {base_key => children_template} -> {base_key => filled copy of children} */
static int
branch_fill_children(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
{
struct branch_fill_arg *a = (struct branch_fill_arg *)argp;
VALUE children = rb_hash_dup((VALUE)*value);
rb_hash_stlike_foreach_with_replace(children, branch_fill_check, branch_fill_counter, (st_data_t)a->counters);
RB_OBJ_WRITE(a->result, value, children);
return ST_CONTINUE;
}

return b.result;
static VALUE
branch_coverage(VALUE branches)
{
struct branch_fill_arg a;
VALUE template = branch_template(branches);
a.counters = RARRAY_AREF(branches, 1);
a.result = rb_hash_dup(template);
rb_hash_stlike_foreach_with_replace(a.result, branch_fill_check, branch_fill_children, (st_data_t)&a);
return a.result;
}

struct method_template_add_arg {
long skip;
long i;
int check_me_set; /* skip the entries in me_set (they are already added) */
};

static void
method_coverage_i(const struct rb_coverage_method_data *method, void *data)
method_template_add(VALUE me)
{
VALUE ncoverages = *(VALUE *)data;
VALUE ncoverage = rb_hash_aref(ncoverages, method->path);

if (!NIL_P(ncoverage)) {
VALUE methods = rb_hash_aref(ncoverage, ID2SYM(rb_intern("methods")));
VALUE key = rb_ary_new_from_args(6, method->owner, method->method_id,
method->first_lineno, method->first_column,
method->last_lineno, method->last_column);
VALUE rcount = method->count;
VALUE previous = rb_hash_aref(methods, key);

if (NIL_P(rcount)) rcount = LONG2FIX(0);
if (NIL_P(previous)) previous = LONG2FIX(0);
if (!POSFIXABLE(FIX2LONG(rcount) + FIX2LONG(previous))) {
rcount = LONG2FIX(FIXNUM_MAX);
}
else {
rcount = LONG2FIX(FIX2LONG(rcount) + FIX2LONG(previous));
struct rb_coverage_method_data d;
VALUE tmpl, key, mes;

if (!rb_coverage_method_data_of(me, Qnil, &d)) return;

tmpl = rb_hash_lookup(method_tmpl_by_path, d.path);
if (NIL_P(tmpl)) {
tmpl = rb_hash_new();
rb_hash_aset(method_tmpl_by_path, d.path, tmpl);
}
key = rb_ary_new_from_args(6, d.owner, d.method_id,
d.first_lineno, d.first_column,
d.last_lineno, d.last_column);
rb_ary_freeze(key);
mes = rb_hash_lookup(tmpl, key);
if (NIL_P(mes)) {
rb_hash_aset(tmpl, key, me);
}
else if (RB_TYPE_P(mes, T_ARRAY)) {
rb_ary_push(mes, me);
}
else {
VALUE ary = rb_ary_hidden_new(2);
rb_ary_push(ary, mes);
rb_ary_push(ary, me);
rb_hash_aset(tmpl, key, ary);
}
}

static int
method_template_add_i(VALUE me, VALUE value, VALUE data)
{
struct method_template_add_arg *arg = (struct method_template_add_arg *)data;
if (arg->i++ >= arg->skip) {
if (arg->check_me_set && RTEST(rb_hash_lookup2(me_set, me, Qfalse))) return ST_CONTINUE;
method_template_add(me);
}
return ST_CONTINUE;
}

static void
method_template_update(void)
{
struct method_template_add_arg arg;

if (NIL_P(method_tmpl_by_path)) {
method_tmpl_by_path = rb_hash_new();
method_tmpl_n_me_set = 0;
method_tmpl_n_cme2counter = 0;
}
if (RTEST(me_set) && RHASH_SIZE(me_set) > (size_t)method_tmpl_n_me_set) {
arg.skip = method_tmpl_n_me_set;
arg.i = 0;
arg.check_me_set = 0;
rb_hash_foreach(me_set, method_template_add_i, (VALUE)&arg);
method_tmpl_n_me_set = arg.i;
}
if (RTEST(cme2counter) && RHASH_SIZE(cme2counter) > (size_t)method_tmpl_n_cme2counter) {
arg.skip = method_tmpl_n_cme2counter;
arg.i = 0;
arg.check_me_set = RTEST(me_set);
rb_hash_foreach(cme2counter, method_template_add_i, (VALUE)&arg);
method_tmpl_n_cme2counter = arg.i;
}
}

static int
method_fill_check(st_data_t key, st_data_t value, st_data_t argp, int error)
{
return ST_REPLACE;
}

static long
method_call_count(VALUE me)
{
VALUE c = rb_hash_lookup2(cme2counter, me, Qnil);
return FIXNUM_P(c) ? FIX2LONG(c) : 0;
}

/* methods: {key => me or [me, ...]} -> {key => count}, where count is the
* sum of the call counts of all the method entries sharing the key
* (methods redefined at the same location) */
static int
method_fill_count(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
{
VALUE mes = (VALUE)*value;
long count;
if (RB_TYPE_P(mes, T_ARRAY)) {
long i;
count = 0;
for (i = 0; i < RARRAY_LEN(mes); i++) {
count += method_call_count(RARRAY_AREF(mes, i));
if (!POSFIXABLE(count)) count = FIXNUM_MAX;
}
rb_hash_aset(methods, key, rcount);
}
else {
count = method_call_count(mes);
}
*value = (st_data_t)LONG2FIX(count);
return ST_CONTINUE;
}

static VALUE
method_coverage(VALUE path)
{
VALUE tmpl = rb_hash_lookup(method_tmpl_by_path, path);
VALUE methods;
if (NIL_P(tmpl)) {
methods = rb_hash_new();
}
else {
methods = rb_hash_dup(tmpl);
rb_hash_stlike_foreach_with_replace(methods, method_fill_check, method_fill_count, 0);
}
return methods;
}

static int
Expand Down Expand Up @@ -294,7 +480,7 @@ coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h)
}

if (current_mode & COVERAGE_TARGET_METHODS) {
rb_hash_aset(h, ID2SYM(rb_intern("methods")), rb_hash_new());
rb_hash_aset(h, ID2SYM(rb_intern("methods")), method_coverage(path));
}

coverage = h;
Expand Down Expand Up @@ -325,11 +511,10 @@ rb_coverage_peek_result(VALUE klass)
rb_raise(rb_eRuntimeError, "coverage measurement is not enabled");
}

rb_hash_foreach(coverages, coverage_peek_result_i, ncoverages);

if (current_mode & COVERAGE_TARGET_METHODS) {
rb_coverage_each_method(method_coverage_i, &ncoverages);
method_template_update();
}
rb_hash_foreach(coverages, coverage_peek_result_i, ncoverages);

rb_hash_freeze(ncoverages);
return ncoverages;
Expand Down Expand Up @@ -406,6 +591,7 @@ rb_coverage_result(int argc, VALUE *argv, VALUE klass)
rb_reset_coverages();
cme2counter = Qnil;
me_set = Qnil;
method_template_reset();
current_state = IDLE;
}
return ncoverages;
Expand Down Expand Up @@ -671,4 +857,5 @@ Init_coverage(void)
rb_define_module_function(rb_mCoverage, "running?", rb_coverage_running, 0);
rb_global_variable(&cme2counter);
rb_global_variable(&me_set);
rb_global_variable(&method_tmpl_by_path);
}
Loading