Skip to content

Commit

Permalink
Merge pull request #194 from DataDog/anmarchenko/rake_skippable_tests…
Browse files Browse the repository at this point in the history
…_percentage

[SDTEST-477] add command line tool to compute a percentage of skippable tests for RSpec
  • Loading branch information
anmarchenko authored Oct 17, 2024
2 parents bbc6b66 + b367801 commit 1d81d80
Show file tree
Hide file tree
Showing 48 changed files with 814 additions and 103 deletions.
2 changes: 1 addition & 1 deletion Appraisals
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ end
alias original_appraise appraise

REMOVED_GEMS = {
check: %w[rbs steep],
check: %w[rbs steep ruby_memcheck],
development: %w[ruby-lsp ruby-lsp-rspec debug irb]
}
RUBY_VERSION = Gem::Version.new(RUBY_ENGINE_VERSION)
Expand Down
1 change: 1 addition & 0 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ target :lib do
library "tmpdir"
library "fileutils"
library "socket"
library "optparse"

repo_path "vendor/rbs"
library "ddtrace"
Expand Down
4 changes: 4 additions & 0 deletions datadog-ci.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/DataDog/datadog-ci-rb"
spec.license = "BSD-3-Clause"

spec.bindir = "exe"
spec.executables = ["ddcirb"]

spec.metadata["allowed_push_host"] = "https://rubygems.org"
spec.metadata["changelog_uri"] = "https://github.com/DataDog/datadog-ci-rb/blob/main/CHANGELOG.md"
spec.metadata["homepage_uri"] = spec.homepage
Expand All @@ -36,6 +39,7 @@ Gem::Specification.new do |spec|
README.md
ext/**/*
lib/**/*
exe/**/*
]].select { |fn| File.file?(fn) } # We don't want directories, only files
.reject { |fn| fn.end_with?(".so", ".bundle") } # Exclude local native binary artifacts

Expand Down
64 changes: 64 additions & 0 deletions docs/CommandLineInterface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Command line interface

This library provides experimental command line interface `ddcirb` to get the percentage of the tests
that will be skipped for the current test run (only when using RSpec).

## Usage

This tool must be used on the same runner as your tests are running on with the same ENV variables.
Run the command in the same folder where you usually run your tests. Gem datadog-ci must be installed.

Available commands:

- `bundle exec ddcirb skipped-tests` - outputs the percentage of skipped tests to stdout. Note that it runs your specs
in dry run mode, don't forget to set RAILS_ENV=test environment variable.
- `bundle exec ddcirb skipped-tests-estimate` - estimates the percentage of skipped tests and outputs to stdout without loading
your test suite and running it in dry run mode. ATTENTION: this is considerably faster but could be very inaccurate.

Example usage:

```bash
$ RAILS_ENV=test bundle exec ddcirb skipped-tests
0.45
```

Available arguments:

- `-f, --file` - output to a file (example: `bundle exec ddcirb skipped-tests -f out`)
- `--verbose` - enable verbose output for debugging purposes (example: `bundle exec ddcirb skipped-tests --verbose`)
- `--spec-path` - path to the folder with RSpec tests (default: `spec`, example: `bundle exec ddcirb skipped-tests --spec-path="myapp/spec"`)
- `--rspec-opts` - additional options to pass to the RSpec when running it in dry run mode (example: `bundle exec ddcirb skipped-tests --rspec-opts="--require rails_helper"`)

## Example usage in Circle CI

This tool could be used to determine [Circle CI parallelism](https://support.circleci.com/hc/en-us/articles/14928385117851-How-to-dynamically-set-job-parallelism) dynamically:

```yaml
version: 2.1

setup: true

orbs:
continuation: circleci/continuation@0.2.0

jobs:
determine-parallelism:
docker:
- image: cimg/base:edge
resource_class: medium
steps:
- checkout
- run:
name: Determine parallelism
command: |
PARALLELISM=$(RAILS_ENV=test bundle exec ddcirb skipped-tests)
echo "{\"parallelism\": $PARALLELISM}" > pipeline-parameters.json
- continuation/continue:
configuration_path: .circleci/continue_config.yml
parameters: pipeline-parameters.json

workflows:
build-setup:
jobs:
- determine-parallelism
```
5 changes: 5 additions & 0 deletions exe/ddcirb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby

require "datadog/ci/cli/cli"

Datadog::CI::CLI.exec(ARGV.first)
24 changes: 24 additions & 0 deletions lib/datadog/ci/cli/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require "datadog"
require "datadog/ci"

require_relative "command/skippable_tests_percentage"
require_relative "command/skippable_tests_percentage_estimate"

module Datadog
module CI
module CLI
def self.exec(action)
case action
when "skipped-tests", "skippable-tests"
Command::SkippableTestsPercentage.new.exec
when "skipped-tests-estimate", "skippable-tests-estimate"
Command::SkippableTestsPercentageEstimate.new.exec
else
puts("Usage: bundle exec ddcirb [command] [options]. Available commands:")
puts(" skippable-tests - calculates the exact percentage of skipped tests and prints it to stdout or file")
puts(" skippable-tests-estimate - estimates the percentage of skipped tests and prints it to stdout or file")
end
end
end
end
end
58 changes: 58 additions & 0 deletions lib/datadog/ci/cli/command/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require "optparse"

module Datadog
module CI
module CLI
module Command
class Base
def exec
action = build_action
result = action&.call

validate!(action)
output(result)
end

private

def build_action
end

def options
return @options if defined?(@options)

ddcirb_options = {}
OptionParser.new do |opts|
opts.banner = "Usage: bundle exec ddcirb [command] [options]\n Available commands: skippable-tests, skippable-tests-estimate"

opts.on("-f", "--file FILENAME", "Output result to file FILENAME")
opts.on("--verbose", "Verbose output to stdout")

command_options(opts)
end.parse!(into: ddcirb_options)

@options = ddcirb_options
end

def command_options(opts)
end

def validate!(action)
if action.nil? || action.failed
Datadog.logger.error("ddcirb failed, exiting")
Kernel.exit(1)
end
end

def output(result)
if options[:file]
File.write(options[:file], result)
else
print(result)
end
end
end
end
end
end
end
27 changes: 27 additions & 0 deletions lib/datadog/ci/cli/command/skippable_tests_percentage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require_relative "base"
require_relative "../../test_optimisation/skippable_percentage/calculator"

module Datadog
module CI
module CLI
module Command
class SkippableTestsPercentage < Base
private

def build_action
::Datadog::CI::TestOptimisation::SkippablePercentage::Calculator.new(
rspec_cli_options: (options[:"rspec-opts"] || "").split,
verbose: !options[:verbose].nil?,
spec_path: options[:"spec-path"] || "spec"
)
end

def command_options(opts)
opts.on("--rspec-opts=[OPTIONS]", "Command line options to pass to RSpec")
opts.on("--spec-path=[SPEC_PATH]", "Relative path to the spec directory, example: spec")
end
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/datadog/ci/cli/command/skippable_tests_percentage_estimate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require_relative "base"
require_relative "../../test_optimisation/skippable_percentage/estimator"

module Datadog
module CI
module CLI
module Command
class SkippableTestsPercentageEstimate < Base
private

def build_action
::Datadog::CI::TestOptimisation::SkippablePercentage::Estimator.new(
verbose: !options[:verbose].nil?,
spec_path: options[:"spec-path"] || "spec"
)
end
end
end
end
end
end
7 changes: 6 additions & 1 deletion lib/datadog/ci/configuration/components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
require_relative "../test_visibility/serializers/factories/test_suite_level"
require_relative "../test_visibility/transport"
require_relative "../transport/adapters/telemetry_webmock_safe_adapter"
require_relative "../test_visibility/null_transport"
require_relative "../transport/api/builder"
require_relative "../utils/parsing"
require_relative "../utils/test_run"
Expand Down Expand Up @@ -203,6 +204,9 @@ def build_test_visibility_api(settings)
end

def build_tracing_transport(settings, api)
# NullTransport ignores traces
return TestVisibility::NullTransport.new if settings.ci.discard_traces
# nil means that default legacy APM transport will be used (only for very old Datadog Agent versions)
return nil if api.nil?

TestVisibility::Transport.new(
Expand All @@ -213,7 +217,8 @@ def build_tracing_transport(settings, api)
end

def build_coverage_writer(settings, api)
return nil if api.nil?
# nil means that coverage event will be ignored
return nil if api.nil? || settings.ci.discard_traces

TestOptimisation::Coverage::Writer.new(
transport: TestOptimisation::Coverage::Transport.new(api: api)
Expand Down
6 changes: 6 additions & 0 deletions lib/datadog/ci/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ def self.add_settings!(base)
o.default true
end

# internal only
option :discard_traces do |o|
o.type :bool
o.default false
end

define_method(:instrument) do |integration_name, options = {}, &block|
return unless enabled

Expand Down
6 changes: 6 additions & 0 deletions lib/datadog/ci/contrib/rspec/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class Settings < Datadog::CI::Contrib::Settings
Utils::Configuration.fetch_service_name(Ext::DEFAULT_SERVICE_NAME)
end
end

# internal only
option :dry_run_enabled do |o|
o.type :bool
o.default false
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def self.included(base)

module InstanceMethods
def run(*args)
return super if ::RSpec.configuration.dry_run?
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
return super unless datadog_configuration[:enabled]

test_name = full_description.strip
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/example_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def self.included(base)
# Instance methods for configuration
module ClassMethods
def run(reporter = ::RSpec::Core::NullReporter)
return super if ::RSpec.configuration.dry_run?
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
return super unless datadog_configuration[:enabled]
return super unless top_level?

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/knapsack_pro/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def self.included(base)

module InstanceMethods
def knapsack__run_specs(*args)
return super if ::RSpec.configuration.dry_run?
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
return super unless datadog_configuration[:enabled]

test_session = test_visibility_component.start_test_session(
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def self.included(base)

module InstanceMethods
def run_specs(*args)
return super if ::RSpec.configuration.dry_run?
return super if ::RSpec.configuration.dry_run? && !datadog_configuration[:dry_run_enabled]
return super unless datadog_configuration[:enabled]

test_session = test_visibility_component.start_test_session(
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/ci/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def is_retry?
# - tests that read files from disk
# - tests that make network requests
# - tests that call external processes
# - tests that use forking or threading
# - tests that use forking
#
# @return [void]
def itr_unskippable!
Expand Down
Loading

0 comments on commit 1d81d80

Please sign in to comment.