Class: ActiveSupport::ContinuousIntegration
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| Inherits: | Object |
| Defined in: | activesupport/lib/active_support/continuous_integration.rb, activesupport/lib/active_support/continuous_integration/group.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
-
COLORS =
# File 'activesupport/lib/active_support/continuous_integration.rb', line 25{ 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 }
Class Method Summary
Instance Attribute Summary
- #results readonly
-
#success? ⇒ Boolean
readonly
Returns true if all steps were successful.
- #multiple_results? ⇒ Boolean readonly private
- #fail_fast? ⇒ Boolean readonly Internal use only
- #failing_fast? ⇒ Boolean readonly Internal use only
Instance Method Summary
-
#echo(text, type:)
Echo text to the terminal in the color corresponding to the type of the text.
-
#failure(title, subtitle = nil)
Display an error heading with the title and optional subtitle to reflect that the run failed.
-
#group(name, parallel: 1, &block)
Declare a group of steps that can be run in parallel.
-
#heading(heading, subtitle = nil, type: :banner, padding: true)
Display a colorized heading followed by an optional subtitle.
- #run(title, subtitle, &block)
-
#step(title, *command)
Declare a step with a title and a command.
- #execute(title, &block) private
- #failures private
- #format_elapsed(seconds) private
- #result_line(title, success, seconds) private
- #timing private
- #colorize(text, type) Internal use only
- #report_step(title, command) Internal use only
Constructor Details
.new ⇒ ContinuousIntegration
# File 'activesupport/lib/active_support/continuous_integration.rb', line 70
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
# 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 Attribute Details
#fail_fast? ⇒ Boolean (readonly)
# File 'activesupport/lib/active_support/continuous_integration.rb', line 171
def fail_fast? ARGV.include?("-f") || ARGV.include?("--fail-fast") end
#failing_fast? ⇒ Boolean (readonly)
# File 'activesupport/lib/active_support/continuous_integration.rb', line 176
def failing_fast? fail_fast? && failures.any? end
#multiple_results? ⇒ Boolean (readonly, private)
[ GitHub ]
# File 'activesupport/lib/active_support/continuous_integration.rb', line 185
def multiple_results? results.size > 1 end
#results (readonly)
[ GitHub ]# File 'activesupport/lib/active_support/continuous_integration.rb', line 34
attr_reader :results
#success? ⇒ Boolean (readonly)
Returns true if all steps were successful.
# File 'activesupport/lib/active_support/continuous_integration.rb', line 122
def success? results.map(&:first).all? end
Instance Method Details
#colorize(text, type)
# File 'activesupport/lib/active_support/continuous_integration.rb', line 166
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.
# File 'activesupport/lib/active_support/continuous_integration.rb', line 152
def echo(text, type:) puts colorize(text, type) end
#execute(title, &block) (private)
[ GitHub ]# File 'activesupport/lib/active_support/continuous_integration.rb', line 189
def execute(title, &block) previous_trap = Signal.trap("INT") { abort colorize("\n❌ #{title} interrupted", :error) } seconds = timing { instance_eval(&block) } unless success? if multiple_results? failures.each do |success, title| unless success echo " ↳ #{title} failed", type: :error end end end end [success?, seconds] ensure Signal.trap("INT", previous_trap || "-") 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
#failures (private)
[ GitHub ]# File 'activesupport/lib/active_support/continuous_integration.rb', line 181
def failures results.reject(&:first) end
#format_elapsed(seconds) (private)
[ GitHub ]# File 'activesupport/lib/active_support/continuous_integration.rb', line 218
def format_elapsed(seconds) min, sec = seconds.divmod(60) "#{"#{min.to_i}m" if min > 0}%.2fs" % sec 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 COLORS for a complete list of options.
#report_step(title, command)
# File 'activesupport/lib/active_support/continuous_integration.rb', line 157
def report_step(title, command) heading title, command.join(" "), type: :title success, seconds = yield result_line(title, success, seconds) results << [success, title] success end
#result_line(title, success, seconds) (private)
[ GitHub ]# File 'activesupport/lib/active_support/continuous_integration.rb', line 209
def result_line(title, success, seconds) elapsed = format_elapsed(seconds) if success echo "\n✅ #{title} passed in #{elapsed}", type: :success else echo "\n❌ #{title} failed in #{elapsed}", type: :error end end
#run(title, subtitle, &block)
[ GitHub ]# 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