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 64

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.

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 55

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

#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 81

def success?
  results.all?
end

Instance Method Details

#colorize(text, type) (private)

[ GitHub ]

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

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 111

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 86

def failure(title, subtitle = nil)
  heading title, subtitle, type: :error
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 98

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 116

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

  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
  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 75

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

#timing (private)

[ GitHub ]

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

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