1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00
pry--pry/spec/config_spec.rb

265 lines
7.6 KiB
Ruby
Raw Normal View History

RSpec.describe Pry::Config do
describe ".from_hash" do
it "returns an object without a default" do
local = described_class.from_hash({})
expect(local.default).to eq(nil)
end
it "returns an object with a default" do
default = described_class.new(nil)
local = described_class.from_hash({}, default)
expect(local.default).to eq(local)
end
it "recursively creates Pry::Config objects from a Hash" do
h = {'foo1' => {'foo2' => {'foo3' => 'foobar'}}}
default = described_class.from_hash(h)
expect(default.foo1).to be_instance_of(described_class)
expect(default.foo1.foo2).to be_instance_of(described_class)
end
end
describe "bug #1552" do
specify "a local key has precendence over its default when the stored value is false" do
local = described_class.from_hash({}, described_class.from_hash('color' => true))
local.color = false
expect(local.color).to eq(false)
end
end
2015-07-27 11:10:13 -04:00
describe "bug #1277" do
specify "a local key has precendence over an inherited method of the same name" do
local = described_class.from_hash(output: 'foobar')
local.extend(Module.new { def output(); 'broken'; end })
2015-07-27 11:10:13 -04:00
expect(local.output).to eq('foobar')
end
end
describe "reserved keys" do
it "raises ReservedKeyError on assignment of a reserved key" do
local = described_class.new
local.instance_variable_get(:@reserved_keys).each do |key|
expect { local[key] = 1 }.to raise_error(described_class::ReservedKeyError)
2014-01-29 19:41:56 -05:00
end
end
end
2014-01-30 20:39:23 -05:00
describe "traversal to parent" do
it "traverses back to the parent when a local key is not found" do
local = described_class.new described_class.from_hash(foo: 1)
2015-03-10 16:49:29 -04:00
expect(local.foo).to eq(1)
2014-01-21 10:32:03 -05:00
end
2014-01-30 20:39:23 -05:00
it "stores a local key and prevents traversal to the parent" do
local = described_class.new described_class.from_hash(foo: 1)
2014-01-30 20:43:13 -05:00
local.foo = 2
2015-03-10 16:49:29 -04:00
expect(local.foo).to eq(2)
2014-01-21 10:32:03 -05:00
end
it "traverses through a chain of parents" do
root = described_class.from_hash({foo: 21})
local1 = described_class.new(root)
local2 = described_class.new(local1)
local3 = described_class.new(local2)
2015-03-10 16:49:29 -04:00
expect(local3.foo).to eq(21)
end
Fix Pry.config.hooks # => parent's hooks PROBLEM DESCRIPTION =================== The child caches a dup of the parent's hooks, so it does the correct thing after being accessed the first time. But the first time it is called, it doesn't return its cached duped value, it returns the parent's value. This causes 2 bugs: 1. The first hook declared will get "lost", because it is declared to the parent hooks, not the child's hooks. And after that first accessing, only the child's hooks will be used 2. The parent's hooks (which are probably the ones from [the default](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/config/default.rb#L35-37)) are going to get hooks declared on them that shouldn't be (well, presumably shouldn't be, given the explicit check for this case and duping to avoid it). So if another config object was initialized with the same parent, it would wind up with hooks that it didn't want/expect. I assume all the other keys work fine because they are value objects, so if anything wanted to change the value, it would reassign the key to the config object rather than mutating the returned value. Mutability *sigh* yallknowwhatimsayin SOLUTION DESCRIPTION ==================== Return the cached duped child value instead of the parent value. EXAMPLE ======= This code from [the docs](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/hooks.rb#L8-11) will not print "hello", because it's being declared to the parent hook. ```ruby Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` This code will, b/c it's being declared to the cached, duped child hook. ```ruby Pry.config.hooks Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` ISSUE IMPACT ============ Fixes #1254 Might fix or have something to do with these issues: * #1244 Totally thought I got it, but then couldn't reproduce again later and just don't understand how JRuby builds, so might just be a problem with how I was trying to reproduce it. * #1171 Maybe, depending on if Eclipse integrates with it using the hooks, alternatively, could be the same issue as the one above, they might both still be broken. Or both fixed and I can't figure out how to prove it. Dunno. OTHER THOUGHTS ============== I'm writing Github flavoured markdown, but I'm not actually sure if it will get rendered as such in the PR. Esp since git considers leading octothorpes as comments and not headers, and removes them... Guess we'll see... bottoms up, friends!
2014-07-20 18:04:51 -04:00
it "stores a local copy of the parents hooks upon accessing them" do
parent = described_class.from_hash(hooks: "parent_hooks")
local = described_class.new parent
Fix Pry.config.hooks # => parent's hooks PROBLEM DESCRIPTION =================== The child caches a dup of the parent's hooks, so it does the correct thing after being accessed the first time. But the first time it is called, it doesn't return its cached duped value, it returns the parent's value. This causes 2 bugs: 1. The first hook declared will get "lost", because it is declared to the parent hooks, not the child's hooks. And after that first accessing, only the child's hooks will be used 2. The parent's hooks (which are probably the ones from [the default](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/config/default.rb#L35-37)) are going to get hooks declared on them that shouldn't be (well, presumably shouldn't be, given the explicit check for this case and duping to avoid it). So if another config object was initialized with the same parent, it would wind up with hooks that it didn't want/expect. I assume all the other keys work fine because they are value objects, so if anything wanted to change the value, it would reassign the key to the config object rather than mutating the returned value. Mutability *sigh* yallknowwhatimsayin SOLUTION DESCRIPTION ==================== Return the cached duped child value instead of the parent value. EXAMPLE ======= This code from [the docs](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/hooks.rb#L8-11) will not print "hello", because it's being declared to the parent hook. ```ruby Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` This code will, b/c it's being declared to the cached, duped child hook. ```ruby Pry.config.hooks Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` ISSUE IMPACT ============ Fixes #1254 Might fix or have something to do with these issues: * #1244 Totally thought I got it, but then couldn't reproduce again later and just don't understand how JRuby builds, so might just be a problem with how I was trying to reproduce it. * #1171 Maybe, depending on if Eclipse integrates with it using the hooks, alternatively, could be the same issue as the one above, they might both still be broken. Or both fixed and I can't figure out how to prove it. Dunno. OTHER THOUGHTS ============== I'm writing Github flavoured markdown, but I'm not actually sure if it will get rendered as such in the PR. Esp since git considers leading octothorpes as comments and not headers, and removes them... Guess we'll see... bottoms up, friends!
2014-07-20 18:04:51 -04:00
local.hooks.gsub! 'parent', 'local'
2015-03-10 16:49:29 -04:00
expect(local.hooks).to eq 'local_hooks'
expect(parent.hooks).to eq('parent_hooks')
Fix Pry.config.hooks # => parent's hooks PROBLEM DESCRIPTION =================== The child caches a dup of the parent's hooks, so it does the correct thing after being accessed the first time. But the first time it is called, it doesn't return its cached duped value, it returns the parent's value. This causes 2 bugs: 1. The first hook declared will get "lost", because it is declared to the parent hooks, not the child's hooks. And after that first accessing, only the child's hooks will be used 2. The parent's hooks (which are probably the ones from [the default](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/config/default.rb#L35-37)) are going to get hooks declared on them that shouldn't be (well, presumably shouldn't be, given the explicit check for this case and duping to avoid it). So if another config object was initialized with the same parent, it would wind up with hooks that it didn't want/expect. I assume all the other keys work fine because they are value objects, so if anything wanted to change the value, it would reassign the key to the config object rather than mutating the returned value. Mutability *sigh* yallknowwhatimsayin SOLUTION DESCRIPTION ==================== Return the cached duped child value instead of the parent value. EXAMPLE ======= This code from [the docs](https://github.com/pry/pry/blob/0ff6fa61d4197feace48b422b5e3d3fb5948cbdd/lib/pry/hooks.rb#L8-11) will not print "hello", because it's being declared to the parent hook. ```ruby Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` This code will, b/c it's being declared to the cached, duped child hook. ```ruby Pry.config.hooks Pry.config.hooks.add_hook(:before_session, :say_hi) { puts "hello" } ``` ISSUE IMPACT ============ Fixes #1254 Might fix or have something to do with these issues: * #1244 Totally thought I got it, but then couldn't reproduce again later and just don't understand how JRuby builds, so might just be a problem with how I was trying to reproduce it. * #1171 Maybe, depending on if Eclipse integrates with it using the hooks, alternatively, could be the same issue as the one above, they might both still be broken. Or both fixed and I can't figure out how to prove it. Dunno. OTHER THOUGHTS ============== I'm writing Github flavoured markdown, but I'm not actually sure if it will get rendered as such in the PR. Esp since git considers leading octothorpes as comments and not headers, and removes them... Guess we'll see... bottoms up, friends!
2014-07-20 18:04:51 -04:00
end
2014-01-21 10:32:03 -05:00
end
describe "#respond_to_missing?" do
before do
@config = described_class.new(nil)
end
it "returns a Method object for a dynamic key" do
@config["key"] = 1
method_obj = @config.method(:key)
2015-03-10 16:49:29 -04:00
expect(method_obj.name).to eq :key
expect(method_obj.call).to eq(1)
end
it "returns a Method object for a setter on a parent" do
config = described_class.from_hash({}, described_class.from_hash(foo: 1))
expect(config.method(:foo=)).to be_an_instance_of(Method)
end
end
describe "#respond_to?" do
before do
@config = described_class.new(nil)
end
it "returns true for a local key" do
@config.zzfoo = 1
2015-03-10 16:49:29 -04:00
expect(@config.respond_to?(:zzfoo)).to eq(true)
end
it "returns false for an unknown key" do
2015-03-10 16:49:29 -04:00
expect(@config.respond_to?(:blahblah)).to eq(false)
end
end
2014-03-14 00:40:09 -04:00
describe "#default" do
it "returns nil" do
local = described_class.new(nil)
2015-03-10 16:49:29 -04:00
expect(local.default).to eq(nil)
2014-03-14 00:40:09 -04:00
end
it "returns the default" do
default = described_class.new(nil)
local = described_class.new(default)
2015-03-10 16:49:29 -04:00
expect(local.default).to eq(default)
2014-03-14 00:40:09 -04:00
end
end
2014-02-02 17:23:23 -05:00
describe "#keys" do
it "returns an array of local keys" do
root = described_class.from_hash({zoo: "boo"}, nil)
local = described_class.from_hash({foo: "bar"}, root)
2015-03-10 16:49:29 -04:00
expect(local.keys).to eq(["foo"])
2014-02-02 17:23:23 -05:00
end
end
2014-02-02 08:52:55 -05:00
describe "#==" do
it "compares equality through the underlying lookup table" do
local1 = described_class.new(nil)
local2 = described_class.new(nil)
2014-02-02 08:52:55 -05:00
local1.foo = "hi"
local2.foo = "hi"
2015-03-10 16:49:29 -04:00
expect(local1).to eq(local2)
2014-02-02 08:52:55 -05:00
end
it "compares equality against an object who does not implement #to_hash" do
local1 = described_class.new(nil)
2015-03-10 16:49:29 -04:00
expect(local1).not_to eq(Object.new)
end
it "returns false when compared against nil" do
# rubocop:disable Style/NilComparison
expect(described_class.new(nil) == nil).to eq(false)
# rubocop:enable Style/NilComparison
end
2014-02-02 08:52:55 -05:00
end
2014-01-31 08:18:32 -05:00
describe "#forget" do
it "forgets a local key" do
local = described_class.new described_class.from_hash(foo: 1)
2014-01-31 08:18:32 -05:00
local.foo = 2
2015-03-10 16:49:29 -04:00
expect(local.foo).to eq 2
2014-01-31 08:18:32 -05:00
local.forget(:foo)
2015-03-10 16:49:29 -04:00
expect(local.foo).to eq(1)
2014-01-31 08:18:32 -05:00
end
end
describe "#to_hash" do
it "provides a copy of local key & value pairs as a Hash" do
local = described_class.new described_class.from_hash(bar: true)
2014-01-31 08:18:32 -05:00
local.foo = "21"
2015-03-10 16:49:29 -04:00
expect(local.to_hash).to eq({ "foo" => "21" })
2014-01-31 08:18:32 -05:00
end
it "returns a duplicate of the lookup table" do
local = described_class.new(nil)
local.to_hash.merge!("foo" => 42)
2015-03-10 16:49:29 -04:00
expect(local.foo).not_to eq(42)
end
2014-01-31 08:18:32 -05:00
end
describe "#merge!" do
before do
@config = described_class.new(nil)
2014-01-31 08:18:32 -05:00
end
it "merges an object who returns a Hash through #to_hash" do
obj = Class.new { def to_hash() {epoch: 1} end }.new
@config.merge!(obj)
2015-03-10 16:49:29 -04:00
expect(@config.epoch).to eq(1)
end
it "merges an object who returns a Hash through #to_h" do
obj = Class.new { def to_h() {epoch: 2} end }.new
@config.merge!(obj)
2015-03-10 16:49:29 -04:00
expect(@config.epoch).to eq(2)
end
it "merges a Hash" do
@config.merge!(epoch: 420)
2015-03-10 16:49:29 -04:00
expect(@config.epoch).to eq(420)
end
it "raises a TypeError for objects who can't become a Hash" do
expect { @config.merge!(Object.new) }.to raise_error TypeError
2014-01-31 08:18:32 -05:00
end
end
2014-03-14 22:05:19 -04:00
describe "#clear" do
before do
@local = described_class.new(nil)
2014-03-14 22:05:19 -04:00
end
it "returns true" do
2015-03-10 16:49:29 -04:00
expect(@local.clear).to eq(true)
2014-03-14 22:05:19 -04:00
end
it "clears local assignments" do
@local.foo = 1
@local.clear
2015-03-10 16:49:29 -04:00
expect(@local.to_hash).to eq({})
2014-03-14 22:05:19 -04:00
end
end
2014-01-30 20:39:23 -05:00
describe "#[]=" do
it "stores keys as strings" do
local = described_class.from_hash({})
2014-01-30 20:39:23 -05:00
local[:zoo] = "hello"
2015-03-10 16:49:29 -04:00
expect(local.to_hash).to eq({ "zoo" => "hello" })
2014-01-21 10:32:03 -05:00
end
end
describe "#[]" do
it "traverses back to a default" do
default = described_class.from_hash({k: 1})
local = described_class.new(default)
expect(local['k']).to eq(1)
end
it "traverses back to a default (2 deep)" do
default1 = described_class.from_hash({k: 1})
default2 = described_class.from_hash({}, default1)
local = described_class.new(default2)
expect(local['k']).to eq(1)
end
it "traverses back to a default that doesn't exist, and returns nil" do
local = described_class.from_hash({}, nil)
expect(local['output']).to eq(nil)
end
context "when returning a Pry::Config::Lazy object" do
it "invokes #call on it" do
c = described_class.from_hash foo: Pry.lazy { 10 }
expect(c['foo']).to eq(10)
end
it "invokes #call upon each access" do
c = described_class.from_hash foo: Pry.lazy { 'foo' }
expect(c['foo']).to_not equal(c['foo'])
end
end
context "when returning an instance of BasicObject" do
it "returns without raising an error" do
c = described_class.from_hash(foo: BasicObject.new)
expect(BasicObject === c['foo']).to be(true)
end
end
end
2014-01-21 10:32:03 -05:00
end