Skip to content

Commit

Permalink
Merge pull request #8 from mocktools/develop
Browse files Browse the repository at this point in the history
RSpec::Mock v0.3.0
  • Loading branch information
bestwebua authored Nov 8, 2024
2 parents 68c01f1 + ee42c03 commit 16f78ff
Show file tree
Hide file tree
Showing 22 changed files with 1,234 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .circleci/gemspecs/compatible
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ Gem::Specification.new do |spec|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.require_paths = %w[lib]

spec.add_runtime_dependency 'colorize', '>= 0.8.1'
spec.add_runtime_dependency 'rspec-core', '~> 3.10'
spec.add_runtime_dependency 'rspec-mocks', '~> 3.10'
spec.add_runtime_dependency 'terminal-table', '~> 3.0'

spec.add_development_dependency 'rspec', '~> 3.13'
end
2 changes: 2 additions & 0 deletions .circleci/gemspecs/latest
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ Gem::Specification.new do |spec|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.require_paths = %w[lib]

spec.add_runtime_dependency 'colorize', '>= 0.8.1'
spec.add_runtime_dependency 'rspec-core', '~> 3.10'
spec.add_runtime_dependency 'rspec-mocks', '~> 3.10'
spec.add_runtime_dependency 'terminal-table', '~> 3.0'

spec.add_development_dependency 'bundler-audit', '~> 0.9.2'
spec.add_development_dependency 'fasterer', '~> 0.11.0'
Expand Down
1 change: 1 addition & 0 deletions .circleci/linter_configs/.commitspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ languageSettings:
- GithubUser

words:
- Flexmock
- bagage
- bagages
- bestwebua
Expand Down
1 change: 1 addition & 0 deletions .circleci/linter_configs/.cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ languageSettings:

words:
- Commiting
- Flexmock
- Trotsenko
- Vladislav
- bestwebua
Expand Down
3 changes: 3 additions & 0 deletions .circleci/linter_configs/.fasterer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

exclude_paths:
- '.circleci/**/*.rb'

speedups:
each_with_index_vs_while: false
7 changes: 7 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ detectors:
exclude:
- RSpec::Mock::Context#respond_to_missing?

UtilityFunction:
exclude:
- ContextHelper#create_file

ManualDispatch:
exclude:
- RSpec::Mock::Context#method_missing
- RSpec::Mock::Context#respond_to_missing?

exclude_paths:
- lib/rspec/mock/migration_analytics/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] - 2024-11-08

### Added

- Added CLI to analyze Flexmock usage and track migration progress to RSpec mocks.

## [0.2.0] - 2024-11-04

### Added
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [Usage](#usage)
- [Configuration](#configuration)
- [Integration](#integration)
- [Migration Analytics](#migration-analytics)
- [Contributing](#contributing)
- [License](#license)
- [Code of Conduct](#code-of-conduct)
Expand Down Expand Up @@ -127,6 +128,46 @@ RSpec.describe Sandbox do
end
```

### Migration Analytics

You can create a Rake task to analyze Flexmock usage and track migration progress to RSpec mocks. Or use the CLI directly.

Example of the Rake task:

```ruby
namespace :rspec_mock do
namespace :migration_analytics do
desc 'Analyze Flexmock usage and track migration progress to RSpec mocks'
task :flexmock, %i[path] do |_, args|
require 'rspec/mock/migration_analytics/cli'

path = args[:path] || 'spec'
puts("\n🔍 Analyzing Flexmock usage in: #{path}")
RSpec::Mock::MigrationAnalytics::Cli.verify_path(path)
end
end
end
```

```bash
# Analyze entire spec directory (default)
rake rspec_mock:migration_analytics:flexmock

# Analyze specific directory
rake rspec_mock:migration_analytics:flexmock spec/services

# Analyze specific file
rake rspec_mock:migration_analytics:flexmock spec/services/sandbox_service_spec.rb
```

Example of the CLI usage:

```bash
ruby cli.rb spec
ruby cli.rb spec/services
ruby cli.rb spec/services/sandbox_service_spec.rb
```

## Contributing

Bug reports and pull requests are welcome on GitHub at <https://github.com/mocktools/ruby-rspec-mock>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Please check the [open tickets](https://github.com/mocktools/ruby-rspec-mock/issues). Be sure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md).
Expand Down
11 changes: 11 additions & 0 deletions lib/rspec/mock/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

module RSpec
module Mock
module MigrationAnalytics
module Tracker
require_relative 'migration_analytics/tracker/base'
require_relative 'migration_analytics/tracker/flexmock'
require_relative 'migration_analytics/tracker/rspec'
end

require_relative 'migration_analytics/file_analyzer'
require_relative 'migration_analytics/cli'
end

require_relative 'configuration'
require_relative 'context'
require_relative 'methods'
Expand Down
185 changes: 185 additions & 0 deletions lib/rspec/mock/migration_analytics/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'colorize'
require 'terminal-table'

module RSpec
module Mock
module MigrationAnalytics
class Cli
class << self
def call
if ::ARGV.empty?
print_usage
exit 1
end

begin
verify_path(::ARGV[0])
rescue => error
puts("\n❌ Error: #{error.message}".red)
puts(error.backtrace) if ENV['DEBUG']
end
end

def verify_path(path)
case
when ::File.directory?(path) then verify_directory(path)
else verify_file(path)
end
end

private

def print_usage
puts('Usage: ruby cli.rb <path_to_spec_file_or_directory>'.yellow)
puts("\nExamples:".blue)
puts(' ruby cli.rb spec/models/user_spec.rb')
puts(' ruby cli.rb spec/models/')
puts(' ruby cli.rb spec/')
end

def verify_directory(dir_path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
results = []
stats = {
total_files: 0,
files_with_mocks: 0,
total_flexmock_occurrences: 0,
total_rspec_mock_occurrences: 0,
files_with_mixed_usage: 0
}

::Dir.glob("#{dir_path}/**/*_spec.rb").each do |file|
stats[:total_files] += 1
result = RSpec::Mock::MigrationAnalytics::FileAnalyzer.call(file)

next unless result[:has_mocks]
stats[:files_with_mocks] += 1
stats[:total_flexmock_occurrences] += result[:flexmock_count]
stats[:total_rspec_mock_occurrences] += result[:rspec_mock_count]
stats[:files_with_mixed_usage] += 1 if result[:has_mixed_usage]
results << result
end

print_summary(results, stats)
end

def verify_file(file_path)
return puts("File not found: #{file_path}".red) unless ::File.exist?(file_path)
return puts("Not a Ruby spec file: #{file_path}".yellow) unless file_path.end_with?('_spec.rb')

print_file_result(RSpec::Mock::MigrationAnalytics::FileAnalyzer.call(file_path))
end

def print_file_result(result)
puts("\n=== Mock Usage Analysis: #{result[:file_path]} ===".blue)

if result[:has_mocks]
print_mock_statistics(result)
print_locations_table('Flexmock Usage', result[:flexmock_locations]) if result[:flexmock_locations].any?
print_locations_table('RSpec Mock Usage', result[:rspec_mock_locations]) if result[:rspec_mock_locations].any?
else
puts('✅ No mocking usage found'.green)
end
end

def print_summary(results, stats)
puts("\n=== Migration Status Report ===".blue)

total_mocks = stats[:total_flexmock_occurrences] + stats[:total_rspec_mock_occurrences]
migration_progress =
total_mocks.zero? ? 100 : (stats[:total_rspec_mock_occurrences].to_f / total_mocks * 100).round(2)

print_summary_table(stats, migration_progress)
print_files_table(results) if results.any?
end

def print_mock_statistics(result)
total_mocks = result[:flexmock_count] + result[:rspec_mock_count]
migration_progress = (result[:rspec_mock_count].to_f / total_mocks * 100).round(2)
puts(
Terminal::Table.new do |t|
t.add_row(['Total Mocks', total_mocks])
t.add_row(['Flexmock Usage', result[:flexmock_count]])
t.add_row(['RSpec Mock Usage', result[:rspec_mock_count]])
t.add_row(['Migration Progress', "#{migration_progress}%"])
end
)
end

def print_locations_table(title, locations)
return if locations.empty?

puts("\n#{title}:".yellow)
puts(
Terminal::Table.new do |table|
table.headings = %w[Line Type Content]
locations.each do |loc|
table.add_row(create_location_row(loc))
end
end
)
end

def create_location_row(loc)
type_str = loc[:type].nil? ? 'unknown' : loc[:type]
color = determine_color(loc[:type])

[
loc[:line_number].to_s.yellow,
type_str.respond_to?(color) ? type_str.send(color) : type_str,
loc[:content]
]
end

def determine_color(type)
case type
when 'migration mock block' then :cyan
when 'expect mock', 'allow mock' then :blue
when 'verifying double' then :green
else :light_white
end
end

def print_summary_table(stats, migration_progress)
puts(
Terminal::Table.new do |table|
table.add_row(['Total Spec Files', stats[:total_files]])
table.add_row(['Files with Mocks', stats[:files_with_mocks]])
table.add_row(['Files with Mixed Usage', stats[:files_with_mixed_usage]])
table.add_row(['Total Flexmock Occurrences', stats[:total_flexmock_occurrences]])
table.add_row(['Total RSpec Mock Occurrences', stats[:total_rspec_mock_occurrences]])
table.add_row(['Migration Progress', "#{migration_progress}%"])
end
)
end

def print_files_table(results)
puts("\n=== Files Requiring Migration ===".red)
puts(
Terminal::Table.new do |table|
table.headings = ['File Path', 'Flexmock Count', 'RSpec Mock Count', 'Progress']
results.sort_by { |row| -row[:flexmock_count] }.each do |result|
table.add_row(create_file_row(result))
end
end
)
end

def create_file_row(result)
total = result[:flexmock_count] + result[:rspec_mock_count]
progress =
total.zero? ? 100 : (result[:rspec_mock_count].to_f / total * 100).round(2)
[
result[:file_path],
result[:flexmock_count],
result[:rspec_mock_count],
"#{progress}%"
]
end
end
end
end
end
end
58 changes: 58 additions & 0 deletions lib/rspec/mock/migration_analytics/file_analyzer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module RSpec
module Mock
module MigrationAnalytics
class FileAnalyzer
def self.call(
file_path,
flexmock_tracker = RSpec::Mock::MigrationAnalytics::Tracker::Flexmock.new,
rspec_tracker = RSpec::Mock::MigrationAnalytics::Tracker::Rspec.new
)
new(file_path, flexmock_tracker, rspec_tracker).call
end

def initialize(file_path, flexmock_tracker, rspec_tracker)
@file_path = file_path
@flexmock_tracker = flexmock_tracker
@rspec_tracker = rspec_tracker
end

def call
build_analytics
generate_report
end

private

attr_reader :file_path, :flexmock_tracker, :rspec_tracker

def build_analytics
::File.read(file_path).split("\n").each_with_index do |line, index|
line_number = index + 1
flexmock_tracker.scan_line(line, line_number)
rspec_tracker.scan_line(line, line_number)
end
end

%i[flexmock_tracker rspec_tracker].each do |method_name|
target_method_name = :"#{method_name}_locations"
define_method(target_method_name) { send(method_name).locations }
define_method(:"#{target_method_name}_any?") { send(target_method_name).any? }
end

def generate_report
{
file_path: file_path,
flexmock_count: flexmock_tracker_locations.size,
rspec_mock_count: rspec_tracker_locations.size,
flexmock_locations: flexmock_tracker_locations,
rspec_mock_locations: rspec_tracker_locations,
has_mocks: flexmock_tracker_locations_any? || rspec_tracker_locations_any?,
has_mixed_usage: flexmock_tracker_locations_any? && rspec_tracker_locations_any?
}
end
end
end
end
end
Loading

0 comments on commit 16f78ff

Please sign in to comment.