Skip to content
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

[CIVIS-2892] datadog_cov native extension for per test code coverage #137

Merged
merged 39 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f989c49
Add 0.8.3 to CHANGELOG.md
anmarchenko Mar 20, 2024
c1cb0c3
Bump version 0.8.2 to 0.8.3
anmarchenko Mar 20, 2024
228ad44
Update lockfiles for datadog-ci 0.8.3
anmarchenko Mar 20, 2024
cfc5ebc
datadog_cov native extension
anmarchenko Mar 14, 2024
717cb0d
test datadog_cov extension
anmarchenko Mar 14, 2024
7c51a7a
fix bool type compilation error
anmarchenko Mar 14, 2024
6cdaa9f
exclude native binary artifacts from gemspec
anmarchenko Mar 14, 2024
3bc3725
don't run code coverage specs on JRuby
anmarchenko Mar 14, 2024
fb9e4aa
do not require native extension on JRuby
anmarchenko Mar 14, 2024
b0a1847
try again
anmarchenko Mar 14, 2024
f60bb79
don't use class name in top-level describe
anmarchenko Mar 14, 2024
cfe2f9c
ITR::Runner has thread local coverage collector
anmarchenko Mar 14, 2024
7e87934
gemfile updates
anmarchenko Mar 14, 2024
6d5380c
ITR code coverage start/stop in Recorder
anmarchenko Mar 14, 2024
5ef7b1e
freeze source file name to reduce object allocations
anmarchenko Mar 15, 2024
cc6fcf2
frozen strings do not make it faster
anmarchenko Mar 15, 2024
8aa5261
require native extension on ITR configuration step
anmarchenko Mar 15, 2024
80a987e
add Datadog::CI::Cov placeholder
anmarchenko Mar 15, 2024
bdc6430
fix jruby tests
anmarchenko Mar 15, 2024
40a7ee5
multithreaded code coverage and mixins tests
anmarchenko Mar 15, 2024
84b7469
note that UTF-8 paths are not supported yet, will be added as an opt-…
anmarchenko Mar 15, 2024
911c4fe
specs for ITR::Runner#start_coverage
anmarchenko Mar 15, 2024
f2817f6
add more specs, try to fix release spec with forcing encoding for git…
anmarchenko Mar 15, 2024
8a55463
exclude cov_spec in JRUby completely
anmarchenko Mar 15, 2024
f9b8917
move cov tests and skip them completely on JRuby
anmarchenko Mar 15, 2024
19bf537
do not compile native extension on unsupported platforms
anmarchenko Mar 20, 2024
0abf879
check that dd_cov_data->root pointer is not NULL to avoid segfault
anmarchenko Mar 20, 2024
0d40a56
minor fixes
anmarchenko Mar 20, 2024
5bad369
store root as RString; convert to char* before using it for prefix check
anmarchenko Mar 20, 2024
a1562d6
initialize dd_cov_data->mode field when allocating dd_cov_data struct
anmarchenko Mar 20, 2024
5eacfbe
use Queue to synchronize threads in multithreaded coverage spec
anmarchenko Mar 20, 2024
5a1fb34
add a simple test for lines coverage
anmarchenko Mar 20, 2024
bf00d6f
store covered lines in hash
anmarchenko Mar 20, 2024
2d4f694
use ruby 2.7 syntax in ITR::Runner
anmarchenko Mar 20, 2024
b010927
add gem datadog explicitly to integration test's Gemfile to make test…
anmarchenko Mar 20, 2024
74ef237
filter out invalid line numbers when tracking lines coverage
anmarchenko Mar 21, 2024
f979f58
use RSTRING_LEN to avoid computing root's length on every event
anmarchenko Mar 21, 2024
a291646
mark dd_cov_data->root as movable and update pointer on compaction
anmarchenko Mar 21, 2024
e4e86f3
move coverage collector to Datadog::CI::ITR::Coverage namespace as it…
anmarchenko Mar 21, 2024
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ Gemfile-*.lock
.ruby-version
.DS_Store
/test.rb

# Native extension binaries
lib/**/*.bundle
lib/**/*.so
lib/**/*.o
anmarchenko marked this conversation as resolved.
Show resolved Hide resolved
lib/**/*.dylib
lib/**/*.dll

17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
## [Unreleased]

## [0.8.3] - 2024-03-20

### Fixed

* fix: cucumber-ruby 9.2 includes breaking change for Cucumber::Core::Test::Result ([#145][])

### Changed

* remove temporary hack and use Core::Remote::Negotiation's new constructor param ([#142][])
* use filter_basic_auth method from Datadog::Core ([#141][])

## [0.8.2] - 2024-03-19

### Fixed
Expand Down Expand Up @@ -176,7 +187,8 @@ Currently test suite level visibility is not used by our instrumentation: it wil

* Ruby versions < 2.7 no longer supported ([#8][])

[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.2...main
[Unreleased]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.3...main
[0.8.3]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.1...v0.8.2
[0.8.1]: https://github.com/DataDog/datadog-ci-rb/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/DataDog/datadog-ci-rb/compare/v0.7.0...v0.8.0
Expand Down Expand Up @@ -247,3 +259,6 @@ Currently test suite level visibility is not used by our instrumentation: it wil
[#131]: https://github.com/DataDog/datadog-ci-rb/issues/131
[#134]: https://github.com/DataDog/datadog-ci-rb/issues/134
[#139]: https://github.com/DataDog/datadog-ci-rb/issues/139
[#141]: https://github.com/DataDog/datadog-ci-rb/issues/141
[#142]: https://github.com/DataDog/datadog-ci-rb/issues/142
[#145]: https://github.com/DataDog/datadog-ci-rb/issues/145
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ gem "pry"
gem "rake"
gem "os"

# To compile native extensions
gem "rake-compiler"

gem "climate_control"

gem "rspec"
Expand Down
24 changes: 22 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require_relative "lib/datadog/ci/version"
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "yard"
require "rake/extensiontask"

RSpec::Core::RakeTask.new(:spec)

Expand Down Expand Up @@ -107,8 +108,12 @@ end
namespace :spec do
desc "" # "Explicitly hiding from `rake -T`"
RSpec::Core::RakeTask.new(:main) do |t, args|
t.pattern = "spec/**/*_spec.rb"
t.exclude_pattern = "spec/**/{contrib}/**/*_spec.rb,"
t.pattern = if RUBY_ENGINE == "jruby"
"spec/datadog/**/*_spec.rb"
else
"spec/datadog/**/*_spec.rb,spec/ddcov/**/*_spec.rb"
end
t.exclude_pattern = "spec/datadog/**/{contrib}/**/*_spec.rb,"
t.rspec_opts = args.to_a.join(" ")
end

Expand All @@ -132,3 +137,18 @@ end

desc "CI task; it runs all tests for current version of Ruby"
task ci: "test:all"

# native extensions
Rake::ExtensionTask.new("datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}") do |ext|
ext.ext_dir = "ext/datadog_cov"
end

task :compile_ext do
if RUBY_ENGINE == "ruby"
Rake::Task[:clean].invoke
Rake::Task[:compile].invoke
end
end

# run compile before any tests are run
Rake::Task["test:all"].prerequisite_tasks.each { |t| t.enhance([:compile_ext]) }
4 changes: 4 additions & 0 deletions datadog-ci.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ Gem::Specification.new do |spec|
LICENSE*
NOTICE
README.md
ext/**/*
lib/**/*
]].select { |fn| File.file?(fn) } # We don't want directories, only files
.reject { |fn| fn.end_with?(".so", ".bundle") } # Exclude local native binary artifacts

spec.require_paths = ["lib"]

spec.add_dependency "msgpack"

spec.extensions = ["ext/datadog_cov/extconf.rb"]
end
192 changes: 192 additions & 0 deletions ext/datadog_cov/datadog_cov.c
anmarchenko marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#include <ruby.h>
#include <ruby/debug.h>

// constants
#define DD_COV_TARGET_FILES 1
#define DD_COV_TARGET_LINES 2

// Data structure
struct dd_cov_data
{
VALUE root;
anmarchenko marked this conversation as resolved.
Show resolved Hide resolved
int mode;
VALUE coverage;
};

static void dd_cov_mark(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;
rb_gc_mark_movable(dd_cov_data->coverage);
rb_gc_mark_movable(dd_cov_data->root);
}

static void dd_cov_free(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;

xfree(dd_cov_data);
}

static void dd_cov_compact(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;
dd_cov_data->coverage = rb_gc_location(dd_cov_data->coverage);
dd_cov_data->root = rb_gc_location(dd_cov_data->root);
}

const rb_data_type_t dd_cov_data_type = {
.wrap_struct_name = "dd_cov",
.function = {
.dmark = dd_cov_mark,
.dfree = dd_cov_free,
.dsize = NULL,
.dcompact = dd_cov_compact},
.flags = RUBY_TYPED_FREE_IMMEDIATELY};

static VALUE dd_cov_allocate(VALUE klass)
{
struct dd_cov_data *dd_cov_data;
VALUE obj = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
dd_cov_data->coverage = rb_hash_new();
dd_cov_data->root = Qnil;
dd_cov_data->mode = DD_COV_TARGET_FILES;
return obj;
}

// DDCov methods
static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE opt;
int mode;

rb_scan_args(argc, argv, "10", &opt);
VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
if (!RTEST(rb_root))
{
rb_raise(rb_eArgError, "root is required");
}

VALUE rb_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("mode")));
if (!RTEST(rb_mode) || rb_mode == ID2SYM(rb_intern("files")))
{
mode = DD_COV_TARGET_FILES;
}
else if (rb_mode == ID2SYM(rb_intern("lines")))
{
mode = DD_COV_TARGET_LINES;
}
else
{
rb_raise(rb_eArgError, "mode is invalid");
}

struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);

dd_cov_data->root = rb_root;
dd_cov_data->mode = mode;

return Qnil;
}

static void dd_cov_update_line_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
{
struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);

const char *filename = rb_sourcefile();
if (filename == NULL)
{
return;
}

if (dd_cov_data->root == Qnil)
{
return;
}

char *c_root = RSTRING_PTR(dd_cov_data->root);
if (c_root == NULL)
{
return;
}
long root_len = RSTRING_LEN(dd_cov_data->root);
// check that root is a prefix of the filename
// so this file is located under the given root
if (strncmp(c_root, filename, root_len) != 0)
{
return;
}

VALUE rb_str_source_file = rb_str_new2(filename);

if (dd_cov_data->mode == DD_COV_TARGET_FILES)
{
rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, Qtrue);
return;
}

// this isn't optimized yet, this is a POC to show that lines coverage is possible
// ITR beta is going to use files coverage, we'll get back to this part when
// we need to implement lines coverage
if (dd_cov_data->mode == DD_COV_TARGET_LINES)
{
int line_number = rb_sourceline();
if (line_number <= 0)
{
return;
}

VALUE rb_lines = rb_hash_aref(dd_cov_data->coverage, rb_str_source_file);
if (rb_lines == Qnil)
{
rb_lines = rb_hash_new();
rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, rb_lines);
}

rb_hash_aset(rb_lines, INT2FIX(line_number), Qtrue);
}
}

static VALUE dd_cov_start(VALUE self)
{
// get current thread
VALUE thval = rb_thread_current();

// add event hook
rb_thread_add_event_hook(thval, dd_cov_update_line_coverage, RUBY_EVENT_LINE, self);

return self;
}

static VALUE dd_cov_stop(VALUE self)
{
// get current thread
VALUE thval = rb_thread_current();
// remove event hook for the current thread
rb_thread_remove_event_hook(thval, dd_cov_update_line_coverage);

struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);

VALUE cov = dd_cov_data->coverage;

dd_cov_data->coverage = rb_hash_new();

return cov;
}

void Init_datadog_cov(void)
{
VALUE mDatadog = rb_define_module("Datadog");
VALUE mCI = rb_define_module_under(mDatadog, "CI");
VALUE mITR = rb_define_module_under(mCI, "ITR");
VALUE mCoverage = rb_define_module_under(mITR, "Coverage");
VALUE cDatadogCov = rb_define_class_under(mCoverage, "DDCov", rb_cObject);

rb_define_alloc_func(cDatadogCov, dd_cov_allocate);

rb_define_method(cDatadogCov, "initialize", dd_cov_initialize, -1);
rb_define_method(cDatadogCov, "start", dd_cov_start, 0);
rb_define_method(cDatadogCov, "stop", dd_cov_stop, 0);
}
18 changes: 18 additions & 0 deletions ext/datadog_cov/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
if RUBY_ENGINE != "ruby" || Gem.win_platform?
warn(
"WARN: Skipping build of code coverage native extension because of unsupported platform."
)

File.write("Makefile", "all install clean: # dummy makefile that does nothing")
exit
end

require "mkmf"

# Tag the native extension library with the Ruby version and Ruby platform.
# This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
# the wrong library is never loaded.
# When requiring, we need to use the exact same string, including the version and the platform.
EXTENSION_NAME = "datadog_cov.#{RUBY_VERSION}_#{RUBY_PLATFORM}".freeze

create_makefile(EXTENSION_NAME)
anmarchenko marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions gemfiles/jruby_9.4_activesupport_4.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ gem "datadog", github: "DataDog/dd-trace-rb", branch: "2.0"
gem "pry"
gem "rake"
gem "os"
gem "rake-compiler"
gem "climate_control"
gem "rspec"
gem "rspec-collection_matchers"
Expand Down
5 changes: 4 additions & 1 deletion gemfiles/jruby_9.4_activesupport_4.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GIT
PATH
remote: ..
specs:
datadog-ci (0.8.2)
datadog-ci (0.8.3)
msgpack

GEM
Expand Down Expand Up @@ -61,6 +61,8 @@ GEM
racc (1.7.3-java)
rainbow (3.1.1)
rake (13.1.0)
rake-compiler (1.2.7)
rake
regexp_parser (2.9.0)
rexml (3.2.6)
rspec (3.13.0)
Expand Down Expand Up @@ -142,6 +144,7 @@ DEPENDENCIES
pimpmychangelog (>= 0.1.2)
pry
rake
rake-compiler
rspec
rspec-collection_matchers
rspec_junit_formatter
Expand Down
1 change: 1 addition & 0 deletions gemfiles/jruby_9.4_activesupport_5.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ gem "datadog", github: "DataDog/dd-trace-rb", branch: "2.0"
gem "pry"
gem "rake"
gem "os"
gem "rake-compiler"
gem "climate_control"
gem "rspec"
gem "rspec-collection_matchers"
Expand Down
5 changes: 4 additions & 1 deletion gemfiles/jruby_9.4_activesupport_5.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GIT
PATH
remote: ..
specs:
datadog-ci (0.8.2)
datadog-ci (0.8.3)
msgpack

GEM
Expand Down Expand Up @@ -61,6 +61,8 @@ GEM
racc (1.7.3-java)
rainbow (3.1.1)
rake (13.1.0)
rake-compiler (1.2.7)
rake
regexp_parser (2.9.0)
rexml (3.2.6)
rspec (3.13.0)
Expand Down Expand Up @@ -142,6 +144,7 @@ DEPENDENCIES
pimpmychangelog (>= 0.1.2)
pry
rake
rake-compiler
rspec
rspec-collection_matchers
rspec_junit_formatter
Expand Down
Loading
Loading