Class: ActiveSupport::ContinuousIntegration

Inherits:
Object
  • Object
show all
Defined in:
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 collapse

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
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeContinuousIntegration

Returns a new instance of ContinuousIntegration.



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

def initialize
  @results = []
end

Instance Attribute Details

#resultsObject (readonly)

Returns the value of attribute results.



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

def results
  @results
end

Class Method Details

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

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


55
56
57
58
59
60
61
62
# File '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 Method Details

#echo(text, type:) ⇒ Object

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.



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

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

#failure(title, subtitle = nil) ⇒ Object

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



86
87
88
# File '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) ⇒ Object

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.



98
99
100
101
# File '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) ⇒ Object

:nodoc:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File '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) ⇒ Object

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"


75
76
77
78
# File 'lib/active_support/continuous_integration.rb', line 75

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

#success?Boolean

Returns true if all steps were successful.

Returns:

  • (Boolean)


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

def success?
  results.all?
end