From 9ac612120108ec3e73e9ce37e1dfd08d49a715ce Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 1 Oct 2020 18:23:28 -0600 Subject: [PATCH 1/5] New stub syntax to support operator methods Works like: stub :[], index : Int32 { 42 } Addresses https://github.com/icy-arctic-fox/spectator/issues/14 --- src/spectator/mocks/double.cr | 17 ++++++++++++++++- src/spectator/mocks/stubs.cr | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/spectator/mocks/double.cr b/src/spectator/mocks/double.cr index ef17e84..cd11aea 100644 --- a/src/spectator/mocks/double.cr +++ b/src/spectator/mocks/double.cr @@ -7,7 +7,7 @@ module Spectator::Mocks def initialize(@spectator_double_name : String, @null = false) end - private macro stub(definition, &block) + private macro stub(definition, *types, &block) {% name = nil params = nil @@ -44,6 +44,21 @@ module Spectator::Mocks params = [] of MacroId args = [] of MacroId body = block + elsif definition.is_a?(SymbolLiteral) # stub :foo, arg : Int32 + name = definition.id + named = false + params = types + if params.last.is_a?(Call) + body = params.last.block + params[-1] = params.last.name + end + args = params.map do |p| + n = p.is_a?(TypeDeclaration) ? p.var : p.id + r = named ? "#{n}: #{n}".id : n + named = true if n.starts_with?('*') + r + end + body = block unless body else raise "Unrecognized stub format" end diff --git a/src/spectator/mocks/stubs.cr b/src/spectator/mocks/stubs.cr index d18c3ab..23a54ee 100644 --- a/src/spectator/mocks/stubs.cr +++ b/src/spectator/mocks/stubs.cr @@ -1,6 +1,6 @@ module Spectator::Mocks module Stubs - private macro stub(definition, _file = __FILE__, _line = __LINE__, &block) + private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, &block) {% receiver = nil name = nil @@ -30,6 +30,21 @@ module Spectator::Mocks params = [] of MacroId args = [] of MacroId body = block + elsif definition.is_a?(SymbolLiteral) # stub :foo, arg : Int32 + name = definition.id + named = false + params = types + if params.last.is_a?(Call) + body = params.last.block + params[-1] = params.last.name + end + args = params.map do |p| + n = p.is_a?(TypeDeclaration) ? p.var : p.id + r = named ? "#{n}: #{n}".id : n + named = true if n.starts_with?('*') + r + end + body = block unless body else raise "Unrecognized stub format" end From 2cdee0716fb830e95c274afdbaea4a889fcf5bcf Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 1 Oct 2020 18:25:29 -0600 Subject: [PATCH 2/5] Bump version to 0.9.27 --- shard.yml | 2 +- src/spectator.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shard.yml b/shard.yml index b7f2fc9..2e63a30 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: spectator -version: 0.9.26 +version: 0.9.27 description: | A feature-rich spec testing framework for Crystal with similarities to RSpec. diff --git a/src/spectator.cr b/src/spectator.cr index a188d08..c5e314d 100644 --- a/src/spectator.cr +++ b/src/spectator.cr @@ -6,7 +6,7 @@ module Spectator extend self # Current version of the Spectator library. - VERSION = "0.9.26" + VERSION = "0.9.27" # Top-level describe method. # All specs in a file must be wrapped in this call. From 8966c0976b2e0013c54c0fa15785e60707aa2992 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 7 Nov 2020 10:56:33 -0700 Subject: [PATCH 3/5] Allow manual specification of return type --- src/spectator/mocks/double.cr | 12 +++++++----- src/spectator/mocks/stubs.cr | 8 ++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/spectator/mocks/double.cr b/src/spectator/mocks/double.cr index cd11aea..2bdbbd4 100644 --- a/src/spectator/mocks/double.cr +++ b/src/spectator/mocks/double.cr @@ -7,7 +7,7 @@ module Spectator::Mocks def initialize(@spectator_double_name : String, @null = false) end - private macro stub(definition, *types, &block) + private macro stub(definition, *types, return_type = :undefined, &block) {% name = nil params = nil @@ -64,7 +64,7 @@ module Spectator::Mocks end %} - def {{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) ::Spectator::Harness.current.mocks.record_call(self, %call) @@ -75,7 +75,7 @@ module Spectator::Mocks end end - def {{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) ::Spectator::Harness.current.mocks.record_call(self, %call) @@ -88,7 +88,7 @@ module Spectator::Mocks end end - def %method({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def %method({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} {% if body && !body.is_a?(Nop) %} {{body.body}} {% else %} @@ -99,7 +99,9 @@ module Spectator::Mocks end # This code shouldn't be reached, but makes the compiler happy to have a matching return type. - {% if definition.is_a?(TypeDeclaration) %} + {% if return_type != :undefined %} + %x = uninitialized {{return_type}} + {% elsif definition.is_a?(TypeDeclaration) %} %x = uninitialized {{definition.type}} {% else %} nil diff --git a/src/spectator/mocks/stubs.cr b/src/spectator/mocks/stubs.cr index 23a54ee..86bcd4c 100644 --- a/src/spectator/mocks/stubs.cr +++ b/src/spectator/mocks/stubs.cr @@ -1,6 +1,6 @@ module Spectator::Mocks module Stubs - private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, &block) + private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, return_type = :undefined, &block) {% receiver = nil name = nil @@ -65,12 +65,12 @@ module Spectator::Mocks %} {% if body && !body.is_a?(Nop) %} - def {{receiver}}%method({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}%method({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} {{body.body}} end {% end %} - def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}{{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} if (%harness = ::Spectator::Harness.current?) %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) @@ -89,7 +89,7 @@ module Spectator::Mocks end end - def {{receiver}}{{name}}({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}{{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} if (%harness = ::Spectator::Harness.current?) %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) From b91103b40a51ad5b1e39c7a2f3ce134c469fb6c5 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 7 Nov 2020 11:04:03 -0700 Subject: [PATCH 4/5] Handle case where block is provided with return type --- src/spectator/mocks/double.cr | 8 +++++--- src/spectator/mocks/stubs.cr | 14 +++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/spectator/mocks/double.cr b/src/spectator/mocks/double.cr index 2bdbbd4..9684aed 100644 --- a/src/spectator/mocks/double.cr +++ b/src/spectator/mocks/double.cr @@ -64,7 +64,7 @@ module Spectator::Mocks end %} - def {{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) ::Spectator::Harness.current.mocks.record_call(self, %call) @@ -75,7 +75,7 @@ module Spectator::Mocks end end - def {{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) ::Spectator::Harness.current.mocks.record_call(self, %call) @@ -88,9 +88,11 @@ module Spectator::Mocks end end - def %method({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def %method({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} {% if body && !body.is_a?(Nop) %} {{body.body}} + {% elsif return_type.is_a?(ArrayLiteral) %} + {{return_type.splat}} {% else %} %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) diff --git a/src/spectator/mocks/stubs.cr b/src/spectator/mocks/stubs.cr index 86bcd4c..8783057 100644 --- a/src/spectator/mocks/stubs.cr +++ b/src/spectator/mocks/stubs.cr @@ -65,12 +65,16 @@ module Spectator::Mocks %} {% if body && !body.is_a?(Nop) %} - def {{receiver}}%method({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}%method({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} {{body.body}} end + {% elsif return_type.is_a?(ArrayLiteral) %} + def {{receiver}}%method({{params.splat}}) : {{return_type.type}} + {{return_type.splat}} + end {% end %} - def {{receiver}}{{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}{{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} if (%harness = ::Spectator::Harness.current?) %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) @@ -79,7 +83,7 @@ module Spectator::Mocks return %stub.call!(%args) { {{original}} } end - {% if body && !body.is_a?(Nop) %} + {% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %} %method({{args.splat}}) {% else %} {{original}} @@ -89,7 +93,7 @@ module Spectator::Mocks end end - def {{receiver}}{{name}}({{params.splat}}){% if return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + def {{receiver}}{{name}}({{params.splat}}){% if return_type.is_a?(ArrayLiteral) %} : {{return_type.type}}{% elsif return_type != :undefined %} : {{return_type.id}}{% elsif definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} if (%harness = ::Spectator::Harness.current?) %args = ::Spectator::Mocks::GenericArguments.create({{args.splat}}) %call = ::Spectator::Mocks::MethodCall.new({{name.symbolize}}, %args) @@ -98,7 +102,7 @@ module Spectator::Mocks return %stub.call!(%args) { {{original}} { |*%ya| yield *%ya } } end - {% if body && !body.is_a?(Nop) %} + {% if body && !body.is_a?(Nop) || return_type.is_a?(ArrayLiteral) %} %method({{args.splat}}) { |*%ya| yield *%ya } {% else %} {{original}} do |*%yield_args| From 27754c9e03331fe4874e0291c2872f7692272e2c Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 7 Nov 2020 11:04:34 -0700 Subject: [PATCH 5/5] Bump version to 0.9.28 --- shard.yml | 2 +- src/spectator.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shard.yml b/shard.yml index 2e63a30..f1d5883 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: spectator -version: 0.9.27 +version: 0.9.28 description: | A feature-rich spec testing framework for Crystal with similarities to RSpec. diff --git a/src/spectator.cr b/src/spectator.cr index c5e314d..3ceaad6 100644 --- a/src/spectator.cr +++ b/src/spectator.cr @@ -6,7 +6,7 @@ module Spectator extend self # Current version of the Spectator library. - VERSION = "0.9.27" + VERSION = "0.9.28" # Top-level describe method. # All specs in a file must be wrapped in this call.