diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..bfa841b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +**Description** + +Replace this text with a description of the changes in this PR. + +----------------- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1265915 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: + - main + + pull_request: + +jobs: + test: + name: Test failing spec detector + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.2 + bundler-cache: true + - name: Lint files + run: bundle exec rake rubocop + - name: Run tests + run: bundle exec rake spec + - name: Upload Failures + uses: actions/upload-artifact@v3 + with: + name: Failures + path: failures_log_*.yml + - name: Upload Exceptions + uses: actions/upload-artifact@v3 + with: + name: Exceptions + path: exceptions_log_*.yml + + print_log: + name: Test print logs task + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.2 + bundler-cache: true + - name: Download Failures + uses: actions/download-artifact@v3 + with: + name: Failures + - name: Download Exceptions + uses: actions/download-artifact@v3 + with: + name: Exceptions + - name: Run combine_log task + run: bundle exec rake failing_specs_detector:combine_log + - name: Upload log file + id: read_file + uses: actions/upload-artifact@v3 + with: + name: failing_specs_log + path: "failing_specs_detector_log.txt" diff --git a/.gitignore b/.gitignore index b04a8c8..c7f5bff 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ # rspec failure tracking .rspec_status + +# log files +exceptions_log_*.yml +failures_log_*.yml +failing_specs_detector_log.txt diff --git a/Gemfile b/Gemfile index a9ce097..1f49266 100644 --- a/Gemfile +++ b/Gemfile @@ -5,5 +5,6 @@ source "https://rubygems.org" gemspec gem 'pry' +gem 'psych', '~> 4.0.6' gem 'rspec', '~>3.12' gem 'rubocop' diff --git a/Gemfile.lock b/Gemfile.lock index 5e07ec2..e9f02b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,6 +19,8 @@ GEM pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) + psych (4.0.6) + stringio racc (1.7.3) rainbow (3.1.1) rake (10.5.0) @@ -51,15 +53,18 @@ GEM rubocop-ast (1.30.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) + stringio (3.1.0) unicode-display_width (2.5.0) PLATFORMS arm64-darwin-21 + x86_64-linux DEPENDENCIES bundler failing_spec_detector! pry + psych (~> 4.0.6) rake (~> 10.0) rspec (~> 3.12) rubocop diff --git a/Rakefile b/Rakefile index b7e9ed5..3c5b792 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,14 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" +require 'rubocop/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :spec +import './lib/tasks/failing_spec_detector/combine_log.rake' + +task default: %i[ + spec + rubocop +] + +RuboCop::RakeTask.new diff --git a/lib/failing_spec_detector.rb b/lib/failing_spec_detector.rb index 62b5e97..29567c3 100644 --- a/lib/failing_spec_detector.rb +++ b/lib/failing_spec_detector.rb @@ -1,4 +1,4 @@ -require "failing_spec_detector/version" +require 'failing_spec_detector/version' module FailingSpecDetector class Error < StandardError; end diff --git a/lib/failing_spec_detector/combiner.rb b/lib/failing_spec_detector/combiner.rb new file mode 100644 index 0000000..ecbbe0f --- /dev/null +++ b/lib/failing_spec_detector/combiner.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module FailingSpecDetector + # Combiner class to combine and group the failures by exception + class Combiner + def initialize(exceptions, failures) + @exceptions = exceptions + @failures = failures + @filename = 'failing_specs_detector_log.txt' + end + + def combine + File.write(@filename, "Failing spec detector:\n\n\n") + return if @exceptions.empty? + + @exceptions.uniq.each do |exception| + File.write(@filename, "#{exception}:\n\n", mode: 'a') + related_examples = @failures.select { |failure| failure.exception == exception } + next if related_examples.empty? + + related_examples.each do |failure| + File.write(@filename, "#{failure.backtrace}:\n", mode: 'a') + end + File.write(@filename, "\n\n\n", mode: 'a') + end + File.write(@filename, '----------------------------------------------------------------', mode: 'a') + end + end +end diff --git a/lib/failing_spec_detector/failing_spec_formatter.rb b/lib/failing_spec_detector/failing_spec_formatter.rb index 7504069..7a1c405 100644 --- a/lib/failing_spec_detector/failing_spec_formatter.rb +++ b/lib/failing_spec_detector/failing_spec_formatter.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require 'rspec/core/formatters/base_text_formatter' +require_relative 'failure' +require 'yaml' module FailingSpecDetector class FailingSpecFormatter < RSpec::Core::Formatters::BaseTextFormatter @@ -10,7 +12,8 @@ def initialize(output) super(output) @failures = [] @exceptions = [] - @filename = 'log.txt' + @failures_filename = "failures_log_#{ENV.fetch('TEST_ENV_NUMBER', nil)}.yml" + @exceptions_filename = "exceptions_log_#{ENV.fetch('TEST_ENV_NUMBER', nil)}.yml" end def example_failed(failure) @@ -18,24 +21,14 @@ def example_failed(failure) @exceptions << exception unless @exceptions.include?(exception) + failure = Failure.new(exception, failure.formatted_backtrace.join("\n")) + @failures << failure end def stop(_notification) - File.open(@filename, 'w') { |f| f.write "Failing spec detector:\n" } - return if @exceptions.empty? - - @exceptions.each do |exception| - File.write(@filename, "#{exception}:\n\n", mode: 'a') - related_examples = @failures.select { |failure| failure.exception.to_s.gsub(/\e\[(\d+)m/, '') == exception } - next if related_examples.empty? - - related_examples.each do |failure| - File.write(@filename, "#{failure.formatted_backtrace.join("\n")}:\n", mode: 'a') - end - File.write(@filename, "\n\n\n", mode: 'a') - end - File.write(@filename, '----------------------------------------------------------------', mode: 'a') + File.write(@exceptions_filename, YAML.dump(@exceptions), mode: 'w') + File.write(@failures_filename, YAML.dump(@failures), mode: 'w') end end end diff --git a/lib/failing_spec_detector/failure.rb b/lib/failing_spec_detector/failure.rb new file mode 100644 index 0000000..ed21b30 --- /dev/null +++ b/lib/failing_spec_detector/failure.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module FailingSpecDetector + # a simplified Failure class allow us to store only the needed information + class Failure + attr_accessor :exception, :backtrace + + def initialize(exception, backtrace) + @exception = exception + @backtrace = backtrace + end + end +end diff --git a/lib/failing_spec_detector/version.rb b/lib/failing_spec_detector/version.rb index ca5862b..7f95b97 100644 --- a/lib/failing_spec_detector/version.rb +++ b/lib/failing_spec_detector/version.rb @@ -1,3 +1,3 @@ module FailingSpecDetector - VERSION = "0.1.0" + VERSION = '0.1.0' end diff --git a/lib/tasks/failing_spec_detector/combine_log.rake b/lib/tasks/failing_spec_detector/combine_log.rake new file mode 100644 index 0000000..31cc7e0 --- /dev/null +++ b/lib/tasks/failing_spec_detector/combine_log.rake @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'yaml' +require_relative '../../failing_spec_detector/failure' +require_relative '../../failing_spec_detector/combiner' + +namespace :failing_specs_detector do + desc 'Print all logs in console' + task :combine_log do + failures = [] + exceptions = [] + failures_file_paths = Dir['failures_log_*.yml'] + exceptions_file_paths = Dir['exceptions_log_*.yml'] + failures_file_paths.each do |file_path| + failures.concat(YAML.load_file(file_path, permitted_classes: [FailingSpecDetector::Failure])) + File.delete(file_path) + end + exceptions_file_paths.each do |file_path| + exceptions.concat(YAML.load_file(file_path)) + File.delete(file_path) + end + + FailingSpecDetector::Combiner.new(exceptions, failures).combine + end +end diff --git a/spec/failing_spec_detector/failing_spec_formatter_spec.rb b/spec/failing_spec_detector/failing_spec_formatter_spec.rb index e7966da..a2f2bdf 100644 --- a/spec/failing_spec_detector/failing_spec_formatter_spec.rb +++ b/spec/failing_spec_detector/failing_spec_formatter_spec.rb @@ -5,10 +5,14 @@ RSpec.describe FailingSpecDetector::FailingSpecFormatter do let(:formatter) { described_class.new(output) } let(:output) { Tempfile.new('./output_to_close') } - let(:expected_file_path) { './spec/support/expected_file.txt' } - let(:actual_file_path) { './log.txt' } - let(:expected_file) { File.new(expected_file_path, 'r') } - let(:actual_file) { File.new(actual_file_path, 'r') } + let(:expected_failures_file_path) { './spec/support/expected_failures_log_.yml' } + let(:expected_exceptions_file_path) { './spec/support/expected_exceptions_log_.yml' } + let(:actual_failures_file_path) { './failures_log_.yml' } + let(:actual_exceptions_file_path) { './exceptions_log_.yml' } + let(:expected_failures_file) { File.new(expected_failures_file_path, 'r') } + let(:expected_exceptions_file) { File.new(expected_exceptions_file_path, 'r') } + let(:actual_failures_file) { File.new(actual_failures_file_path, 'r') } + let(:actual_exceptions_file) { File.new(actual_exceptions_file_path, 'r') } let(:examples) { [failed_notification1, failed_notification2, failed_notification3] } let(:expected_exceptions) { [failed_notification1.exception.to_s, failed_notification3.exception.to_s] } @@ -54,9 +58,10 @@ example end - it 'prints the failing specs backtraces grouped by exception' do + it 'stores the failing specs failures and exceptions in yml files' do mock_run_specs - expect(FileUtils.compare_file(actual_file, expected_file)).to be_truthy + expect(FileUtils.compare_file(actual_failures_file, expected_failures_file)).to be_truthy + expect(FileUtils.compare_file(actual_exceptions_file, expected_exceptions_file)).to be_truthy end def mock_run_specs diff --git a/spec/support/expected_exceptions_log_.yml b/spec/support/expected_exceptions_log_.yml new file mode 100644 index 0000000..e63ee86 --- /dev/null +++ b/spec/support/expected_exceptions_log_.yml @@ -0,0 +1,3 @@ +--- +- Test Error 1 +- Test Error 2 diff --git a/spec/support/expected_failures_log_.yml b/spec/support/expected_failures_log_.yml new file mode 100644 index 0000000..ee9e536 --- /dev/null +++ b/spec/support/expected_failures_log_.yml @@ -0,0 +1,10 @@ +--- +- !ruby/object:FailingSpecDetector::Failure + exception: Test Error 1 + backtrace: "/spec/one_spec.rb:11:in `some_method'" +- !ruby/object:FailingSpecDetector::Failure + exception: Test Error 1 + backtrace: "/spec/two_spec.rb:20:in `some_method'" +- !ruby/object:FailingSpecDetector::Failure + exception: Test Error 2 + backtrace: "/spec/three_spec.rb:4:in `some_method'" diff --git a/spec/support/expected_file.txt b/spec/support/expected_file.txt deleted file mode 100644 index 379edf2..0000000 --- a/spec/support/expected_file.txt +++ /dev/null @@ -1,15 +0,0 @@ -Failing spec detector: -Test Error 1: - -/spec/one_spec.rb:11:in `some_method': -/spec/two_spec.rb:20:in `some_method': - - - -Test Error 2: - -/spec/three_spec.rb:4:in `some_method': - - - ----------------------------------------------------------------- \ No newline at end of file