├── lib ├── ci_logger │ ├── version.rb │ ├── registry.rb │ ├── minitest │ │ └── integration.rb │ ├── rspec │ │ ├── integration.rb │ │ └── example_group_methods.rb │ ├── railtie.rb │ └── logger.rb ├── tasks │ └── ci_logger_tasks.rake └── ci_logger.rb ├── Gemfile ├── bin └── test ├── .github ├── dependabot.yml └── workflows │ └── ruby.yml ├── gemfiles ├── rails_7_2.gemfile ├── rails_8_0.gemfile ├── rails_8_1.gemfile ├── rails_6_1.gemfile ├── rails_7_1.gemfile └── rails_7_0.gemfile ├── .gitignore ├── Rakefile ├── test ├── ci_logger_test.rb ├── test_helper.rb ├── rails_integration_test.rb ├── dummy │ └── fake_app.rb ├── registry_test.rb ├── minitest_integration_test.rb ├── logger_test.rb └── rspec_integration_test.rb ├── ci_logger.gemspec ├── MIT-LICENSE └── README.md /lib/ci_logger/version.rb: -------------------------------------------------------------------------------- 1 | module CiLogger 2 | VERSION = '1.0.0' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'sqlite3' 6 | -------------------------------------------------------------------------------- /lib/tasks/ci_logger_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :ci_logger do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.expand_path("../test", __dir__) 3 | 4 | require "bundler/setup" 5 | require "rails/plugin/test" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /gemfiles/rails_7_2.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 7.2.0" 4 | gem "sqlite3" 5 | gem "logger", ">= 1.6.0" 6 | 7 | gemspec path: '../' 8 | -------------------------------------------------------------------------------- /gemfiles/rails_8_0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 8.0.0" 4 | gem "sqlite3" 5 | gem "logger", ">= 1.6.0" 6 | 7 | gemspec path: '../' 8 | -------------------------------------------------------------------------------- /gemfiles/rails_8_1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 8.1.0" 4 | gem "sqlite3" 5 | gem "logger", ">= 1.6.0" 6 | 7 | gemspec path: '../' 8 | -------------------------------------------------------------------------------- /gemfiles/rails_6_1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 6.1.0" 4 | gem "sqlite3", "~> 1.4" 5 | gem "concurrent-ruby", "< 1.3.5" 6 | gemspec path: '../' 7 | -------------------------------------------------------------------------------- /gemfiles/rails_7_1.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 7.1.0" 4 | gem "sqlite3", "~> 1.4" 5 | gem "logger", ">= 1.6.0" 6 | 7 | gemspec path: '../' 8 | -------------------------------------------------------------------------------- /gemfiles/rails_7_0.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", "~> 7.0.0" 4 | gem "sqlite3", "~> 1.4" 5 | gem "concurrent-ruby", "< 1.3.5" 6 | 7 | gemspec path: '../' 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /doc/ 3 | /log/*.log 4 | /pkg/ 5 | /tmp/ 6 | /test/dummy/db/*.sqlite3 7 | /test/dummy/db/*.sqlite3-* 8 | /test/dummy/log/*.log 9 | /test/dummy/storage/ 10 | /test/dummy/tmp/ 11 | .byebug_history 12 | Gemfile.lock 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | require "bundler/gem_tasks" 4 | 5 | require "rake/testtask" 6 | 7 | Rake::TestTask.new(:test) do |t| 8 | t.libs << 'test' 9 | t.pattern = 'test/**/*_test.rb' 10 | t.verbose = false 11 | end 12 | 13 | task default: :test 14 | -------------------------------------------------------------------------------- /test/ci_logger_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CiLoggerTest < ActiveSupport::TestCase 4 | test "CiLogger.new return CiLogger::Logger instance" do 5 | logger = CiLogger.new(Logger.new(STDOUT)) 6 | assert_instance_of CiLogger::Logger, logger 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | 4 | require 'bundler/setup' 5 | require 'rails' 6 | require 'action_controller/railtie' 7 | require 'ci_logger' 8 | require_relative './dummy/fake_app.rb' 9 | require 'minitest/autorun' 10 | require 'rspec/rails' 11 | 12 | -------------------------------------------------------------------------------- /lib/ci_logger.rb: -------------------------------------------------------------------------------- 1 | require "ci_logger/version" 2 | require "ci_logger/railtie" 3 | require "ci_logger/logger" 4 | 5 | module CiLogger 6 | class << self 7 | def new(original) 8 | Logger.new(original) 9 | end 10 | 11 | def enabled? 12 | Rails.application.config.ci_logger.enabled 13 | end 14 | 15 | def disabled? 16 | !enabled? 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/ci_logger/registry.rb: -------------------------------------------------------------------------------- 1 | module CiLogger 2 | module Registry 3 | class << self 4 | def register(logger) 5 | @loggers ||= [] 6 | @loggers << logger 7 | end 8 | 9 | def sync 10 | @loggers.each(&:sync) 11 | end 12 | 13 | def clear 14 | @loggers.each(&:clear) 15 | end 16 | 17 | def debug(...) 18 | @loggers.each { _1.debug(...) } 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/ci_logger/minitest/integration.rb: -------------------------------------------------------------------------------- 1 | module CiLogger 2 | module Minitest 3 | module Integration 4 | def before_teardown 5 | super 6 | if CiLogger.disabled? 7 | Registry.sync 8 | elsif passed? || skipped? 9 | Registry.clear 10 | else 11 | Registry.sync 12 | end 13 | end 14 | end 15 | end 16 | end 17 | class ActiveSupport::TestCase 18 | include CiLogger::Minitest::Integration 19 | end 20 | -------------------------------------------------------------------------------- /test/rails_integration_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RailsIntegrationTest < ActiveSupport::TestCase 4 | if Rails.gem_version >= Gem::Version.new('7.1') 5 | test "Rails.logger is a BroadcastLogger and it has instances of CiLogger" do 6 | assert Rails.logger.is_a?(ActiveSupport::BroadcastLogger) 7 | assert Rails.logger.instance_variable_get(:@broadcasts).all? { |logger| logger.is_a?(CiLogger::Logger) } 8 | end 9 | else 10 | test "Rails.logger is an CiLogger" do 11 | assert Rails.logger.is_a?(CiLogger::Logger) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/fake_app.rb: -------------------------------------------------------------------------------- 1 | FakeApp = Class.new(Rails::Application) 2 | ENV['RAILS_ENV'] ||= 'test' 3 | FakeApp.config.session_store :cookie_store, key: '_myapp_session' 4 | FakeApp.config.eager_load = false 5 | FakeApp.config.hosts << 'www.example.com' if FakeApp.config.respond_to?(:hosts) 6 | FakeApp.config.root = File.dirname(__FILE__) 7 | FakeApp.config.ci_logger.enabled = true 8 | FakeApp.initialize! 9 | 10 | FakeApp.routes.draw do 11 | resources :users 12 | end 13 | 14 | class UsersController < ActionController::Base 15 | def index 16 | render plain: 'hello' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/ci_logger/rspec/integration.rb: -------------------------------------------------------------------------------- 1 | require "ci_logger/rspec/example_group_methods" 2 | 3 | RSpec.configure do |config| 4 | config.include CiLogger::Rspec::ExampleGroupMethods 5 | 6 | config.prepend_before do |example| 7 | next if CiLogger.disabled? 8 | 9 | CiLogger::Registry.debug("start example at #{example.location}") 10 | end 11 | 12 | config.append_after do |example| 13 | if CiLogger.disabled? 14 | CiLogger::Registry.sync 15 | elsif passed? 16 | CiLogger::Registry.clear 17 | else 18 | CiLogger::Registry.debug("finish example at #{example.location}") 19 | CiLogger::Registry.sync 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/ci_logger/rspec/example_group_methods.rb: -------------------------------------------------------------------------------- 1 | module CiLogger 2 | module Rspec 3 | module ExampleGroupMethods 4 | # original from https://github.com/rspec/rspec-rails/blob/229f5f7ca9c0de0c7bca452450416d988f7cf612/lib/rspec/rails/example/system_example_group.rb#L28-L37 5 | def passed? 6 | return false if RSpec.current_example.exception 7 | return true unless defined?(::RSpec::Expectations::FailureAggregator) 8 | 9 | failure_notifier = ::RSpec::Support.failure_notifier 10 | return true unless failure_notifier.is_a?(::RSpec::Expectations::FailureAggregator) 11 | 12 | failure_notifier.failures.empty? && failure_notifier.other_errors.empty? 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ci_logger.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/ci_logger/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "ci_logger" 5 | spec.version = CiLogger::VERSION 6 | spec.authors = ["willnet"] 7 | spec.email = ["netwillnet@gmail.com"] 8 | spec.homepage = "https://github.com/willnet/ci_logger" 9 | spec.summary = "Faster logger for CI" 10 | spec.description = "Faster logger for CI" 11 | spec.license = "MIT" 12 | 13 | spec.metadata["homepage_uri"] = spec.homepage 14 | spec.metadata["source_code_uri"] = "https://github.com/willnet/ci_logger" 15 | spec.metadata["changelog_uri"] = "https://github.com/willnet/ci_logger" 16 | 17 | spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 18 | 19 | spec.required_ruby_version = ">= 2.7.0" 20 | spec.add_dependency "railties", ">= 6.1.0" 21 | spec.add_development_dependency "rspec-rails" 22 | end 23 | -------------------------------------------------------------------------------- /lib/ci_logger/railtie.rb: -------------------------------------------------------------------------------- 1 | module CiLogger 2 | class Railtie < ::Rails::Railtie 3 | config.ci_logger = ActiveSupport::OrderedOptions.new 4 | config.ci_logger.enabled = false 5 | 6 | config.before_initialize do 7 | if CiLogger.enabled? 8 | if defined?(ActiveSupport::BroadcastLogger) && Rails.logger.is_a?(ActiveSupport::BroadcastLogger) 9 | Rails.logger.instance_variable_get(:@broadcasts).map! do |logger| 10 | CiLogger::Logger.new(logger) 11 | end 12 | else 13 | Rails.logger = CiLogger::Logger.new(Rails.logger) 14 | end 15 | 16 | begin 17 | require "rspec/core" 18 | require "ci_logger/rspec/integration" 19 | rescue LoadError 20 | # do nothing 21 | end 22 | 23 | begin 24 | require "active_support/test_case" 25 | require "ci_logger/minitest/integration" 26 | rescue LoadError 27 | # do nothing 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 willnet 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/registry_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RegistryTest < ActiveSupport::TestCase 4 | LOGFILE_PATH_1 = '/tmp/ciloggertest_1.log' 5 | LOGFILE_PATH_2 = '/tmp/ciloggertest_2.log' 6 | 7 | setup do 8 | log1 = Logger.new(LOGFILE_PATH_1) 9 | log2 = Logger.new(LOGFILE_PATH_2) 10 | @logger1 = CiLogger::Logger.new(log1) 11 | @logger2 = CiLogger::Logger.new(log2) 12 | end 13 | 14 | test "Registry#sync syncs all registered loggers" do 15 | @logger1.debug 'ci_logger1' 16 | @logger2.debug 'ci_logger2' 17 | CiLogger::Registry.sync 18 | assert File.read(LOGFILE_PATH_1).match?('ci_logger1') 19 | assert File.read(LOGFILE_PATH_2).match?('ci_logger2') 20 | end 21 | 22 | test "Registry#debug calls debug method of all registered loggers" do 23 | mock1 = Minitest::Mock.new.expect(:call, nil, ['ci_logger!']) 24 | mock2 = Minitest::Mock.new.expect(:call, nil, ['ci_logger!']) 25 | @logger1.stub(:debug, mock1) do 26 | @logger2.stub(:debug, mock2) do 27 | CiLogger::Registry.debug('ci_logger!') 28 | end 29 | end 30 | 31 | mock1.verify 32 | mock2.verify 33 | end 34 | 35 | teardown do 36 | CiLogger::Registry.clear 37 | File.unlink(LOGFILE_PATH_1) 38 | File.unlink(LOGFILE_PATH_2) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/minitest_integration_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MinitestIntegrationTest < ActiveSupport::TestCase 4 | test_class = Class.new(ActiveSupport::TestCase) do 5 | # Avoid the prefix "test_" as it makes the test subject to the CiLogger itself. 6 | def sample_success 7 | Rails.logger.debug 'hoge' 8 | assert true 9 | end 10 | 11 | def sample_skip 12 | Rails.logger.debug 'hoge' 13 | skip "not yet" 14 | end 15 | 16 | def sample_failure 17 | Rails.logger.debug 'hoge' 18 | assert false 19 | end 20 | end 21 | 22 | LOGFILE_PATH = Rails.root.join('log/test.log').to_s 23 | 24 | setup do 25 | File.exist?(LOGFILE_PATH) && File.truncate(LOGFILE_PATH, 0) 26 | end 27 | 28 | test "success test doesn't write logs on CiLogger enabled" do 29 | test_class.new('sample_success').run 30 | 31 | assert File.empty?(LOGFILE_PATH) 32 | end 33 | 34 | test "skip test doesn't write logs on CiLogger enabled" do 35 | test_class.new('sample_skip').run 36 | 37 | assert File.empty?(LOGFILE_PATH) 38 | end 39 | 40 | test "failure test writes logs on CiLogger enabled" do 41 | begin 42 | test_class.new('sample_failure').run 43 | rescue Minitest::Assertion 44 | # do nothing 45 | end 46 | assert_not File.empty?(LOGFILE_PATH) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/logger_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LoggerTest < ActiveSupport::TestCase 4 | LOGFILE_PATH = '/tmp/ciloggertest.log' 5 | 6 | class TestFormatter < ::Logger::Formatter 7 | def hoge 8 | 'hoge' 9 | end 10 | end 11 | 12 | setup do 13 | log = Logger.new(LOGFILE_PATH, level: :info) 14 | log.formatter = TestFormatter.new 15 | @logger = CiLogger::Logger.new(log) 16 | end 17 | 18 | teardown do 19 | @logger.clear 20 | File.unlink(LOGFILE_PATH) 21 | end 22 | 23 | test "CiLogger::Logger#info doesn't output immediately" do 24 | @logger.info 'ci_logger!' 25 | 26 | assert_not File.read(LOGFILE_PATH).match?('ci_logger!') 27 | end 28 | 29 | test "CiLogger::Logger#sync output before it write" do 30 | @logger.info 'ci_logger!' 31 | @logger.sync 32 | assert File.read(LOGFILE_PATH).match?('ci_logger!') 33 | end 34 | 35 | test "CiLogger::Logger supports block" do 36 | @logger.info do 37 | 'ci_logger!' 38 | end 39 | @logger.sync 40 | assert File.read(LOGFILE_PATH).match?('ci_logger!') 41 | end 42 | 43 | test "CiLogger::Logger#sync doesn't output log if original loglevel is higher" do 44 | @logger.debug 'ci_logger!' 45 | @logger.sync 46 | assert_not File.read(LOGFILE_PATH).match?('ci_logger!') 47 | end 48 | 49 | test "CiLogger::Logger accepts methods original logger has" do 50 | # tagged is a extension method of the Rails Logger 51 | Rails.logger.tagged('hello') { |l| l.info('world') } 52 | end 53 | 54 | test "CiLogger::Logger uses formatter that original logger has" do 55 | assert_equal 'hoge', @logger.formatter.hoge 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/ci_logger/logger.rb: -------------------------------------------------------------------------------- 1 | require "ci_logger/registry" 2 | 3 | module CiLogger 4 | class Logger 5 | def initialize(original) 6 | @original = original 7 | Registry.register(self) 8 | end 9 | 10 | def sync 11 | temporary_log.each do |l| 12 | @original.add(l[:severity], l[:message], l[:progname]) 13 | end 14 | temporary_log.clear 15 | end 16 | 17 | def clear 18 | temporary_log.clear 19 | end 20 | 21 | def add(severity, message = nil, progname = nil) 22 | if progname.nil? 23 | progname = @progname 24 | end 25 | if message.nil? 26 | if block_given? 27 | message = yield 28 | else 29 | message = progname 30 | progname = @progname 31 | end 32 | end 33 | temporary_log << { severity: severity, message: message, progname: progname } 34 | end 35 | 36 | def debug(progname = nil, &block) 37 | add(::Logger::DEBUG, nil, progname, &block) 38 | end 39 | 40 | def info(progname = nil, &block) 41 | add(::Logger::INFO, nil, progname, &block) 42 | end 43 | 44 | def warn(progname = nil, &block) 45 | add(::Logger::WARN, nil, progname, &block) 46 | end 47 | 48 | def error(progname = nil, &block) 49 | add(::Logger::ERROR, nil, progname, &block) 50 | end 51 | 52 | def fatal(progname = nil, &block) 53 | add(::Logger::FATAL, nil, progname, &block) 54 | end 55 | 56 | def unknown(progname = nil, &block) 57 | add(::Logger::UNKNOWN, nil, progname, &block) 58 | end 59 | 60 | private 61 | 62 | def temporary_log 63 | @temporary_log ||= [] 64 | end 65 | 66 | def method_missing(...) 67 | @original.send(...) 68 | end 69 | 70 | def respond_to_missing?(...) 71 | @original.respond_to?(...) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CiLogger 2 | 3 | In our CI process, we often look at logs to figure out issues with flaky tests—tests that fail sometimes but not always, especially when we can't make them fail in the same way on our own computers. The problem is, it's hard to tell which logs are about the failed tests because logs from tests that passed and failed are all mixed together. 4 | 5 | CiLogger makes this easier by only keeping logs from tests that didn't pass, including the tricky flaky ones. This means when a flaky test won't fail the same way for us locally, we can quickly find and look at the right logs to help us understand and fix the problem. 6 | 7 | ## prerequisite 8 | 9 | - rspec or minitest 10 | - rails(>= 6.1) 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | ```ruby 17 | gem 'ci_logger', group: :test 18 | ``` 19 | 20 | And then execute: 21 | ```bash 22 | $ bundle 23 | ``` 24 | 25 | Or install it yourself as: 26 | ```bash 27 | $ gem install ci_logger 28 | ``` 29 | 30 | Add this line to your config/environments/test.rb 31 | 32 | ```ruby 33 | config.ci_logger.enabled = ENV['CI'] 34 | ``` 35 | 36 | You can replace `ENV['CI']` with what you like. 37 | 38 | ## Replace loggers besides rails logger 39 | 40 | CiLogger replaces Rails.logger by default, but other loggers can be replaced. 41 | 42 | ```ruby 43 | your_logger = CiLogger.new(your_logger) 44 | your_logger.debug('debug!') # This is only output when the test fails 45 | ``` 46 | 47 | ## Contributing 48 | 49 | We welcome contributions! 50 | 51 | 1. Fork the repository and create a new branch. 52 | 2. Make your changes and add tests if needed. 53 | 3. Run tests to ensure everything works: 54 | ```bash 55 | $ bundle exec rake 56 | ``` 57 | 4. Open a pull request on GitHub. 58 | 59 | Thank you for your support! 60 | 61 | ## License 62 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 63 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake 6 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 7 | 8 | name: Ruby 9 | 10 | on: 11 | push: 12 | branches: [ main ] 13 | pull_request: 14 | branches: [ main ] 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4'] 22 | gemfile: 23 | - rails_6_1 24 | - rails_7_0 25 | - rails_7_1 26 | - rails_7_2 27 | - rails_8_0 28 | - rails_8_1 29 | exclude: 30 | - ruby-version: '3.4' 31 | gemfile: rails_6_1 32 | - ruby-version: '3.4' 33 | gemfile: rails_7_0 34 | - ruby-version: '3.4' 35 | gemfile: rails_7_1 36 | - ruby-version: '3.3' 37 | gemfile: rails_6_1 38 | - ruby-version: '3.3' 39 | gemfile: rails_7_0 40 | - ruby-version: '2.7' 41 | gemfile: rails_7_2 42 | - ruby-version: '3.0' 43 | gemfile: rails_7_2 44 | - ruby-version: '2.7' 45 | gemfile: rails_8_0 46 | - ruby-version: '3.0' 47 | gemfile: rails_8_0 48 | - ruby-version: '3.1' 49 | gemfile: rails_8_0 50 | - ruby-version: '2.7' 51 | gemfile: rails_8_1 52 | - ruby-version: '3.0' 53 | gemfile: rails_8_1 54 | - ruby-version: '3.1' 55 | gemfile: rails_8_1 56 | env: 57 | BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile 58 | 59 | steps: 60 | - uses: actions/checkout@v5 61 | - name: Set up Ruby 62 | uses: ruby/setup-ruby@v1 63 | with: 64 | ruby-version: ${{ matrix.ruby-version }} 65 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 66 | - name: Run tests 67 | run: bundle exec rake 68 | -------------------------------------------------------------------------------- /test/rspec_integration_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RspecIntegrationTest < ActiveSupport::TestCase 4 | LOGFILE_PATH = Rails.root.join('log/test.log').to_s 5 | 6 | setup do 7 | RSpec.configuration.output_stream = File.open('/dev/null', 'w') 8 | File.exist?(LOGFILE_PATH) && File.truncate(LOGFILE_PATH, 0) 9 | @reporter = RSpec.configuration.formatter_loader.reporter 10 | end 11 | 12 | test "success test doesn't write logs on CiLogger enabled" do 13 | group = RSpec.describe 'hello', type: :request 14 | group.example do 15 | get '/users' 16 | expect(response.status).to eq 200 17 | end 18 | group.run(@reporter) 19 | assert File.empty?(LOGFILE_PATH) 20 | end 21 | 22 | test "skip test doesn't write logs on CiLogger enabled" do 23 | group = RSpec.describe 'hello', type: :request 24 | group.example do 25 | skip 26 | get '/users' 27 | expect(response.status).to eq 500 28 | end 29 | group.run(@reporter) 30 | assert File.empty?(LOGFILE_PATH) 31 | end 32 | 33 | test "failure request spec write logs on CiLogger enabled" do 34 | group = RSpec.describe 'hello', type: :request 35 | group.example do 36 | get '/users' 37 | expect(response.status).to eq 500 38 | end 39 | group.run(@reporter) 40 | assert_not File.empty?(LOGFILE_PATH) 41 | end 42 | 43 | test "failure model spec write logs on CiLogger enabled" do 44 | group = RSpec.describe 'hello', type: :model 45 | group.example do 46 | expect(true).to eq false 47 | end 48 | group.run(@reporter) 49 | assert_not File.empty?(LOGFILE_PATH) 50 | end 51 | 52 | test "exception test write logs on CiLogger enabled" do 53 | group = RSpec.describe 'hello', type: :request 54 | group.example { raise } 55 | group.run(@reporter) 56 | assert_not File.empty?(LOGFILE_PATH) 57 | end 58 | 59 | test "success test writes logs on CiLogger disabled" do 60 | Rails.application.config.ci_logger.enabled = false 61 | group = RSpec.describe 'hello', type: :request 62 | group.example do 63 | get '/users' 64 | expect(response.status).to eq 200 65 | end 66 | group.run(@reporter) 67 | assert_not File.empty?(LOGFILE_PATH) 68 | ensure 69 | Rails.application.config.ci_logger.enabled = true 70 | end 71 | 72 | test "failure test write logs on CiLogger disabled" do 73 | Rails.application.config.ci_logger.enabled = false 74 | group = RSpec.describe 'hello', type: :request 75 | group.example do 76 | get '/users' 77 | expect(response.status).to eq 500 78 | end 79 | group.run(@reporter) 80 | assert_not File.empty?(LOGFILE_PATH) 81 | ensure 82 | Rails.application.config.ci_logger.enabled = true 83 | end 84 | 85 | test "exception test write logs on CiLogger disabled" do 86 | Rails.application.config.ci_logger.enabled = false 87 | group = RSpec.describe 'hello', type: :request 88 | group.example { raise } 89 | group.run(@reporter) 90 | assert_not File.empty?(LOGFILE_PATH) 91 | ensure 92 | Rails.application.config.ci_logger.enabled = true 93 | end 94 | end 95 | --------------------------------------------------------------------------------