123456789_123456789_123456789_123456789_123456789_

Class: ActiveSupport::ContinuousIntegration

Relationships & Source Files
Inherits: Object
Defined in: activesupport/lib/active_support/continuous_integration.rb

Overview

Provides a DSL for declaring a continuous integration workflow that can be run either locally or in the cloud. Each step is timed, reports success/error, and is aggregated into a collective report that reports total runtime, as well as whether the entire run was successful or not.

Example:

ActiveSupport::ContinuousIntegration.run do
  step "Setup", "bin/setup --skip-server"
  step "Style: Ruby", "bin/rubocop"
  step "Security: Gem audit", "bin/bundler-audit"
  step "Tests: Rails", "bin/rails test test:system"

  if success?
    step "Signoff: Ready for merge and deploy", "gh signoff"
  else
    failure "Skipping signoff; CI failed.", "Fix the issues and try again."
  end
end

Starting with ::Rails 8.1, a default bin/ci and config/ci.rb file are created to provide out-of-the-box CI.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.newContinuousIntegration

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 66

def initialize
  @results = []
end

Class Method Details

.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)

Perform a CI run. Execute each step, show their results and runtime, and exit with a non-zero status if there are any failures.

Pass an optional title, subtitle, and a block that declares the steps to be executed.

Sets the CI environment variable to “true” to allow for conditional behavior in the app, like enabling eager loading and disabling logging.

A ‘fail fast’ option can be passed as a CLI argument (-f or –fail-fast). This exits with a non-zero status directly after a step fails.

Example:

ActiveSupport::ContinuousIntegration.run do
  step "Setup", "bin/setup --skip-server"
  step "Style: Ruby", "bin/rubocop"
  step "Security: Gem audit", "bin/bundler-audit"
  step "Tests: Rails", "bin/rails test test:system"

  if success?
    step "Signoff: Ready for merge and deploy", "gh signoff"
  else
    failure "Skipping signoff; CI failed.", "Fix the issues and try again."
  end
end
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 57

def self.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)
  new.tap do |ci|
    ENV["CI"] = "true"
    ci.heading title, subtitle, padding: false
    ci.report(title, &block)
    abort unless ci.success?
  end
end

Instance Attribute Details

#fail_fast?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 156

def fail_fast?
  ARGV.include?("-f") || ARGV.include?("--fail-fast")
end

#multiple_results?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 151

def multiple_results?
  results.size > 1
end

#results (readonly)

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 33

attr_reader :results

#success?Boolean (readonly)

Returns true if all steps were successful.

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 83

def success?
  results.map(&:first).all?
end

Instance Method Details

#colorize(text, type) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 168

def colorize(text, type)
  "#{COLORS.fetch(type)}#{text}\033[0m"
end

#echo(text, type:)

Echo text to the terminal in the color corresponding to the type of the text.

Examples:

echo "This is going to be green!", type: :success
echo "This is going to be red!", type: :error

See COLORS for a complete list of options.

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 113

def echo(text, type:)
  puts colorize(text, type)
end

#failure(title, subtitle = nil)

Display an error heading with the title and optional subtitle to reflect that the run failed.

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 88

def failure(title, subtitle = nil)
  heading title, subtitle, type: :error
end

#failures

This method is for internal use only.
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 146

def failures
  results.reject(&:first)
end

#heading(heading, subtitle = nil, type: :banner, padding: true)

Display a colorized heading followed by an optional subtitle.

Examples:

heading "Smoke Testing", "End-to-end tests verifying key functionality", padding: false
heading "Skipping video encoding tests", "Install FFmpeg to run these tests", type: :error

See COLORS for a complete list of options.

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 100

def heading(heading, subtitle = nil, type: :banner, padding: true)
  echo "#{padding ? "\n\n" : ""}#{heading}", type: type
  echo "#{subtitle}#{padding ? "\n" : ""}", type: :subtitle if subtitle
end

#report(title, &block)

This method is for internal use only.
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 118

def report(title, &block)
  Signal.trap("INT") { abort colorize("\n❌ #{title} interrupted", :error) }

  ci = self.class.new
  elapsed = timing { ci.instance_eval(&block) }

  if ci.success?
    echo "\n✅ #{title} passed in #{elapsed}", type: :success
  else
    echo "\n❌ #{title} failed in #{elapsed}", type: :error

    abort if ci.fail_fast?

    if ci.multiple_results?
      ci.failures.each do |success, title|
        unless success
          echo "#{title} failed", type: :error
        end
      end
    end
  end

  results.concat ci.results
ensure
  Signal.trap("INT", "-")
end

#step(title, *command)

Declare a step with a title and a command. The command can either be given as a single string or as multiple strings that will be passed to system as individual arguments (and therefore correctly escaped for paths etc).

Examples:

step "Setup", "bin/setup"
step "Single test", "bin/rails", "test", "--name", "test_that_is_one"
[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 77

def step(title, *command)
  heading title, command.join(" "), type: :title
  report(title) { results << [ system(*command), title ] }
end

#timing (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/continuous_integration.rb', line 161

def timing
  started_at = Time.now.to_f
  yield
  min, sec = (Time.now.to_f - started_at).divmod(60)
  "#{"#{min}m" if min > 0}%.2fs" % sec
end