diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ccde097..13a340d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,10 +22,6 @@ test_containers: description: Resource class to use type: string default: medium - edge: - description: Use latest version of dependencies during testing - type: boolean - default: false resource_class: <> - &container_base_environment BUNDLE_GEMFILE: /app/Gemfile @@ -33,7 +29,6 @@ test_containers: JRUBY_OPTS: --dev - &container_parameters_environment - *container_base_environment - - COVERAGE_BASE_DIR: coverage - &container_base image: <> environment: *container_parameters_environment @@ -76,14 +71,6 @@ step_bundle_install: &step_bundle_install else echo "All required gems were found in cache." fi -step_lint: &step_lint - run: - name: Lint with standardrb - # There's no straightforward way to get the number of available processors & CPU threads in CircleCI. - # Currently it always return 18 physical processors and 36 threads, regardless of executor size. - # The workaround is to use `cpu.shares / 1024`: - # https://discuss.circleci.com/t/environment-variable-set-to-the-number-of-available-cpus/32670/4 - command: PARALLEL_PROCESSOR_COUNT=$((`cat /sys/fs/cgroup/cpu/cpu.shares` / 1024)) bundle exec standardrb step_appraisal_install: &step_appraisal_install run: name: Install Appraisal gems @@ -137,8 +124,6 @@ filters_only_release_tags: &filters_only_release_tags orbs: orb: - orbs: - codecov: codecov/codecov@3.2.3 jobs: build: <<: *test_job_default @@ -156,17 +141,6 @@ orbs: - bundle-{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ checksum ".circleci/images/primary/binary_version" }}-<>-{{ checksum "lib/datadog/ci/version.rb" }} - *check_exact_bundle_cache_hit - *step_bundle_install - - when: - condition: - equal: [<< parameters.edge >>, true] - steps: - - *step_appraisal_update # Run on latest version of all gems we integrate with - - when: - condition: - not: - equal: [<< parameters.edge >>, true] - steps: - - *step_appraisal_install # Run on a stable set of gems we integrate with - *save_bundle_checksum - save_cache: key: '{{ .Environment.CIRCLE_CACHE_VERSION }}-bundled-repo-<>-{{ .Environment.CIRCLE_SHA1 }}' @@ -188,50 +162,9 @@ orbs: - restore_cache: keys: - bundle-{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ checksum ".circleci/images/primary/binary_version" }}-<>-{{ checksum "lib/datadog/ci/version.rb" }}-{{ .Branch }}-{{ checksum ".circleci/bundle_checksum" }} - - run: - name: Set coverage report directory - command: | - # Create a unique coverage directory for this job, to avoid conflicts when merging all results - echo 'export COVERAGE_DIR="$COVERAGE_BASE_DIR/versions/$CIRCLE_JOB/$CIRCLE_NODE_INDEX"' >> $BASH_ENV - *step_run_all_tests - store_test_results: path: /tmp/rspec - - persist_to_workspace: - root: . - paths: - - coverage - lint: - <<: *test_job_default - steps: - - restore_cache: - keys: - - '{{ .Environment.CIRCLE_CACHE_VERSION }}-bundled-repo-<>-{{ .Environment.CIRCLE_SHA1 }}' - - restore_cache: - keys: - - bundle-{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ checksum ".circleci/images/primary/binary_version" }}-<>-{{ checksum "lib/datadog/ci/version.rb" }}-{{ .Branch }}-{{ checksum ".circleci/bundle_checksum" }} - - *step_lint - coverage: - <<: *test_job_default - steps: - - restore_cache: - keys: - - '{{ .Environment.CIRCLE_CACHE_VERSION }}-bundled-repo-<>-{{ .Environment.CIRCLE_SHA1 }}' - - restore_cache: - keys: - - bundle-{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ checksum ".circleci/images/primary/binary_version" }}-<>-{{ checksum "lib/datadog/ci/version.rb" }}-{{ .Branch }}-{{ checksum ".circleci/bundle_checksum" }} - - attach_workspace: - at: /tmp/workspace - - run: - name: Generate combined coverage report for all tests - command: COVERAGE_DIR=/tmp/workspace/coverage bundle exec rake coverage:report - - codecov/upload: - file: /tmp/workspace/coverage/report/coverage.xml - - run: - name: Generate individual coverage report for each Ruby version - command: COVERAGE_DIR=/tmp/workspace/coverage bundle exec rake coverage:report_per_ruby_version - - store_artifacts: - path: /tmp/workspace/coverage/report/ - destination: coverage commands: docker-wait: description: Wait for containers to listen on a TCP port. @@ -297,95 +230,10 @@ job_configuration: workflows: version: 2 build-and-test: - jobs: - - orb/lint: - <<: *config-3_3-small - name: lint - requires: - - build-3.2 - - orb/coverage: - <<: *config-3_3-small - name: coverage - requires: - - test-2.7 - - test-3.0 - - test-3.1 - - test-3.2 - - test-3.3 - - test-3.4 - # ADD NEW RUBIES HERE - - test-jruby-9.4 - - orb/build: - <<: *config-2_7 - name: build-2.7 - - orb/test: - <<: *config-2_7 - name: test-2.7 - requires: - - build-2.7 - - orb/build: - <<: *config-3_0 - name: build-3.0 - - orb/test: - <<: *config-3_0 - name: test-3.0 - requires: - - build-3.0 - - orb/build: - <<: *config-3_1 - name: build-3.1 - - orb/test: - <<: *config-3_1 - name: test-3.1 - requires: - - build-3.1 - - orb/build: - <<: *config-3_2 - name: build-3.2 - - orb/test: - <<: *config-3_2 - name: test-3.2 - requires: - - build-3.2 - - orb/build: - <<: *config-3_3 - name: build-3.3 - - orb/test: - <<: *config-3_3 - name: test-3.3 - requires: - - build-3.3 - - orb/build: - <<: *config-3_4 - name: build-3.4 - - orb/test: - <<: *config-3_4 - name: test-3.4 - requires: - - build-3.4 - # ADD NEW RUBIES HERE - - orb/build: - <<: *config-jruby-9_4 - name: build-jruby-9.4 - - orb/test: - <<: *config-jruby-9_4 - name: test-jruby-9.4 - requires: - - build-jruby-9.4 - # This workflow runs the same `build` and `test` jobs as above on a schedule. - edge: - triggers: - - schedule: - cron: '0 0 * * 1-5' # Every weekday - filters: - branches: - only: - - main jobs: - orb/build: <<: *config-2_7 name: build-2.7 - edge: true - orb/test: <<: *config-2_7 name: test-2.7 @@ -394,7 +242,6 @@ workflows: - orb/build: <<: *config-3_0 name: build-3.0 - edge: true - orb/test: <<: *config-3_0 name: test-3.0 @@ -403,7 +250,6 @@ workflows: - orb/build: <<: *config-3_1 name: build-3.1 - edge: true - orb/test: <<: *config-3_1 name: test-3.1 @@ -412,7 +258,6 @@ workflows: - orb/build: <<: *config-3_2 name: build-3.2 - edge: true - orb/test: <<: *config-3_2 name: test-3.2 @@ -421,7 +266,6 @@ workflows: - orb/build: <<: *config-3_3 name: build-3.3 - edge: true - orb/test: <<: *config-3_3 name: test-3.3 @@ -430,7 +274,6 @@ workflows: - orb/build: <<: *config-3_4 name: build-3.4 - edge: true - orb/test: <<: *config-3_4 name: test-3.4 @@ -440,7 +283,6 @@ workflows: - orb/build: <<: *config-jruby-9_4 name: build-jruby-9.4 - edge: true - orb/test: <<: *config-jruby-9_4 name: test-jruby-9.4 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b2949916..40eb8767 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,8 +6,20 @@ on: # The branches below must be a subset of the branches above branches: [main] jobs: - check: - name: Check types + lint: + name: Lint + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + - name: Install dependencies + run: bundle install + - run: bundle exec standardrb + + typecheck: + name: Type checking runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..84ec3ea1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,226 @@ +# Please do NOT manually edit this file. +# This file is generated by 'bundle exec rake github:actions:test_template' +--- +name: Unit Tests +'on': +- push +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}" +jobs: + compute_tasks: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + engine: + - name: ruby + version: '3.4' + alias: ruby-34 + - name: ruby + version: '3.3' + alias: ruby-33 + - name: ruby + version: '3.2' + alias: ruby-32 + - name: ruby + version: '3.1' + alias: ruby-31 + - name: ruby + version: '3.0' + alias: ruby-30 + - name: ruby + version: '2.7' + alias: ruby-27 + - name: jruby + version: '9.4' + alias: jruby-94 + container: + image: ghcr.io/datadog/images-rb/engines/${{ matrix.engine.name }}:${{ matrix.engine.version }} + outputs: + ruby-34-matrix: "${{ steps.set-matrix.outputs.ruby-34 }}" + ruby-33-matrix: "${{ steps.set-matrix.outputs.ruby-33 }}" + ruby-32-matrix: "${{ steps.set-matrix.outputs.ruby-32 }}" + ruby-31-matrix: "${{ steps.set-matrix.outputs.ruby-31 }}" + ruby-30-matrix: "${{ steps.set-matrix.outputs.ruby-30 }}" + ruby-27-matrix: "${{ steps.set-matrix.outputs.ruby-27 }}" + jruby-94-matrix: "${{ steps.set-matrix.outputs.jruby-94 }}" + steps: + - uses: actions/checkout@v4 + - run: bundle install + - id: set-matrix + run: | + matrix_json=$(bundle exec rake github:generate_matrix) + # Debug output + echo "Generated JSON:" + echo "$matrix_json" + # Set the output + echo "${{ matrix.engine.alias }}=$(echo "$matrix_json")" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-${{ matrix.engine.alias }} + retention-days: 1 + path: 'Gemfile.lock + + ' + test-ruby-34: + name: 'ruby-3.4: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-34-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:3.4 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-34 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-ruby-33: + name: 'ruby-3.3: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-33-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:3.3 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-33 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-ruby-32: + name: 'ruby-3.2: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-32-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:3.2 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-32 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-ruby-31: + name: 'ruby-3.1: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-31-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:3.1 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-31 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-ruby-30: + name: 'ruby-3.0: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-30-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:3.0 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-30 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-ruby-27: + name: 'ruby-2.7: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.ruby-27-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/ruby:2.7 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-ruby-27 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} + test-jruby-94: + name: 'jruby-9.4: ${{ matrix.task }} (${{ matrix.group }})' + needs: + - compute_tasks + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: "${{ fromJSON(needs.compute_tasks.outputs.jruby-94-matrix) }}" + container: + image: ghcr.io/datadog/images-rb/engines/jruby:9.4 + env: + BUNDLE_GEMFILE: "${{ matrix.gemfile }}" + steps: + - uses: actions/checkout@v4 + - name: Configure Git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/download-artifact@v4 + with: + name: bundled-lock-${{ github.run_id }}-jruby-94 + - run: bundle install && bundle exec rake compile_ext + - name: Test ${{ matrix.task }} with ${{ matrix.gemfile }} + run: bundle exec rake spec:${{ matrix.task }} diff --git a/Gemfile b/Gemfile index ed2fa82f..02e92e93 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,6 @@ gem "pimpmychangelog", ">= 0.1.2" # coverage gem "simplecov" -gem "simplecov-cobertura", "~> 2.1.0" # type checks, memory checks, etc. group :check do diff --git a/Rakefile b/Rakefile index c5edfd4c..1ed7ef79 100644 --- a/Rakefile +++ b/Rakefile @@ -20,21 +20,6 @@ if Gem.loaded_specs.key? "ruby_memcheck" ) end -RSpec::Core::RakeTask.new(:spec) - -Dir.glob("tasks/*.rake").each { |r| import r } - -YARD::Rake::YardocTask.new(:docs) do |t| - # Options defined in `.yardopts` are read first, then merged with - # options defined here. - # - # It's recommended to define options in `.yardopts` instead of here, - # as `.yardopts` can be read by external YARD tools, like the - # hot-reload YARD server `yard server --reload`. - - t.options += ["--title", "datadog-ci #{Datadog::CI::VERSION} documentation"] -end - # ADD NEW RUBIES HERE TEST_METADATA = { "main" => { @@ -90,6 +75,21 @@ TEST_METADATA = { } } +RSpec::Core::RakeTask.new(:spec) + +Dir.glob("tasks/*.rake").each { |r| import r } + +YARD::Rake::YardocTask.new(:docs) do |t| + # Options defined in `.yardopts` are read first, then merged with + # options defined here. + # + # It's recommended to define options in `.yardopts` instead of here, + # as `.yardopts` can be read by external YARD tools, like the + # hot-reload YARD server `yard server --reload`. + + t.options += ["--title", "datadog-ci #{Datadog::CI::VERSION} documentation"] +end + namespace :test do task all: TEST_METADATA.map { |k, _| "test:#{k}" } diff --git a/spec/datadog/ci/git/local_repository_spec.rb b/spec/datadog/ci/git/local_repository_spec.rb index 4b7add09..00552cf4 100644 --- a/spec/datadog/ci/git/local_repository_spec.rb +++ b/spec/datadog/ci/git/local_repository_spec.rb @@ -104,7 +104,7 @@ def with_custom_git_environment describe ".git_repository_url" do subject { described_class.git_repository_url } - it { is_expected.to eq("git@github.com:DataDog/datadog-ci-rb.git") } + it { is_expected.to include("DataDog/datadog-ci-rb") } it_behaves_like "emits telemetry metric", :inc, "git.command", 1 it_behaves_like "emits telemetry metric", :distribution, "git.command_ms" diff --git a/spec/datadog/ci/git/packfiles_spec.rb b/spec/datadog/ci/git/packfiles_spec.rb index 07842e5a..438d831a 100644 --- a/spec/datadog/ci/git/packfiles_spec.rb +++ b/spec/datadog/ci/git/packfiles_spec.rb @@ -7,8 +7,8 @@ before { skip if PlatformHelpers.jruby? } let(:commits) { Datadog::CI::Git::LocalRepository.git_commits } - let(:included_commits) { commits[0..1] } - let(:excluded_commits) { commits[2..] } + let(:included_commits) { commits[0..1] || [] } + let(:excluded_commits) { commits[2..] || [] } describe ".generate" do it "yields packfile" do diff --git a/spec/datadog/ci/test_optimisation/coverage/event_spec.rb b/spec/datadog/ci/test_optimisation/coverage/event_spec.rb index 9eea1445..21ab83a3 100644 --- a/spec/datadog/ci/test_optimisation/coverage/event_spec.rb +++ b/spec/datadog/ci/test_optimisation/coverage/event_spec.rb @@ -112,8 +112,8 @@ Datadog::CI::Git::LocalRepository.remove_instance_variable(:@prefix_to_root) end - new_root = Dir.pwd.gsub("/#{current_folder}", "") - new_root = "/" if new_root.empty? + # new_root is one level up from the current folder + new_root = File.dirname(Dir.pwd) allow(Datadog::CI::Git::LocalRepository).to receive(:root).and_return(new_root) end diff --git a/spec/datadog/ci/test_visibility/component_spec.rb b/spec/datadog/ci/test_visibility/component_spec.rb index ba0badcf..78e62aa7 100644 --- a/spec/datadog/ci/test_visibility/component_spec.rb +++ b/spec/datadog/ci/test_visibility/component_spec.rb @@ -417,7 +417,7 @@ expect(Datadog::CI::Git::LocalRepository).to receive(:git_commits).and_return(["dcc6f4b112b1f4f21a22deae586f67dc637ba77e"]) expect(Datadog::CI::Git::SearchCommits).to receive(:new).and_return(search_commits) expect(search_commits).to receive(:call) do |repo_url, commits| - expect(repo_url).to eq("git@github.com:DataDog/datadog-ci-rb.git") + expect(repo_url).to include("DataDog/datadog-ci-rb") commits end diff --git a/tasks/appraisal_conversion.rb b/tasks/appraisal_conversion.rb new file mode 100644 index 00000000..685c5449 --- /dev/null +++ b/tasks/appraisal_conversion.rb @@ -0,0 +1,49 @@ +require 'pathname' + +# This module translates our custom mapping between appraisal and bundler. +# +# It cannot be included into `Appraisal` file, because it was invoked via `instance_eval`. +module AppraisalConversion + module_function + + @gemfile_dir = 'gemfiles' + @definition_dir = 'appraisal' + + def to_bundle_gemfile(group) + gemfile = "#{runtime_identifier}_#{group}.gemfile".tr('-', '_') + path = root_path.join(gemfile_dir, gemfile) + + if path.exist? + path.to_s + else + raise "Gemfile not found at #{path}" + end + end + + def definition + path = root_path.join(@definition_dir, "#{runtime_identifier}.rb") + + if path.exist? + path.to_s + else + raise "Definition not found at #{path}" + end + end + + def runtime_identifier + major, minor, = Gem::Version.new(RUBY_ENGINE_VERSION).segments + "#{RUBY_ENGINE}-#{major}.#{minor}" + end + + def gemfile_pattern + root_path + gemfile_dir + "#{runtime_identifier.tr('-', '_')}_*.gemfile" + end + + def gemfile_dir + @gemfile_dir + end + + def root_path + Pathname.pwd + end +end diff --git a/tasks/coverage.rake b/tasks/coverage.rake index 045ba1e6..70d80536 100644 --- a/tasks/coverage.rake +++ b/tasks/coverage.rake @@ -8,15 +8,7 @@ namespace :coverage do SimpleCov.collate resultset_files do coverage_dir "#{ENV.fetch("COVERAGE_DIR", "coverage")}/report" - if ENV["CI"] == "true" - require "simplecov-cobertura" - formatter SimpleCov::Formatter::MultiFormatter.new( - [SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::CoberturaFormatter] # Used by codecov - ) - else - formatter SimpleCov::Formatter::HTMLFormatter - end + formatter SimpleCov::Formatter::HTMLFormatter end end diff --git a/tasks/github.rake b/tasks/github.rake new file mode 100644 index 00000000..b05aa867 --- /dev/null +++ b/tasks/github.rake @@ -0,0 +1,171 @@ +require 'json' +require "psych" +require 'ostruct' +require_relative 'appraisal_conversion' + +# rubocop:disable Metrics/BlockLength +namespace :github do + namespace :actions do + task :test_template do |t| + ubuntu = "ubuntu-22.04" + + runtimes = [ + "ruby:3.4", + "ruby:3.3", + "ruby:3.2", + "ruby:3.1", + "ruby:3.0", + "ruby:2.7", + "jruby:9.4", + ].map do |runtime| + engine, version = runtime.split(':') + runtime_alias = "#{engine}-#{version.gsub('.', '')}" + + OpenStruct.new( + "engine" => engine, + "version" => version, + "alias" => runtime_alias, + "image" => "ghcr.io/datadog/images-rb/engines/#{engine}:#{version}" + ) + end + + test_jobs = runtimes.map do |runtime| + { + "test-#{runtime.alias}" => { + "name" => "#{runtime.engine}-#{runtime.version}: ${{ matrix.task }} (${{ matrix.group }})", + "needs" => ["compute_tasks"], + "runs-on" => ubuntu, + "strategy" => { + "fail-fast" => false, + "matrix" => { + "include" => "${{ fromJSON(needs.compute_tasks.outputs.#{runtime.alias}-matrix) }}" + } + }, + "container" => { + "image" => runtime.image, + "env" => { + "BUNDLE_GEMFILE" => "${{ matrix.gemfile }}" + } + }, + "steps" => [ + { "uses" => "actions/checkout@v4" }, + { + "name" => "Configure Git", + "run" => 'git config --global --add safe.directory "$GITHUB_WORKSPACE"' + }, + { + "uses" => "actions/download-artifact@v4", + "with" => { + "name" => "bundled-lock-${{ github.run_id }}-#{runtime.alias}", + } + }, + { "run" => "bundle install && bundle exec rake compile_ext" }, + { + "name" => "Test ${{ matrix.task }} with ${{ matrix.gemfile }}", + "run" => "bundle exec rake spec:${{ matrix.task }}" + } + ] + } + } + end + + compute_tasks = { + "runs-on" => ubuntu, + "strategy" => { + "fail-fast" => false, + "matrix" => { + "engine" => runtimes.map do |runtime| + { "name" => runtime.engine, "version" => runtime.version, "alias" => runtime.alias } + end + } + }, + "container" =>{ + "image" => "ghcr.io/datadog/images-rb/engines/${{ matrix.engine.name }}:${{ matrix.engine.version }}" + }, + "outputs" => runtimes.each_with_object({}) do |runtime, hash| + hash["#{runtime.alias}-matrix"] = "${{ steps.set-matrix.outputs.#{runtime.alias} }}" + end, + "steps" => [ + { "uses" => "actions/checkout@v4" }, + { "run" => "bundle install" }, + { + "id" => "set-matrix", + "run" => <<~BASH + matrix_json=$(bundle exec rake github:generate_matrix) + # Debug output + echo "Generated JSON:" + echo "$matrix_json" + # Set the output + echo "${{ matrix.engine.alias }}=$(echo "$matrix_json")" >> $GITHUB_OUTPUT + BASH + }, + { + "uses" => "actions/upload-artifact@v4", + "with" => { + "name" => "bundled-lock-${{ github.run_id }}-${{ matrix.engine.alias }}", + "retention-days" => 1, + "path" => <<~STRING + Gemfile.lock + STRING + } + }, + ] + } + + base = { + "name" => 'Unit Tests', + "on" => ['push'], + "concurrency" => { + "group" => '${{ github.workflow }}-${{ github.ref }}', + "cancel-in-progress" => '${{ github.ref != \'refs/heads/master\' }}' + }, + "jobs" => { + "compute_tasks" => compute_tasks, + **test_jobs.reduce(&:merge) + } + } + + # `Psych.dump` directly creates anchors, but Github Actions does not support anchors for YAML, + # convert to JSON first to avoid anchors + json = JSON.dump(base) + yaml = Psych.safe_load(json) + + string = +"" + string << <<~EOS + # Please do NOT manually edit this file. + # This file is generated by 'bundle exec rake #{t.name}' + EOS + string << Psych.dump(yaml, line_width: 120) + File.binwrite(".github/workflows/test.yml", string) + end + end + + task :generate_matrix do + matrix = TEST_METADATA + + ruby_version = RUBY_VERSION[0..2] + array = [] + matrix.each do |key, spec_metadata| + spec_metadata.each do |group, rubies| + matched = if RUBY_PLATFORM == 'java' + rubies.include?("✅ #{ruby_version}") && rubies.include?('✅ jruby') + else + rubies.include?("✅ #{ruby_version}") + end + + if matched + gemfile = AppraisalConversion.to_bundle_gemfile(group) rescue "Gemfile" + + array << { + group: group, + gemfile: gemfile, + task: key + } + end + end + end + + puts JSON.dump(array) + end +end +# rubocop:enable Metrics/BlockLength