Skip to Content Skip to Search

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.

Namespace
Methods
E
F
G
H
N
R
S

Constants

COLORS = { banner: "\033[1;32m", # Green title: "\033[1;35m", # Purple subtitle: "\033[1;90m", # Medium Gray error: "\033[1;31m", # Red success: "\033[1;32m", # Green progress: "\033[1;36m" # Cyan }
 

Attributes

[R] results

Class Public methods

new()

# File activesupport/lib/active_support/continuous_integration.rb, line 70
def initialize
  @results = []
end

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
# File activesupport/lib/active_support/continuous_integration.rb, line 58
def self.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)
  ENV["CI"] = "true"
  new.tap { |ci| ci.run(title, subtitle, &block) }
end

Instance Public methods

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 ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.

# File activesupport/lib/active_support/continuous_integration.rb, line 152
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.

# File activesupport/lib/active_support/continuous_integration.rb, line 127
def failure(title, subtitle = nil)
  heading title, subtitle, type: :error
end

group(name, parallel: 1, &block)

Declare a group of steps that can be run in parallel. Steps within the group are collected first, then executed either concurrently (when parallel > 1) or sequentially (when parallel is 1).

When running in parallel, each step’s output is captured to avoid interleaving, and a progress display shows which steps are currently running.

Sub-groups within a parallel group occupy a single parallel slot and run their steps sequentially.

Examples:

group "Checks", parallel: 3 do
  step "Style: Ruby", "bin/rubocop"
  step "Security: Brakeman", "bin/brakeman --quiet"
  step "Security: Gem audit", "bin/bundler-audit"
end

group "Tests" do
  step "Unit tests", "bin/rails test"
  step "System tests", "bin/rails test:system"
end
# File activesupport/lib/active_support/continuous_integration.rb, line 112
def group(name, parallel: 1, &block)
  if parallel <= 1
    instance_eval(&block)
  else
    Group.new(self, name, parallel: parallel, &block).run
  end
  abort if failing_fast?
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 ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.

# File activesupport/lib/active_support/continuous_integration.rb, line 139
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

run(title, subtitle, &block)

# File activesupport/lib/active_support/continuous_integration.rb, line 63
def run(title, subtitle, &block)
  heading title, subtitle, padding: false
  success, seconds = execute(title, &block)
  result_line(title, success, seconds)
  abort unless success?
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"
# File activesupport/lib/active_support/continuous_integration.rb, line 81
def step(title, *command)
  previous_trap = Signal.trap("INT") { abort colorize("\n❌ #{title} interrupted", :error) }
  report_step(title, command) do
    started = Time.now.to_f
    [system(*command), Time.now.to_f - started]
  end
  abort if failing_fast?
ensure
  Signal.trap("INT", previous_trap || "-")
end

success?()

Returns true if all steps were successful.

# File activesupport/lib/active_support/continuous_integration.rb, line 122
def success?
  results.map(&:first).all?
end