Add Rubocop and run checks in CI

This should finally allow us to adopt code style lints, as well
as others such as performance linting, and have them enforced by
the Github Actions continuous integration (CI) jobs.

For now, I'm choosing to depend on the 'panolint' gem from
Panorama Education, which is my current workplace, as I'd like
to adopt and stay consistent with the setting used there.

Changes
  * Move development and test dependencies from gemspec to Gemfile
  * Add dependency on 'panolint' gem
  * Add .rubocop.yml
  * Fix all existing issues
  * Run rubocop in github actions CI
This commit is contained in:
Marc Siegel 2021-05-10 12:26:41 -04:00
parent cb95b8d85d
commit 25114d0c1d
13 changed files with 343 additions and 256 deletions

View File

@ -27,3 +27,5 @@ jobs:
with:
name: ${{ matrix.ruby }}
file: ./coverage/coverage.xml
- run: bundle exec rubocop
if: matrix.ruby == '3.0'

2
.rubocop.yml Normal file
View File

@ -0,0 +1,2 @@
inherit_gem:
panolint: rubocop.yml

19
Gemfile
View File

@ -1,9 +1,26 @@
# frozen_string_literal: true
source "https://rubygems.org"
# CI-only dependencies go here
if ENV["CI"] == "true"
if ENV["CI"] == "true" # rubocop:disable Style/IfUnlessModifier
gem "simplecov-cobertura", require: false, group: "test"
end
# Specify gem's dependencies in docile.gemspec
gemspec
group :test do
gem "rspec", "~> 3.10"
gem "simplecov", require: false
end
# Excluded from CI except on latest MRI Ruby, to reduce compatibility burden
group :checks do
gem "panolint", github: "panorama-ed/panolint", branch: "main"
end
# Optional, only used locally to release to rubygems.org
group :release, optional: true do
gem "rake"
end

View File

@ -1,9 +1,11 @@
# frozen_string_literal: true
require "rake/clean"
require "bundler/gem_tasks"
require "rspec/core/rake_task"
# Default task for `rake` is to run rspec
task :default => [:spec]
task default: [:spec]
# Use default rspec rake task
RSpec::Core::RakeTask.new

View File

@ -1,5 +1,6 @@
$:.push File.expand_path("../lib", __FILE__)
require "docile/version"
# frozen_string_literal: true
require_relative "lib/docile/version"
Gem::Specification.new do |s|
s.name = "docile"
@ -17,15 +18,12 @@ Gem::Specification.new do |s|
"semver.org."
s.license = "MIT"
# Files included in the gem
s.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
s.require_paths = ["lib"]
# Specify oldest supported Ruby version (2.5 to support JRuby 9.2.17.0)
s.required_ruby_version = ">= 2.5.0"
s.add_development_dependency "rake", "~> 12.3.3"
s.add_development_dependency "rspec", "~> 3.9"
# Files included in the gem
s.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
s.require_paths = ["lib"]
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require "docile/version"
require "docile/execution"
require "docile/fallback_context_proxy"
@ -86,7 +88,9 @@ module Docile
exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
end
ruby2_keywords :dsl_eval_with_block_return if respond_to?(:ruby2_keywords, true)
if respond_to?(:ruby2_keywords, true)
ruby2_keywords :dsl_eval_with_block_return
end
module_function :dsl_eval_with_block_return
# Execute a block in the context of an immutable object whose methods,

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Docile
# @api private
#
@ -7,15 +9,15 @@ module Docile
# If {NoMethodError} is caught then the exception object will be extended
# by this module to add filter functionalities.
module BacktraceFilter
FILTER_PATTERN = /lib\/docile/
FILTER_PATTERN = %r{/lib/docile/}.freeze
def backtrace
super.select { |trace| trace !~ FILTER_PATTERN }
super.reject { |trace| trace =~ FILTER_PATTERN }
end
if ::Exception.public_method_defined?(:backtrace_locations)
def backtrace_locations
super.select { |location| location.absolute_path !~ FILTER_PATTERN }
super.reject { |location| location.absolute_path =~ FILTER_PATTERN }
end
end
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require "docile/fallback_context_proxy"
module Docile
@ -10,13 +12,16 @@ module Docile
# objects.
#
# @see Docile.dsl_eval_immutable
#
# rubocop:disable Style/MissingRespondToMissing
class ChainingFallbackContextProxy < FallbackContextProxy
# Proxy methods as in {FallbackContextProxy#method_missing}, replacing
# `receiver` with the returned value.
def method_missing(method, *args, &block)
@__receiver__ = super(method, *args, &block)
end
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
end
# rubocop:enable Style/MissingRespondToMissing
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Docile
# @api private
#
@ -15,7 +17,7 @@ module Docile
# @param block [Proc] the block of DSL commands to be executed
# @return [Object] the return value of the block
def exec_in_proxy_context(dsl, proxy_type, *args, &block)
block_context = eval("self", block.binding)
block_context = eval("self", block.binding) # rubocop:disable Style/EvalWithLocation
# Use #equal? to test strict object identity (assuming that this dictum
# from the Ruby docs holds: "[u]nlike ==, the equal? method should never
@ -38,6 +40,7 @@ module Docile
block_context.instance_variables.each do |ivar|
next unless proxy_context.instance_variables.include?(ivar)
value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
block_context.instance_variable_set(ivar, value_from_dsl_proxy)
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
require "set"
module Docile
@ -13,11 +15,13 @@ module Docile
# This is useful for implementing DSL evaluation in the context of an object.
#
# @see Docile.dsl_eval
#
# rubocop:disable Style/MissingRespondToMissing
class FallbackContextProxy
# The set of methods which will **not** be proxied, but instead answered
# by this object directly.
NON_PROXIED_METHODS = Set[:__send__, :object_id, :__id__, :==, :equal?,
:"!", :"!=", :instance_exec, :instance_variables,
:!, :!=, :instance_exec, :instance_variables,
:instance_variable_get, :instance_variable_set,
:remove_instance_variable]
@ -54,14 +58,18 @@ module Docile
singleton_class.
send(:define_method, :method_missing) do |method, *args, &block|
m = method.to_sym
if !NON_FALLBACK_METHODS.include?(m) && !fallback.respond_to?(m) && receiver.respond_to?(m)
if !NON_FALLBACK_METHODS.member?(m) &&
!fallback.respond_to?(m) &&
receiver.respond_to?(m)
receiver.__send__(method.to_sym, *args, &block)
else
super(method, *args, &block)
end
end
singleton_class.send(:ruby2_keywords, :method_missing) if singleton_class.respond_to?(:ruby2_keywords, true)
if singleton_class.respond_to?(:ruby2_keywords, true)
singleton_class.send(:ruby2_keywords, :method_missing)
end
# instrument a helper method to remove the above instrumentation
singleton_class.
@ -74,12 +82,8 @@ module Docile
# @return [Array<Symbol>] Instance variable names, excluding
# {NON_PROXIED_INSTANCE_VARIABLES}
#
# @note on Ruby 1.8.x, the instance variable names are actually of
# type `String`.
def instance_variables
# Ruby 1.8.x returns string names, convert to symbols for compatibility
super.select { |v| !NON_PROXIED_INSTANCE_VARIABLES.include?(v.to_sym) }
super.reject { |v| NON_PROXIED_INSTANCE_VARIABLES.include?(v) }
end
# Proxy all methods, excluding {NON_PROXIED_METHODS}, first to `receiver`
@ -99,4 +103,5 @@ module Docile
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
end
# rubocop:enable Style/MissingRespondToMissing
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Docile
# The current version of this library
VERSION = "1.3.5"

View File

@ -1,9 +1,64 @@
# frozen_string_literal: true
require "spec_helper"
require "singleton"
# TODO: Factor single spec file into multiple specs
# rubocop:disable RSpec/MultipleDescribes
describe Docile do
describe ".dsl_eval" do
before :each do
stub_const("Pizza",
Struct.new(:cheese, :pepperoni, :bacon, :sauce))
stub_const("PizzaBuilder", Class.new do
# rubocop:disable all
def cheese(v=true); @cheese = v; end
def pepperoni(v=true); @pepperoni = v; end
def bacon(v=true); @bacon = v; end
def sauce(v=nil); @sauce = v; end
# rubocop:enable all
def build
Pizza.new(!!@cheese, !!@pepperoni, !!@bacon, @sauce)
end
end)
stub_const("InnerDSL", Class.new do
def initialize
@b = "b"
end
attr_accessor :b
def c
"inner c"
end
end)
stub_const("OuterDSL", Class.new do
def initialize
@a = "a"
end
attr_accessor :a
def c
"outer c"
end
def inner(&block)
Docile.dsl_eval(InnerDSL.new, &block)
end
def inner_with_params(param, &block)
Docile.dsl_eval(InnerDSL.new, param, :foo, &block)
end
end)
end
def outer(&block)
Docile.dsl_eval(OuterDSL.new, &block)
end
context "when DSL context object is an Array" do
let(:array) { [] }
@ -27,23 +82,15 @@ describe Docile do
end
it "doesn't proxy #__id__" do
Docile.dsl_eval(array) { expect(__id__).not_to eq(array.__id__) }
described_class.dsl_eval(array) do
expect(__id__).not_to eq(array.__id__)
end
end
it "raises NoMethodError if the DSL object doesn't implement the method" do
expect { Docile.dsl_eval(array) { no_such_method } }.to raise_error(NoMethodError)
end
end
Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
class PizzaBuilder
def cheese(v=true); @cheese = v; end
def pepperoni(v=true); @pepperoni = v; end
def bacon(v=true); @bacon = v; end
def sauce(v=nil); @sauce = v; end
def build
Pizza.new(!!@cheese, !!@pepperoni, !!@bacon, @sauce)
it "raises NoMethodError if DSL object doesn't implement the method" do
expect do
described_class.dsl_eval(array) { no_such_method }
end.to raise_error(NoMethodError)
end
end
@ -56,7 +103,7 @@ describe Docile do
Docile.dsl_eval(builder) do
bacon
cheese
sauce @sauce
sauce @sauce # rubocop:disable RSpec/InstanceVariable
end.build
end
@ -65,38 +112,13 @@ describe Docile do
end
end
class InnerDSL
def initialize; @b = "b"; end
attr_accessor :b
def c; "inner c"; end
end
class OuterDSL
def initialize; @a = "a"; end
attr_accessor :a
def c; "outer c"; end
def inner(&block)
Docile.dsl_eval(InnerDSL.new, &block)
end
def inner_with_params(param, &block)
Docile.dsl_eval(InnerDSL.new, param, :foo, &block)
end
end
def outer(&block)
Docile.dsl_eval(OuterDSL.new, &block)
end
context "when given parameters for the DSL block" do
def parameterized(*args, &block)
Docile.dsl_eval(OuterDSL.new, *args, &block)
end
it "passes parameters to the block" do
parameterized(1,2,3) do |x,y,z|
parameterized(1, 2, 3) do |x, y, z|
expect(x).to eq(1)
expect(y).to eq(2)
expect(z).to eq(3)
@ -108,8 +130,8 @@ describe Docile do
end
it "find outer dsl parameters in inner dsl scope" do
parameterized(1,2,3) do |a,b,c|
inner_with_params(c) do |d,e|
parameterized(1, 2, 3) do |a, b, c|
inner_with_params(c) do |d, e|
expect(a).to eq(1)
expect(b).to eq(2)
expect(c).to eq(3)
@ -121,41 +143,46 @@ describe Docile do
end
context "when block's context has helper methods which call DSL methods" do
class BlockContextWithHelperMethods
def initialize(array_as_dsl)
@array_as_dsl = array_as_dsl
end
# Classic dynamic programming factorial, using the methods of {Array}
# as a DSL to implement it, via helper methods {#calculate_factorials}
# and {#save_factorials} which are defined in this class, so therefore
# outside the block.
def factorial_as_dsl_against_array(n)
Docile.dsl_eval(@array_as_dsl) { calculate_factorials(n) }.last
end
# Uses the helper method {#save_factorials} below.
def calculate_factorials(n)
(2..n).each { |i| save_factorial(i) }
end
# Uses the methods {Array#push} and {Array#at} as a DSL from a helper
# method defined in the block's context. Successfully calling this
# proves that we can find helper methods from outside the block, and
# then find DSL methods from inside those helper methods.
def save_factorial(i)
push(i * at(i - 1))
end
end
subject { context.method(:factorial_as_dsl_against_array) }
let(:context) { BlockContextWithHelperMethods.new(array_as_dsl) }
let(:context) do
class_block_context_with_helper_methods.new(array_as_dsl)
end
let(:array_as_dsl) { [1, 1] }
let(:class_block_context_with_helper_methods) do
Class.new do
def initialize(array_as_dsl)
@array_as_dsl = array_as_dsl
end
# Classic dynamic programming factorial, using the methods of {Array}
# as a DSL to implement it, via helper methods {#calculate_factorials}
# and {#save_factorials} which are defined in this class, so therefore
# outside the block.
def factorial_as_dsl_against_array(num)
Docile.dsl_eval(@array_as_dsl) { calculate_factorials(num) }.last
end
# Uses the helper method {#save_factorials} below.
def calculate_factorials(num)
(2..num).each { |i| save_factorial(i) }
end
# Uses the methods {Array#push} and {Array#at} as a DSL from a helper
# method defined in the block's context. Successfully calling this
# proves that we can find helper methods from outside the block, and
# then find DSL methods from inside those helper methods.
def save_factorial(num)
push(num * at(num - 1))
end
end
end
it "finds DSL methods within helper method defined in block's context" do
# see https://en.wikipedia.org/wiki/Factorial
# rubocop:disable Layout/ExtraSpacing
[
[0, 1],
[1, 1],
@ -177,6 +204,7 @@ describe Docile do
array_as_dsl.replace([1, 1])
expect(subject.call(n)).to eq expected_factorial
end
# rubocop:enable Layout/ExtraSpacing
end
it "removes fallback instrumentation from the DSL object after block" do
@ -200,7 +228,7 @@ describe Docile do
end
it "removes fallback instrumentation from the DSL object after block" do
expect { subject.call(5) rescue nil }.
expect { subject.call(5) rescue nil }. # rubocop:disable Style/RescueModifier
not_to change { context.respond_to?(:method_missing) }.
from(false)
end
@ -208,28 +236,31 @@ describe Docile do
end
context "when DSL have NoMethod error inside" do
class DSLWithNoMethod
def initialize(b); @b = b; end
attr_accessor :b
def push_element
@b.push 1
let(:class_dsl_with_no_method) do
Class.new do
def push_element
nil.push(1)
end
end
end
it "raise NoMethodError error from nil" do
Docile.dsl_eval(DSLWithNoMethod.new(nil)) do
described_class.dsl_eval(class_dsl_with_no_method.new) do
expect { push_element }.
to raise_error(NoMethodError, /undefined method `push' (for|on) nil:NilClass/)
to raise_error(
NoMethodError,
/undefined method `push' (for|on) nil:NilClass/
)
end
end
end
context "when DSL blocks are nested" do
context "method lookup" do
describe "method lookup" do
# rubocop:disable Style/SingleLineMethods
it "finds method of outer dsl in outer dsl scope" do
outer { expect(a).to eq("a") }
outer { inner {}; expect(c).to eq("outer c") }
outer { inner {}; expect(c).to eq("outer c") } # rubocop:disable Style/Semicolon
end
it "finds method of inner dsl in inner dsl scope" do
@ -256,9 +287,10 @@ describe Docile do
outer { expect(a).to eq("a") }
outer { inner { expect(a).to eq("a") } }
end
# rubocop:enable Style/SingleLineMethods
end
context "local variable lookup" do
describe "local variable lookup" do
it "finds local variable from block context in outer dsl scope" do
foo = "foo"
outer { expect(foo).to eq("foo") }
@ -270,25 +302,35 @@ describe Docile do
end
end
context "instance variable lookup" do
describe "instance variable lookup" do
# rubocop:disable RSpec/InstanceVariable
it "finds instance variable from block definition in outer dsl scope" do
@iv1 = "iv1"; outer { expect(@iv1).to eq("iv1") }
@iv1 = "iv1"
outer { expect(@iv1).to eq("iv1") }
end
it "proxies instance variable assignments in block in outer dsl scope back into block's context" do
@iv1 = "foo"; outer { @iv1 = "bar" }; expect(@iv1).to eq("bar")
it "proxies instance variable assignments in block in outer dsl scope "\
"back into block's context" do
@iv1 = "foo"
outer { @iv1 = "bar" }
expect(@iv1).to eq("bar")
end
it "finds instance variable from block definition in inner dsl scope" do
@iv2 = "iv2"; outer { inner { expect(@iv2).to eq("iv2") } }
@iv2 = "iv2"
outer { inner { expect(@iv2).to eq("iv2") } }
end
it "proxies instance variable assignments in block in inner dsl scope back into block's context" do
@iv2 = "foo"; outer { inner { @iv2 = "bar" } }; expect(@iv2).to eq("bar")
it "proxies instance variable assignments in block in inner dsl scope "\
"back into block's context" do
@iv2 = "foo"
outer { inner { @iv2 = "bar" } }
expect(@iv2).to eq("bar")
end
# rubocop:enable RSpec/InstanceVariable
end
context "identity of 'self' inside nested dsl blocks" do
describe "identity of 'self' inside nested dsl blocks" do
# see https://github.com/ms-ati/docile/issues/31
subject do
identified_selves = {}
@ -322,36 +364,44 @@ describe Docile do
end
context "when DSL context object is a Dispatch pattern" do
class DispatchScope
def params
{ :a => 1, :b => 2, :c => 3 }
let(:class_message_dispatcher) do
Class.new do
def initialize
@responders = {}
end
def add_responder(path, &block)
@responders[path] = block
end
def dispatch(path, request)
Docile.
dsl_eval(class_dispatch_scope.new, request, &@responders[path])
end
def class_dispatch_scope
Class.new do
def params
{ a: 1, b: 2, c: 3 }
end
end
end
end
end
class MessageDispatch
include Singleton
def initialize
@responders = {}
end
def add_responder path, &block
@responders[path] = block
end
def dispatch path, request
Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
end
let(:message_dispatcher_instance) do
class_message_dispatcher.new
end
def respond(path, &block)
MessageDispatch.instance.add_responder(path, &block)
message_dispatcher_instance.add_responder(path, &block)
end
def send_request(path, request)
MessageDispatch.instance.dispatch(path, request)
message_dispatcher_instance.dispatch(path, request)
end
# rubocop:disable RSpec/InstanceVariable
it "dispatches correctly" do
@first = @second = nil
@ -363,12 +413,16 @@ describe Docile do
@second = "Got a new #{bike}"
end
def x(y) ; "Got a #{y}"; end
respond "/third" do |third|
expect(x(third)).to eq("Got a third thing")
def third(val)
"Got a #{val}"
end
respond "/third" do |arg|
expect(third(arg)).to eq("Got a third thing")
end
fourth = nil
respond "/params" do |arg|
fourth = params[arg]
end
@ -382,133 +436,122 @@ describe Docile do
expect(@second).to eq("Got a new ten speed")
expect(fourth).to eq(2)
end
# rubocop:enable RSpec/InstanceVariable
end
context "when DSL context object is the same as the block's context object" do
class DSLContextSameAsBlockContext
def foo(v = nil)
@foo = v if v
@foo
end
context "when DSL context object is same as the block's context object" do
let(:class_context_same_as_block_context) do
Class.new do
def foo(val = nil)
@foo = val if val
@foo
end
def bar(v = nil)
@bar = v if v
@bar
end
def bar(val = nil)
@bar = val if val
@bar
end
def dsl_eval(block)
Docile.dsl_eval(self, &block)
end
def dsl_eval(block)
Docile.dsl_eval(self, &block)
end
def dsl_eval_string(string)
block = binding.eval("proc { #{string} }")
dsl_eval(block)
def dsl_eval_string(string)
block = binding.eval("proc { #{string} }") # rubocop:disable all
dsl_eval(block)
end
end
end
let(:dsl) { DSLContextSameAsBlockContext.new }
let(:dsl) { class_context_same_as_block_context.new }
it "calls DSL methods and sets instance variables on the DSL context object" do
dsl.dsl_eval_string('foo 0; bar 1')
it "calls DSL methods and sets state on the DSL context object" do
dsl.dsl_eval_string("foo 0; bar 1")
expect(dsl.foo).to eq(0)
expect(dsl.bar).to eq(1)
end
context "when the DSL object is frozen" do
it "can call non-mutative code without raising an exception" do
expect { dsl.freeze.dsl_eval_string('1 + 2') }.not_to raise_error
expect { dsl.freeze.dsl_eval_string("1 + 2") }.not_to raise_error
end
end
end
context "when NoMethodError is raised" do
specify "#backtrace does not include path to Docile's source file" do
begin
Docile.dsl_eval(Object.new) { foo }
rescue NoMethodError => e
expect(e.backtrace).not_to include(match(/lib\/docile/))
end
specify "#backtrace doesn't include path to Docile's sources" do
described_class.dsl_eval(Object.new) { foo }
rescue NoMethodError => e
expect(e.backtrace).not_to include(match(%r{/lib/docile/}))
end
if ::Exception.public_method_defined?(:backtrace_locations)
specify "#backtrace_locations also does not include path to Docile's source file" do
begin
Docile.dsl_eval(Object.new) { foo }
rescue NoMethodError => e
expect(e.backtrace_locations.map(&:absolute_path)).not_to include(match(/lib\/docile/))
specify "#backtrace_locations doesn't include path to Docile's sources" do
described_class.dsl_eval(Object.new) { foo }
rescue NoMethodError => e
expect(e.backtrace_locations.map(&:absolute_path)).
not_to include(match(%r{/lib/docile/}))
end
end
context "when a DSL method has a keyword argument" do
let(:class_with_method_with_keyword_arg) do
Class.new do
attr_reader :v0, :v1, :v2
def set(arg, kw1:, kw2:)
@v0 = arg
@v1 = kw1
@v2 = kw2
end
end
end
end
if RUBY_VERSION >= "2.0.0"
context "when a DSL method has a keyword argument" do
class DSLMethodWithKeywordArgument
attr_reader :v0, :v1, :v2
class_eval(<<-METHOD)
def set(v0, v1:, v2:)
@v0 = v0
@v1 = v1
@v2 = v2
end
METHOD
end
let(:dsl) { class_with_method_with_keyword_arg.new }
let(:dsl) do
DSLMethodWithKeywordArgument.new
end
it "calls such DSL methods with no stderr output" do
# This is to check warnings related to keyword argument is not output.
# See: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
expect do
described_class.dsl_eval(dsl) { set(0, kw2: 2, kw1: 1) }
end.
not_to output.to_stderr
it "calls such DSL methods with no stderr output" do
# This is to check warnings related to keyword argument is not output.
# See: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
expect { Docile.dsl_eval(dsl) { set(0, v2: 2, v1: 1) } }.
not_to output.to_stderr
expect(dsl.v0).to eq 0
expect(dsl.v1).to eq 1
expect(dsl.v2).to eq 2
end
expect(dsl.v0).to eq 0
expect(dsl.v1).to eq 1
expect(dsl.v2).to eq 2
end
end
if RUBY_VERSION >= "2.0.0"
context "when a DSL method has a double splat" do
class DSLMethodWithDoubleSplat
context "when a DSL method has a double splat" do
let(:class_with_method_with_double_splat) do
Class.new do
attr_reader :arguments, :options
# Use class_eval because Ruby 1.x does not support double splat
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def configure(*arguments, **options)
@arguments = arguments.dup
@options = options.dup
end
METHOD
def configure(*arguments, **options)
@arguments = arguments.dup
@options = options.dup
end
end
end
let(:dsl) { DSLMethodWithDoubleSplat.new }
let(:dsl) { class_with_method_with_double_splat.new }
it "correctly passes keyword arguments" do
Docile.dsl_eval(dsl) { configure(1, a: 1) }
it "correctly passes keyword arguments" do
described_class.dsl_eval(dsl) { configure(1, a: 1) }
expect(dsl.arguments).to eq [1]
expect(dsl.options).to eq({ a: 1 })
end
expect(dsl.arguments).to eq [1]
expect(dsl.options).to eq({ a: 1 })
end
it "correctly passes hash arguments" do
described_class.dsl_eval(dsl) { configure(1, { a: 1 }) }
if RUBY_VERSION >= "3.0.0"
it "correctly passes hash arguments on Ruby 3+" do
Docile.dsl_eval(dsl) { configure(1, { a: 1 }) }
expect(dsl.arguments).to eq [1, { a: 1 }]
expect(dsl.options).to eq({})
end
elsif RUBY_VERSION >= "2.0.0"
it "correctly passes hash arguments on Ruby 2" do
Docile.dsl_eval(dsl) { configure(1, { a: 1 }) }
expect(dsl.arguments).to eq [1]
expect(dsl.options).to eq({ a: 1 })
end
expect(dsl.arguments).to eq [1, { a: 1 }]
expect(dsl.options).to eq({})
else
expect(dsl.arguments).to eq [1]
expect(dsl.options).to eq({ a: 1 })
end
end
end
@ -538,9 +581,8 @@ describe Docile do
end
describe ".dsl_eval_immutable" do
context "when DSL context object is a frozen String" do
let(:original) { "I'm immutable!".freeze }
let(:original) { "I'm immutable!".freeze } # rubocop:disable Style/RedundantFreeze
let!(:result) { execute_non_mutating_dsl_against_string }
def execute_non_mutating_dsl_against_string
@ -575,16 +617,17 @@ describe Docile do
end
end
end
end
describe Docile::FallbackContextProxy do
describe "#instance_variables" do
subject { create_fcp_and_set_one_instance_variable.instance_variables }
let(:expected_type_of_names) { type_of_ivar_names_on_this_ruby }
let(:actual_type_of_names) { subject.first.class }
let(:excluded) { Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES }
let(:excluded) do
Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES
end
def create_fcp_and_set_one_instance_variable
fcp = Docile::FallbackContextProxy.new(nil, nil)
@ -609,5 +652,4 @@ describe Docile::FallbackContextProxy do
expect(actual_type_of_names).to eq(expected_type_of_names)
end
end
end

View File

@ -1,27 +1,30 @@
# frozen_string_literal: true
# Code coverage (via SimpleCov)
begin
require "simplecov"
SimpleCov.start do
add_filter "/spec/" # exclude test code
add_filter "/vendor/" # exclude gems which are cached in CI
end
require "simplecov"
# On CI we publish coverage to codecov.io
# To use codecov-action, we need to generate XML based covarage report
if ENV["CI"] == "true"
require "simplecov-cobertura"
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
end
# Due to circular dependency (simplecov depends on docile), remove docile and require again below
Object.send(:remove_const, :Docile)
$LOADED_FEATURES.reject! { |f| f =~ /\/lib\/docile/ }
rescue LoadError
warn "warning: simplecov or codecov gems not found; skipping coverage"
SimpleCov.start do
add_filter "/spec/" # exclude test code
add_filter "/vendor/" # exclude gems which are cached in CI
end
lib_dir = File.join(File.dirname(File.dirname(__FILE__)), "lib")
$LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include? lib_dir
# On CI we publish coverage to codecov.io
# To use the codecov action, we need to generate XML based coverage report
if ENV["CI"] == "true"
begin
require "simplecov-cobertura"
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
rescue LoadError
warn "simplecov-cobertura gem not found - not generating XML for codecov.io"
end
end
# Due to circular dependency (SimpleCov depends on Docile), remove docile and
# then require the docile gem again below.
Object.send(:remove_const, :Docile)
$LOADED_FEATURES.reject! { |f| f.include?("/lib/docile") }
# Require Docile again, now with coverage enabled
lib_dir = File.join(File.dirname(File.dirname(__FILE__)), "lib")
$LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include? lib_dir
require "docile"