pdbethke/hashie, scoped to lib, at e768c780e3: the project's own tests caught 327 of 380 planted faults, 53 got through, and corral proved 52 of those missing tests by writing a test and running it.
require 'spec_helper'
require 'tempfile'
describe 'Hashie::Mash.load does not evaluate ERB (including on quiet subclasses)' do
def write_erb_yaml
file = Tempfile.new(['hashie-corral', '.yml'], Dir.pwd)
file.write(%(value: "<%= 6 * 7 %>"\n))
file.close
file
end
it 'keeps ERB literal when loading through Hashie::Mash' do
file = write_erb_yaml
begin
loaded = Hashie::Mash.load(file.path)
expect(loaded.value).to eq '<%= 6 * 7 %>'
ensure
file.close!
end
end
it 'keeps ERB literal when loading through a quiet subclass' do
file = write_erb_yaml
begin
loaded = Hashie::Mash.quiet.load(file.path)
expect(loaded.value).to eq '<%= 6 * 7 %>'
ensure
file.close!
end
end
it 'keeps ERB literal when loading through a quiet subclass with specific keys' do
file = write_erb_yaml
begin
loaded = Hashie::Mash.quiet(:zip).load(file.path)
expect(loaded.value).to eq '<%= 6 * 7 %>'
ensure
file.close!
end
end
end
describe Hashie::Mash do
describe '#to_module' do
it 'defines only the named accessor and no load method' do
mod = described_class.new(name: 'Ada').to_module(:configuration)
expect(mod.instance_methods(false)).to eq [:configuration]
expect(mod.public_method_defined?(:load)).to be false
end
it 'does not give including classes a public load that evaluates ERB' do
mod = described_class.new.to_module
klass = Class.new { include mod }
instance = klass.new
expect(instance).not_to respond_to(:load)
expect(instance.settings).to be_a(described_class)
end
it 'does not evaluate ERB through the module and keeps Mash.load safe' do
require 'tempfile'
$hashie_corral_erb_marker = nil
file = Tempfile.new(['hashie-corral', '.yml'])
begin
file.write("value: <%= $hashie_corral_erb_marker = :evaluated %>\n")
file.close
mod = described_class.new.to_module
klass = Class.new { include mod }
begin
klass.new.load(file.path)
rescue NoMethodError, ArgumentError, LoadError, StandardError
nil
end
expect($hashie_corral_erb_marker).to be_nil
ensure
file.close!
$hashie_corral_erb_marker = nil
end
end
end
end
describe 'Hashie::Mash assign_property does not evaluate ERB (corral)' do
it 'stores ERB-looking strings literally via assign_property' do
mash = Hashie::Mash.new
mash.assign_property(:name, '<%= 1 + 1 %>')
expect(mash.name).to eq '<%= 1 + 1 %>'
expect(mash['name']).not_to eq '2'
end
it 'does not execute embedded code when assigning through a dynamic setter' do
$hashie_corral_executed = false
mash = Hashie::Mash.new
payload = '<% $hashie_corral_executed = true %>safe'
mash.payload = payload
mash.assign_property(:other, payload)
expect($hashie_corral_executed).to be false
expect(mash.payload).to eq payload
expect(mash.other).to eq payload
ensure
$hashie_corral_executed = nil
end
it 'leaves plain strings and non-strings unchanged' do
mash = Hashie::Mash.new
mash.assign_property(:plain, 'hello')
mash.assign_property(:num, 5)
expect(mash.plain).to eq 'hello'
expect(mash.num).to eq 5
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s0/m4 · line 110 proven
+ m.send :define_method, :load do |path, *_args|
+ require 'erb'
+ require 'yaml'
+ mash.class.new(::YAML.safe_load(ERB.new(File.read(path)).result) || {})
+ ends0/m3 · line 156 proven
+ define_singleton_method(:load) do |path, *_args|
+ require 'erb'
+ require 'yaml'
+ new(::YAML.safe_load(ERB.new(File.read(path)).result) || {})
+ ends7/m4 · line 430 proven
+ if value.is_a?(String) + require 'erb' + value = ERB.new(value).result + end
require 'spec_helper'
class DashCorralRequiredNilInit < Hashie::Dash
property :name, required: true
property :email
end
describe DashCorralRequiredNilInit do
it 'rejects construction with nil attributes when a required property is unset' do
expect { described_class.new(nil) }
.to raise_error(ArgumentError, /the property `name` is required/)
end
it 'rejects construction with no usable attributes' do
expect { described_class.new({}) }.to raise_error(ArgumentError)
expect { described_class.new(email: 'a@b.c') }.to raise_error(ArgumentError)
end
it 'still allows construction when the required property is provided' do
expect(described_class.new(name: 'Bob').name).to eq 'Bob'
end
end
describe 'Hashie::Dash#update_attributes! required-property enforcement (corral)' do
it 'raises when a required property has been removed from the dash' do
klass = Class.new(Hashie::Dash) do
property :name, required: true
end
dash = klass.new(name: 'Ada')
dash.delete(:name)
expect { dash.update_attributes!({}) }
.to raise_error(ArgumentError, /the property `name` is required/)
end
it 'raises when an update makes a conditionally required property required while it is unset' do
klass = Class.new(Hashie::Dash) do
property :flag
property :name, required: -> { self[:flag] }
end
dash = klass.new(flag: false)
expect(dash[:name]).to be_nil
expect { dash.update_attributes!(flag: true) }
.to raise_error(ArgumentError, /the property `name` is required/)
end
it 'does not raise when required properties are set' do
klass = Class.new(Hashie::Dash) do
property :name, required: true
end
dash = klass.new(name: 'Ada')
expect { dash.update_attributes!(name: 'Grace') }.not_to raise_error
expect(dash.name).to eq 'Grace'
end
end
describe 'Hashie::Dash update_attributes! with a nil default on a required property (corral)' do
let(:corral_klass) do
Class.new(Hashie::Dash) do
property :flag
property :x, default: nil, required: :flag
end
end
it 'does not leave a nil value stored when the required check fails' do
dash = corral_klass.new(flag: false)
dash.delete(:x)
expect(dash.key?(:x)).to be false
expect { dash.update_attributes!(flag: true) }.to raise_error(ArgumentError, /the property `x`/)
expect(dash.key?(:x)).to be false
end
it 'still rejects an unset required property' do
dash = corral_klass.new(flag: false)
expect { dash[:x] = nil; dash[:flag] = true; dash[:x] = nil }.to raise_error(ArgumentError)
end
it 'rejects undeclared keys for reading and writing' do
dash = corral_klass.new(flag: false)
expect { dash[:nope] }.to raise_error(NoMethodError)
expect { dash[:nope] = 1 }.to raise_error(NoMethodError)
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s2/m1 · line 162 proven
- _assert_required_attributes_set!
s2/m5 · line 282 proven
+ store(prop, nil) if value.nil?
s2/m3 · line 291 proven
- - _assert_required_attributes_set!
require 'spec_helper'
require_relative '../../../lib/hashie/extensions/coercion.rb'
describe Hashie::Extensions::Coercion do
describe 'coerce_value strict registration (corral)' do
let(:corral_class) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
end
end
it 'records strict rules in strict_value_coercions and not in lenient ones' do
corral_class.coerce_value Integer, Float
expect(corral_class.strict_value_coercions).to eq(Integer => Float)
expect(corral_class.lenient_value_coercions).to eq({})
end
it 'gives strict rules precedence over a later lenient rule for the same class' do
corral_class.coerce_value Integer, Float
corral_class.coerce_value Integer, String, strict: false
expect(corral_class.strict_value_coercions[Integer]).to eq(Float)
hash = corral_class.new
hash[:a] = 5
expect(hash[:a]).to eql(5.0)
end
end
end
describe Hashie::Extensions::Coercion do
describe 'corral: non-strict coerce_value bookkeeping' do
class CorralGrandParent; end
class CorralParent < CorralGrandParent; end
class CorralChild < CorralParent; end
let(:corral_klass) do
Class.new(Hash) { include Hashie::Extensions::Coercion }
end
it 'records non-strict rules only in lenient_value_coercions' do
corral_klass.coerce_value(CorralChild, String, strict: false)
expect(corral_klass.lenient_value_coercions[CorralChild]).to eq(String)
expect(corral_klass.lenient_value_coercions[CorralParent]).to eq(String)
expect(corral_klass.strict_value_coercions).to be_empty
end
it 'records strict rules only in strict_value_coercions' do
corral_klass.coerce_value(Integer, String)
expect(corral_klass.strict_value_coercions[Integer]).to eq(String)
expect(corral_klass.lenient_value_coercions).to be_empty
end
it 'does not leak non-strict numeric rules into strict rules' do
corral_klass.coerce_value(Integer, String, strict: false)
expect(corral_klass.lenient_value_coercions[Integer]).to eq(String)
expect(corral_klass.strict_value_coercions).not_to have_key(Integer)
end
end
end
describe Hashie::Extensions::Coercion do
let(:corral_class) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_key :count, Integer
coerce_value String, Symbol
end
end
it 'prefers the key coercion over the value-type coercion when both apply' do
hash = corral_class.new
hash[:count] = '42'
expect(hash[:count]).to be_an(Integer)
expect(hash[:count]).to eq(42)
end
it 'prefers the key coercion when using set_value_with_coercion directly' do
hash = corral_class.new
hash.set_value_with_coercion(:count, '7')
expect(hash[:count]).to eq(7)
end
it 'still falls back to the value coercion for keys without a key rule' do
hash = corral_class.new
hash[:other] = 'abc'
expect(hash[:other]).to eq(:abc)
end
it 'raises CoercionError when the key coercion fails even if a value rule exists' do
klass = Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_key :count, ->(_v) { raise ArgumentError, 'nope' }
coerce_value String, Symbol
end
hash = klass.new
expect { hash[:count] = 'abc' }.to raise_error(Hashie::CoercionError, /nope/)
end
end
describe Hashie::Extensions::Coercion do
class CorralFailingArgNew
def initialize(_value)
raise ArgumentError, 'bad value'
end
end
class CorralFailingRuntimeNew
def initialize(_value)
raise 'boom'
end
end
let(:corral_hash_class) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_key :arg, CorralFailingArgNew
coerce_key :run, CorralFailingRuntimeNew
coerce_value Float, CorralFailingArgNew
end
end
it 'raises CoercionError when the key coercion constructor fails, and does not store the raw value' do
instance = corral_hash_class.new
expect { instance[:arg] = 'raw' }.to raise_error(Hashie::CoercionError, /cannot coerce property `:arg`/)
expect(instance.key?(:arg)).to be(false)
end
it 'does not silently store the unconverted value when the constructor raises a StandardError' do
instance = corral_hash_class.new
expect { instance[:run] = 'raw' }.to raise_error(StandardError, /boom/)
expect(instance.key?(:run)).to be(false)
end
it 'raises CoercionError for value-type coercions whose constructor fails' do
instance = corral_hash_class.new
expect { instance[:other] = 1.5 }.to raise_error(Hashie::CoercionError)
expect(instance.key?(:other)).to be(false)
end
it 'propagates constructor failure through the coercion callable directly' do
callable = corral_hash_class.fetch_coercion(CorralFailingArgNew)
expect { callable.call('raw') }.to raise_error(ArgumentError, /bad value/)
end
end
describe Hashie::Extensions::Coercion do
describe 'strict versus lenient precedence (corral)' do
let(:corral_klass) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_value Integer, String
coerce_value Integer, Float, strict: false
end
end
it 'registers both a strict and a lenient rule for the same source class' do
expect(corral_klass.strict_value_coercions[Integer]).to eq(String)
expect(corral_klass.lenient_value_coercions[Integer]).to eq(Float)
end
it 'prefers the strict rule when looking up a value coercion' do
expect(corral_klass.value_coercion(5)).to eq(String)
end
it 'stores the value converted through the strict rule' do
instance = corral_klass.new
instance[:a] = 5
expect(instance[:a]).to eq('5')
expect(instance[:a]).to be_a(String)
end
end
end
describe Hashie::Extensions::Coercion do
describe 'coerce_key re-registration (corral)' do
def corral_build_class
Class.new(Hash) { include Hashie::Extensions::Coercion }
end
it 'overrides an earlier rule when the same key is registered again on one class' do
klass = corral_build_class
klass.coerce_key :corral_foo, Integer
expect(klass.key_coercion(:corral_foo)).to eq(Integer)
klass.coerce_key :corral_foo, String
expect(klass.key_coercion(:corral_foo)).to eq(String)
instance = klass.new
instance[:corral_foo] = 42
expect(instance[:corral_foo]).to eq('42')
expect(instance[:corral_foo]).to be_a(String)
end
it 'lets a subclass override an inherited rule without changing the parent' do
parent = corral_build_class
parent.coerce_key :corral_bar, Integer
child = Class.new(parent)
child.coerce_key :corral_bar, String
expect(child.key_coercion(:corral_bar)).to eq(String)
expect(parent.key_coercion(:corral_bar)).to eq(Integer)
child_instance = child.new
child_instance[:corral_bar] = 7
expect(child_instance[:corral_bar]).to eq('7')
parent_instance = parent.new
parent_instance[:corral_bar] = '7'
expect(parent_instance[:corral_bar]).to eq(7)
end
it 'overrides with coerce_keys for multiple keys' do
klass = corral_build_class
klass.coerce_keys :corral_a, :corral_b, Integer
klass.coerce_keys :corral_a, :corral_b, Symbol
instance = klass.new
instance[:corral_a] = 'x'
instance[:corral_b] = 'y'
expect(instance[:corral_a]).to eq(:x)
expect(instance[:corral_b]).to eq(:y)
end
end
end
# --- corral: separate test file (unmergeable) — s5/m3 ---
# lang: 2 authored parts each declare "initialize", which is not a test and cannot be renamed safely
require 'spec_helper'
require_relative '../../../lib/hashie/extensions/coercion.rb'
describe Hashie::Extensions::Coercion do
class CorralFailingTarget
def initialize(_value)
raise ArgumentError, 'corral conversion failure'
end
end
let(:corral_klass) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_value String, CorralFailingTarget
end
end
describe '#convert_value' do
it 'propagates a failure instead of silently returning the unconverted value' do
instance = corral_klass.new
expect { instance.convert_value('raw') }.to raise_error(ArgumentError, /corral conversion failure/)
end
it 'propagates a failure raised by a callable coercer' do
klass = Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_value String, ->(_v) { raise TypeError, 'corral callable failure' }
end
expect { klass.new.convert_value('raw') }.to raise_error(TypeError, /corral callable failure/)
end
it 'still converts successfully when the coercion works' do
klass = Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_value String, Integer
end
expect(klass.new.convert_value('3')).to eq(3)
end
end
describe 'assignment' do
it 'raises CoercionError rather than storing the unconverted value' do
instance = corral_klass.new
expect { instance[:foo] = 'raw' }.to raise_error(Hashie::CoercionError)
expect(instance).not_to have_key(:foo)
end
end
end
# --- corral: separate test file (unmergeable) — s6/m4 ---
# lang: 2 authored parts each declare "initialize", which is not a test and cannot be renamed safely
# frozen_string_literal: true
require 'spec_helper'
describe Hashie::Extensions::Coercion do
class CorralStrictElement
def initialize(value)
raise ArgumentError, 'bad element' unless value.is_a?(String) && value.start_with?('ok')
@value = value
end
end
let(:corral_class) do
Class.new(Hash) do
include Hashie::Extensions::Coercion
coerce_key :numbers, [Integer]
coerce_key :strict, [CorralStrictElement]
end
end
it 'raises CoercionError when an element of a container cannot be converted (no to_i)' do
hash = corral_class.new
bad = Object.new
expect { hash[:numbers] = ['1', bad] }.to raise_error(Hashie::CoercionError)
expect(hash.key?(:numbers)).to be(false)
end
it 'raises CoercionError when an element constructor fails' do
hash = corral_class.new
expect { hash[:strict] = ['ok1', 'nope'] }.to raise_error(Hashie::CoercionError)
expect(hash.key?(:strict)).to be(false)
end
it 'still coerces valid container elements' do
hash = corral_class.new
hash[:numbers] = %w[1 2]
expect(hash[:numbers]).to eq([1, 2])
end
it 'does not store unconverted elements when build_container_coercion fails directly' do
conversion = corral_class.build_container_coercion(Array, Integer)
expect { conversion.call(['1', Object.new]) }.to raise_error(NoMethodError)
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s1/m2 · line 65 proven
- into = self.class.key_coercion(key) || self.class.value_coercion(value) + into = self.class.value_coercion(value) || self.class.key_coercion(key)
s5/m3 · line 105 proven
- return self.class.fetch_coercion(into).call(val) if into + if into + begin + return self.class.fetch_coercion(into).call(val) + rescue StandardError + return val + end + end
s7/m2 · line 159 proven
- attrs.each { |key| key_coercions[key] = into }
+ attrs.each { |key| key_coercions[key] ||= into }s0/m3 · line 230 proven
- strict_value_coercions[from] = into + lenient_value_coercions[from] = into
s0/m4 · line 232 proven
- lenient_value_coercions[from] = into + strict_value_coercions[from] = into
s4/m4 · line 268 proven
- strict_value_coercions[from] || lenient_value_coercions[from] + lenient_value_coercions[from] || strict_value_coercions[from]
s2/m2 · line 318 proven
- type.new(value) + begin + type.new(value) + rescue StandardError + value + end
s6/m4 · line 346 proven
- type.new(value.map { |v| value_coerce.call(v) })
+ type.new(value.map { |v| (value_coerce.call(v) rescue v) })require 'spec_helper'
require_relative '../../../lib/hashie/extensions/indifferent_access.rb'
# frozen_string_literal: true
describe Hashie::Extensions::IndifferentAccess do
describe 'nested hashes that report indifferent_access? as false (corral)' do
def corral_build_false_reporting_hash
nested = { a: 1 }
def nested.indifferent_access?
false
end
nested
end
it 'still converts such a hash when it is written as a value' do
outer = described_class.inject({})
outer[:x] = corral_build_false_reporting_hash
expect(outer['x'].keys).to eq(['a'])
expect(outer['x']['a']).to eq(1)
expect(outer[:x][:a]).to eq(1)
end
it 'still converts such a hash when it is the result of merge' do
base = described_class.inject({})
merged = base.merge(corral_build_false_reporting_hash)
expect(merged.keys).to eq(['a'])
expect(merged['a']).to eq(1)
end
it 'does not treat such a hash as indifferent in update (keys get normalized)' do
outer = described_class.inject({})
outer.update(corral_build_false_reporting_hash)
expect(outer.keys).to eq(['a'])
expect(outer['a']).to eq(1)
expect(outer[:a]).to eq(1)
end
end
end
describe Hashie::Extensions::IndifferentAccess do
describe 'update with a hash that responds to indifferent_access? but returns false (corral)' do
let(:corral_indifferent_class) do
Class.new(Hash) { include Hashie::Extensions::IndifferentAccess }
end
it 'normalizes symbol keys and nested hashes instead of taking the regular_update shortcut' do
target = corral_indifferent_class.new
other = { foo: 1, bar: { baz: 2 } }
def other.indifferent_access?
false
end
target.update(other)
expect(target.keys.sort).to eq(%w[bar foo])
expect(target['foo']).to eq(1)
expect(target[:foo]).to eq(1)
expect(target[:bar][:baz]).to eq(2)
expect(target['bar']['baz']).to eq(2)
end
it 'does not create duplicate entries when updating with such a hash' do
target = corral_indifferent_class.new
target['foo'] = 1
other = { foo: 2 }
def other.indifferent_access?
false
end
target.update(other)
expect(target.size).to eq(1)
expect(target['foo']).to eq(2)
end
end
end
# --- corral: separate test file (unmergeable) — s1/m4 ---
# lang: 2 authored parts each declare "other", which is not a test and cannot be renamed safely
require 'spec_helper'
require_relative '../../../lib/hashie/extensions/indifferent_access.rb'
describe Hashie::Extensions::IndifferentAccess do
let(:corral_klass) do
Class.new(Hash) { include Hashie::Extensions::IndifferentAccess }
end
it 'normalizes keys when updating from a hash that responds to indifferent_access? with false' do
other = { cat: 'meow', dog: { name: 'Mango' } }
def other.indifferent_access?
false
end
target = corral_klass.new
target.update(other)
expect(target.keys).to eq(%w[cat dog])
expect(target['cat']).to eq('meow')
expect(target[:cat]).to eq('meow')
expect(target[:dog]['name']).to eq('Mango')
expect(target[:dog][:name]).to eq('Mango')
end
it 'does not create duplicate entries when updating with a non-indifferent hash that denies indifference' do
other = { name: 'new' }
def other.indifferent_access?
false
end
target = corral_klass.new
target['name'] = 'old'
target.update(other)
expect(target.size).to eq(1)
expect(target['name']).to eq('new')
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s0/m4 · line 266 proven
- !(other.respond_to?(:indifferent_access?) && - other.indifferent_access?) + !other.respond_to?(:indifferent_access?)
s1/m4 · line 273 proven
- other.respond_to?(:indifferent_access?) && - other.indifferent_access? + other.respond_to?(:indifferent_access?)
s1/m2 · line 274 proven
- other.indifferent_access? + !other.indifferent_access?
require 'spec_helper'
require_relative '../../../lib/hashie/extensions/method_access.rb'
describe Hashie::Extensions::MethodOverridingWriter do
let(:corral_class) do
Class.new(Hash) do
include Hashie::Extensions::MethodOverridingWriter
end
end
subject(:corral_hash) { corral_class.new }
it 'responds to writer-style names' do
expect(corral_hash.respond_to?(:abc=)).to be true
end
it 'does not claim to respond to query-style names' do
expect(corral_hash.respond_to?(:abc?)).to be false
expect(corral_hash.respond_to?('anything_else?')).to be false
end
it 'does not claim to respond to query-style names even when a key exists' do
corral_hash['abc'] = 1
expect(corral_hash.respond_to?(:abc?)).to be false
end
it 'raises NoMethodError for query-style calls' do
expect { corral_hash.abc? }.to raise_error(NoMethodError)
end
it 'does not respond to plain undefined names' do
expect(corral_hash.respond_to?(:abc)).to be false
end
end
describe Hashie::Extensions::MethodOverridingInitializer do
class CorralInitOrderHash < Hash
include Hashie::Extensions::MethodOverridingInitializer
attr_reader :corral_seen
def []=(key, value)
@corral_seen ||= []
@corral_seen << [key, respond_to?(:"__#{key}")]
super
end
end
it 'redefines the conflicting method before storing the value' do
hash = CorralInitOrderHash.new(size: 'large')
expect(hash.corral_seen).to eq [['size', true]]
end
it 'keeps the value readable and aliases the original method' do
hash = CorralInitOrderHash.new(size: 'large')
expect(hash.size).to eq 'large'
expect(hash.__size).to eq 1
end
end
describe Hashie::Extensions::MethodWriter do
class CorralWriterHash < Hash
include Hashie::Extensions::MethodWriter
end
subject(:hash) { CorralWriterHash.new }
it 'raises NoMethodError for a writer call with no arguments and stores nothing' do
expect { hash.send(:awesome=) }.to raise_error(NoMethodError)
expect(hash).to be_empty
end
it 'raises NoMethodError for a writer call with more than one argument and stores nothing' do
expect { hash.send(:awesome=, 1, 2) }.to raise_error(NoMethodError)
expect(hash).to be_empty
end
it 'still writes with exactly one argument' do
hash.send(:awesome=, 'sauce')
expect(hash['awesome']).to eq 'sauce'
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s7/m3 · line 96 proven
- return self[convert_key(Regexp.last_match[1])] = args.first if args.size == 1 && name.to_s =~ /(.*)=$/ + return self[convert_key(Regexp.last_match[1])] = args.first if name.to_s =~ /(.*)=$/
s3/m4 · line 277 proven
- return true if name.to_s.end_with?('=')
+ return true if name.to_s.end_with?('=', '?')s4/m3 · line 347 proven
- _redefine_method(skey) if _method?(skey) - self[skey] = value + self[skey] = value + _redefine_method(skey) if _method?(skey)
require 'spec_helper'
require_relative '../../lib/hashie/extensions/dash/property_translation.rb'
describe Hashie::Extensions::Dash::PropertyTranslation do
class CorralPropertyTranslationDash < Hashie::Dash
include Hashie::Extensions::Dash::PropertyTranslation
property :name, from: :fullName
property :age
end
let(:corral_dash) { CorralPropertyTranslationDash.new }
describe '#property_exists?' do
it 'returns true for a declared property' do
expect(corral_dash.property_exists?(:name)).to eq(true)
expect(corral_dash.property_exists?(:age)).to eq(true)
end
it 'raises NoMethodError for an undeclared property' do
expect { corral_dash.property_exists?(:missing) }.to raise_error(NoMethodError)
end
it 'raises NoMethodError for a translated source key that is not itself a property' do
expect { corral_dash.property_exists?(:fullName) }.to raise_error(NoMethodError)
end
end
describe 'assigning undeclared keys' do
it 'does not allow an undeclared key to be assigned via []=' do
expect { corral_dash[:missing] = 1 }.to raise_error(NoMethodError)
expect(corral_dash.key?(:missing)).to eq(false)
end
it 'writes translated values only to the declared destination property' do
corral_dash[:fullName] = 'Ada'
expect(corral_dash.name).to eq('Ada')
expect(corral_dash.keys.map(&:to_sym) - %i[name age]).to be_empty
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s1/m2 · line 302 proven
- _fail_no_property_error!(property) unless self.class.property?(property) + _fail_no_property_error!(property) if false
require 'spec_helper'
describe Hashie::Rash do
describe 'exact literal precedence (corral)' do
it 'stops after an exact string key even when a regexp also matches' do
rash = described_class.new(/^user/ => 'profile', 'user/admin' => 'admin')
expect(rash.all('user/admin').to_a).to eq ['admin']
yielded = []
rash.all('user/admin') { |v| yielded << v }
expect(yielded).to eq ['admin']
end
it 'does not invoke callable pattern values when an exact key matches' do
calls = 0
rash = described_class.new(
/^user/ => proc { |_m| calls += 1; 'called' },
'user/admin' => 'admin'
)
expect(rash.all('user/admin').to_a).to eq ['admin']
expect(calls).to eq 0
end
it 'stops after an exact numeric key even when a range also covers it' do
rash = described_class.new(5 => 'exact', (1..10) => 'range')
expect(rash.all(5).to_a).to eq ['exact']
expect(rash.all(6).to_a).to eq ['range']
end
it 'stops after an exact regexp key even when string keys match it' do
rash = described_class.new(/abc/ => 'pattern', 'abc' => 'literal')
expect(rash.all(/abc/).to_a).to eq ['pattern']
end
end
end
describe Hashie::Rash do
it 'fetch returns the first matching value, not the last' do
rash = described_class.new(/hello/ => 'first', /world/ => 'second')
expect(rash.fetch('hello world')).to eq 'first'
end
it 'fetch stops at the first match and does not invoke later callable values' do
calls = []
rash = described_class.new
rash[/hello/] = proc { |_m| calls << :first; 'first' }
rash[/world/] = proc { |_m| calls << :second; 'second' }
expect(rash.fetch('hello world')).to eq 'first'
expect(calls).to eq [:first]
end
it 'fetch returns a matched nil value instead of raising or using the default' do
rash = described_class.new(/abc/ => nil)
expect(rash.fetch('xabcx')).to be_nil
expect(rash.fetch('xabcx', 'fallback')).to be_nil
expect { |y| rash.fetch('xabcx', &y) }.not_to yield_control
end
end
describe Hashie::Rash do
describe 'delegation to the underlying hash (corral)' do
let(:rash_corral) do
described_class.new('a' => 'first_value', /b/ => 'second_value')
end
it 'does not leak an unrelated stored value when a delegated call returns a falsy result' do
# Hash#key? returns false here, so the original falls through to `super`
# (NoMethodError). A mutant returning @hash.values.first would leak
# 'first_value' instead.
expect { rash_corral.key?('missing') }.to raise_error(NoMethodError)
end
it 'never returns a stored value for a falsy delegated result' do
result = begin
rash_corral.key?('missing')
rescue NoMethodError
:no_method_error
end
expect(result).to eq :no_method_error
expect(result).not_to eq 'first_value'
end
it 'raises NoMethodError for an empty hash delegated predicate returning false' do
empty = described_class.new
expect { empty.empty? }.to raise_error(NoMethodError) if empty.instance_variable_get(:@hash).empty?
end
it 'still delegates truthy results' do
expect(rash_corral.key?('a')).to be true
expect(rash_corral.size).to eq 2
end
end
end
describe Hashie::Rash do
describe 'corral: method_missing delegation evaluates exactly once' do
let(:corral_rash) { described_class.new('a' => 1, 'b' => 2) }
it 'invokes a delegated block-taking method only once per entry' do
seen = []
corral_rash.each { |k, _v| seen << k }
expect(seen).to eq %w[a b]
end
it 'returns the value of a delegated mutating call instead of re-running it' do
expect(corral_rash.delete('a')).to eq 1
expect(corral_rash.keys).to eq ['b']
end
it 'does not run the delegated side effect twice for store-like calls' do
calls = 0
corral_rash.each_key { |_k| calls += 1 }
expect(calls).to eq 2
end
end
end
describe Hashie::Rash, 'optimization ordering (corral)' do
it 'reorders regexes so the most frequently matched one is tried first' do
rash = described_class.new(/a/ => 'A', /b/ => 'B')
rash.optimize_every = 1_000
3.times { expect(rash['b']).to eq 'B' }
expect(rash['a']).to eq 'A'
# Counts: /b/ => 3, /a/ => 1. The next string lookup triggers optimization.
rash.optimize_every = 1
# Both regexes match 'ab'; after sorting by descending hit count, /b/ must come first.
expect(rash['ab']).to eq 'B'
expect(rash.all('ab').to_a.first).to eq 'B'
end
end
describe Hashie::Rash do
describe 'underlying hash defaults (corral)' do
subject(:corral_rash) do
described_class.new(
'known' => 'first-value',
/pat/ => 'second-value'
)
end
it 'does not fabricate values for missing keys via delegated hash access' do
expect(corral_rash.values_at('missing')).to eq [nil]
expect(corral_rash.values_at('known', 'missing')).to eq ['first-value', nil]
end
it 'returns nil from lookup for non-matching keys' do
expect(corral_rash['nomatch']).to be_nil
expect(corral_rash[12_345]).to be_nil
expect(corral_rash.all('nomatch').to_a).to eq []
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s6/m1 · line 43 proven
- @hash = {}
+ @hash = Hash.new { |h, _k| h.values.first }s2/m1 · line 116 proven
- all(key) do |value| - return value - end + found = nil + all(key) do |value| + found = value + end + return found unless found.nil?
s0/m2 · line 141 proven
- return
s3/m1 · line 183 proven
- @hash.send(...) || super + @hash.send(...) || @hash.values.first
s3/m2 · line 183 proven
- @hash.send(...) || super + (@hash.send(...) && @hash.send(...)) || super
s5/m1 · line 199 proven
- @regexes = @regexes.sort_by { |regex| -@regex_counts[regex] }
+ @regexes = @regexes.sort_by { |regex| @regex_counts[regex] }require 'spec_helper'
describe Hashie::Extensions::Dash::IndifferentAccess do
context 'when PropertyTranslation is included after IndifferentAccess (corral)' do
let(:klass_late_translation) do
Class.new(Hashie::Dash) do
include Hashie::Extensions::Dash::IndifferentAccess
include Hashie::Extensions::Dash::PropertyTranslation
property :name, from: :fullName
end
end
it 'reports a symbol translation source as a property without stringifying before the lookup' do
expect(klass_late_translation.property?(:fullName)).to be true
end
it 'still reports the declared property under either key type' do
expect(klass_late_translation.property?(:name)).to be true
expect(klass_late_translation.property?('name')).to be true
expect(klass_late_translation.property?(:missing)).to be false
end
end
context 'when IndifferentAccess is included after PropertyTranslation' do
let(:klass_early_translation) do
Class.new(Hashie::Dash) do
include Hashie::Extensions::Dash::PropertyTranslation
include Hashie::Extensions::Dash::IndifferentAccess
property :name, from: :fullName
end
end
it 'resolves translation sources under both key types' do
expect(klass_early_translation.property?(:fullName)).to be true
expect(klass_early_translation.property?('fullName')).to be true
expect(klass_early_translation.property?(:missing)).to be false
end
end
end
describe Hashie::Extensions::Dash::IndifferentAccess::ClassMethods do
describe '.transformed_property with string-declared transformers (corral)' do
let(:string_declared) do
Class.new(Hashie::Trash) do
include Hashie::Extensions::Dash::IndifferentAccess
property 'age', transform_with: ->(value) { value.to_i }
end
end
let(:symbol_declared) do
Class.new(Hashie::Trash) do
include Hashie::Extensions::Dash::IndifferentAccess
property :age, transform_with: ->(value) { value.to_i }
end
end
it 'finds a transformer declared under a string key when looked up by string' do
expect(string_declared.transformed_property('age', '37')).to eq(37)
end
it 'finds a transformer declared under a string key when looked up by symbol' do
expect(string_declared.transformed_property(:age, '37')).to eq(37)
end
it 'finds a transformer declared under a symbol key when looked up by string' do
expect(symbol_declared.transformed_property('age', '37')).to eq(37)
end
it 'finds a transformer declared under a symbol key when looked up by symbol' do
expect(symbol_declared.transformed_property(:age, '37')).to eq(37)
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s2/m2 · line 116 proven
- transform = transforms[property_name] || transforms[property_name.to_sym] + transform = transforms[property_name.to_sym]
s0/m1 · line 136 proven
- translation_exists?(name) + translation_exists?(name.to_s)
require 'spec_helper'
describe Hashie::Extensions::SymbolizeKeys do
describe 'key case preservation (corral)' do
it 'preserves the case of converted keys, including nested ones' do
input = { 'Foo' => 1, 'BAR' => { 'Baz' => 2 }, 'Arr' => [{ 'QuX' => 3 }], 7 => 'num' }
result = described_class.symbolize_keys(input)
expect(result).to eq(Foo: 1, BAR: { Baz: 2 }, Arr: [{ QuX: 3 }], 7 => 'num')
end
it 'does not merge or overwrite keys that differ only by case' do
input = { 'A' => 1, 'a' => 2 }
result = described_class.symbolize_keys(input)
expect(result.size).to eq 2
expect(result[:A]).to eq 1
expect(result[:a]).to eq 2
end
it 'preserves case in place via symbolize_keys!' do
hash = { 'MixedCase' => 'v', :Already => 'w' }
described_class.symbolize_keys!(hash)
expect(hash).to eq(MixedCase: 'v', Already: 'w')
end
it 'preserves case when extended on a hash instance' do
hash = { 'CamelKey' => { 'InnerKey' => 1 } }.extend(described_class)
expect(hash.symbolize_keys!).to eq(CamelKey: { InnerKey: 1 })
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s1/m2 · line 130 proven
- key.respond_to?(:to_sym) ? key.to_sym : key + key.respond_to?(:to_sym) ? key.to_s.downcase.to_sym : key
require 'spec_helper'
begin
require 'active_support'
require 'active_support/core_ext/hash/indifferent_access'
rescue LoadError
nil
end
unless defined?(::ActiveSupport::HashWithIndifferentAccess)
module ActiveSupport
class HashWithIndifferentAccess < ::Hash; end
end
end
describe Hashie::Extensions::DeepLocate do
describe 'ActiveSupport indifferent access detection (corral)' do
it 'finds symbol keys in a plain Hash even when ActiveSupport HWIA is defined' do
plain = { user: { name: 'Ada' }, other: 1 }
expect(described_class.deep_locate(:name, plain)).to eq([{ name: 'Ada' }])
end
it 'does not match string keys in a plain Hash when searching by symbol' do
plain = { 'name' => 'Ada' }
expect(described_class.deep_locate(:name, plain)).to eq([])
end
it 'matches string-converted keys for an ActiveSupport HashWithIndifferentAccess' do
indifferent = ::ActiveSupport::HashWithIndifferentAccess.new
indifferent['name'] = 'Ada'
result = described_class.deep_locate(:name, indifferent)
expect(result.size).to eq(1)
expect(result.first).to equal(indifferent)
end
end
end
describe Hashie::Extensions::DeepLocate do
describe 'ActiveSupport indifferent access key handling (corral)' do
before do
@corral_defined_stub = false
unless defined?(::ActiveSupport::HashWithIndifferentAccess)
module ::ActiveSupport; end
::ActiveSupport.const_set(:HashWithIndifferentAccess, Class.new(::Hash))
@corral_defined_stub = true
end
end
after do
if @corral_defined_stub
::ActiveSupport.send(:remove_const, :HashWithIndifferentAccess)
end
end
it 'converts the search key to a string for HashWithIndifferentAccess objects' do
hwia = ::ActiveSupport::HashWithIndifferentAccess.new
hwia['name'] = 'Ada'
hwia['other'] = 1
result = described_class.deep_locate(:name, hwia)
expect(result.size).to eq(1)
expect(result.first).to equal(hwia)
end
it 'finds nested string-keyed hashes inside a HashWithIndifferentAccess' do
inner = { 'name' => 'Ada' }
hwia = ::ActiveSupport::HashWithIndifferentAccess.new
hwia['user'] = inner
result = described_class.deep_locate(:name, hwia)
expect(result.size).to eq(1)
expect(result.first).to equal(inner)
end
it 'does not convert the search key for plain Hash objects' do
plain = { user: { name: 'Ada' } }
expect(described_class.deep_locate(:name, plain)).to eq([{ name: 'Ada' }])
end
it 'does not match string keys with a symbol search on plain Hash objects' do
plain = { 'name' => 'Ada' }
expect(described_class.deep_locate(:name, plain)).to eq([])
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s2/m1 · line 94 proven
- object.is_a?(::ActiveSupport::HashWithIndifferentAccess) + !object.is_a?(::ActiveSupport::HashWithIndifferentAccess)
s2/m2 · line 94 proven
- object.is_a?(::ActiveSupport::HashWithIndifferentAccess) + object.instance_of?(::Hash)
require 'spec_helper'
require_relative '../../../lib/hashie/extensions/strict_key_access.rb'
describe Hashie::Extensions::StrictKeyAccess do
class StrictKeyAccessCorralHash < Hash
include Hashie::Extensions::StrictKeyAccess
end
let(:corral_message) do
'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
end
let(:corral_instance) { StrictKeyAccessCorralHash.new }
it 'DefaultError has a descriptive message' do
error = Hashie::Extensions::StrictKeyAccess::DefaultError.new
expect(error.message).to eq corral_message
expect(error.message).not_to be_empty
end
it 'default raises DefaultError with the descriptive message' do
expect { corral_instance.default(:x) }
.to raise_error(Hashie::Extensions::StrictKeyAccess::DefaultError, corral_message)
end
it 'default= raises DefaultError with the descriptive message' do
expect { corral_instance.default = 1 }
.to raise_error(Hashie::Extensions::StrictKeyAccess::DefaultError, corral_message)
end
it 'default_proc raises DefaultError with the descriptive message' do
expect { corral_instance.default_proc }
.to raise_error(Hashie::Extensions::StrictKeyAccess::DefaultError, corral_message)
end
it 'default_proc= raises DefaultError with the descriptive message' do
expect { corral_instance.default_proc = proc {} }
.to raise_error(Hashie::Extensions::StrictKeyAccess::DefaultError, corral_message)
end
it 'still raises KeyError for missing keys and values' do
corral_instance[:a] = 1
expect { corral_instance[:missing] }.to raise_error(KeyError)
expect { corral_instance.key(99) }.to raise_error(KeyError)
expect(corral_instance.key(1)).to eq :a
end
end
describe Hashie::Extensions::StrictKeyAccess::DefaultError do
let(:corral_expected_message) do
'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
end
it 'has the exact documented message when constructed directly' do
expect(described_class.new.message).to eq corral_expected_message
end
it 'is a StandardError' do
expect(described_class.new).to be_a StandardError
end
it 'carries the exact message when raised by #default' do
hash = {}.extend(Hashie::Extensions::StrictKeyAccess)
expect { hash.default }
.to raise_error(described_class, corral_expected_message)
end
it 'carries the exact message when raised by #default=' do
hash = {}.extend(Hashie::Extensions::StrictKeyAccess)
expect { hash.default = 1 }
.to raise_error(described_class, corral_expected_message)
end
it 'carries the exact message when raised by #default_proc' do
hash = {}.extend(Hashie::Extensions::StrictKeyAccess)
expect { hash.default_proc }
.to raise_error(described_class, corral_expected_message)
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s1/m2 · line 36 proven
- super('Setting or using a default with Hashie::Extensions::StrictKeyAccess ' \
+ raise ArgumentError, 'default' if false
+ super('Setting or using a default with Hashie::Extensions::StrictKeyAccess ' \
+ 'does not make sense'.reverse + ' ' \s1/m1 · line 37 proven
- super('Setting or using a default with Hashie::Extensions::StrictKeyAccess ' \
- 'does not make sense'
- )
+ super('')require 'spec_helper'
require_relative '../../../../lib/hashie/extensions/mash/define_accessors.rb'
describe Hashie::Extensions::Mash::DefineAccessors do
let(:klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
describe 'generated predicate (?) accessor' do
it 'returns strictly true for a truthy value, not the value itself' do
instance = klass.new('name' => 'Ada')
expect(instance.name?).to eq(true)
# second call goes through the generated method
expect(instance.name?).to eq(true)
end
it 'returns strictly false for a missing key, not nil' do
instance = klass.new
expect(instance.name?).to eq(false)
expect(instance.name?).to eq(false)
expect(instance.key?('name')).to eq(false)
end
it 'coerces falsy and non-boolean values to booleans' do
instance = klass.new('flag' => false, 'count' => 5, 'empty' => nil)
expect(instance.flag?).to eq(false)
expect(instance.count?).to eq(true)
expect(instance.empty?).to eq(false)
end
it 'works on a singleton-extended Mash as well' do
instance = Hashie::Mash.new('name' => 'Ada').with_accessors!
expect(instance.name?).to eq(true)
expect(instance.name?).to eq(true)
expect(instance.missing?).to eq(false)
end
end
end
# frozen_string_literal: true
describe Hashie::Extensions::Mash::DefineAccessors do
let(:corral_klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
describe 'generated initializing reader (bang)' do
it 'initializes only the key matching the method name without the suffix' do
mash = corral_klass.new
result = mash.foo!
expect(result).to be_a(Hashie::Mash)
expect(mash.key?('foo')).to be true
expect(mash.key?('foo!')).to be false
expect(mash.keys).to eq(['foo'])
end
it 'returns the existing value stored under the base key' do
mash = corral_klass.new('foo' => 'existing')
expect(mash.foo!).to eq('existing')
expect(mash.foo!).to eq('existing')
expect(mash.keys).to eq(['foo'])
end
it 'keeps nested initialization working on repeated calls' do
mash = corral_klass.new
mash.address!.city = 'London'
expect(mash.address!.city).to eq('London')
expect(mash['address']['city']).to eq('London')
expect(mash.key?('address!')).to be false
end
it 'behaves the same on an extended instance' do
mash = Hashie::Mash.new('bar' => 5).with_accessors!
expect(mash.bar!).to eq(5)
mash.baz!
expect(mash.key?('baz')).to be true
expect(mash.key?('baz!')).to be false
end
end
end
describe Hashie::Extensions::Mash::DefineAccessors do
describe 'generated predicate (corral)' do
let(:klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
it 'returns exactly true for a truthy value' do
instance = klass.new(name: 'Ada')
expect(instance.name?).to eq(true)
# second call goes through the generated method
expect(instance.name?).to eq(true)
end
it 'returns exactly false for a missing key' do
instance = klass.new
expect(instance.name?).to eq(false)
expect(instance.name?).to eq(false)
end
it 'returns exactly false for a false value' do
instance = klass.new(flag: false)
expect(instance.flag?).to eq(false)
expect(instance.flag?).to eq(false)
end
it 'returns exactly false for a nil value' do
instance = klass.new(thing: nil)
expect(instance.thing?).to eq(false)
expect(instance.thing?).to eq(false)
end
it 'returns true for falsy-looking but truthy values such as 0 and empty string' do
instance = klass.new(zero: 0, blank: '')
expect(instance.zero?).to eq(true)
expect(instance.zero?).to eq(true)
expect(instance.blank?).to eq(true)
expect(instance.blank?).to eq(true)
end
it 'reads only the key matching its method name' do
instance = klass.new(a: 1)
expect(instance.b?).to eq(false)
expect(instance.a?).to eq(true)
end
it 'behaves the same on an extended Mash instance' do
instance = Hashie::Mash.new(name: 'Ada').with_accessors!
expect(instance.name?).to eq(true)
expect(instance.name?).to eq(true)
expect(instance.missing?).to eq(false)
expect(instance.missing?).to eq(false)
end
end
end
describe 'Hashie::Extensions::Mash::DefineAccessors generated predicate reads the matching key' do
let(:corral_klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
it 'returns true for a truthy value stored under the base key' do
instance = corral_klass.new('foo' => 'present')
expect(instance.foo?).to eq true
end
it 'returns true on repeated calls once the predicate is defined' do
instance = corral_klass.new('foo' => 'present')
expect(instance.foo?).to eq true
expect(corral_klass).to be_method_defined(:foo?)
expect(instance.foo?).to eq true
end
it 'returns false when the base key holds a falsey value' do
instance = corral_klass.new('foo' => false)
expect(instance.foo?).to eq false
instance2 = corral_klass.new('bar' => nil)
expect(instance2.bar?).to eq false
end
it 'returns false when the base key is missing' do
expect(corral_klass.new.foo?).to eq false
end
it 'reflects later writes to the base key' do
instance = corral_klass.new
expect(instance.foo?).to eq false
instance[:foo] = 1
expect(instance.foo?).to eq true
end
it 'works for a single extended Mash too' do
instance = Hashie::Mash.new('foo' => 'x').with_accessors!
expect(instance.foo?).to eq true
expect(instance.baz?).to eq false
end
end
describe Hashie::Extensions::Mash::DefineAccessors::Ext do
let(:corral_accessors) { described_class.new }
let(:corral_klass) do
accessors = corral_accessors
Class.new(Hashie::Mash) { include accessors }
end
it 'reader reads the literal method-name key when it exists, not the generated key' do
corral_accessors.define_reader('name', :alias_name)
mash = corral_klass.new('alias_name' => 'literal', 'name' => 'other')
expect(mash.alias_name).to eq('literal')
end
it 'reader reads the key matching its generated key when the method-name key is absent' do
corral_accessors.define_reader('name', :alias_name)
mash = corral_klass.new('name' => 'Ada')
expect(mash.alias_name).to eq('Ada')
end
end
# frozen_string_literal: true
describe Hashie::Extensions::Mash::DefineAccessors do
describe 'generated readers read exactly the key matching the method name' do
subject(:klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
it 'returns the value of the matching key, not a similarly named key, on first and later calls' do
instance = klass.new('corral_title' => 'Ada', 'corral_title_x' => 'wrong', 'corral_title_' => 'wrong2')
expect(instance.corral_title).to eq('Ada')
expect(klass).to be_method_defined(:corral_title)
expect(instance.corral_title).to eq('Ada')
end
it 'does not return a value when only a suffixed lookalike key exists' do
instance = klass.new('corral_other_x' => 'wrong')
expect(instance.corral_other).to be_nil
expect(instance.corral_other).to be_nil
end
it 'reads the matching key through a directly defined reader' do
accessors = Hashie::Extensions::Mash::DefineAccessors::Ext.new
accessors.define_reader('corral_direct', :corral_direct)
direct_klass = Class.new(Hashie::Mash) { include accessors }
instance = direct_klass.new('corral_direct' => 'right', 'corral_direct_x' => 'wrong')
expect(instance.corral_direct).to eq('right')
end
it 'works on an extended Mash instance too' do
instance = Hashie::Mash.new('corral_ext' => 'right', 'corral_ext_x' => 'wrong').with_accessors!
expect(instance.corral_ext).to eq('right')
expect(instance.corral_ext).to eq('right')
end
end
end
describe Hashie::Extensions::Mash::DefineAccessors do
describe 'generated underscore (underbang) reader for a missing key' do
let(:klass_corral) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
it 'returns a fresh empty mash without storing it, on first and later calls' do
mash = klass_corral.new
2.times do
result = mash.address_
expect(result).to be_a(Hashie::Mash)
expect(result).to be_an_instance_of(klass_corral)
expect(result).to be_empty
expect(mash.key?('address')).to be false
end
expect(klass_corral).to be_method_defined(:address_)
end
it 'allows chained assignment on the temporary mash without mutating the receiver' do
mash = klass_corral.new
mash.address_.city = 'London'
expect(mash.key?('address')).to be false
expect(mash.address_.city).to be_nil
end
it 'reads the matching key when it is present' do
mash = klass_corral.new('address' => { 'city' => 'Paris' })
expect(mash.address_.city).to eq 'Paris'
end
it 'behaves the same when extended on a single instance' do
mash = Hashie::Mash.new.with_accessors!
expect(mash.address_).to be_a(Hashie::Mash)
expect(mash.address_).to be_empty
expect(mash.key?('address')).to be false
end
end
end
describe Hashie::Extensions::Mash::DefineAccessors do
describe 'generated writer (corral)' do
let(:klass) { Class.new(Hashie::Mash) { include Hashie::Extensions::Mash::DefineAccessors } }
it 'writes only the key matching its method name on first call' do
mash = klass.new
mash.foo = 'bar'
expect(mash.keys).to eq(['foo'])
expect(mash['foo']).to eq('bar')
expect(mash).not_to have_key('__writer_called')
end
it 'writes only the matching key on repeated calls through the defined method' do
mash = klass.new
mash.foo = 1
mash.foo = 2
mash.baz = 3
expect(mash.keys.sort).to eq(%w[baz foo])
expect(mash.to_h).to eq('foo' => 2, 'baz' => 3)
end
it 'does not add extra keys on a Mash extended with accessors' do
mash = Hashie::Mash.new.with_accessors!
mash.name = 'Ada'
expect(mash.to_h).to eq('name' => 'Ada')
end
it 'does not add extra keys to a pre-populated mash' do
mash = klass.new('existing' => 1)
mash.other = 2
expect(mash.to_h).to eq('existing' => 1, 'other' => 2)
end
end
end
describe Hashie::Extensions::Mash::DefineAccessors do
describe 'accessor generation (corral)' do
let(:klass) do
Class.new(Hashie::Mash) do
include Hashie::Extensions::Mash::DefineAccessors
def foo
if self[:foo] == 1
super
else
:bar
end
end
end
end
it 'keeps generated accessors below the including class so its overrides still win' do
instance = klass.new
instance.foo = 1
# first call goes through the override's super, which generates the reader
expect(instance.foo).to eq 1
expect(klass).not_to be_method_defined(:foo).or be_method_defined(:foo)
instance.foo = 2
# after generation, the class's own override must still take precedence
expect(instance.foo).to eq :bar
instance.foo = 1
expect(instance.foo).to eq 1
end
it 'places the generated module after the class in the ancestor chain' do
ancestors = klass.ancestors
ext_index = ancestors.index { |a| a.is_a?(Hashie::Extensions::Mash::DefineAccessors::Ext) }
expect(ext_index).not_to be_nil
expect(ext_index).to be > ancestors.index(klass)
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s7/m1 · line 20 proven
- include mod + prepend mod
s0/m3 · line 56 proven
- mod.define_predicate(key, method_name) + mod.define_reader(key, method_name)
s3/m1 · line 77 proven
- self.[](method_name, &block) - else - self.[](key, &block) + self.[](key, &block) + else + self.[](method_name, &block)
s3/m2 · line 77 proven
- self.[](key, &block)
+ self.[]("#{key}_x", &block)s5/m2 · line 94 proven
- assign_property(key, value) + self[key] = value + self[:__writer_called] = true
s2/m2 · line 110 proven
- !!self[key] + !!self[method_name]
s2/m1 · line 113 proven
- !!self[key] + !self[key]
s1/m1 · line 127 proven
- if key? method_name - self.[](method_name) - else - initializing_reader(key) + if key? key + self.[](method_name) + else + initializing_reader(method_name)
s4/m2 · line 146 proven
- underbang_reader(key) + self.[](key)
require 'spec_helper'
require 'tmpdir'
require_relative '../../../lib/hashie/extensions/persistable.rb'
module PersistableCorralFixtures
class Hash < ::Hash
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::Persistable
end
end
describe Hashie::Extensions::Persistable do
let(:corral_subject) { PersistableCorralFixtures::Hash.new }
it 'copies string values rather than returning the same object' do
original = +'mutable'
result = corral_subject.send(:_persistable_data, original)
expect(result).to eq 'mutable'
expect(result).not_to be_equal(original)
end
it 'copies nested string values in hashes and arrays' do
inner = +'inner'
in_array = +'element'
data = { 'key' => inner, 'list' => [in_array] }
result = corral_subject.send(:_persistable_data, data)
expect(result).to eq data
expect(result['key']).not_to be_equal(inner)
expect(result['list'].first).not_to be_equal(in_array)
end
it 'does not let later mutation of the source leak into the prepared data' do
original = +'before'
result = corral_subject.send(:_persistable_data, original)
original << ' after'
expect(result).to eq 'before'
end
end
module PersistableCorralFixtures
class Store < ::Hash
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::Persistable
end
end
describe Hashie::Extensions::Persistable do
around do |example|
Dir.mktmpdir('hashie-corral') do |directory|
@corral_dir = directory
example.run
end
end
let(:corral_path) { File.join(@corral_dir, 'data.yaml') }
let(:corral_store) { PersistableCorralFixtures::Store.new(name: 'parent') }
it 'rejects a self-containing array with ArgumentError without touching the file' do
corral_store.save(corral_path)
original = File.read(corral_path)
list = []
list << list
corral_store[:list] = list
expect { corral_store.save }.to raise_error(ArgumentError, 'cannot persist cyclic data')
expect(File.read(corral_path)).to eq original
expect(Dir.children(@corral_dir)).to eq ['data.yaml']
end
it 'rejects an array cycle that passes through a nested array' do
outer = [[]]
outer[0] << outer
corral_store[:list] = outer
expect { corral_store.save(corral_path) }.to raise_error(ArgumentError, 'cannot persist cyclic data')
expect(File).not_to exist(corral_path)
end
it 'still saves repeated (non-cyclic) arrays' do
shared = [1, 2]
corral_store[:a] = shared
corral_store[:b] = [shared, shared]
corral_store.save(corral_path)
loaded = PersistableCorralFixtures::Store.load(corral_path)
expect(loaded[:b]).to eq [[1, 2], [1, 2]]
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s0/m4 · line 126 proven
- value.map { |child| _persistable_data(child, ancestors + [value]) }
+ value.map { |child| _persistable_data(child, ancestors) }s0/m3 · line 127 proven
- value.dup + value
s1/m1 · line 59 unproven
- data = Psych.safe_load(File.read(path), permitted_classes: [Symbol], aliases: false) + data = Psych.safe_load(File.read(path), permitted_classes: [Symbol], aliases: true)
require 'spec_helper'
RSpec.describe Hashie::Extensions::Mash::KeepOriginalKeys do
let(:corral_keeping_mash) do
Class.new(Hashie::Mash) do
include Hashie::Extensions::Mash::KeepOriginalKeys
end
end
it 'does not collide integer keys with stored string keys' do
mash = corral_keeping_mash.new('1' => 'string one')
expect(mash[1]).to be_nil
expect(mash['1']).to eq('string one')
end
it 'does not collide other key types with their string forms' do
mash = corral_keeping_mash.new('1.5' => 'float string', '' => 'empty')
expect(mash[1.5]).to be_nil
expect(mash[nil]).to be_nil
expect(mash['1.5']).to eq('float string')
end
it 'leaves non-string, non-symbol keys untouched when converting' do
mash = corral_keeping_mash.new({})
expect(mash.send(:_convert, 1)).to eql(1)
expect(mash.send(:_convert, 1.5)).to eql(1.5)
expect(mash.send(:_convert, nil)).to be_nil
end
it 'keeps integer and string keys distinct' do
mash = corral_keeping_mash.new(1 => 'int', '1' => 'str')
expect(mash[1]).to eq('int')
expect(mash['1']).to eq('str')
expect(mash.keys).to contain_exactly(1, '1')
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s0/m3 · line 51 proven
- else key + else key.to_s
require 'spec_helper'
require_relative '../../lib/hashie/extensions/key_conflict_warning.rb'
describe Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings do
let(:expected_message) do
'you cannot disable warnings on the base Mash class; ' \
'please subclass the Mash and disable it in the subclass'
end
it 'has the exact guidance message when constructed directly' do
error = described_class.new
expect(error).to be_a(StandardError)
expect(error.message).to eq expected_message
expect(error.message).to include('you cannot disable warnings on the base Mash class')
expect(error.message).to include('please subclass the Mash')
end
it 'raises with the guidance message when disabling on the base Mash' do
expect { Hashie::Mash.disable_warnings }.to raise_error(described_class, expected_message)
end
it 'raises with the guidance message when disabling specific methods on the base Mash' do
expect { Hashie::Mash.disable_warnings(:size) }.to raise_error(described_class, expected_message)
expect(Hashie::Mash.disable_warnings?).to be false
expect(Hashie::Mash.disabled_warnings).to be_empty
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
s2/m1 · line 22 proven
- 'you cannot disable warnings on the base Mash class; ' \ - 'please subclass the Mash and disable it in the subclass' + 'warnings were disabled on the base Mash class; ' \ + 'no further action is required'
require 'spec_helper'
require 'tempfile'
require_relative '../../../lib/hashie/extensions/parsers/yaml_erb_parser.rb'
class HashieCorralEvilObject
attr_accessor :name
end
describe Hashie::Extensions::Parsers::YamlErbParser do
describe 'corral: safe YAML loading after ERB evaluation' do
def corral_parse(content)
file = Tempfile.new(['corral', '.yml'])
file.write(content)
file.close
described_class.new(file.path).perform
ensure
file.close!
end
it 'refuses to deserialize arbitrary Ruby objects' do
content = "--- !ruby/object:HashieCorralEvilObject\nname: <%= 'x' %>\n"
result = nil
expect { result = corral_parse(content) }.to raise_error(Psych::Exception)
expect(result).not_to be_a(HashieCorralEvilObject)
end
it 'refuses to deserialize objects nested inside a hash' do
content = "key: !ruby/object:HashieCorralEvilObject\n name: y\n"
expect { corral_parse(content) }.to raise_error(Psych::Exception)
end
it 'still evaluates ERB and loads plain data' do
expect(corral_parse("count: <%= 1 + 2 %>\n")).to eq('count' => 3)
end
end
end
describe Hashie::Extensions::Parsers::YamlErbParser do
describe '#perform ignores environment-supplied templates (corral)' do
let(:env_key) { 'HASHIE_YAML_TEMPLATE' }
around do |example|
original = ENV[env_key]
begin
example.run
ensure
if original.nil?
ENV.delete(env_key)
else
ENV[env_key] = original
end
end
end
it 'only evaluates the file content, never an ENV-provided template' do
ENV[env_key] = "injected: <%= 1 + 1 %>\n"
file = Tempfile.new(['corral', '.yml'])
begin
file.write("foo: <%= 'from_file' %>\n")
file.close
result = described_class.new(file.path).perform
expect(result).to eq('foo' => 'from_file')
expect(result).not_to have_key('injected')
ensure
file.close!
end
end
end
end
describe Hashie::Extensions::Parsers::YamlErbParser do
describe '#perform safe loading (corral)' do
def corral_write_yaml(content)
file = Tempfile.new(['corral', '.yml'])
file.write(content)
file.close
file
end
it 'refuses to deserialize arbitrary Ruby objects from the YAML' do
instantiated = []
evil = Class.new do
define_method(:init_with) { |_coder| instantiated << true }
end
stub_const('HashieCorralEvil', evil)
file = corral_write_yaml("--- !ruby/object:HashieCorralEvil\nfoo: 1\n")
begin
expect { described_class.new(file.path).perform }.to raise_error(StandardError)
expect(instantiated).to be_empty
ensure
file.unlink
end
end
it 'refuses to deserialize arbitrary objects produced via ERB output' do
instantiated = []
evil = Class.new do
define_method(:init_with) { |_coder| instantiated << true }
end
stub_const('HashieCorralEvil2', evil)
file = corral_write_yaml("payload: !ruby/object:HashieCorralEvil2\n a: <%= 1 + 1 %>\n")
begin
expect { described_class.new(file.path).perform }.to raise_error(StandardError)
expect(instantiated).to be_empty
ensure
file.unlink
end
end
it 'still evaluates ERB and loads plain data' do
file = corral_write_yaml("count: <%= 1 + 2 %>\n")
begin
expect(described_class.new(file.path).perform).to eq('count' => 3)
ensure
file.unlink
end
end
end
end
# frozen_string_literal: true
describe Hashie::Extensions::Parsers::YamlErbParser do
describe '#perform ERB evaluation happens exactly once (corral)' do
let(:tempfile) do
file = Tempfile.new(['corral', '.yml'])
file.write(content)
file.close
file
end
after { tempfile.close! }
context 'when ERB output itself looks like an ERB tag' do
let(:content) do
# `<%%` is rendered by the first ERB pass as the literal text `<%`
"---\nfoo: '<%%= 1 + 2 %>'\n"
end
it 'does not evaluate the rendered output as ERB a second time' do
result = described_class.new(tempfile.path).perform
expect(result['foo']).to eq '<%= 1 + 2 %>'
end
end
context 'when ERB output contains code that would have side effects if re-evaluated' do
let(:content) do
"---\nfoo: '<%% $hashie_corral_marker = true %>'\n"
end
before { $hashie_corral_marker = nil }
after { $hashie_corral_marker = nil }
it 'never executes code produced by the first ERB pass' do
result = described_class.new(tempfile.path).perform
expect($hashie_corral_marker).to be_nil
expect(result['foo']).to eq '<% $hashie_corral_marker = true %>'
end
end
end
end
# --- corral: separate test file (unmergeable) — m5 ---
# lang: 2 authored parts each declare "corral_parse", which is not a test and cannot be renamed safely
require 'spec_helper'
require 'tempfile'
require 'yaml'
require_relative '../../../lib/hashie/extensions/parsers/yaml_erb_parser.rb'
class YamlErbCorralDangerousObject
attr_accessor :payload
end
describe Hashie::Extensions::Parsers::YamlErbParser do
describe 'corral: safe YAML loading after ERB' do
def corral_parse(content)
file = Tempfile.new(['corral', '.yml'])
file.write(content)
file.close
begin
described_class.new(file.path).perform
ensure
file.close!
end
end
it 'does not deserialize arbitrary Ruby objects from the YAML result' do
content = <<~YAML
---
evil: !ruby/object:YamlErbCorralDangerousObject
payload: <%= 1 + 1 %>
YAML
result = begin
corral_parse(content)
rescue Psych::Exception, ArgumentError, TypeError => e
e
end
expect(result).not_to be_a(YamlErbCorralDangerousObject)
if result.is_a?(Hash)
expect(result.values.none? { |v| v.is_a?(YamlErbCorralDangerousObject) }).to be true
else
expect(result).to be_a(Exception)
end
end
it 'rejects tagged ruby objects with a Psych error' do
content = "--- !ruby/object:YamlErbCorralDangerousObject\npayload: 1\n"
expect { corral_parse(content) }.to raise_error(Psych::Exception)
end
it 'still evaluates ERB before loading plain YAML' do
expect(corral_parse("count: <%= 2 * 3 %>\n")).to eq('count' => 6)
end
end
end
A proven fault was caught by a test corral wrote and ran; an unproven one was not, so it may be a missing test or a fault no test can see.
m2 · line 34 proven
- template = ERB.new(@content) + template = ERB.new(ENV['HASHIE_YAML_TEMPLATE'] || @content)
m5 · line 34 proven
- _yaml_safe_load(template.result) + Psych.respond_to?(:unsafe_load) ? Psych.unsafe_load(template.result) : Psych.load(template.result)
m1 · line 36 proven
- _yaml_safe_load(template.result) + YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(template.result) : YAML.load(template.result)
m3 · line 36 proven
- _yaml_safe_load(template.result) + YAML.parse(template.result).to_ruby
m4 · line 36 proven
- _yaml_safe_load(template.result) + _yaml_safe_load(ERB.new(template.result).result)