Skip to content
Merged
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
101 changes: 53 additions & 48 deletions docs/DevelopmentGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This guide covers some of the common how-tos and technical reference material fo

The trace library uses Docker Compose to create a Ruby environment to develop and test within, as well as containers for any dependencies that might be necessary for certain kinds of tests.

To start a development environment, choose a target Ruby version then run the following:
To start a development environment, choose a target Ruby or JRuby version. Then run the following:

```bash
# In the root directory of the project...
Expand All @@ -28,12 +28,15 @@ cd ~/dd-trace-rb
# Create and start a Ruby 3.3 test environment with its dependencies
docker compose run --rm tracer-3.3 /bin/bash

# or a JRuby test environment with its dependencies
docker compose run --rm tracer-jruby-9.4 /bin/bash

# Then inside the container (e.g. `root@2a73c6d8673e:/app`)...
# Install the library dependencies
bundle install
```

Then within this container you can [run tests](#running-tests), or [run code quality checks](#checking-code-quality).
Within this container, you can [run tests](#running-tests) or [run code quality checks](#checking-code-quality).

## Testing

Expand All @@ -43,7 +46,7 @@ The test suite uses [RSpec](https://rspec.info/) tests to verify the correctness

New tests should be written as RSpec tests in the `spec/datadog` folder. Test files should generally mirror the structure of `lib`.

All changes should be covered by a corresponding RSpec tests. Unit tests are preferred, and integration tests are accepted where appropriate (e.g. acceptance tests, verifying compatibility with datastores, etc) but should be kept to a minimum.
All changes should be covered by corresponding RSpec tests. Unit tests are preferred, and integration tests are accepted where appropriate (e.g. acceptance tests, verifying compatibility with datastores, etc) but should be kept to a minimum.

### Running tests

Expand All @@ -54,31 +57,31 @@ Any file that is **not** inside a `contrib` folder is part of the core library.
You can run all the tests for the core library with:

```
$ bundle exec rake test:main
bundle exec rake test:main
```

##### Specific core tests

You can also run only a subset of the core library tests.

For example, for the test files [utils_spec.rb](https://github.com/DataDog/dd-trace-rb/blob/d66a6688f6cddab6a8c3cb1d6e0a6bfe86928e44/spec/datadog/core/utils_spec.rb) and
For example, for the test files [utils_spec.rb](https://github.com/DataDog/dd-trace-rb/blob/d66a6688f6cddab6a8c3cb1d6e0a6bfe86928e44/spec/datadog/core/utils_spec.rb) and
[error_spec.rb](https://github.com/DataDog/dd-trace-rb/blob/d66a6688f6cddab6a8c3cb1d6e0a6bfe86928e44/spec/datadog/core/error_spec.rb), you can run:

```bash
# Runs all tests in utils_spec.rb
$ bundle exec rspec spec/datadog/core/utils_spec.rb
bundle exec rspec spec/datadog/core/utils_spec.rb

# Runs only the test in line 24
$ bundle exec rspec spec/datadog/core/utils_spec.rb:24
# Runs only the test on line 24
bundle exec rspec spec/datadog/core/utils_spec.rb:24

# Runs the test in line 24 of utils_spec.rb and all the tests in error_spec.rb
$ bundle exec rspec spec/datadog/core/utils_spec.rb:24 spec/datadog/core/error_spec.rb
# Runs the test on line 24 of utils_spec.rb and all the tests in error_spec.rb
bundle exec rspec spec/datadog/core/utils_spec.rb:24 spec/datadog/core/error_spec.rb
```

#### All tests

`bundle exec rake ci` will run the entire test suite with any given Ruby runtime, just as CI does.
However, this is not recommended because it is going take a long time.
However, this is not recommended because it will take a long time.

Instead, run [specific core tests](#specific-core-tests), [integation tests](#for-integrations), or [the core library tests](#for-the-core-library).

Expand All @@ -90,9 +93,9 @@ Any file that **is** inside a `contrib` folder is part of an integration.

To get a list of the test tasks, run `bundle exec rake -T test:`

To run test, run `bundle exec rake test:<spec_name>`
To run a test, run `bundle exec rake test:<spec_name>`

Take `bundle exec rake test:redis` as example, multiple versions of `redis` from different dependency definitions are being tested (from `Matrixfile`).
Take `bundle exec rake test:redis` as example: multiple versions of `redis` from different dependency definitions are being tested (from `Matrixfile`).


```ruby
Expand All @@ -105,7 +108,7 @@ Take `bundle exec rake test:redis` as example, multiple versions of `redis` from
}
```

If the dependency groups are prepared (with up-to-date gemfile and lockfile), the test task would install them before running the test.
If the dependency groups are prepared (with up-to-date gemfile and lockfile), the test task will install them before running the test.

**Working with different dependencies**

Expand All @@ -117,39 +120,39 @@ You can find them by running the following command:
bundle exec rake -T dependency:
```

Dependency group definitions are located under `appraisal/` directory using the same DSL provided by [Appraisal](https://github.com/thoughtbot/appraisal). These definitions are used to generate `gemfiles/*.gemfile` and then `gemfiles/*.lock`. All the files are underscored and prefixed with Ruby runtime.
Dependency group definitions are located under the `appraisal/` directory using the same DSL provided by [Appraisal](https://github.com/thoughtbot/appraisal). These definitions are used to generate `gemfiles/*.gemfile` and then `gemfiles/*.lock`. All the files are underscored and prefixed with the Ruby or JRuby runtime version.

> [!IMPORTANT]
> Do NOT manually edit `gemfiles/*.gemfile` or `gemfiles/*.lock`. Instead, make changes to `appraisal/*.rb` and propagates your changes programmatically
> Do NOT manually edit `gemfiles/*.gemfile` or `gemfiles/*.lock`. Instead, make changes to `appraisal/*.rb` and propagate your changes programmatically.

To find out existing gemfiles in your environment, run
To find the existing gemfiles in your environment, run

```bash
bundle exec rake dependency:list
```

`dependency:list` is convenient to look for a specific gemfile path before assigning it to the environment variable `BUNDLE_GEMFILE` for doing all kinds of stuff.
`dependency:list` is convenient to look for a specific gemfile path before assigning it to the environment variable `BUNDLE_GEMFILE`. `BUNDLE_GEMFILE` is useful for doing all kinds of stuff, such as:

```bash
env BUNDLE_GEMFILE=/app/gemfiles/ruby_3.3_stripe_latest.gemfile bundle update stripe
```

After introducing a new dependency group or changing existing one, run `bundle exec rake dependency:generate` to propagate the changes to the gemfile. `dependency:generate` is idempotent and only changes `gemfiles/*.gemfile` but not `gemfiles/*.lock`.
After introducing a new dependency group or changing an existing one, run `bundle exec rake dependency:generate` to propagate the changes to the gemfile. `dependency:generate` is idempotent and only changes `gemfiles/*.gemfile` but not `gemfiles/*.lock`.

To keep lockfile up-to-date with the gemfile, run `bundle exec rake dependency:lock`.
To keep lockfiles up-to-date with the gemfile, run `bundle exec rake dependency:lock`.

To install, run `bundle exec rake dependency:install`.
To install the dependencies, run `bundle exec rake dependency:install`.

Both `dependency:lock` and `dependency:install` can be provided with a specific gemfile path (from `dependency:list`) or pattern to target specific groups. For example:

```bash
# Generates lockfiles for all the stripe groups with `stripe_*` pattern
# Generate lockfiles for all the stripe groups with `stripe_*` pattern
bundle exec rake dependency:lock['/app/gemfiles/ruby_3.3_stripe_*.gemfile']
# or only generate lockfile for `stripe_latest` group
# or only generate lockfile for the `stripe_latest` group
bundle exec rake dependency:lock['/app/gemfiles/ruby_3.3_stripe_latest.gemfile']
```

**How to add new dependency group**
**How to add a new dependency group**

> [!IMPORTANT]
> Add a new group only if the existing groups do not meet your requirements, or if adding a new dependency to an existing group is impractical.
Expand All @@ -171,18 +174,18 @@ For example, if you want tests to run only on Ruby 3.3 for tracing, you can defi
}
```

2. Define the required gems in the corresponding Appraisal file. For this example, we are going to use [`Appraisal/ruby-3.3.rb`](../Appraisal/ruby-3.3.rb). Let's define what `rails-edge` group needs.
2. Define the required gems in the corresponding Appraisal file. For this example, we are going to use [`Appraisal/ruby-3.3.rb`](../Appraisal/ruby-3.3.rb). Let's define what the `rails-edge` group needs.

```ruby
appraise 'rails-edge' do
gem 'rails', '>= 8'
end
```

3. Now let's generate that dependency Gemfile with `rake`, simply run
3. Now let's generate that dependency Gemfile with `rake`. Simply run

> [!IMPORTANT]
> Ensure you are using Ruby 3.3 as the current Ruby version (`ruby -v`) or run commands within a Docker container.
> Ensure you are either using Ruby 3.3 as the current Ruby version (`ruby -v`) or running commands within a Docker container.

```console
$ bundle exec rake dependency:generate
Expand Down Expand Up @@ -242,7 +245,7 @@ At this point, the new tests will be automatically included in the CI that runs
When running tests, you may pass additional args as parameters to the Rake task. For example:

```
# Runs Redis tests with seed 1234
# Run Redis tests with seed 1234
$ bundle exec rake test:redis'[--seed 1234]'
```

Expand All @@ -261,13 +264,13 @@ $ bundle exec rake coverage:report
A webpage will be generated at `coverage/report/index.html` with the resulting report.

Because you are likely not running all tests locally, your report will contain partial coverage results.
You *must* check the CI step `coverage` for the complete test coverage report, ensuring coverage is not
decreased.
You *must* check the CI step `coverage` for the complete test coverage report, ensuring coverage does not
decrease.

**Ensuring tests don't leak resources**

Tests execution can create resources that are hard to track: threads, sockets, files, etc. Because these resources can come
from the both the test setup as well as the code under test, making sure all resources are properly disposed is important
Test execution can create resources that are hard to track: threads, sockets, files, etc. Because these resources can come
from both the test setup as well as the test code itself, making sure all resources are properly disposed of is important
to prevent the application from inadvertently creating cumulative resources during its execution.

When running tests that utilize threads, you might see an error message similar to this one:
Expand All @@ -288,21 +291,23 @@ Thread Backtrace:
This means that this test did not finish all threads by the time the test had finished. In this case, the thread
creation can be traced to `workers_integration_spec.rb:245:in 'new'`. The thread itself is sleeping at `workers_integration_spec.rb:262:in 'sleep'`.

The actionable in this case would be to ensure that the thread created in `workers_integration_spec.rb:245` is properly terminated by invoking `Thread#join` during the test tear down, which will wait for the thread to finish before returning.
The actionable in this case would be to ensure that the thread created in `workers_integration_spec.rb:245` is properly terminated by invoking `Thread#join` during the test teardown, which will wait for the thread to finish before returning.

Depending on the situation, the thread in question might need to be forced to terminate. It's recommended to have a mechanism in place to terminate it (a shared variable that changes value when the thread should exit), but as a last resort, `Thread#terminate` forces the thread to finish. Keep in mind that regardless of the termination method, `Thread#join` must be called to ensure that the thread has completely finished its shutdown process.

**The APM Test Agent**

The APM test agent emulates the APM endpoints of the Datadog Agent. The Test Agent container
runs alongside the Ruby tracer locally and in CI, handles all traces during test runs and performs a number
runs alongside the Ruby tracer locally and in CI, handles all traces during test runs, and performs a number
of 'Trace Checks'. For more information on these checks, see:
https://github.com/DataDog/dd-apm-test-agent#trace-invariant-checks

The APM Test Agent also emits helpful logging, which can be viewed in local testing or in CircleCI as a job step for tracer and contrib
tests. Locally, to get Test Agent logs:
The APM Test Agent also emits helpful logging, which can be viewed in local testing or in CI as a job step for tracer and contrib
tests. Locally, to get Test Agent logs run:

$ docker-compose logs -f testagent
```
docker-compose logs -f testagent
```

Read more about the APM Test Agent:
https://github.com/datadog/dd-apm-test-agent#readme
Expand All @@ -314,29 +319,29 @@ https://github.com/datadog/dd-apm-test-agent#readme
Most of the library uses Rubocop to enforce [code style](https://github.com/bbatsov/ruby-style-guide) and quality. To check, run:

```
$ bundle exec rake rubocop
bundle exec rake rubocop
```

To change your code to the version that rubocop wants, run:

```
$ bundle exec rake rubocop -A
bundle exec rake rubocop -A
```

Profiling and Dynamic Instrumentation use [standard](https://github.com/standardrb/standard)
instead of Rubocop. To check files with standard, run:

```
$ bundle exec rake standard
bundle exec rake standard
```

To change your code to the version that standard wants, run:

```
$ bundle exec rake standard:fix
bundle exec rake standard:fix
```

For non-Ruby code, follow the instructions below to debug locally, if CI failed with respective linter.
For non-Ruby code, follow the instructions below to debug locally, if CI failed with the respective linter.

- For `yamllint`, run:
```bash
Expand Down Expand Up @@ -383,12 +388,12 @@ If you modify any of the `.proto` files under `./spec/datadog/tracing/contrib/gr
testing the `grpc` integration, you'll need to regenerate the Ruby code by running:

```
$ docker run \
--platform linux/amd64 \
-v ${PWD}:/app \
-w /app \
ruby:latest \
./spec/datadog/tracing/contrib/grpc/support/gen_proto.sh
docker run \
--platform linux/amd64 \
-v ${PWD}:/app \
-w /app \
ruby:latest \
./spec/datadog/tracing/contrib/grpc/support/gen_proto.sh
```

### Community pull requests
Expand Down
Loading