Class: ActiveSupport::ContinuousIntegration
- 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 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
-
#results ⇒ Object
readonly
Returns the value of attribute results.
Class Method Summary collapse
-
.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block) ⇒ Object
Perform a CI run.
Instance Method Summary collapse
-
#echo(text, type:) ⇒ Object
Echo text to the terminal in the color corresponding to the type of the text.
-
#fail_fast? ⇒ Boolean
:nodoc:.
-
#failure(title, subtitle = nil) ⇒ Object
Display an error heading with the title and optional subtitle to reflect that the run failed.
-
#failures ⇒ Object
:nodoc:.
-
#heading(heading, subtitle = nil, type: :banner, padding: true) ⇒ Object
Display a colorized heading followed by an optional subtitle.
-
#initialize ⇒ ContinuousIntegration
constructor
A new instance of ContinuousIntegration.
-
#multiple_results? ⇒ Boolean
:nodoc:.
-
#report(title, &block) ⇒ Object
:nodoc:.
-
#step(title, *command) ⇒ Object
Declare a step with a title and a command.
-
#success? ⇒ Boolean
Returns true if all steps were successful.
Constructor Details
#initialize ⇒ ContinuousIntegration
Returns a new instance of ContinuousIntegration.
66 67 68 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 66 def initialize @results = [] end |
Instance Attribute Details
#results ⇒ Object (readonly)
Returns the value of attribute results.
33 34 35 |
# File 'activesupport/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.
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
57 58 59 60 61 62 63 64 |
# 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 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.
113 114 115 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 113 def echo(text, type:) puts colorize(text, type) end |
#fail_fast? ⇒ Boolean
:nodoc:
156 157 158 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 156 def fail_fast? ARGV.include?("-f") || ARGV.include?("--fail-fast") end |
#failure(title, subtitle = nil) ⇒ Object
Display an error heading with the title and optional subtitle to reflect that the run failed.
88 89 90 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 88 def failure(title, subtitle = nil) heading title, subtitle, type: :error end |
#failures ⇒ Object
:nodoc:
146 147 148 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 146 def failures results.reject(&:first) 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.
100 101 102 103 |
# 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 |
#multiple_results? ⇒ Boolean
:nodoc:
151 152 153 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 151 def multiple_results? results.size > 1 end |
#report(title, &block) ⇒ Object
:nodoc:
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# 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) ⇒ 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"
77 78 79 80 |
# 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 |
#success? ⇒ Boolean
Returns true if all steps were successful.
83 84 85 |
# File 'activesupport/lib/active_support/continuous_integration.rb', line 83 def success? results.map(&:first).all? end |