mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Implement arguments case equality
Implements https://github.com/icy-arctic-fox/spectator/issues/47 Some specs are failing and need to be resolved before the new feature is considered done.
This commit is contained in:
parent
0177a678f9
commit
e2130d12d3
3 changed files with 205 additions and 65 deletions
|
@ -11,7 +11,7 @@ Spectator.describe "GitHub Issue #47" do
|
||||||
|
|
||||||
let(fake) { mock(Original) }
|
let(fake) { mock(Original) }
|
||||||
|
|
||||||
xspecify do
|
specify do
|
||||||
expect(fake).to receive(:foo).with("arg1", arg2: "arg2")
|
expect(fake).to receive(:foo).with("arg1", arg2: "arg2")
|
||||||
fake.foo("arg1", "arg2")
|
fake.foo("arg1", "arg2")
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,18 +20,61 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#[]" do
|
describe "#[](index)" do
|
||||||
context "with an index" do
|
|
||||||
it "returns a positional argument" do
|
it "returns a positional argument" do
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
expect(arguments[0]).to eq(42)
|
expect(arguments[0]).to eq(42)
|
||||||
expect(arguments[1]).to eq("foo")
|
expect(arguments[1]).to eq("foo")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns splat arguments" do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(arguments[2]).to eq(:x)
|
||||||
|
expect(arguments[3]).to eq(:y)
|
||||||
|
expect(arguments[4]).to eq(:z)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a symbol" do
|
context "with named positional arguments" do
|
||||||
it "returns a named argument" do
|
subject(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
|
||||||
|
it "returns a positional argument" do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(arguments[0]).to eq(42)
|
||||||
|
expect(arguments[1]).to eq("foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns splat arguments" do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(arguments[2]).to eq(:x)
|
||||||
|
expect(arguments[3]).to eq(:y)
|
||||||
|
expect(arguments[4]).to eq(:z)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#[](symbol)" do
|
||||||
|
it "returns a keyword argument" do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(arguments[:bar]).to eq("baz")
|
||||||
|
expect(arguments[:qux]).to eq(123)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with named positional arguments" do
|
||||||
|
subject(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
|
||||||
|
it "returns a positional argument" do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(arguments[:arg1]).to eq(42)
|
||||||
|
expect(arguments[:arg2]).to eq("foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a keyword argument" do
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
expect(arguments[:bar]).to eq("baz")
|
expect(arguments[:bar]).to eq("baz")
|
||||||
expect(arguments[:qux]).to eq(123)
|
expect(arguments[:qux]).to eq(123)
|
||||||
|
@ -62,33 +105,57 @@ Spectator.describe Spectator::Arguments do
|
||||||
context "with equal arguments" do
|
context "with equal arguments" do
|
||||||
let(other) { arguments }
|
let(other) { arguments }
|
||||||
|
|
||||||
it "returns true" do
|
it { is_expected.to be_true }
|
||||||
is_expected.to be_true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different arguments" do
|
context "with different arguments" do
|
||||||
let(other) { Spectator::Arguments.new({123, :foo, "bar"}, nil, nil, {opt: "foobar"}) }
|
let(other) { Spectator::Arguments.new({123, :foo, "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) }
|
||||||
|
|
||||||
it "returns false" do
|
it { is_expected.to be_false }
|
||||||
is_expected.to be_false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the same kwargs in a different order" do
|
context "with the same kwargs in a different order" do
|
||||||
let(other) { Spectator::Arguments.new(arguments.args, nil, nil, {qux: 123, bar: "baz"}) }
|
let(other) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: 123, bar: "baz"}) }
|
||||||
|
|
||||||
it "returns true" do
|
it { is_expected.to be_true }
|
||||||
is_expected.to be_true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a missing kwarg" do
|
context "with a missing kwarg" do
|
||||||
let(other) { Spectator::Arguments.new(arguments.args, nil, nil, {bar: "baz"}) }
|
let(other) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz"}) }
|
||||||
|
|
||||||
it "returns false" do
|
it { is_expected.to be_false }
|
||||||
is_expected.to be_false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with an extra kwarg" do
|
||||||
|
let(other) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz", qux: 123, extra: 0}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with different splat arguments" do
|
||||||
|
let(other) { Spectator::Arguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with mixed positional tuple types" do
|
||||||
|
let(other) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, arguments.splat_name, arguments.splat, arguments.kwargs) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with mixed positional tuple types (flipped)" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(other) { Spectator::Arguments.new({42, "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with args spilling over into splat and mixed positional tuple types" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(other) { Spectator::Arguments.new({42, "foo", :x, :y, :z}, nil, nil, {bar: "baz", qux: 123}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -98,49 +165,96 @@ Spectator.describe Spectator::Arguments do
|
||||||
context "with equal arguments" do
|
context "with equal arguments" do
|
||||||
let(pattern) { arguments }
|
let(pattern) { arguments }
|
||||||
|
|
||||||
it "returns true" do
|
it { is_expected.to be_true }
|
||||||
is_expected.to be_true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with matching arguments" do
|
||||||
|
let(pattern) { Spectator::Arguments.new({Int32, /foo/}, :splat, {Symbol, Symbol, :z}, {bar: /baz/, qux: Int32}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-matching arguments" do
|
||||||
|
let(pattern) { Spectator::Arguments.new({Float64, /bar/}, :splat, {String, Int32, :x}, {bar: /foo/, qux: "123"}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different arguments" do
|
context "with different arguments" do
|
||||||
let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, nil, nil, {opt: "foobar"}) }
|
let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) }
|
||||||
|
|
||||||
it "returns false" do
|
it { is_expected.to be_false }
|
||||||
is_expected.to be_false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the same kwargs in a different order" do
|
context "with the same kwargs in a different order" do
|
||||||
let(pattern) { Spectator::Arguments.new(arguments.args, nil, nil, {qux: 123, bar: "baz"}) }
|
let(pattern) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: Int32, bar: /baz/}) }
|
||||||
|
|
||||||
it "returns true" do
|
it { is_expected.to be_true }
|
||||||
is_expected.to be_true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with an additional kwarg" do
|
||||||
|
let(pattern) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/}) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a missing kwarg" do
|
context "with a missing kwarg" do
|
||||||
let(pattern) { Spectator::Arguments.new(arguments.args, nil, nil, {bar: "baz"}) }
|
let(pattern) { Spectator::Arguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/, qux: Int32, extra: 0}) }
|
||||||
|
|
||||||
it "returns false" do
|
it { is_expected.to be_false }
|
||||||
is_expected.to be_false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with matching types and regex" do
|
context "with different splat arguments" do
|
||||||
let(pattern) { Spectator::Arguments.new({Int32, /foo/}, nil, nil, {bar: String, qux: 123}) }
|
let(pattern) { Spectator::Arguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) }
|
||||||
|
|
||||||
it "returns true" do
|
it { is_expected.to be_false }
|
||||||
is_expected.to be_true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different types and regex" do
|
context "with matching mixed positional tuple types" do
|
||||||
let(pattern) { Spectator::Arguments.new({Symbol, /bar/}, nil, nil, {bar: String, qux: 42}) }
|
let(pattern) { Spectator::Arguments.new({arg1: Int32, arg2: /foo/}, arguments.splat_name, arguments.splat, arguments.kwargs) }
|
||||||
|
|
||||||
it "returns false" do
|
it { is_expected.to be_true }
|
||||||
is_expected.to be_false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with non-matching mixed positional tuple types" do
|
||||||
|
let(pattern) { Spectator::Arguments.new({arg1: Float64, arg2: /bar/}, arguments.splat_name, arguments.splat, arguments.kwargs) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with matching args spilling over into splat and mixed positional tuple types" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(pattern) { Spectator::Arguments.capture(Int32, /foo/, Symbol, Symbol, :z, bar: /baz/, qux: Int32) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-matching args spilling over into splat and mixed positional tuple types" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(pattern) { Spectator::Arguments.capture(Float64, /bar/, Symbol, String, :z, bar: /foo/, qux: Int32) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with matching mixed named positional and keyword arguments" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(pattern) { Spectator::Arguments.capture(/foo/, Symbol, :y, Symbol, arg1: Int32, bar: /baz/, qux: 123) }
|
||||||
|
|
||||||
|
it { is_expected.to be_true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-matching mixed named positional and keyword arguments" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(pattern) { Spectator::Arguments.capture(5, Symbol, :z, Symbol, arg2: /foo/, bar: /baz/, qux: Int32) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-matching mixed named positional and keyword arguments" do
|
||||||
|
let(arguments) { Spectator::Arguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
|
let(pattern) { Spectator::Arguments.capture(/bar/, String, :y, Symbol, arg1: 0, bar: /foo/, qux: Float64) }
|
||||||
|
|
||||||
|
it { is_expected.to be_false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -74,6 +74,15 @@ module Spectator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns all named positional and keyword arguments as a named tuple.
|
||||||
|
def named : NamedTuple
|
||||||
|
{% if Args < NamedTuple %}
|
||||||
|
args.merge(kwargs)
|
||||||
|
{% else %}
|
||||||
|
kwargs
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
|
||||||
# Constructs a string representation of the arguments.
|
# Constructs a string representation of the arguments.
|
||||||
def to_s(io : IO) : Nil
|
def to_s(io : IO) : Nil
|
||||||
return io << "(no args)" if args.empty? && ((splat = @splat).nil? || splat.empty?) && kwargs.empty?
|
return io << "(no args)" if args.empty? && ((splat = @splat).nil? || splat.empty?) && kwargs.empty?
|
||||||
|
@ -127,31 +136,48 @@ module Spectator
|
||||||
|
|
||||||
# Checks if another set of arguments matches this set of arguments.
|
# Checks if another set of arguments matches this set of arguments.
|
||||||
def ===(other : Arguments)
|
def ===(other : Arguments)
|
||||||
{% if Args < NamedTuple %}
|
self_args = args
|
||||||
if (other_args = other.args).is_a?(NamedTuple)
|
other_args = other.args
|
||||||
args.each do |k, v|
|
|
||||||
return false unless other_args.has_key?(k)
|
case {self_args, other_args}
|
||||||
return false unless v === other_args[k]
|
when {Tuple, Tuple} then compare(positional, other.positional, kwargs, other.kwargs)
|
||||||
end
|
when {Tuple, NamedTuple} then compare(kwargs, other.named, positional, other_args, other.splat)
|
||||||
|
when {NamedTuple, Tuple} then compare(positional, other.positional, kwargs, other.kwargs)
|
||||||
else
|
else
|
||||||
return false if args.size != other_args
|
self_args === other_args && (!splat || splat === other.splat) && compare_named_tuples(kwargs, other.kwargs)
|
||||||
args.each_with_index do |k, v, i|
|
|
||||||
return false unless v === other_args.unsafe_fetch(i)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{% else %}
|
|
||||||
return false unless args === other.positional
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
if splat = @splat
|
|
||||||
return false unless splat === other.splat
|
|
||||||
end
|
|
||||||
|
|
||||||
kwargs.each do |k, v|
|
private def compare(self_positional : Tuple, other_positional : Tuple, self_kwargs : NamedTuple, other_kwargs : NamedTuple)
|
||||||
return false unless other.kwargs.has_key?(k)
|
self_positional === other_positional && compare_named_tuples(self_kwargs, other_kwargs)
|
||||||
return false unless v === other.kwargs[k]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private def compare(self_kwargs : NamedTuple, other_named : NamedTuple, self_positional : Tuple, other_args : NamedTuple, other_splat : Tuple?)
|
||||||
|
return false unless compare_named_tuples(self_kwargs, other_named)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
other_args.each do |k, v2|
|
||||||
|
next if self_kwargs.has_key?(k) # Covered by named arguments.
|
||||||
|
|
||||||
|
v1 = self_positional.fetch(i) { return false }
|
||||||
|
i += 1
|
||||||
|
return false unless v1 === v2
|
||||||
|
end
|
||||||
|
|
||||||
|
other_splat.try &.each do |v2|
|
||||||
|
v1 = self_positional.fetch(i) { return false }
|
||||||
|
i += 1
|
||||||
|
return false unless v1 === v2
|
||||||
|
end
|
||||||
|
|
||||||
|
i == self_positional.size
|
||||||
|
end
|
||||||
|
|
||||||
|
private def compare_named_tuples(a : NamedTuple, b : NamedTuple)
|
||||||
|
a.each do |k, v1|
|
||||||
|
v2 = b.fetch(k) { return false }
|
||||||
|
return false unless v1 === v2
|
||||||
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue