Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exploration #11

Draft
wants to merge 59 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6afbd7a
Add guard for TDD
pr0d1r2 Sep 16, 2023
b5aea93
Remove redundant default guard configuration
pr0d1r2 Sep 16, 2023
bd10efb
Configure guard-rspec
pr0d1r2 Sep 16, 2023
0d561c9
Remove redundant comments
pr0d1r2 Sep 16, 2023
30b20f3
Add guard-rubocop with default configuration
pr0d1r2 Sep 16, 2023
d957c62
Configure RuboCop
pr0d1r2 Sep 16, 2023
08bb688
Use new RuboCop cops and store TODOs
pr0d1r2 Sep 16, 2023
f80c26e
Use all cops available and store TODOs
pr0d1r2 Sep 16, 2023
3da3bf0
Suggest extensions in RuboCop for the future
pr0d1r2 Sep 16, 2023
066166b
Use recent Ruby version
pr0d1r2 Sep 16, 2023
c7737c5
Use indentation by spaces instead of tabs
pr0d1r2 Sep 16, 2023
119312f
Prefer single-quoted strings
pr0d1r2 Sep 16, 2023
995e494
Remove redundant return
pr0d1r2 Sep 16, 2023
f385929
Regenerate RuboCop TODO after changes
pr0d1r2 Sep 16, 2023
34f617b
Remove redundant self
pr0d1r2 Sep 16, 2023
33f97e5
Favor a normal if-statement
pr0d1r2 Sep 16, 2023
4199489
Use def with parentheses when there are parameters
pr0d1r2 Sep 16, 2023
7d8df2c
Disable cop as it generates too much visual noise
pr0d1r2 Sep 16, 2023
aef9053
Use newer hash syntax
pr0d1r2 Sep 16, 2023
589cfdb
Add missing frozen string literal comment
pr0d1r2 Sep 16, 2023
370df1f
Use explicit attr_reader
pr0d1r2 Sep 16, 2023
27a1328
Use anonymous positional arguments forwarding
pr0d1r2 Sep 16, 2023
e192d31
Do not use unsafe 'or'
pr0d1r2 Sep 16, 2023
7d765cb
Use spaces after commas to improve visual distinction
pr0d1r2 Sep 16, 2023
3b6d176
Disable cop causing visual overload in one-liners with hashes
pr0d1r2 Sep 16, 2023
ed9df4e
Trigger the only spec when implementation files change
pr0d1r2 Sep 16, 2023
060d12b
Use spaces for better visual distinction
pr0d1r2 Sep 16, 2023
3c04bef
Use spaces for better visual distinction
pr0d1r2 Sep 16, 2023
2956aee
Remove trailing whitespaces
pr0d1r2 Sep 16, 2023
e3f25c4
Disable cop that makes no sense in the current codebase
pr0d1r2 Sep 16, 2023
e807e5e
Use guard clause
pr0d1r2 Sep 16, 2023
dd16ee5
Use array literal
pr0d1r2 Sep 16, 2023
be77fe8
Group accessors
pr0d1r2 Sep 16, 2023
215ca69
Properly narrow public interface
pr0d1r2 Sep 16, 2023
f735e6b
Use anonymous block forwarding
pr0d1r2 Sep 16, 2023
d2fd986
Visually distinct unused method argument
pr0d1r2 Sep 16, 2023
e4c20c1
Encapsulate assignments in conditionals to distinct them
pr0d1r2 Sep 16, 2023
a070a50
Right hand side of multi-line assignment was on the same line as the …
pr0d1r2 Sep 16, 2023
dd4588b
Remove redundant RuboCop TODO
pr0d1r2 Sep 16, 2023
7fb0713
Regenerate RuboCop TODO after last changes
pr0d1r2 Sep 16, 2023
55205dd
Use shorter line. Improve express intent
pr0d1r2 Sep 16, 2023
798b55f
Freeze limits for LineLength and MethodLength
pr0d1r2 Sep 16, 2023
9586ef7
Freeze other limits
pr0d1r2 Sep 16, 2023
b8761db
Allow for methods on blocks in specs
pr0d1r2 Sep 16, 2023
409cb01
Explicitly make constants public or private
pr0d1r2 Sep 16, 2023
dbdd568
Do not shadow outer local variable
pr0d1r2 Sep 16, 2023
285462a
Project is small enough to not have conflicts
pr0d1r2 Sep 16, 2023
019368a
Add missing TDD trigger for gemspec
pr0d1r2 Sep 16, 2023
aba5864
Make gem require recent ruby version
pr0d1r2 Sep 16, 2023
12d870b
Add rubygems required MFA
pr0d1r2 Sep 17, 2023
a8f6eee
Move development dependencies from gemspec to 'gemfile'
pr0d1r2 Sep 17, 2023
b94856a
Remove redundant TODO
pr0d1r2 Sep 17, 2023
0ebda46
Regenerate RuboCop TODO after recent changes
pr0d1r2 Sep 17, 2023
087bb72
Freeze gem versions in 'gemfile'
pr0d1r2 Sep 17, 2023
2f4d374
Use gem grouping that better reflect reality
pr0d1r2 Sep 17, 2023
8840146
Actually match dots in regexp
pr0d1r2 Sep 17, 2023
95850e0
Exclude specs from block limiting
pr0d1r2 Sep 17, 2023
13eaa7a
Allow rspec focus
pr0d1r2 Sep 17, 2023
d616aa9
Allow referencing private constants using strings
pr0d1r2 Sep 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
inherit_from: .rubocop_todo.yml

AllCops:
NewCops: enable
SuggestExtensions: true
EnabledByDefault: true
TargetRubyVersion: 3.2.2

Style/Copyright:
Enabled: false

Style/MethodCallWithArgsParentheses:
Enabled: false

Layout/RedundantLineBreak:
Enabled: false

Style/MissingElse:
Enabled: false

Lint/ConstantResolution:
Enabled: false

Layout/LineLength:
Max: 129

Metrics/MethodLength:
Max: 29

Metrics/AbcSize:
Max: 37

Metrics/BlockLength:
Max: 12
Exclude:
- 'spec/**/*_spec.rb'

Metrics/CyclomaticComplexity:
Max: 9

Metrics/PerceivedComplexity:
Max: 10

Style/MethodCalledOnDoEndBlock:
Exclude:
- 'spec/**/*_spec.rb'

Style/ClassEqualityComparison:
Exclude:
- 'lib/rspec/memory/trace.rb'

Style/StringHashKeys:
Exclude:
- 'spec/rspec/memory_spec.rb'
52 changes: 52 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-09-17 04:59:27 UTC using RuboCop version 1.56.3.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 7
# Configuration parameters: Include, IgnoredGems, OnlyFor.
# Include: **/*.gemfile, **/Gemfile, **/gems.rb
Bundler/GemComment:
Exclude:
- 'gems.rb'

# Offense count: 1
# Configuration parameters: EnforcedStyle, Include.
# SupportedStyles: Gemfile, gems.rb
# Include: **/Gemfile, **/gems.rb, **/Gemfile.lock, **/gems.locked
Bundler/GemFilename:
Exclude:
- 'gems.rb'

# Offense count: 1
# Configuration parameters: AllowedMethods.
# AllowedMethods: enums
Lint/ConstantDefinitionInBlock:
Exclude:
- 'lib/rspec/memory/trace.rb'

# Offense count: 2
Lint/StructNewOverride:
Exclude:
- 'lib/rspec/memory/trace.rb'

# Offense count: 3
# Configuration parameters: AllowedConstants.
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'lib/rspec/memory/matchers/limit_allocations.rb'
- 'lib/rspec/memory/trace.rb'

# Offense count: 13
# Configuration parameters: RequireForNonPublicMethods.
Style/DocumentationMethod:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'lib/rspec/memory/matchers/limit_allocations.rb'
- 'lib/rspec/memory/trace.rb'
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby-3.2.2
43 changes: 43 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

RSPEC_CONFIG = {
cmd: 'bundle exec rspec',
all_on_start: true,
all_after_pass: true,
halt_on_fail: true,
results_file: '/tmp/.guard_rspec_results-rspec-memory'
}.freeze

guard :rspec, RSPEC_CONFIG do
require 'guard/rspec/dsl'
dsl = Guard::RSpec::Dsl.new(self)

# Feel free to open issues for suggestions and improvements

# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)

# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)

%w[matchers/limit_allocations.rb trace.rb].each do |file_path|
watch("lib/rspec/memory/#{file_path}") { 'spec/rspec/memory_spec.rb' }
end
end

RUBOCOP_CONFIG = {
cli: '--display-cop-names --parallel',
all_on_start: false,
halt_on_fail: true
}.freeze

guard :rubocop, RUBOCOP_CONFIG do
watch(/.+\.rb$/)
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
watch('Guardfile')
watch('rspec-memory.gemspec')
end
16 changes: 12 additions & 4 deletions gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@

source 'https://rubygems.org'

ruby '3.2.2'

# Specify your gem's dependencies in rspec-memory.gemspec
gemspec

group :maintenance, optional: true do
gem "bake-gem"
gem "bake-modernize"
gem 'bake-gem', '~> 0.4.0'
gem 'bake-modernize', '~> 0.17.8'
end

group :development, :test do
gem 'covered', '~> 0.25.0', require: false
gem 'guard-rspec', '~> 4.7'
gem 'guard-rubocop', '~> 1.5'
end

group :test do
gem "bake-test"
gem "bake-test-external"
gem 'bake-test', '~> 0.2.0'
gem 'bake-test-external', '~> 0.3.3'
end
2 changes: 1 addition & 1 deletion lib/rspec/memory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
require_relative 'memory/matchers/limit_allocations'

RSpec.shared_context RSpec::Memory do
include RSpec::Memory::Matchers
include RSpec::Memory::Matchers
end
186 changes: 94 additions & 92 deletions lib/rspec/memory/matchers/limit_allocations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,96 +8,98 @@
require 'rspec/expectations'

module RSpec
module Memory
module Matchers
class LimitAllocations
include RSpec::Matchers::Composable

def initialize(allocations = {}, count: nil, size: nil)
@count = count
@size = size

@allocations = {}
@errors = []

allocations.each do |klass, count|
self.of(klass, count: count)
end
end

def supports_block_expectations?
true
end

def of(klass, **limits)
@allocations[klass] = limits

return self
end

private def check(value, limit)
case limit
when Range
unless limit.include? value
yield "expected within #{limit}"
end
when Integer
unless value == limit
yield "expected exactly #{limit}"
end
end
end

def matches?(given_proc)
return true unless trace = Trace.capture(@allocations.keys, &given_proc)

if @count or @size
# If the spec specifies a total limit, we have a limit which we can enforce which takes all allocations into account:
total = trace.total

check(total.count, @count) do |expected|
@errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances"
end if @count

check(total.size, @size) do |expected|
@errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes"
end if @size
else
# Otherwise unspecified allocations are considered an error:
trace.ignored.each do |klass, allocation|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, but it was not specified"
end
end

trace.allocated.each do |klass, allocation|
next unless acceptable = @allocations[klass]

check(allocation.count, acceptable[:count]) do |expected|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
end

check(allocation.size, acceptable[:size]) do |expected|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} bytes"
end
end

return @errors.empty?
end

def failure_message
"exceeded allocation limit: #{@errors.join(', ')}"
end
end

if respond_to?(:ruby2_keywords, true)
def limit_allocations(count: nil, size: nil, **allocations)
LimitAllocations.new(allocations, count: count, size: size)
end
else
def limit_allocations(*arguments)
LimitAllocations.new(*arguments)
end
end
end
end
module Memory
module Matchers
class LimitAllocations
include RSpec::Matchers::Composable

def initialize(allocations = {}, count: nil, size: nil)
@count = count
@size = size

@allocations = {}
@errors = []

allocations.each do |klass, cnt|
of(klass, count: cnt)
end
end

def supports_block_expectations?
true
end

def of(klass, **limits)
@allocations[klass] = limits

self
end

def matches?(given_proc)
return true unless (trace = Trace.capture(@allocations.keys, &given_proc))

if @count || @size
# If the spec specifies a total limit, we have a limit which we can enforce which takes all allocations into account:
total = trace.total

if @count
check(total.count, @count) do |expected|
@errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} instances"
end
end

if @size
check(total.size, @size) do |expected|
@errors << "allocated #{total.count} instances, #{total.size} bytes, #{expected} bytes"
end
end
else
# Otherwise unspecified allocations are considered an error:
trace.ignored.each do |klass, allocation|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, but it was not specified"
end
end

trace.allocated.each do |klass, allocation|
next unless (acceptable = @allocations[klass])

check(allocation.count, acceptable[:count]) do |expected|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} instances"
end

check(allocation.size, acceptable[:size]) do |expected|
@errors << "allocated #{allocation.count} #{klass} instances, #{allocation.size} bytes, #{expected} bytes"
end
end

@errors.empty?
end

private

def check(value, limit)
case limit
when Range
yield "expected within #{limit}" unless limit.include? value
when Integer
yield "expected exactly #{limit}" unless value == limit
end
end

def failure_message
"exceeded allocation limit: #{@errors.join(', ')}"
end
end

if respond_to?(:ruby2_keywords, true)
def limit_allocations(count: nil, size: nil, **allocations)
LimitAllocations.new(allocations, count:, size:)
end
else
def limit_allocations(*)
LimitAllocations.new(*)
end
end
end
end
end
Loading