Code Monkey home page Code Monkey logo

spectator's People

Contributors

davidepaolotua avatar icy-arctic-fox avatar matthewmcgarvey avatar stufro avatar toddsundsted avatar watzon avatar yb66 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

spectator's Issues

Getting `Channel::ClosedError: Channel is closed` after v0.9.36

Seems the removal of a workaround in v0.9.36 is causing problems. Removing require "openssl" is causing some applications to get errors.

One instance is NeuraLegion/sslscan.cr#7

The error is:

Error: Channel is closed

Channel::ClosedError: Channel is closed
  /usr/share/crystal/src/channel.cr:228:8 in 'send'
  /usr/share/crystal/src/log/dispatch.cr:55:7 in 'dispatch'
  /usr/share/crystal/src/log/backend.cr:24:5 in 'dispatch'
  /usr/share/crystal/src/log/log.cr:36:3 in 'initialize'
  src/sslscan/report.cr:68:5 in 'new'
  src/sslscan/scanner.cr:25:7 in 'scan'
  spec/sslscan/scanner_spec.cr:47:16 in '->'
  /usr/share/crystal/src/primitives.cr:255:3 in 'value'
  lib/spectator/src/spectator/matchers/type_matcher.cr:21:7 in 'match?'
  lib/spectator/src/spectator/matchers/standard_matcher.cr:27:10 in 'match'
  lib/spectator/src/spectator/expectations/expectation_partial.cr:23:20 in 'to'
  spec/sslscan/scanner_spec.cr:47:7 in '__temp_68'
  spec/sslscan/scanner_spec.cr:45:1 in '->'
  /usr/share/crystal/src/primitives.cr:255:3 in 'call'
  lib/spectator/src/spectator/test_wrapper.cr:27:7 in 'run'
  lib/spectator/src/spectator/test_wrapper.cr:39:47 in '->'
  /usr/share/crystal/src/primitives.cr:255:3 in 'run_example'
  lib/spectator/src/spectator/runnable_example.cr:21:9 in 'capture_result'
  lib/spectator/src/spectator/runnable_example.cr:10:7 in 'run_impl'
  lib/spectator/src/spectator/example.cr:48:7 in 'run'
  lib/spectator/src/spectator/harness.cr:31:7 in 'run'
  lib/spectator/src/spectator/runner.cr:63:18 in 'run_example'
  lib/spectator/src/spectator/runner.cr:38:9 in 'collect_results'
  /usr/share/crystal/src/time.cr:356:5 in 'run'
  lib/spectator/src/spectator.cr:105:5 in 'run'
  lib/spectator/src/spectator.cr:79:29 in '->'
  /usr/share/crystal/src/primitives.cr:255:3 in 'run'
  /usr/share/crystal/src/crystal/main.cr:45:14 in 'main'
  /usr/share/crystal/src/crystal/main.cr:119:3 in 'main'
  __libc_start_main
  _start
  ???

Changing the spec_helper.cr file to include the src/ directory or openssl before Spectator fixes the issue. However, this shouldn't be an issue at all.

Noticed line/column skew during incremental porting of RSpec files over to Spectator

I've noticed with 0.10 that the reported line/column of compilation errors are not always accurate. I am not sure whether this is a Spectator issue or really a Crystal compiler issue. When compiling a RSpec file which has been ran through ruby_crystal_codemod and half-ported over to Crystal/Spectator, crystal spec spec/foo_spec.cr --error-trace will report a syntax error on a blank line just before another context block and a mysterious internal error. When I remove --error-trace, I can sometimes see the correct error message.

Example

Error: expanding macro


In spec/reader_spec.cr:582:1

 582 | 
       ^-------
Error: expanding macro


In spec/reader_spec.cr:621:1

 621 | 
       ^------
Error: expanding macro


In spec/reader_spec.cr:666:1

 666 | 
       ^------
Error: expanding macro


Unhandled exception: Negative argument (ArgumentError)
  from /crystal/src/string.cr:5024:13 in '*'
  from /crystal/src/compiler/crystal/exception.cr:108:7 in 'append_error_indicator'
  from /crystal/src/string/builder.cr:28:5 in 'error_body'
  from /crystal/src/compiler/crystal/semantic/exception.cr:122:10 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:137:9 in 'append_to_s'
  from /crystal/src/compiler/crystal/semantic/exception.cr:95:7 in 'run'
  from /crystal/src/compiler/crystal.cr:11:1 in '__crystal_main'
  from /crystal/src/crystal/main.cr:110:5 in 'main'
  from src/env/__libc_start_main.c:94:2 in 'libc_start_main_stage2'

let(...) { Constant } resolves the constant based on where the let is called, not where the let was defined

I ran into an odd constant scope resolution problem when I was porting some RSpec specs which defined test classes inside the describe blocks:

Example

require "./spec_helper"

Spectator.describe Test do
  module TestFoo
    class TestClass
    end
  end

  let(foo) { TestFoo::TestClass }

  describe "something else" do
    module TestFoo
    end

    it "must resolve constants using the context the let() was defined in" do
      p foo
    end
  end
end

Expected Result

foo being defined in the outer-most describe block would resolve its constant in that context, not the describe block it was called from.

Actual Result

foo is resolved in the describe block it was called within. The inner-most TestFoo shadows the outer-most TestFoo, which causes the constant not to be found.

error in line 1
Error: while requiring "./spec/test_spec.cr"


In spec/test_spec.cr:3:11

 3 | Spectator.describe Test do
               ^-------
Error: expanding macro


In spec/test_spec.cr:3:1

 3 | Spectator.describe Test do
     ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in lib/spectator/src/spectator.cr:27:3

 27 | macro describe(description, &block)

Which expanded to:

 >  1 |     # This macro creates the foundation for all specs.
 >  2 |     # Every group of examples is defined a separate module - `SpectatorExamples`.
 >  3 |     # There's multiple reasons for this.
 >  4 |     #
 >  5 |     # The first reason is to provide namespace isolation.
 >  6 |     # We don't want the spec code to accidentally pickup types and values from the `Spectator` module.
 >  7 |     # Another reason is that we need a root module to put all examples and groups in.
 >  8 |     # And lastly, the spec DSL needs to be given to the block of code somehow.
 >  9 |     # The DSL is included in the `SpectatorTest` class.
 > 10 |     #
 > 11 |     # For more information on how the DSL works, see the `DSL` module.
 > 12 | 
 > 13 |     # Root-level class that contains all examples and example groups.
 > 14 |     class SpectatorTest
 > 15 |       # Pass off the description argument and block to `DSL::StructureDSL.describe`.
 > 16 |       # That method will handle creating a new group for this spec.
 > 17 |       describe(Test) do
 > 18 |   module TestFoo
 > 19 |     class TestClass
 > 20 |     end
 > 21 |   end
 > 22 |   let(foo) do
 > 23 |     TestFoo::TestClass
 > 24 |   end
 > 25 |   describe("something else") do
 > 26 |     module TestFoo
 > 27 |     end
 > 28 |     it("must resolve constants using the context the let() was defined in") do
 > 29 |       p(foo)
 > 30 |     end
 > 31 |   end
 > 32 | end
 > 33 |     end
 > 34 |   
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in lib/spectator/src/spectator.cr:27:3

 27 | macro describe(description, &block)

Which expanded to:

 >  1 |     # This macro creates the foundation for all specs.
 >  2 |     # Every group of examples is defined a separate module - `SpectatorExamples`.
 >  3 |     # There's multiple reasons for this.
 >  4 |     #
 >  5 |     # The first reason is to provide namespace isolation.
 >  6 |     # We don't want the spec code to accidentally pickup types and values from the `Spectator` module.
 >  7 |     # Another reason is that we need a root module to put all examples and groups in.
 >  8 |     # And lastly, the spec DSL needs to be given to the block of code somehow.
 >  9 |     # The DSL is included in the `SpectatorTest` class.
 > 10 |     #
 > 11 |     # For more information on how the DSL works, see the `DSL` module.
 > 12 | 
 > 13 |     # Root-level class that contains all examples and example groups.
 > 14 |     class SpectatorTest
 > 15 |       # Pass off the description argument and block to `DSL::StructureDSL.describe`.
 > 16 |       # That method will handle creating a new group for this spec.
 > 17 |       describe(Test) do
 > 18 |   module TestFoo
 > 19 |     class TestClass
 > 20 |     end
 > 21 |   end
 > 22 |   let(foo) do
 > 23 |     TestFoo::TestClass
 > 24 |   end
 > 25 |   describe("something else") do
 > 26 |     module TestFoo
 > 27 |     end
 > 28 |     it("must resolve constants using the context the let() was defined in") do
 > 29 |       p(foo)
 > 30 |     end
 > 31 |   end
 > 32 | end
 > 33 |     end
 > 34 |   
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in lib/spectator/src/spectator/dsl/groups.cr:48:5

 48 | macro describe(what, &block)

Which expanded to:

 >  1 |       context(Test) do
 >  2 |   module TestFoo
 >  3 |     class TestClass
 >  4 |     end
 >  5 |   end
 >  6 |   let(foo) do
 >  7 |     TestFoo::TestClass
 >  8 |   end
 >  9 |   describe("something else") do
 > 10 |     module TestFoo
 > 11 |     end
 > 12 |     it("must resolve constants using the context the let() was defined in") do
 > 13 |       p(foo)
 > 14 |     end
 > 15 |   end
 > 16 | end
 > 17 |     
Error: expanding macro


In spec/test_spec.cr:11:1

 11 | describe "something else" do
    ^-------
Error: expanding macro


In spec/test_spec.cr:11:1

 11 | describe "something else" do
    ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in lib/spectator/src/spectator/dsl/groups.cr:48:5

 48 | macro describe(what, &block)

Which expanded to:

 > 1 |       context("something else") do
 > 2 |   module TestFoo
 > 3 |   end
 > 4 |   it("must resolve constants using the context the let() was defined in") do
 > 5 |     p(foo)
 > 6 |   end
 > 7 | end
 > 8 |     
Error: expanding macro


In spec/test_spec.cr:15:5

 15 | it "must resolve constants using the context the let() was defined in" do
      ^-
Error: expanding macro


In spec/test_spec.cr:15:5

 15 | it "must resolve constants using the context the let() was defined in" do
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in lib/spectator/src/spectator/dsl/examples.cr:6:5

 6 | macro it(description = nil, &block)

Which expanded to:

 >  1 |       
 >  2 |         def __temp_33
 >  3 |           p(foo)
 >  4 |         end
 >  5 |       
 >  6 | 
 >  7 |       
 >  8 |         __temp_34 = ::Spectator::Source.new("/home/postmodern/test/crystal/spectator/spec/test_spec.cr", line: 15, end_line: 17)
 >  9 |       
 > 10 |       ::Spectator::SpecBuilder.add_example(
 > 11 |         "must resolve constants using the context the let() was defined in",
 > 12 |         __temp_34,
 > 13 |         SpectatorTest::Context__temp_27::Context__temp_31
 > 14 |       ) { |test| test.as(SpectatorTest::Context__temp_27::Context__temp_31).__temp_33 }
 > 15 |     
Error: instantiating 'SpectatorTest::Context__temp_27::Context__temp_31#__temp_33()'


In spec/test_spec.cr:16:9

 16 | p foo
        ^--
Error: instantiating 'foo()'


In spec/test_spec.cr:9:3

 9 | let(foo) { TestFoo::TestClass }
     ^
Error: expanding macro


There was a problem expanding macro 'let'

Called macro defined in lib/spectator/src/spectator/dsl/values.cr:3:5

 3 | macro let(name, &block)

Which expanded to:

 >  1 |       @__temp_30 : ::Spectator::ValueWrapper?
 >  2 | 
 >  3 |       def foo
 >  4 |         TestFoo::TestClass
 >  5 |       end
 >  6 | 
 >  7 |       def foo
 >  8 |         if (wrapper = @__temp_30)
 >  9 |           wrapper.as(::Spectator::TypedValueWrapper(typeof(previous_def))).value
 > 10 |         else
 > 11 |           previous_def.tap do |value|
 > 12 |             @__temp_30 = ::Spectator::TypedValueWrapper.new(value)
 > 13 |           end
 > 14 |         end
 > 15 |       end
 > 16 |     
Error: instantiating 'previous_def()'


In spec/test_spec.cr:9:14

 9 | let(foo) { TestFoo::TestClass }
                ^-----------------
Error: undefined constant TestFoo::TestClass

Mock not working for Process.run with block.

I am attempting to stub Process.run, but the variant which receives a block. I cannot find an example of how to successfully define the stub method which accepts the block and how to expect/allow the stub method to receive a block? My ultimate goal is to test when Process.run raises a File::NotFoundError exception and whether my code correctly rescues that exception and raises another more descriptive exception.

require "./spec_helper"

Spectator.describe Test do
  mock Process do
    stub self.run(command,shell,output,&block)
  end

  let(command) { "ls -l" }

  before_each do
    expect(Process).to receive(:run).with(command, shell: true, output: :pipe)
  end

  it "must stub Process.run" do
    Process.run(command, shell: true, output: :pipe) do |process|
    end
  end
end
Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro 'stub'

Code in spec/test_spec.cr:6:11

 6 | end
             ^
Called macro defined in lib/spectator/src/spectator/mocks/stubs.cr:3:13

 3 | private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, return_type = :undefined, &block)

Which expanded to:

 > 26 | __temp_72.mocks.record_call(self, __temp_74)
 > 27 | if (__temp_75 = __temp_72.mocks.find_stub(self, __temp_74))
 > 28 |   return __temp_75.call!(__temp_73) { previous_def { |*__temp_76| yield *__temp_76 } }
                                              ^-----------
Error: there is no previous definition of 'run'

It does not work well with HTTP::Client

I'm trying to test a class that makes a HTTP GET request using Spectator. The previous test, using stdlib's spec, works just fine and running the application is also successful, but when using Spectator it fails.

Here's a reduced case that fails:

require "spectator"
require "http"

Spectator.describe "something" do
  it "makes a request" do
     url = "http http://httpbin.org/deflate "
     body = HTTP::Client.get(url).body
     expect(body).not_to be_nil
  end
end

Upon running crystal spec I get the following error:

Module validation failed: !dbg attachment points at wrong subprogram for function
!10 = distinct !DISubprogram(name: "initialize", linkageName: "initialize", scope: !5, file: !5, line: 115, type: !6, scopeLine: 115, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !0, retainedNodes: !2)
void (%"OpenSSL::BIO"*, %TCPSocket*)* @"*OpenSSL::BIO#initialize<TCPSocket>:Nil"
  %2 = call i8** @"~OpenSSL::BIO::CRYSTAL_BIO:read"(), !dbg !12
!12 = !DILocation(line: 14, column: 5, scope: !13)
!13 = distinct !DISubprogram(name: "set_data", linkageName: "set_data", scope: !5, file: !5, line: 13, type: !6, scopeLine: 13, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !0, retainedNodes: !2)
!13 = distinct !DISubprogram(name: "set_data", linkageName: "set_data", scope: !5, file: !5, line: 13, type: !6, scopeLine: 13, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !0, retainedNodes: !2)
 (Exception)
  from ???
  from ???
  from ???
  from ???
  from ???
  from ???
  from ???
  from __crystal_main
  from main
  from __libc_start_main
  from _start
  from ???
Error: you've found a bug in the Crystal compiler. Please open an issue, including source code that will allow us to reproduce the bug: https://github.com/crystal-lang/crystal/issues

Any idea what might be causing it?

Cannot seem to test when new is called

Ran into this when porting RSpec tests which stubs the new method so that the tests control the instance object that's returned.

require "./spec_helper"

Spectator.describe Test do
  module TestFoo
    class TestClass
      def initialize
      end

      # the method we are testing
      def self.test
        new().test
      end

      # the method we want to ensure gets called
      def test
      end
    end
  end

  let(test_class) { TestFoo::TestClass }
  let(test_instance) { test_class.new }

  describe "something else" do
    mock TestFoo::TestClass do
      stub new
    end

    it "must test when new is called" do
      expect(test_class).to receive(:new).with(no_args).and_return(test_instance)
      expect(test_instance).to receive(:test)

      test_class.test
    end
  end
end
Test
  something else
    must test when new is called

Failures:

  1) Test something else must test when new is called
     Failure: test_class did not receive #new(#<Spectator::Mocks::NoArguments:0x7f3fba62cbe0>) : SpectatorTest::Context__temp_27::TestFoo::TestClass at spec/test_spec.cr:27 at least once with any arguments

       expected: At least once with any arguments
       received: 0 time(s)

     # spec/test_spec.cr:26

Finished in 466 microseconds
1 examples, 1 failures, 0 errors, 0 pending

Allow matching any line in spec grouping

Related to #19 but expanded to support passing a line number within the grouping of a spec. If the line number matches a context block, every spec within that context should be run. The same goes for the describe block. If the line number falls on a specific spec then that should be run specifically, still.

Error: undefined constant TestValue

Suddenly started getting this error with spectator ~> 0.9 and ~> 0.10.

$ crystal spec --error-trace
error in line 4
Error: while requiring "./spec/chars_spec.cr"


In spec/chars_spec.cr:4:11

 4 | Spectator.describe Chars do
               ^-------
Error: expanding macro


In spec/chars_spec.cr:4:1

 4 | Spectator.describe Chars do
     ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in macro 'macro_140000956373072'

 46 | macro describe(description, *tags, **metadata, &block)

Which expanded to:

 >   1 |         class ::SpectatorTestContext
 >   2 |           describe(Chars,  ) do
 >   3 |   describe("NUMERIC") do
 >   4 |     subject do
 >   5 |       Chars::NUMERIC
 >   6 |     end
 >   7 |     it("should provide a numeric CharSet") do
 >   8 |       (expect(subject)).to(be =~ "0123456789")
 >   9 |     end
 >  10 |   end
 >  11 |   describe("OCTAL") do
 >  12 |     subject do
 >  13 |       Chars::OCTAL
 >  14 |     end
 >  15 |     it("should provide an octal CharSet") do
 >  16 |       (expect(subject)).to(be =~ "01234567")
 >  17 |     end
 >  18 |   end
 >  19 |   describe("UPPERCASE_HEXADECIMAL") do
 >  20 |     subject do
 >  21 |       Chars::UPPERCASE_HEXADECIMAL
 >  22 |     end
 >  23 |     it("should provide an upper-case hexadecimal CharSet") do
 >  24 |       (expect(subject)).to(be =~ "0123456789ABCDEF")
 >  25 |     end
 >  26 |   end
 >  27 |   describe("LOWERCASE_HEXADECIMAL") do
 >  28 |     subject do
 >  29 |       Chars::LOWERCASE_HEXADECIMAL
 >  30 |     end
 >  31 |     it("should provide a lower-case hexadecimal CharSet") do
 >  32 |       (expect(subject)).to(be =~ "0123456789abcdef")
 >  33 |     end
 >  34 |   end
 >  35 |   describe("HEXADECIMAL") do
 >  36 |     subject do
 >  37 |       Chars::HEXADECIMAL
 >  38 |     end
 >  39 |     it("should provide a hexadecimal CharSet") do
 >  40 |       (expect(subject)).to(be =~ "0123456789ABCDEFabcdef")
 >  41 |     end
 >  42 |   end
 >  43 |   describe("UPPERCASE_ALPHA") do
 >  44 |     subject do
 >  45 |       Chars::UPPERCASE_ALPHA
 >  46 |     end
 >  47 |     it("should provide an upper-case alpha CharSet") do
 >  48 |       (expect(subject)).to(be =~ "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
 >  49 |     end
 >  50 |   end
 >  51 |   describe("LOWERCASE_ALPHA") do
 >  52 |     subject do
 >  53 |       Chars::LOWERCASE_ALPHA
 >  54 |     end
 >  55 |     it("should provide a lower-case alpha CharSet") do
 >  56 |       (expect(subject)).to(be =~ "abcdefghijklmnopqrstuvwxyz")
 >  57 |     end
 >  58 |   end
 >  59 |   describe("ALPHA") do
 >  60 |     subject do
 >  61 |       Chars::ALPHA
 >  62 |     end
 >  63 |     it("should provide an alpha CharSet") do
 >  64 |       (expect(subject)).to(be =~ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
 >  65 |     end
 >  66 |   end
 >  67 |   describe("ALPHA_NUMERIC") do
 >  68 |     subject do
 >  69 |       Chars::ALPHA_NUMERIC
 >  70 |     end
 >  71 |     it("should provide an alpha-numeric CharSet") do
 >  72 |       (expect(subject)).to(be =~ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
 >  73 |     end
 >  74 |   end
 >  75 |   describe("VISIBLE") do
 >  76 |     subject do
 >  77 |       Chars::VISIBLE
 >  78 |     end
 >  79 |     it("should provide a visible CharSet") do
 >  80 |       (expect(subject)).to(be =~ "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")
 >  81 |     end
 >  82 |   end
 >  83 |   describe("SPACE") do
 >  84 |     subject do
 >  85 |       Chars::SPACE
 >  86 |     end
 >  87 |     it("should provide a space CharSet") do
 >  88 |       (expect(subject)).to(be =~ "\t\n\v\f\r ")
 >  89 |     end
 >  90 |   end
 >  91 |   describe("PUNCTUATION") do
 >  92 |     subject do
 >  93 |       Chars::PUNCTUATION
 >  94 |     end
 >  95 |     it("should provide a punctuation CharSet") do
 >  96 |       (expect(subject)).to(be =~ " !\"'(),-.:;?[]`{}~")
 >  97 |     end
 >  98 |   end
 >  99 |   describe("SYMBOLS") do
 > 100 |     subject do
 > 101 |       Chars::SYMBOLS
 > 102 |     end
 > 103 |     it("should provide a symbols CharSet") do
 > 104 |       (expect(subject)).to(be =~ " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
 > 105 |     end
 > 106 |   end
 > 107 | end
 > 108 |         end
 > 109 |       
Error: expanding macro


In spec/chars_spec.cr:15:11

 15 | 
                ^-------
Error: expanding macro


In spec/chars_spec.cr:8:1

 8 | it "should provide a numeric CharSet" do
 ^-
Error: expanding macro


In spec/chars_spec.cr:8:1

 8 | it "should provide a numeric CharSet" do
 ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_860, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_861() : Nil
 > 11 |             (expect(subject)).to(be =~ "0123456789")
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("should provide a numeric CharSet"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/chars.cr/spec/chars_spec.cr", 8, 10),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_860
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_852::Group__temp_856) do
 > 21 |               
 > 22 |                 __temp_861
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_852::Group__temp_856.class)'


In spec/chars_spec.cr:8:1

 8 | it "should provide a numeric CharSet" do
 ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_860, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_861() : Nil
 > 11 |             (expect(subject)).to(be =~ "0123456789")
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("should provide a numeric CharSet"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/chars.cr/spec/chars_spec.cr", 8, 10),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_860
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_852::Group__temp_856) do
 > 21 |               
 > 22 |                 __temp_861
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_852::Group__temp_856.class)'


In spec/chars_spec.cr:8:1

 8 | it "should provide a numeric CharSet" do
 ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_860, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_861() : Nil
 > 11 |             (expect(subject)).to(be =~ "0123456789")
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("should provide a numeric CharSet"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/chars.cr/spec/chars_spec.cr", 8, 10),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_860
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_852::Group__temp_856) do
 > 21 |               
 > 22 |                 __temp_861
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating '__temp_861()'


In spec/chars_spec.cr:9:29

 9 | expect(subject).to be =~ "0123456789"
                           ^-
Error: instantiating 'Spectator::Matchers::TruthyMatcher#=~(String)'


In lib/spectator/src/spectator/matchers/truthy_matcher.cr:101:18

 101 | expected = TestValue.new(value)
                  ^--------
Error: undefined constant TestValue

Missing no_args equivalent

Porting some RSpec specs over to Crystal and noticed that is no equivalent for no_args.

expect(subject).to receive(:new).with(no_args).and_return(instance)

Not sure if thee's a workaround?

Print random seed?

Thanks for this great shard, works splendidly for me! ๐Ÿ™‡

Super minor thing I noticed:
It doesn't seem to print the random seed when -r is given.

Would be nice if it could do that, so that a failure that
depends on test-order can be easily reproduced.

Problem with mock when using abstract class

When using an abstract class in the following example, i get an error that the method register_hook is not being implemented.

require "./spec_helper"

abstract class SdkInterface
  abstract def register_hook(name, &block)
end

class Example
  def initialize(@sdk : Sdk)
    
  end

  def configure
    @sdk.register_hook("name") do
      nil
    end
  end
end

class Sdk < SdkInterface
  def initialize
  end

  def register_hook(name, &block)
    nil
  end
end

Spectator.describe Example do
  mock Sdk do
    stub register_hook(name, &block)
  end

  describe "#configure" do
    it "registers a block on configure" do
      sdk = Sdk.new
      example_class = Example.new(sdk)
      allow(sdk).to receive(register_hook())

      example_class.configure

      expect(sdk).to have_received(register_hook()).with("name")
    end
  end
end

If i change the Sdk class to not inherit from SdkInterface it works as expected.

Stubbing [] methods

I want to create a class mock that has the following method:

def [](key : String)
  find_key key
end

I couldn't find a way to stub that. When I try something like this:

mock MyMock do
  stub [](key)
end

It produces an error like this:

 7 | stub [](name)
          ^
Error: for empty arrays use '[] of ElementType'

Is there any way to achieve this?

Mocks fail to compile for methods with free variables

I was trying to test chars.cr against Crystal 1.6.2 and Spectator 0.11.4, but got this strange error coming from deep within enumerable.cr. The beginning of the error is a macro expansion of a method stub. I assume I need to update how I'm defining the stubs?

    mock Chars::CharSet do
      stub :|, other : Chars::CharSet
    end

    let(other) { described_class.new }
    let(return_value) { described_class.new }

    it "must call #+" do
      expect(subject).to receive(:|).with(other).and_return(return_value)

      expect(subject + other).to be(return_value)
    end

Error Trace

$ crystal spec --error-trace 
There was a problem expanding macro 'finished'

Called macro defined in macro 'define_subtype'

 46 | macro finished

Which expanded to:

 > 1 |             stub_type Chars::CharSet
 > 2 | 
 > 3 |             stub(:|, other : Chars::CharSet)
 > 4 |           
Error: expanding macro


There was a problem expanding macro 'stub_type'

Called macro defined in lib/spectator/src/spectator/mocks/stubbable.cr:339:13

 339 | private macro stub_type(type_name = @type)

Which expanded to:

 >    1 |       
 >    2 |       
 >    3 |         
 >    4 |           default_stub  def colorize(
 >    5 |             r : UInt8, g : UInt8, b : UInt8, 
 >    6 |             
 >    7 |             
 >    8 |           )
 >    9 |             super
 >   10 |           end
 >   11 |         
 >   12 |           default_stub  def colorize(
 >   13 |             fore : UInt8, 
 >   14 |             
 >   15 |             
 >   16 |           )
 >   17 |             super
 >   18 |           end
 >   19 |         
 >   20 |           default_stub  def colorize(
 >   21 |             fore : Symbol, 
 >   22 |             
 >   23 |             
 >   24 |           )
 >   25 |             super
 >   26 |           end
 >   27 |         
 >   28 |           default_stub  def colorize(
 >   29 |             fore : Color, 
 >   30 |             
 >   31 |             
 >   32 |           )
 >   33 |             super
 >   34 |           end
 >   35 |         
 >   36 |           default_stub  def colorize(
 >   37 |             
 >   38 |             
 >   39 |             
 >   40 |           ) : Colorize::Object
 >   41 |             super
 >   42 |           end
 >   43 |         
 >   44 | 
 >   45 |         
 >   46 |       
 >   47 |         
 >   48 |           abstract_stub  def ==(
 >   49 |             other, 
 >   50 |             
 >   51 |             
 >   52 |           )
 >   53 |             super
 >   54 |           end
 >   55 |         
 >   56 |           default_stub  def !=(
 >   57 |             other, 
 >   58 |             
 >   59 |             
 >   60 |           )
 >   61 |             super
 >   62 |           end
 >   63 |         
 >   64 |           default_stub  def !~(
 >   65 |             other, 
 >   66 |             
 >   67 |             
 >   68 |           )
 >   69 |             super
 >   70 |           end
 >   71 |         
 >   72 |           default_stub  def ===(
 >   73 |             other : JSON::Any, 
 >   74 |             
 >   75 |             
 >   76 |           )
 >   77 |             super
 >   78 |           end
 >   79 |         
 >   80 |           default_stub  def ===(
 >   81 |             other, 
 >   82 |             
 >   83 |             
 >   84 |           )
 >   85 |             super
 >   86 |           end
 >   87 |         
 >   88 |           default_stub  def =~(
 >   89 |             other, 
 >   90 |             
 >   91 |             
 >   92 |           )
 >   93 |             super
 >   94 |           end
 >   95 |         
 >   96 |           abstract_stub  def hash(
 >   97 |             hasher, 
 >   98 |             
 >   99 |             
 >  100 |           )
 >  101 |             super
 >  102 |           end
 >  103 |         
 >  104 |           default_stub  def hash(
 >  105 |             
 >  106 |             
 >  107 |             
 >  108 |           )
 >  109 |             super
 >  110 |           end
 >  111 |         
 >  112 |           abstract_stub  def to_s(
 >  113 |             io : IO, 
 >  114 |             
 >  115 |             
 >  116 |           ) : Nil
 >  117 |             super
 >  118 |           end
 >  119 |         
 >  120 |           default_stub  def to_s(
 >  121 |             
 >  122 |             
 >  123 |             
 >  124 |           ) : String
 >  125 |             super
 >  126 |           end
 >  127 |         
 >  128 |           default_stub  def inspect(
 >  129 |             io : IO, 
 >  130 |             
 >  131 |             
 >  132 |           ) : Nil
 >  133 |             super
 >  134 |           end
 >  135 |         
 >  136 |           default_stub  def inspect(
 >  137 |             
 >  138 |             
 >  139 |             
 >  140 |           ) : String
 >  141 |             super
 >  142 |           end
 >  143 |         
 >  144 |           default_stub  def pretty_print(
 >  145 |             pp : PrettyPrint, 
 >  146 |             
 >  147 |             
 >  148 |           ) : Nil
 >  149 |             super
 >  150 |           end
 >  151 |         
 >  152 |           default_stub  def pretty_inspect(
 >  153 |             width = 79, newline = "\n", indent = 0, 
 >  154 |             
 >  155 |             
 >  156 |           ) : String
 >  157 |             super
 >  158 |           end
 >  159 |         
 >  160 |           default_stub  def tap(
 >  161 |             
 >  162 |             
 >  163 |             &
 >  164 |           )
 >  165 |             super { |*__temp_1063| yield *__temp_1063 }
 >  166 |           end
 >  167 |         
 >  168 |           default_stub  def try(
 >  169 |             
 >  170 |             
 >  171 |             &
 >  172 |           )
 >  173 |             super { |*__temp_1063| yield *__temp_1063 }
 >  174 |           end
 >  175 |         
 >  176 |           default_stub  def in?(
 >  177 |             collection : Object, 
 >  178 |             
 >  179 |             
 >  180 |           ) : Bool
 >  181 |             super
 >  182 |           end
 >  183 |         
 >  184 |           default_stub  def in?(
 >  185 |             *values : Object, 
 >  186 |             
 >  187 |             
 >  188 |           ) : Bool
 >  189 |             super
 >  190 |           end
 >  191 |         
 >  192 |           default_stub  def not_nil!(
 >  193 |             
 >  194 |             
 >  195 |             
 >  196 |           )
 >  197 |             super
 >  198 |           end
 >  199 |         
 >  200 |           default_stub  def itself(
 >  201 |             
 >  202 |             
 >  203 |             
 >  204 |           )
 >  205 |             super
 >  206 |           end
 >  207 |         
 >  208 |           abstract_stub  def dup(
 >  209 |             
 >  210 |             
 >  211 |             
 >  212 |           )
 >  213 |             super
 >  214 |           end
 >  215 |         
 >  216 |           default_stub  def unsafe_as(
 >  217 |             type : T.class, 
 >  218 |             
 >  219 |             
 >  220 |           ) forall T
 >  221 |             super
 >  222 |           end
 >  223 |         
 >  224 |           default_stub  def crystal_type_id(
 >  225 |             
 >  226 |             
 >  227 |             
 >  228 |           ) : Int32
 >  229 |             super
 >  230 |           end
 >  231 |         
 >  232 |           default_stub  def to_json(
 >  233 |             io : IO, 
 >  234 |             
 >  235 |             
 >  236 |           ) : Nil
 >  237 |             super
 >  238 |           end
 >  239 |         
 >  240 |           default_stub  def to_json(
 >  241 |             
 >  242 |             
 >  243 |             
 >  244 |           ) : String
 >  245 |             super
 >  246 |           end
 >  247 |         
 >  248 |           default_stub  def to_pretty_json(
 >  249 |             indent : String = "  ", 
 >  250 |             
 >  251 |             
 >  252 |           ) : String
 >  253 |             super
 >  254 |           end
 >  255 |         
 >  256 |           default_stub  def to_pretty_json(
 >  257 |             io : IO, indent : String = "  ", 
 >  258 |             
 >  259 |             
 >  260 |           ) : Nil
 >  261 |             super
 >  262 |           end
 >  263 |         
 >  264 | 
 >  265 |         
 >  266 |           default_stub protected def self.set_crystal_type_id(
 >  267 |             ptr, 
 >  268 |             
 >  269 |             
 >  270 |           )
 >  271 |             super
 >  272 |           end
 >  273 |         
 >  274 |           default_stub  def self.from_json(
 >  275 |             string_or_io, root : String, 
 >  276 |             
 >  277 |             
 >  278 |           )
 >  279 |             super
 >  280 |           end
 >  281 |         
 >  282 |           default_stub  def self.from_json(
 >  283 |             string_or_io, 
 >  284 |             
 >  285 |             
 >  286 |           )
 >  287 |             super
 >  288 |           end
 >  289 |         
 >  290 |       
 >  291 |         
 >  292 |           default_stub  def object_id(
 >  293 |             
 >  294 |             
 >  295 |             
 >  296 |           ) : UInt64
 >  297 |             super
 >  298 |           end
 >  299 |         
 >  300 |           default_stub  def ==(
 >  301 |             other : self, 
 >  302 |             
 >  303 |             
 >  304 |           )
 >  305 |             super
 >  306 |           end
 >  307 |         
 >  308 |           default_stub  def ==(
 >  309 |             other : JSON::Any, 
 >  310 |             
 >  311 |             
 >  312 |           )
 >  313 |             super
 >  314 |           end
 >  315 |         
 >  316 |           default_stub  def ==(
 >  317 |             other, 
 >  318 |             
 >  319 |             
 >  320 |           )
 >  321 |             super
 >  322 |           end
 >  323 |         
 >  324 |           default_stub  def same?(
 >  325 |             other : Reference, 
 >  326 |             
 >  327 |             
 >  328 |           ) : Bool
 >  329 |             super
 >  330 |           end
 >  331 |         
 >  332 |           default_stub  def same?(
 >  333 |             other : Nil, 
 >  334 |             
 >  335 |             
 >  336 |           )
 >  337 |             super
 >  338 |           end
 >  339 |         
 >  340 |           default_stub  def dup(
 >  341 |             
 >  342 |             
 >  343 |             
 >  344 |           )
 >  345 |             super
 >  346 |           end
 >  347 |         
 >  348 |           default_stub  def hash(
 >  349 |             hasher, 
 >  350 |             
 >  351 |             
 >  352 |           )
 >  353 |             super
 >  354 |           end
 >  355 |         
 >  356 |           default_stub  def inspect(
 >  357 |             io : IO, 
 >  358 |             
 >  359 |             
 >  360 |           ) : Nil
 >  361 |             super
 >  362 |           end
 >  363 |         
 >  364 |           default_stub  def pretty_print(
 >  365 |             pp, 
 >  366 |             
 >  367 |             
 >  368 |           ) : Nil
 >  369 |             super
 >  370 |           end
 >  371 |         
 >  372 |           default_stub  def to_s(
 >  373 |             io : IO, 
 >  374 |             
 >  375 |             
 >  376 |           ) : Nil
 >  377 |             super
 >  378 |           end
 >  379 |         
 >  380 |           default_stub private def exec_recursive(
 >  381 |             method, 
 >  382 |             
 >  383 |             &
 >  384 |           )
 >  385 |             super { |*__temp_1063| yield *__temp_1063 }
 >  386 |           end
 >  387 |         
 >  388 |           default_stub private def exec_recursive_clone(
 >  389 |             
 >  390 |             
 >  391 |             &
 >  392 |           )
 >  393 |             super { |*__temp_1063| yield *__temp_1063 }
 >  394 |           end
 >  395 |         
 >  396 | 
 >  397 |         
 >  398 |       
 >  399 |         
 >  400 |           abstract_stub  def each(
 >  401 |             
 >  402 |             
 >  403 |             & : (T ->)
 >  404 |           )
 >  405 |             super { |*__temp_1063| yield *__temp_1063 }
 >  406 |           end
 >  407 |         
 >  408 |           default_stub  def all?(
 >  409 |             
 >  410 |             
 >  411 |             & : (T ->)
 >  412 |           ) : Bool
 >  413 |             super { |*__temp_1063| yield *__temp_1063 }
 >  414 |           end
 >  415 |         
 >  416 |           default_stub  def all?(
 >  417 |             pattern, 
 >  418 |             
 >  419 |             
 >  420 |           ) : Bool
 >  421 |             super
 >  422 |           end
 >  423 |         
 >  424 |           default_stub  def all?(
 >  425 |             
 >  426 |             
 >  427 |             
 >  428 |           ) : Bool
 >  429 |             super
 >  430 |           end
 >  431 |         
 >  432 |           default_stub  def any?(
 >  433 |             
 >  434 |             
 >  435 |             & : (T ->)
 >  436 |           ) : Bool
 >  437 |             super { |*__temp_1063| yield *__temp_1063 }
 >  438 |           end
 >  439 |         
 >  440 |           default_stub  def any?(
 >  441 |             pattern, 
 >  442 |             
 >  443 |             
 >  444 |           ) : Bool
 >  445 |             super
 >  446 |           end
 >  447 |         
 >  448 |           default_stub  def any?(
 >  449 |             
 >  450 |             
 >  451 |             
 >  452 |           ) : Bool
 >  453 |             super
 >  454 |           end
 >  455 |         
 >  456 |           default_stub  def chunks(
 >  457 |             
 >  458 |             
 >  459 |             &block : (T -> U)
 >  460 |           ) forall U
 >  461 |             super { |*__temp_1063| yield *__temp_1063 }
 >  462 |           end
 >  463 |         
 >  464 |           default_stub private def chunks_internal(
 >  465 |             original_block : (T -> U), 
 >  466 |             
 >  467 |             &
 >  468 |           ) forall U
 >  469 |             super { |*__temp_1063| yield *__temp_1063 }
 >  470 |           end
 >  471 |         
 >  472 |           default_stub  def compact_map(
 >  473 |             
 >  474 |             
 >  475 |             & : (T -> _)
 >  476 |           )
 >  477 |             super { |*__temp_1063| yield *__temp_1063 }
 >  478 |           end
 >  479 |         
 >  480 |           default_stub  def count(
 >  481 |             
 >  482 |             
 >  483 |             & : (T ->)
 >  484 |           ) : Int32
 >  485 |             super { |*__temp_1063| yield *__temp_1063 }
 >  486 |           end
 >  487 |         
 >  488 |           default_stub  def count(
 >  489 |             item, 
 >  490 |             
 >  491 |             
 >  492 |           ) : Int32
 >  493 |             super
 >  494 |           end
 >  495 |         
 >  496 |           default_stub  def cycle(
 >  497 |             n, 
 >  498 |             
 >  499 |             & : (T ->)
 >  500 |           ) : Nil
 >  501 |             super { |*__temp_1063| yield *__temp_1063 }
 >  502 |           end
 >  503 |         
 >  504 |           default_stub  def cycle(
 >  505 |             
 >  506 |             
 >  507 |             & : (T ->)
 >  508 |           ) : Nil
 >  509 |             super { |*__temp_1063| yield *__temp_1063 }
 >  510 |           end
 >  511 |         
 >  512 |           default_stub  def each_cons(
 >  513 |             count : Int, reuse = false, 
 >  514 |             
 >  515 |             &
 >  516 |           )
 >  517 |             super { |*__temp_1063| yield *__temp_1063 }
 >  518 |           end
 >  519 |         
 >  520 |           default_stub private def each_cons_internal(
 >  521 |             count : Int, reuse, cons, 
 >  522 |             
 >  523 |             &
 >  524 |           )
 >  525 |             super { |*__temp_1063| yield *__temp_1063 }
 >  526 |           end
 >  527 |         
 >  528 |           default_stub  def each_cons_pair(
 >  529 |             
 >  530 |             
 >  531 |             & : (T, T ->)
 >  532 |           ) : Nil
 >  533 |             super { |*__temp_1063| yield *__temp_1063 }
 >  534 |           end
 >  535 |         
 >  536 |           default_stub  def each_slice(
 >  537 |             count : Int, reuse = false, 
 >  538 |             
 >  539 |             &
 >  540 |           )
 >  541 |             super { |*__temp_1063| yield *__temp_1063 }
 >  542 |           end
 >  543 |         
 >  544 |           default_stub private def each_slice_internal(
 >  545 |             count : Int, type, reuse, 
 >  546 |             
 >  547 |             &
 >  548 |           )
 >  549 |             super { |*__temp_1063| yield *__temp_1063 }
 >  550 |           end
 >  551 |         
 >  552 |           default_stub  def each_with_index(
 >  553 |             offset = 0, 
 >  554 |             
 >  555 |             &
 >  556 |           )
 >  557 |             super { |*__temp_1063| yield *__temp_1063 }
 >  558 |           end
 >  559 |         
 >  560 |           default_stub  def each_with_object(
 >  561 |             obj : U, 
 >  562 |             
 >  563 |             & : (T, U ->)
 >  564 |           ) : U forall U
 >  565 |             super { |*__temp_1063| yield *__temp_1063 }
 >  566 |           end
 >  567 |         
 >  568 |           default_stub  def find(
 >  569 |             if_none = nil, 
 >  570 |             
 >  571 |             & : (T ->)
 >  572 |           )
 >  573 |             super { |*__temp_1063| yield *__temp_1063 }
 >  574 |           end
 >  575 |         
 >  576 |           default_stub  def find!(
 >  577 |             
 >  578 |             
 >  579 |             & : (T ->)
 >  580 |           ) : T
 >  581 |             super { |*__temp_1063| yield *__temp_1063 }
 >  582 |           end
 >  583 |         
 >  584 |           default_stub  def first(
 >  585 |             
 >  586 |             
 >  587 |             &
 >  588 |           )
 >  589 |             super { |*__temp_1063| yield *__temp_1063 }
 >  590 |           end
 >  591 |         
 >  592 |           default_stub  def first(
 >  593 |             count : Int, 
 >  594 |             
 >  595 |             
 >  596 |           ) : Array(T)
 >  597 |             super
 >  598 |           end
 >  599 |         
 >  600 |           default_stub  def first(
 >  601 |             
 >  602 |             
 >  603 |             
 >  604 |           ) : T
 >  605 |             super
 >  606 |           end
 >  607 |         
 >  608 |           default_stub  def first?(
 >  609 |             
 >  610 |             
 >  611 |             
 >  612 |           ) : T | ::Nil
 >  613 |             super
 >  614 |           end
 >  615 |         
 >  616 |           default_stub  def flat_map(
 >  617 |             
 >  618 |             
 >  619 |             & : (T -> _)
 >  620 |           )
 >  621 |             super { |*__temp_1063| yield *__temp_1063 }
 >  622 |           end
 >  623 |         
 >  624 |           default_stub private def flat_map_type(
 >  625 |             elem, 
 >  626 |             
 >  627 |             
 >  628 |           )
 >  629 |             super
 >  630 |           end
 >  631 |         
 >  632 |           default_stub  def group_by(
 >  633 |             
 >  634 |             
 >  635 |             & : (T -> U)
 >  636 |           ) forall U
 >  637 |             super { |*__temp_1063| yield *__temp_1063 }
 >  638 |           end
 >  639 |         
 >  640 |           default_stub  def in_groups_of(
 >  641 |             size : Int, filled_up_with : U = nil, 
 >  642 |             
 >  643 |             
 >  644 |           ) forall U
 >  645 |             super
 >  646 |           end
 >  647 |         
 >  648 |           default_stub  def in_groups_of(
 >  649 |             size : Int, filled_up_with : U = nil, reuse = false, 
 >  650 |             
 >  651 |             &
 >  652 |           ) forall U
 >  653 |             super { |*__temp_1063| yield *__temp_1063 }
 >  654 |           end
 >  655 |         
 >  656 |           default_stub  def includes?(
 >  657 |             obj, 
 >  658 |             
 >  659 |             
 >  660 |           ) : Bool
 >  661 |             super
 >  662 |           end
 >  663 |         
 >  664 |           default_stub  def index(
 >  665 |             
 >  666 |             
 >  667 |             & : (T ->)
 >  668 |           ) : Int32 | ::Nil
 >  669 |             super { |*__temp_1063| yield *__temp_1063 }
 >  670 |           end
 >  671 |         
 >  672 |           default_stub  def index(
 >  673 |             obj, 
 >  674 |             
 >  675 |             
 >  676 |           ) : Int32 | ::Nil
 >  677 |             super
 >  678 |           end
 >  679 |         
 >  680 |           default_stub  def index!(
 >  681 |             
 >  682 |             
 >  683 |             & : (T ->)
 >  684 |           ) : Int32
 >  685 |             super { |*__temp_1063| yield *__temp_1063 }
 >  686 |           end
 >  687 |         
 >  688 |           default_stub  def index!(
 >  689 |             obj, 
 >  690 |             
 >  691 |             
 >  692 |           ) : Int32
 >  693 |             super
 >  694 |           end
 >  695 |         
 >  696 |           default_stub  def index_by(
 >  697 |             
 >  698 |             
 >  699 |             & : (T -> U)
 >  700 |           ) : Hash(U, T) forall U
 >  701 |             super { |*__temp_1063| yield *__temp_1063 }
 >  702 |           end
 >  703 |         
 >  704 |           default_stub  def reduce(
 >  705 |             memo, 
 >  706 |             
 >  707 |             &
 >  708 |           )
 >  709 |             super { |*__temp_1063| yield *__temp_1063 }
 >  710 |           end
 >  711 |         
 >  712 |           default_stub  def reduce(
 >  713 |             
 >  714 |             
 >  715 |             &
 >  716 |           )
 >  717 |             super { |*__temp_1063| yield *__temp_1063 }
 >  718 |           end
 >  719 |         
 >  720 |           default_stub  def reduce?(
 >  721 |             
 >  722 |             
 >  723 |             &
 >  724 |           )
 >  725 |             super { |*__temp_1063| yield *__temp_1063 }
 >  726 |           end
 >  727 |         
 >  728 |           default_stub  def accumulate(
 >  729 |             initial : U, 
 >  730 |             
 >  731 |             
 >  732 |           ) : Array(U) forall U
 >  733 |             super
 >  734 |           end
 >  735 |         
 >  736 |           default_stub  def accumulate(
 >  737 |             
 >  738 |             
 >  739 |             
 >  740 |           ) : Array(T)
 >  741 |             super
 >  742 |           end
 >  743 |         
 >  744 |           default_stub  def accumulate(
 >  745 |             initial : U, 
 >  746 |             
 >  747 |             &block : (U, T -> U)
 >  748 |           ) : Array(U) forall U
 >  749 |             super { |*__temp_1063| yield *__temp_1063 }
 >  750 |           end
 >  751 |         
 >  752 |           default_stub  def accumulate(
 >  753 |             
 >  754 |             
 >  755 |             &block : (T, T -> T)
 >  756 |           ) : Array(T)
 >  757 |             super { |*__temp_1063| yield *__temp_1063 }
 >  758 |           end
 >  759 |         
 >  760 |           default_stub  def join(
 >  761 |             io : IO, separator = "", 
 >  762 |             
 >  763 |             
 >  764 |           ) : Nil
 >  765 |             super
 >  766 |           end
 >  767 |         
 >  768 |           default_stub  def join(
 >  769 |             separator, io : IO, 
 >  770 |             
 >  771 |             
 >  772 |           ) : Nil
 >  773 |             super
 >  774 |           end
 >  775 |         
 >  776 |           default_stub  def join(
 >  777 |             separator = "", 
 >  778 |             
 >  779 |             
 >  780 |           ) : String
 >  781 |             super
 >  782 |           end
 >  783 |         
 >  784 |           default_stub  def join(
 >  785 |             io : IO, separator = "", 
 >  786 |             
 >  787 |             & : (T, IO ->)
 >  788 |           )
 >  789 |             super { |*__temp_1063| yield *__temp_1063 }
 >  790 |           end
 >  791 |         
 >  792 |           default_stub  def join(
 >  793 |             separator, io : IO, 
 >  794 |             
 >  795 |             &
 >  796 |           )
 >  797 |             super { |*__temp_1063| yield *__temp_1063 }
 >  798 |           end
 >  799 |         
 >  800 |           default_stub  def join(
 >  801 |             separator = "", 
 >  802 |             
 >  803 |             & : (T ->)
 >  804 |           )
 >  805 |             super { |*__temp_1063| yield *__temp_1063 }
 >  806 |           end
 >  807 |         
 >  808 |           default_stub  def map(
 >  809 |             
 >  810 |             
 >  811 |             & : (T -> U)
 >  812 |           ) : Array(U) forall U
 >  813 |             super { |*__temp_1063| yield *__temp_1063 }
 >  814 |           end
 >  815 |         
 >  816 |           default_stub  def map_with_index(
 >  817 |             offset = 0, 
 >  818 |             
 >  819 |             & : (T, Int32 -> U)
 >  820 |           ) : Array(U) forall U
 >  821 |             super { |*__temp_1063| yield *__temp_1063 }
 >  822 |           end
 >  823 |         
 >  824 |           default_stub  def max(
 >  825 |             
 >  826 |             
 >  827 |             
 >  828 |           ) : T
 >  829 |             super
 >  830 |           end
 >  831 |         
 >  832 |           default_stub  def max?(
 >  833 |             
 >  834 |             
 >  835 |             
 >  836 |           ) : T | ::Nil
 >  837 |             super
 >  838 |           end
 >  839 |         
 >  840 |           default_stub  def max_by(
 >  841 |             
 >  842 |             
 >  843 |             & : (T -> U)
 >  844 |           ) : T forall U
 >  845 |             super { |*__temp_1063| yield *__temp_1063 }
 >  846 |           end
 >  847 |         
 >  848 |           default_stub  def max_by?(
 >  849 |             
 >  850 |             
 >  851 |             & : (T -> U)
 >  852 |           ) : T | ::Nil forall U
 >  853 |             super { |*__temp_1063| yield *__temp_1063 }
 >  854 |           end
 >  855 |         
 >  856 |           default_stub private def max_by_internal(
 >  857 |             
 >  858 |             
 >  859 |             & : (T -> U)
 >  860 |           ) forall U
 >  861 |             super { |*__temp_1063| yield *__temp_1063 }
 >  862 |           end
 >  863 |         
 >  864 |           default_stub  def max_of(
 >  865 |             
 >  866 |             
 >  867 |             & : (T -> U)
 >  868 |           ) : U forall U
 >  869 |             super { |*__temp_1063| yield *__temp_1063 }
 >  870 |           end
 >  871 |         
 >  872 |           default_stub  def max_of?(
 >  873 |             
 >  874 |             
 >  875 |             & : (T -> U)
 >  876 |           ) : U | ::Nil forall U
 >  877 |             super { |*__temp_1063| yield *__temp_1063 }
 >  878 |           end
 >  879 |         
 >  880 |           default_stub private def max_of_internal(
 >  881 |             
 >  882 |             
 >  883 |             & : (T -> U)
 >  884 |           ) forall U
 >  885 |             super { |*__temp_1063| yield *__temp_1063 }
 >  886 |           end
 >  887 |         
 >  888 |           default_stub  def min(
 >  889 |             
 >  890 |             
 >  891 |             
 >  892 |           ) : T
 >  893 |             super
 >  894 |           end
 >  895 |         
 >  896 |           default_stub  def min?(
 >  897 |             
 >  898 |             
 >  899 |             
 >  900 |           ) : T | ::Nil
 >  901 |             super
 >  902 |           end
 >  903 |         
 >  904 |           default_stub  def min_by(
 >  905 |             
 >  906 |             
 >  907 |             & : (T -> U)
 >  908 |           ) : T forall U
 >  909 |             super { |*__temp_1063| yield *__temp_1063 }
 >  910 |           end
 >  911 |         
 >  912 |           default_stub  def min_by?(
 >  913 |             
 >  914 |             
 >  915 |             & : (T -> U)
 >  916 |           ) : T | ::Nil forall U
 >  917 |             super { |*__temp_1063| yield *__temp_1063 }
 >  918 |           end
 >  919 |         
 >  920 |           default_stub private def min_by_internal(
 >  921 |             
 >  922 |             
 >  923 |             & : (T -> U)
 >  924 |           ) forall U
 >  925 |             super { |*__temp_1063| yield *__temp_1063 }
 >  926 |           end
 >  927 |         
 >  928 |           default_stub  def min_of(
 >  929 |             
 >  930 |             
 >  931 |             & : (T -> U)
 >  932 |           ) : U forall U
 >  933 |             super { |*__temp_1063| yield *__temp_1063 }
 >  934 |           end
 >  935 |         
 >  936 |           default_stub  def min_of?(
 >  937 |             
 >  938 |             
 >  939 |             & : (T -> U)
 >  940 |           ) : U | ::Nil forall U
 >  941 |             super { |*__temp_1063| yield *__temp_1063 }
 >  942 |           end
 >  943 |         
 >  944 |           default_stub private def min_of_internal(
 >  945 |             
 >  946 |             
 >  947 |             & : (T -> U)
 >  948 |           ) forall U
 >  949 |             super { |*__temp_1063| yield *__temp_1063 }
 >  950 |           end
 >  951 |         
 >  952 |           default_stub  def minmax(
 >  953 |             
 >  954 |             
 >  955 |             
 >  956 |           ) : ::Tuple(T, T)
 >  957 |             super
 >  958 |           end
 >  959 |         
 >  960 |           default_stub  def minmax?(
 >  961 |             
 >  962 |             
 >  963 |             
 >  964 |           ) : ::Tuple(T | ::Nil, T | ::Nil)
 >  965 |             super
 >  966 |           end
 >  967 |         
 >  968 |           default_stub  def minmax_by(
 >  969 |             
 >  970 |             
 >  971 |             & : (T -> U)
 >  972 |           ) : ::Tuple(T, T) forall U
 >  973 |             super { |*__temp_1063| yield *__temp_1063 }
 >  974 |           end
 >  975 |         
 >  976 |           default_stub  def minmax_by?(
 >  977 |             
 >  978 |             
 >  979 |             & : (T -> U)
 >  980 |           ) : ::Tuple(T, T) | ::Tuple(Nil, Nil) forall U
 >  981 |             super { |*__temp_1063| yield *__temp_1063 }
 >  982 |           end
 >  983 |         
 >  984 |           default_stub private def minmax_by_internal(
 >  985 |             
 >  986 |             
 >  987 |             & : (T -> U)
 >  988 |           ) forall U
 >  989 |             super { |*__temp_1063| yield *__temp_1063 }
 >  990 |           end
 >  991 |         
 >  992 |           default_stub  def minmax_of(
 >  993 |             
 >  994 |             
 >  995 |             & : (T -> U)
 >  996 |           ) : ::Tuple(U, U) forall U
 >  997 |             super { |*__temp_1063| yield *__temp_1063 }
 >  998 |           end
 >  999 |         
 > 1000 |           default_stub  def minmax_of?(
 > 1001 |             
 > 1002 |             
 > 1003 |             & : (T -> U)
 > 1004 |           ) : ::Tuple(U, U) | ::Tuple(Nil, Nil) forall U
 > 1005 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1006 |           end
 > 1007 |         
 > 1008 |           default_stub private def minmax_of_internal(
 > 1009 |             
 > 1010 |             
 > 1011 |             & : (T -> U)
 > 1012 |           ) forall U
 > 1013 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1014 |           end
 > 1015 |         
 > 1016 |           default_stub private def compare_or_raise(
 > 1017 |             value, memo, 
 > 1018 |             
 > 1019 |             
 > 1020 |           )
 > 1021 |             super
 > 1022 |           end
 > 1023 |         
 > 1024 |           default_stub  def none?(
 > 1025 |             
 > 1026 |             
 > 1027 |             & : (T ->)
 > 1028 |           ) : Bool
 > 1029 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1030 |           end
 > 1031 |         
 > 1032 |           default_stub  def none?(
 > 1033 |             pattern, 
 > 1034 |             
 > 1035 |             
 > 1036 |           ) : Bool
 > 1037 |             super
 > 1038 |           end
 > 1039 |         
 > 1040 |           default_stub  def none?(
 > 1041 |             
 > 1042 |             
 > 1043 |             
 > 1044 |           ) : Bool
 > 1045 |             super
 > 1046 |           end
 > 1047 |         
 > 1048 |           default_stub  def one?(
 > 1049 |             
 > 1050 |             
 > 1051 |             & : (T ->)
 > 1052 |           ) : Bool
 > 1053 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1054 |           end
 > 1055 |         
 > 1056 |           default_stub  def one?(
 > 1057 |             pattern, 
 > 1058 |             
 > 1059 |             
 > 1060 |           ) : Bool
 > 1061 |             super
 > 1062 |           end
 > 1063 |         
 > 1064 |           default_stub  def one?(
 > 1065 |             
 > 1066 |             
 > 1067 |             
 > 1068 |           ) : Bool
 > 1069 |             super
 > 1070 |           end
 > 1071 |         
 > 1072 |           default_stub  def partition(
 > 1073 |             
 > 1074 |             
 > 1075 |             & : (T ->)
 > 1076 |           ) : ::Tuple(Array(T), Array(T))
 > 1077 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1078 |           end
 > 1079 |         
 > 1080 |           default_stub  def reject(
 > 1081 |             
 > 1082 |             
 > 1083 |             & : (T ->)
 > 1084 |           )
 > 1085 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1086 |           end
 > 1087 |         
 > 1088 |           default_stub  def reject(
 > 1089 |             type : U.class, 
 > 1090 |             
 > 1091 |             
 > 1092 |           ) forall U
 > 1093 |             super
 > 1094 |           end
 > 1095 |         
 > 1096 |           default_stub  def reject(
 > 1097 |             pattern, 
 > 1098 |             
 > 1099 |             
 > 1100 |           ) : Array(T)
 > 1101 |             super
 > 1102 |           end
 > 1103 |         
 > 1104 |           default_stub  def sample(
 > 1105 |             n : Int, random = Random::DEFAULT, 
 > 1106 |             
 > 1107 |             
 > 1108 |           ) : Array(T)
 > 1109 |             super
 > 1110 |           end
 > 1111 |         
 > 1112 |           default_stub  def sample(
 > 1113 |             random = Random::DEFAULT, 
 > 1114 |             
 > 1115 |             
 > 1116 |           ) : T
 > 1117 |             super
 > 1118 |           end
 > 1119 |         
 > 1120 |           default_stub  def select(
 > 1121 |             
 > 1122 |             
 > 1123 |             & : (T ->)
 > 1124 |           )
 > 1125 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1126 |           end
 > 1127 |         
 > 1128 |           default_stub  def select(
 > 1129 |             type : U.class, 
 > 1130 |             
 > 1131 |             
 > 1132 |           ) : Array(U) forall U
 > 1133 |             super
 > 1134 |           end
 > 1135 |         
 > 1136 |           default_stub  def select(
 > 1137 |             pattern, 
 > 1138 |             
 > 1139 |             
 > 1140 |           ) : Array(T)
 > 1141 |             super
 > 1142 |           end
 > 1143 |         
 > 1144 |           default_stub  def size(
 > 1145 |             
 > 1146 |             
 > 1147 |             
 > 1148 |           ) : Int32
 > 1149 |             super
 > 1150 |           end
 > 1151 |         
 > 1152 |           default_stub  def empty?(
 > 1153 |             
 > 1154 |             
 > 1155 |             
 > 1156 |           ) : Bool
 > 1157 |             super
 > 1158 |           end
 > 1159 |         
 > 1160 |           default_stub  def skip(
 > 1161 |             count : Int, 
 > 1162 |             
 > 1163 |             
 > 1164 |           )
 > 1165 |             super
 > 1166 |           end
 > 1167 |         
 > 1168 |           default_stub  def skip_while(
 > 1169 |             
 > 1170 |             
 > 1171 |             & : (T ->)
 > 1172 |           ) : Array(T)
 > 1173 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1174 |           end
 > 1175 |         
 > 1176 |           default_stub  def sum(
 > 1177 |             initial, 
 > 1178 |             
 > 1179 |             
 > 1180 |           )
 > 1181 |             super
 > 1182 |           end
 > 1183 |         
 > 1184 |           default_stub  def sum(
 > 1185 |             
 > 1186 |             
 > 1187 |             
 > 1188 |           )
 > 1189 |             super
 > 1190 |           end
 > 1191 |         
 > 1192 |           default_stub  def sum(
 > 1193 |             initial, 
 > 1194 |             
 > 1195 |             & : (T ->)
 > 1196 |           )
 > 1197 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1198 |           end
 > 1199 |         
 > 1200 |           default_stub  def sum(
 > 1201 |             
 > 1202 |             
 > 1203 |             & : (T ->)
 > 1204 |           )
 > 1205 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1206 |           end
 > 1207 |         
 > 1208 |           default_stub private def additive_identity(
 > 1209 |             reflect, 
 > 1210 |             
 > 1211 |             
 > 1212 |           )
 > 1213 |             super
 > 1214 |           end
 > 1215 |         
 > 1216 |           default_stub  def product(
 > 1217 |             initial : Number, 
 > 1218 |             
 > 1219 |             
 > 1220 |           )
 > 1221 |             super
 > 1222 |           end
 > 1223 |         
 > 1224 |           default_stub  def product(
 > 1225 |             
 > 1226 |             
 > 1227 |             
 > 1228 |           )
 > 1229 |             super
 > 1230 |           end
 > 1231 |         
 > 1232 |           default_stub  def product(
 > 1233 |             initial : Number, 
 > 1234 |             
 > 1235 |             & : (T ->)
 > 1236 |           )
 > 1237 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1238 |           end
 > 1239 |         
 > 1240 |           default_stub  def product(
 > 1241 |             
 > 1242 |             
 > 1243 |             & : (T -> _)
 > 1244 |           )
 > 1245 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1246 |           end
 > 1247 |         
 > 1248 |           default_stub  def take_while(
 > 1249 |             
 > 1250 |             
 > 1251 |             & : (T ->)
 > 1252 |           ) : Array(T)
 > 1253 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1254 |           end
 > 1255 |         
 > 1256 |           default_stub  def tally_by(
 > 1257 |             hash, 
 > 1258 |             
 > 1259 |             &
 > 1260 |           )
 > 1261 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1262 |           end
 > 1263 |         
 > 1264 |           default_stub  def tally_by(
 > 1265 |             
 > 1266 |             
 > 1267 |             &block : (T -> U)
 > 1268 |           ) : Hash(U, Int32) forall U
 > 1269 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1270 |           end
 > 1271 |         
 > 1272 |           default_stub  def tally(
 > 1273 |             hash, 
 > 1274 |             
 > 1275 |             
 > 1276 |           )
 > 1277 |             super
 > 1278 |           end
 > 1279 |         
 > 1280 |           default_stub  def tally(
 > 1281 |             
 > 1282 |             
 > 1283 |             
 > 1284 |           ) : Hash(T, Int32)
 > 1285 |             super
 > 1286 |           end
 > 1287 |         
 > 1288 |           default_stub  def to_a(
 > 1289 |             
 > 1290 |             
 > 1291 |             
 > 1292 |           )
 > 1293 |             super
 > 1294 |           end
 > 1295 |         
 > 1296 |           default_stub  def to_h(
 > 1297 |             
 > 1298 |             
 > 1299 |             
 > 1300 |           )
 > 1301 |             super
 > 1302 |           end
 > 1303 |         
 > 1304 |           default_stub  def to_h(
 > 1305 |             
 > 1306 |             
 > 1307 |             & : (T -> Tuple(K, V))
 > 1308 |           ) forall K, V
 > 1309 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1310 |           end
 > 1311 |         
 > 1312 |           default_stub  def zip(
 > 1313 |             *others : Indexable | Iterable | Iterator, 
 > 1314 |             
 > 1315 |             &
 > 1316 |           )
 > 1317 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1318 |           end
 > 1319 |         
 > 1320 |           default_stub  def zip(
 > 1321 |             *others : Indexable | Iterable | Iterator, 
 > 1322 |             
 > 1323 |             
 > 1324 |           )
 > 1325 |             super
 > 1326 |           end
 > 1327 |         
 > 1328 |           default_stub  def zip?(
 > 1329 |             *others : Indexable | Iterable | Iterator, 
 > 1330 |             
 > 1331 |             &
 > 1332 |           )
 > 1333 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1334 |           end
 > 1335 |         
 > 1336 |           default_stub  def zip?(
 > 1337 |             *others : Indexable | Iterable | Iterator, 
 > 1338 |             
 > 1339 |             
 > 1340 |           )
 > 1341 |             super
 > 1342 |           end
 > 1343 |         
 > 1344 |           default_stub  def to_set(
 > 1345 |             
 > 1346 |             
 > 1347 |             
 > 1348 |           ) : Set(T)
 > 1349 |             super
 > 1350 |           end
 > 1351 |         
 > 1352 | 
 > 1353 |         
 > 1354 |           default_stub  def self.zip(
 > 1355 |             main, others : U, 
 > 1356 |             
 > 1357 |             &
 > 1358 |           ) forall U
 > 1359 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1360 |           end
 > 1361 |         
 > 1362 |           default_stub  def self.zip?(
 > 1363 |             main, others : U, 
 > 1364 |             
 > 1365 |             &
 > 1366 |           ) forall U
 > 1367 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1368 |           end
 > 1369 |         
 > 1370 |           default_stub  def self.element_type(
 > 1371 |             x, 
 > 1372 |             
 > 1373 |             
 > 1374 |           )
 > 1375 |             super
 > 1376 |           end
 > 1377 |         
 > 1378 |       
 > 1379 | 
 > 1380 |       
 > 1381 |         default_stub  def byte_set(
 > 1382 |           
 > 1383 |           
 > 1384 |           
 > 1385 |         ) : Set(Int32)
 > 1386 |           
 > 1387 |             super
 > 1388 |           end
 > 1389 |         
 > 1390 |       
 > 1391 |         default_stub  def char_set(
 > 1392 |           
 > 1393 |           
 > 1394 |           
 > 1395 |         ) : Set(Char)
 > 1396 |           
 > 1397 |             super
 > 1398 |           end
 > 1399 |         
 > 1400 |       
 > 1401 |         default_stub  def bytes(
 > 1402 |           
 > 1403 |           
 > 1404 |           
 > 1405 |         ) : Array(Int32)
 > 1406 |           
 > 1407 |             super
 > 1408 |           end
 > 1409 |         
 > 1410 |       
 > 1411 |         default_stub  def chars(
 > 1412 |           
 > 1413 |           
 > 1414 |           
 > 1415 |         ) : Array(Char)
 > 1416 |           
 > 1417 |             super
 > 1418 |           end
 > 1419 |         
 > 1420 |       
 > 1421 |         default_stub  def <<(
 > 1422 |           value : Char, 
 > 1423 |           
 > 1424 |           
 > 1425 |         ) : CharSet
 > 1426 |           
 > 1427 |             super
 > 1428 |           end
 > 1429 |         
 > 1430 |       
 > 1431 |         default_stub  def <<(
 > 1432 |           value : UInt8, 
 > 1433 |           
 > 1434 |           
 > 1435 |         ) : CharSet
 > 1436 |           
 > 1437 |             super
 > 1438 |           end
 > 1439 |         
 > 1440 |       
 > 1441 |         default_stub  def <<(
 > 1442 |           value : Int32, 
 > 1443 |           
 > 1444 |           
 > 1445 |         ) : CharSet
 > 1446 |           
 > 1447 |             super
 > 1448 |           end
 > 1449 |         
 > 1450 |       
 > 1451 |         default_stub  def includes_byte?(
 > 1452 |           byte : UInt8, 
 > 1453 |           
 > 1454 |           
 > 1455 |         ) : Bool
 > 1456 |           
 > 1457 |             super
 > 1458 |           end
 > 1459 |         
 > 1460 |       
 > 1461 |         default_stub  def includes_byte?(
 > 1462 |           byte : Int32, 
 > 1463 |           
 > 1464 |           
 > 1465 |         ) : Bool
 > 1466 |           
 > 1467 |             super
 > 1468 |           end
 > 1469 |         
 > 1470 |       
 > 1471 |         default_stub  def includes_char?(
 > 1472 |           char : Char, 
 > 1473 |           
 > 1474 |           
 > 1475 |         ) : Bool
 > 1476 |           
 > 1477 |             super
 > 1478 |           end
 > 1479 |         
 > 1480 |       
 > 1481 |         default_stub  def includes?(
 > 1482 |           byte : UInt8 | Int32, 
 > 1483 |           
 > 1484 |           
 > 1485 |         ) : Bool
 > 1486 |           
 > 1487 |             super
 > 1488 |           end
 > 1489 |         
 > 1490 |       
 > 1491 |         default_stub  def includes?(
 > 1492 |           char : Char, 
 > 1493 |           
 > 1494 |           
 > 1495 |         ) : Bool
 > 1496 |           
 > 1497 |             super
 > 1498 |           end
 > 1499 |         
 > 1500 |       
 > 1501 |         default_stub  def each_byte(
 > 1502 |           
 > 1503 |           
 > 1504 |           &block : (Int32 ->)
 > 1505 |         )
 > 1506 |           
 > 1507 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1508 |           end
 > 1509 |         
 > 1510 |       
 > 1511 |         default_stub  def each_char(
 > 1512 |           
 > 1513 |           
 > 1514 |           &block : (Char ->)
 > 1515 |         )
 > 1516 |           
 > 1517 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1518 |           end
 > 1519 |         
 > 1520 |       
 > 1521 |         default_stub  def each(
 > 1522 |           *args, 
 > 1523 |           **options, 
 > 1524 |           
 > 1525 |         )
 > 1526 |           
 > 1527 |             super
 > 1528 |           end
 > 1529 |         
 > 1530 |       
 > 1531 |         default_stub  def each(
 > 1532 |           *args, 
 > 1533 |           **options, 
 > 1534 |           &
 > 1535 |         )
 > 1536 |           
 > 1537 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1538 |           end
 > 1539 |         
 > 1540 |       
 > 1541 |         default_stub  def select_bytes(
 > 1542 |           
 > 1543 |           
 > 1544 |           &block : (Int32 -> Bool)
 > 1545 |         ) : Array(Int32)
 > 1546 |           
 > 1547 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1548 |           end
 > 1549 |         
 > 1550 |       
 > 1551 |         default_stub  def select_chars(
 > 1552 |           
 > 1553 |           
 > 1554 |           &block : (Char -> Bool)
 > 1555 |         ) : Array(Char)
 > 1556 |           
 > 1557 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1558 |           end
 > 1559 |         
 > 1560 |       
 > 1561 |         default_stub  def select(
 > 1562 |           *args, 
 > 1563 |           **options, 
 > 1564 |           
 > 1565 |         )
 > 1566 |           
 > 1567 |             super
 > 1568 |           end
 > 1569 |         
 > 1570 |       
 > 1571 |         default_stub  def select(
 > 1572 |           *args, 
 > 1573 |           **options, 
 > 1574 |           &
 > 1575 |         )
 > 1576 |           
 > 1577 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1578 |           end
 > 1579 |         
 > 1580 |       
 > 1581 |         default_stub  def map_bytes(
 > 1582 |           
 > 1583 |           
 > 1584 |           &block : (Int32 -> T)
 > 1585 |         ) : Array(T) forall T
 > 1586 |           
 > 1587 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1588 |           end
 > 1589 |         
 > 1590 |       
 > 1591 |         default_stub  def map_chars(
 > 1592 |           
 > 1593 |           
 > 1594 |           &block : (Char -> T)
 > 1595 |         ) : Array(T) forall T
 > 1596 |           
 > 1597 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1598 |           end
 > 1599 |         
 > 1600 |       
 > 1601 |         default_stub  def map(
 > 1602 |           *args, 
 > 1603 |           **options, 
 > 1604 |           
 > 1605 |         )
 > 1606 |           
 > 1607 |             super
 > 1608 |           end
 > 1609 |         
 > 1610 |       
 > 1611 |         default_stub  def map(
 > 1612 |           *args, 
 > 1613 |           **options, 
 > 1614 |           &
 > 1615 |         )
 > 1616 |           
 > 1617 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1618 |           end
 > 1619 |         
 > 1620 |       
 > 1621 |         default_stub  def random_byte(
 > 1622 |           random = Random::DEFAULT, 
 > 1623 |           
 > 1624 |           
 > 1625 |         ) : Int32
 > 1626 |           
 > 1627 |             super
 > 1628 |           end
 > 1629 |         
 > 1630 |       
 > 1631 |         default_stub  def random_char(
 > 1632 |           random = Random::DEFAULT, 
 > 1633 |           
 > 1634 |           
 > 1635 |         ) : Char
 > 1636 |           
 > 1637 |             super
 > 1638 |           end
 > 1639 |         
 > 1640 |       
 > 1641 |         default_stub  def each_random_byte(
 > 1642 |           n : Int, 
 > 1643 |           
 > 1644 |           &block : (Int32 ->)
 > 1645 |         )
 > 1646 |           
 > 1647 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1648 |           end
 > 1649 |         
 > 1650 |       
 > 1651 |         default_stub  def each_random_char(
 > 1652 |           n : Int, 
 > 1653 |           
 > 1654 |           &block : (Char ->)
 > 1655 |         )
 > 1656 |           
 > 1657 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1658 |           end
 > 1659 |         
 > 1660 |       
 > 1661 |         default_stub  def random_bytes(
 > 1662 |           length : Int, random = Random::DEFAULT, 
 > 1663 |           
 > 1664 |           
 > 1665 |         ) : Array(Int32)
 > 1666 |           
 > 1667 |             super
 > 1668 |           end
 > 1669 |         
 > 1670 |       
 > 1671 |         default_stub  def random_bytes(
 > 1672 |           lengths : Array(Int) | Range(Int, Int), random = Random::DEFAULT, 
 > 1673 |           
 > 1674 |           
 > 1675 |         ) : Array(Int32)
 > 1676 |           
 > 1677 |             super
 > 1678 |           end
 > 1679 |         
 > 1680 |       
 > 1681 |         default_stub  def random_chars(
 > 1682 |           length : Int, random = Random::DEFAULT, 
 > 1683 |           
 > 1684 |           
 > 1685 |         ) : Array(Char)
 > 1686 |           
 > 1687 |             super
 > 1688 |           end
 > 1689 |         
 > 1690 |       
 > 1691 |         default_stub  def random_chars(
 > 1692 |           lengths : Array(Int) | Range(Int, Int), random = Random::DEFAULT, 
 > 1693 |           
 > 1694 |           
 > 1695 |         ) : Array(Char)
 > 1696 |           
 > 1697 |             super
 > 1698 |           end
 > 1699 |         
 > 1700 |       
 > 1701 |         default_stub  def random_string(
 > 1702 |           length : Int | Array(Int) | Range(Int, Int), 
 > 1703 |           
 > 1704 |           
 > 1705 |         ) : String
 > 1706 |           
 > 1707 |             super
 > 1708 |           end
 > 1709 |         
 > 1710 |       
 > 1711 |         default_stub  def random_distinct_bytes(
 > 1712 |           length : Int, 
 > 1713 |           
 > 1714 |           
 > 1715 |         ) : Array(Int32)
 > 1716 |           
 > 1717 |             super
 > 1718 |           end
 > 1719 |         
 > 1720 |       
 > 1721 |         default_stub  def random_distinct_bytes(
 > 1722 |           length : Array(Int), random = Random::DEFAULT, 
 > 1723 |           
 > 1724 |           
 > 1725 |         ) : Array(Int32)
 > 1726 |           
 > 1727 |             super
 > 1728 |           end
 > 1729 |         
 > 1730 |       
 > 1731 |         default_stub  def random_distinct_bytes(
 > 1732 |           length : Range(Int, Int), 
 > 1733 |           
 > 1734 |           
 > 1735 |         ) : Array(Int32)
 > 1736 |           
 > 1737 |             super
 > 1738 |           end
 > 1739 |         
 > 1740 |       
 > 1741 |         default_stub  def random_distinct_chars(
 > 1742 |           length : Int, 
 > 1743 |           
 > 1744 |           
 > 1745 |         ) : Array(Char)
 > 1746 |           
 > 1747 |             super
 > 1748 |           end
 > 1749 |         
 > 1750 |       
 > 1751 |         default_stub  def random_distinct_chars(
 > 1752 |           length : Array(Int), random = Random::DEFAULT, 
 > 1753 |           
 > 1754 |           
 > 1755 |         ) : Array(Char)
 > 1756 |           
 > 1757 |             super
 > 1758 |           end
 > 1759 |         
 > 1760 |       
 > 1761 |         default_stub  def random_distinct_chars(
 > 1762 |           length : Range(Int, Int), 
 > 1763 |           
 > 1764 |           
 > 1765 |         ) : Array(Char)
 > 1766 |           
 > 1767 |             super
 > 1768 |           end
 > 1769 |         
 > 1770 |       
 > 1771 |         default_stub  def random_distinct_string(
 > 1772 |           length : Int | Array(Int) | Range(Int, Int), 
 > 1773 |           
 > 1774 |           
 > 1775 |         ) : String
 > 1776 |           
 > 1777 |             super
 > 1778 |           end
 > 1779 |         
 > 1780 |       
 > 1781 |         default_stub  def each_substring_with_index(
 > 1782 |           data : String, min_length : Int = 4, 
 > 1783 |           
 > 1784 |           &block : (String, Int32 ->)
 > 1785 |         )
 > 1786 |           
 > 1787 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1788 |           end
 > 1789 |         
 > 1790 |       
 > 1791 |         default_stub  def substrings_with_indexes(
 > 1792 |           data : String, min_length : Int = 4, 
 > 1793 |           
 > 1794 |           
 > 1795 |         ) : Array(Tuple(String, Int32))
 > 1796 |           
 > 1797 |             super
 > 1798 |           end
 > 1799 |         
 > 1800 |       
 > 1801 |         default_stub  def each_substring(
 > 1802 |           data : String, min_length : Int = 4, 
 > 1803 |           
 > 1804 |           &block : (String ->)
 > 1805 |         )
 > 1806 |           
 > 1807 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1808 |           end
 > 1809 |         
 > 1810 |       
 > 1811 |         default_stub  def substrings(
 > 1812 |           data : String, min_length : Int = 4, 
 > 1813 |           
 > 1814 |           
 > 1815 |         ) : Array(String)
 > 1816 |           
 > 1817 |             super
 > 1818 |           end
 > 1819 |         
 > 1820 |       
 > 1821 |         default_stub  def |(
 > 1822 |           other : CharSet, 
 > 1823 |           
 > 1824 |           
 > 1825 |         ) : CharSet
 > 1826 |           
 > 1827 |             super
 > 1828 |           end
 > 1829 |         
 > 1830 |       
 > 1831 |         default_stub  def +(
 > 1832 |           other : CharSet, 
 > 1833 |           
 > 1834 |           
 > 1835 |         ) : CharSet
 > 1836 |           
 > 1837 |             super
 > 1838 |           end
 > 1839 |         
 > 1840 |       
 > 1841 |         default_stub  def -(
 > 1842 |           other : CharSet, 
 > 1843 |           
 > 1844 |           
 > 1845 |         ) : CharSet
 > 1846 |           
 > 1847 |             super
 > 1848 |           end
 > 1849 |         
 > 1850 |       
 > 1851 |         default_stub  def &(
 > 1852 |           other : CharSet, 
 > 1853 |           
 > 1854 |           
 > 1855 |         ) : CharSet
 > 1856 |           
 > 1857 |             super
 > 1858 |           end
 > 1859 |         
 > 1860 |       
 > 1861 |         default_stub  def intersects?(
 > 1862 |           other : CharSet, 
 > 1863 |           
 > 1864 |           
 > 1865 |         ) : Bool
 > 1866 |           
 > 1867 |             super
 > 1868 |           end
 > 1869 |         
 > 1870 |       
 > 1871 |         default_stub  def subset_of?(
 > 1872 |           other : CharSet, 
 > 1873 |           
 > 1874 |           
 > 1875 |         ) : Bool
 > 1876 |           
 > 1877 |             super
 > 1878 |           end
 > 1879 |         
 > 1880 |       
 > 1881 |         default_stub  def ==(
 > 1882 |           other : CharSet, 
 > 1883 |           
 > 1884 |           
 > 1885 |         ) : Bool
 > 1886 |           
 > 1887 |             super
 > 1888 |           end
 > 1889 |         
 > 1890 |       
 > 1891 |         default_stub  def ===(
 > 1892 |           other : String, 
 > 1893 |           
 > 1894 |           
 > 1895 |         ) : Bool
 > 1896 |           
 > 1897 |             super
 > 1898 |           end
 > 1899 |         
 > 1900 |       
 > 1901 |         default_stub  def =~(
 > 1902 |           string : String, 
 > 1903 |           
 > 1904 |           
 > 1905 |         )
 > 1906 |           
 > 1907 |             super
 > 1908 |           end
 > 1909 |         
 > 1910 |       
 > 1911 |         default_stub  def to_a(
 > 1912 |           *args, 
 > 1913 |           **options, 
 > 1914 |           
 > 1915 |         )
 > 1916 |           
 > 1917 |             super
 > 1918 |           end
 > 1919 |         
 > 1920 |       
 > 1921 |         default_stub  def to_a(
 > 1922 |           *args, 
 > 1923 |           **options, 
 > 1924 |           &
 > 1925 |         )
 > 1926 |           
 > 1927 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1928 |           end
 > 1929 |         
 > 1930 |       
 > 1931 |         default_stub  def to_set(
 > 1932 |           *args, 
 > 1933 |           **options, 
 > 1934 |           
 > 1935 |         )
 > 1936 |           
 > 1937 |             super
 > 1938 |           end
 > 1939 |         
 > 1940 |       
 > 1941 |         default_stub  def to_set(
 > 1942 |           *args, 
 > 1943 |           **options, 
 > 1944 |           &
 > 1945 |         )
 > 1946 |           
 > 1947 |             super { |*__temp_1063| yield *__temp_1063 }
 > 1948 |           end
 > 1949 |         
 > 1950 |       
 > 1951 | 
 > 1952 |       
 > 1953 |         default_stub  def self.new(
 > 1954 |           values : Enumerable(UInt8 | Int32 | Char), 
 > 1955 |           
 > 1956 |           
 > 1957 |         )
 > 1958 |           super
 > 1959 |         end
 > 1960 |       
 > 1961 |         default_stub  def self.new(
 > 1962 |           values : Indexable(UInt8 | Int32 | Char), 
 > 1963 |           
 > 1964 |           
 > 1965 |         )
 > 1966 |           super
 > 1967 |         end
 > 1968 |       
 > 1969 |         default_stub  def self.new(
 > 1970 |           
 > 1971 |           
 > 1972 |           
 > 1973 |         )
 > 1974 |           super
 > 1975 |         end
 > 1976 |       
 > 1977 |         default_stub  def self.[](
 > 1978 |           *values : UInt8 | Int32 | Char | Range(UInt8, UInt8) | Range(Int32, Int32) | Range(Char, Char), 
 > 1979 |           
 > 1980 |           
 > 1981 |         ) : CharSet
 > 1982 |           super
 > 1983 |         end
 > 1984 |       
 > 1985 |     
Error: expanding macro


In lib/spectator/src/spectator/mocks/stubbable.cr:161:58

 161 | {{ if method.return_type && method.return_type.resolve == NoReturn
                                                      ^------
Error: undefined constant U


In /var/lib/snapd/snap/crystal/1384/share/crystal/src/enumerable.cr:466:48

 466 | def each_with_object(obj : U, & : T, U ->) : U forall U
                                                    ^
Error: undefined constant U

Are collection matchers work with objects?

I have this test that fails:

let(nodes) do
  [
    Node.new("<div></div>"),
    Node.new("<div></div>"),
    Node.new("<div></div>")
  ]
end

it "contains valid nodes" do
  expect(described_class.new(nodes).to_a).to contain_exactly nodes
end

There the output:

Failures:

  1) Katana::Html::NodeCollection#initialize contains the passed elements
     Failure: (described_class.new(nodes)).to_a does not contain exactly {nodes}

       expected: [[Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693f00 children=[#<XML::Element:0x10c685e00 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693e40 children=[#<XML::Element:0x10c685d80 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693d80 children=[#<XML::Element:0x10c685d00 name="div">]>)]]
         actual: [Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693f00 children=[#<XML::Element:0x10c685e00 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693e40 children=[#<XML::Element:0x10c685d80 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693d80 children=[#<XML::Element:0x10c685d00 name="div">]>)]
        missing: [Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693f00 children=[#<XML::Element:0x10c685e00 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693e40 children=[#<XML::Element:0x10c685d80 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693d80 children=[#<XML::Element:0x10c685d00 name="div">]>)]
          extra: [Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693f00 children=[#<XML::Element:0x10c685e00 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693e40 children=[#<XML::Element:0x10c685d80 name="div">]>), Katana::Html::Node(@is_text=false, @origin=#<XML::HTMLDocument:0x10c693d80 children=[#<XML::Element:0x10c685d00 name="div">]>)]

It seems the matcher cannot match the objects and for the test to pass, I need to write it like this:

it "contains valid nodes" do
  described_class.new(nodes).to_a.each_with_index do |node, i|
    expect(node).to eq nodes[i]
  end
end

Better error when attempting to use string interpolation in it descriptions?

If you attempt to do something like it "sets #{my_var} on object" you get an ugly compiler error like

$ crystal spec spec/i18next_plurals_spec.cr
Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro '_spectator_metadata'

Code in macro 'it'

 4 | _spectator_metadata(__temp_36, :metadata,  )
     ^
Called macro defined in lib/spectator/src/spectator/dsl/metadata.cr:6:13

 6 | private macro _spectator_metadata(name, source, *tags, **metadata)

Which expanded to:

 > 1 | private def self.__temp_36
                        ^--------
Error: can't declare def dynamically

Would it be possible to have a more helpful error for this? You can check for string interpolation in macros by doing something like {% if string.is_a?(StringInterpolation) %}

Allow defining doubles without a block

When porting RSpec specs to Spectator, often times the doubles lack any body or stubs.

let(:foo) { double(:foo) }

I think it would be useful to support defining doubles without a block:

double :foo
double :bar

let(args) { {double(:foo), double(:bar)} }

stub method(arg1,arg2,...) syntax seems to no longer work in 0.10/master

After upgrading to spectator 0.10 I noticed that the stub method(arg1, ...) syntax is now being confused as a method call.

Trying stub method(arg1,arg2) { true } gives me the same error, as well.

Spec

require "./spec_helper"
require "../src/command_kit/man"

Spectator.describe CommandKit::Man do
  module TestMan
    class TestCommand
      include CommandKit::Man
    end
  end

  let(command_class) { TestMan::TestCommand }

  subject { command_class.new }

  describe "#man" do
    mock TestMan::TestCommand do
      stub system(command, arg)
    end

    let(man_page) { "foo" }

    it "must call system() with the given man page" do
      expect(subject).to receive(:system).with("man", [man_page])

      subject.man(man_page)
    end

    context "when given the section: keyword argument" do
      let(section) { 7 }

      it "must call system() with the given section number and man page" do
        expect(subject).to receive(:system).with("man", [section.to_s, man_page])

        subject.man(man_page, section: section)
      end
    end
  end
end

Errror

 crystal spec spec/man_spec.cr  --error-trace
error in line 1
Error: while requiring "./spec/man_spec.cr"


In spec/man_spec.cr:4:11

 4 | Spectator.describe CommandKit::Man do
               ^-------
Error: expanding macro


In spec/man_spec.cr:4:1

 4 | Spectator.describe CommandKit::Man do
     ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in macro 'macro_140629716498624'

 46 | macro describe(description, *tags, **metadata, &block)

Which expanded to:

 >  1 |         class ::SpectatorTestContext
 >  2 |           describe(CommandKit::Man,  ) do
 >  3 |   module TestMan
 >  4 |     class TestCommand
 >  5 |       include CommandKit::Man
 >  6 |     end
 >  7 |   end
 >  8 |   let(command_class) do
 >  9 |     TestMan::TestCommand
 > 10 |   end
 > 11 |   subject do
 > 12 |     command_class.new
 > 13 |   end
 > 14 |   describe("#man") do
 > 15 |     mock(TestMan::TestCommand) do
 > 16 |       stub(system(command, arg))
 > 17 |     end
 > 18 |     let(man_page) do
 > 19 |       "foo"
 > 20 |     end
 > 21 |     it("must call system() with the given man page") do
 > 22 |       (expect(subject)).to((receive(:system)).with("man", [man_page]))
 > 23 |       subject.man(man_page)
 > 24 |     end
 > 25 |     context("when given the section: keyword argument") do
 > 26 |       let(section) do
 > 27 |         7
 > 28 |       end
 > 29 |       it("must call system() with the given section number and man page") do
 > 30 |         (expect(subject)).to((receive(:system)).with("man", [section.to_s, man_page]))
 > 31 |         subject.man(man_page, section: section)
 > 32 |       end
 > 33 |     end
 > 34 |   end
 > 35 | end
 > 36 |         end
 > 37 |       
Error: expanding macro


In spec/man_spec.cr:15:1

 15 | describe "#man" do
    ^-------
Error: expanding macro


In spec/man_spec.cr:22:1

 22 | it "must call system() with the given man page" do
  ^-
Error: expanding macro


In spec/man_spec.cr:22:1

 22 | it "must call system() with the given man page" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_67, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_68() : Nil
 > 11 |             (expect(subject)).to((receive(:system)).with("man", [man_page]))
 > 12 | subject.man(man_page)
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must call system() with the given man page"),
 > 18 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/commit_kit.cr/spec/man_spec.cr", 22, 26),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_67
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_57) do
 > 23 |               
 > 24 |                 __temp_68
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_57.class)'


In spec/man_spec.cr:22:1

 22 | it "must call system() with the given man page" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_67, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_68() : Nil
 > 11 |             (expect(subject)).to((receive(:system)).with("man", [man_page]))
 > 12 | subject.man(man_page)
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must call system() with the given man page"),
 > 18 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/commit_kit.cr/spec/man_spec.cr", 22, 26),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_67
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_57) do
 > 23 |               
 > 24 |                 __temp_68
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_57.class)'


In spec/man_spec.cr:22:1

 22 | it "must call system() with the given man page" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_67, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_68() : Nil
 > 11 |             (expect(subject)).to((receive(:system)).with("man", [man_page]))
 > 12 | subject.man(man_page)
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must call system() with the given man page"),
 > 18 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/commit_kit.cr/spec/man_spec.cr", 22, 26),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_67
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_57) do
 > 23 |               
 > 24 |                 __temp_68
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating '__temp_68()'


In spec/man_spec.cr:24:9

 24 | 
              ^--
Error: instantiating 'SpectatorTestContext::Group__temp_51::TestMan::TestCommand#man(String)'


In src/command_kit/man.cr:36:9

 36 | system("man", [section.to_s, page])
      ^-----
Error: instantiating 'system(String, Array(String))'


In spec/man_spec.cr:6:11

 6 | class TestCommand
           ^
Error: expanding macro


There was a problem expanding macro 'stub'

Called macro defined in lib/spectator/src/spectator/mocks/stubs.cr:3:13

 3 | private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, return_type = :undefined, &block)

Which expanded to:

 >  1 |       
 >  2 | 
 >  3 |       
 >  4 | 
 >  5 |       def system(command, arg)
 >  6 |         if (__temp_60 = ::Spectator::Harness.current?)
 >  7 |           __temp_61 = ::Spectator::Mocks::GenericArguments.create(command, arg)
 >  8 |           __temp_62 = ::Spectator::Mocks::MethodCall.new(:system, __temp_61)
 >  9 |           __temp_60.mocks.record_call(self, __temp_62)
 > 10 |           if (__temp_63 = __temp_60.mocks.find_stub(self, __temp_62))
 > 11 |             return __temp_63.call!(__temp_61) { system }
 > 12 |           end
 > 13 | 
 > 14 |           
 > 15 |             system
 > 16 |           
 > 17 |         else
 > 18 |           system
 > 19 |         end
 > 20 |       end
 > 21 | 
 > 22 |       def system(command, arg)
 > 23 |         if (__temp_60 = ::Spectator::Harness.current?)
 > 24 |           __temp_61 = ::Spectator::Mocks::GenericArguments.create(command, arg)
 > 25 |           __temp_62 = ::Spectator::Mocks::MethodCall.new(:system, __temp_61)
 > 26 |           __temp_60.mocks.record_call(self, __temp_62)
 > 27 |           if (__temp_63 = __temp_60.mocks.find_stub(self, __temp_62))
 > 28 |             return __temp_63.call!(__temp_61) { system { |*__temp_64| yield *__temp_64 } }
 > 29 |           end
 > 30 | 
 > 31 |           
 > 32 |             system do |*__temp_65|
 > 33 |               yield *__temp_65
 > 34 |             end
 > 35 |           
 > 36 |         else
 > 37 |           system do |*__temp_65|
 > 38 |             yield *__temp_65
 > 39 |           end
 > 40 |         end
 > 41 |       end
 > 42 |     
Error: wrong number of arguments for 'SpectatorTestContext::Group__temp_51::TestMan::TestCommand#system' (given 0, expected 2)

Overloads are:
 - SpectatorTestContext::Group__temp_51::TestMan::TestCommand#system(command, arg)
 - SpectatorTestContext::Group__temp_51::TestMan::TestCommand#system(command, arg, &block)

shards.yml

development_dependencies:
  spectator:
    gitlab: arctic-fox/spectator
      # branch: master
    version: "~> 0.10"

Example line numbers do not seem to match the source

Having a spec file

require "./spec_helper"

Spectator.describe Thing do
  it "works" do
    expect(false).to eq(true)
  end
end

crystal spec reports

     Failure: false does not equal true

         actual: false
       expected: true

     # spec/bisect_spec.cr:21

The example is defined at line 4 but (I suppose) after macro expansion it appears on line 21. This is a problem in case one uses tools like vim-test which run the spec-under-cursor using the line number of it.

Can anything be done to make the line numbers match?

Execution order of around + before/after hooks

At the moment the order in which Spectator executes around + before/after hooks is:

before each
around each before
in the example
around each after
after each

Spectator docs:

When mixed with the other "each" and "all" hooks, around_each starts execution after before_each and finishes execution before after_each.

This makes it difficult to do things like:

describe "example" do
  around_each do |proc|
    default_state = described_class.save_state
    proc.call
    described_class.load_state(default_state)
  end

  context "with state1" do
    before_each { described_class.load_state(state1) }
    it โ€ฆ
  end

  context "with state2" do
    before_each { described_class.load_state(state2) }
    it โ€ฆ
  end
end

Obviously there are work-arounds for the above, but I don't feel that they are as neat as using a combination of around + before/after hooks.

The order of execution also differs from the order in which Rspec executes the same hooks.

Rspec docs:

around each before
before each
in the example
after each
around each after

I was wondering if there is a reason for this difference and if it would be possible to bring this bit of functionality into sync with Rspec?

How to spec classes that contain class which shadow top-level primitives?

An interesting problem. I'm attempting to port this Ruby code which defines a Type class which contains other classes such as Int32. In Crystal I had to explicitly specify ::Int32 to avoid constant shadowing. However, Spectator is having issues with crystal_type_id. It appears that when describing Hexdump::Type the Int32 class contained within is shadowing the top level Int32.

Source

module Hexdump
  #
  # @api private
  #
  # @since 1.0.0
  #
  class Type

    enum Endian
      LITTLE
      BIG
      NETWORK = BIG

      def self.native : Endian
        int = 1_i32
        native_bytes = Bytes.new(pointerof(int).as(UInt8 *), sizeof(typeof(int)))
        big_endian_bytes = Bytes.new(sizeof(typeof(int)))

        IO::ByteFormat::BigEndian.encode(int,big_endian_bytes)

        if native_bytes == big_endian_bytes; BIG
        else                                 LITTLE
        end
      end
    end

    # The size in bytes of the type.
    #
    # @return [1, 2, 4, 8]
    getter size : ::Int32

    getter? signed : ::Bool

    # The endian-ness of the type.
    #
    # @return [:little, :big, nil]
    getter endian : Endian?

    #
    # Initializes the type.
    #
    # @param [Symbol] name
    #
    # @param [:little, :big, nil] endian
    #
    # @param [1, 2, 4, 8] size
    #
    # @param [Boolean] signed
    #
    # @raise [ArgumentError]
    #   Invalid `endian:` or `size:` values.
    #
    def initialize(@size : ::Int32, @signed : ::Bool, @endian : Endian? = nil)
    end

    #
    # Whether the type is unsigned.
    #
    # @return [Boolean]
    #
    def unsigned?
      !signed?
    end

    # The native endian-ness.
    NATIVE_ENDIAN = Endian.native

    #
    # Represents a signed integer type.
    #
    class Int < self

      #
      # Initializes the int type.
      #
      # @param [:little, :big] endian (NATIVE_ENDIAN)
      #   The endian-ness of the int type.
      #
      def initialize(size : Int32, endian : Endian? = NATIVE_ENDIAN)
        super(size: size, signed: true, endian: endian)
      end
    end

    class Int32 < Int

      #
      # @see Int#initialize
      #
      def initialize(endian : Endian? = NATIVE_ENDIAN)
        super(size: 4, endian: endian)
      end
    end

  end
end

Specs

require "./spec_helper"
require "../src/hexdump/type"

Spectator.describe Hexdump::Type do
  describe "#initialize" do
    let(size) { 4 }
    let(signed) { true }

    subject { described_class.new(size: size, signed: signed) }

    it "must set #size" do
      expect(subject.size).to eq(size)
    end

    it "must not set #endian" do
      expect(subject.endian).to be(nil)
    end

    it "must set #signed?" do
      expect(subject.signed?).to eq(signed)
    end

    context "when given endian:" do
      let(endian) { Hexdump::Type::Endian::BIG }

      subject do
        described_class.new(size: size, signed: signed, endian: endian)
      end

      it "must set #endian" do
        expect(subject.endian).to eq(endian)
      end
    end
  end

  describe "#signed?" do
    let(size) { 4 }

    subject { described_class.new(size: size, signed: signed) }

    context "when initialized with signed: true" do
      let(signed) { true }

      it do
        expect(subject.signed?).to be(true)
      end
    end

    context "when initialized with signed: false" do
      let(signed) { false }

      it do
        expect(subject.signed?).to be(false)
      end
    end
  end

  describe "#unsigned?" do
    let(size) { 4 }

    subject { described_class.new(size: size, signed: signed) }

    context "when initialized with signed: true" do
      let(signed) { true }

      it do
        expect(subject.unsigned?).to be(false)
      end
    end

    context "when initialized with signed: false" do
      let(signed) { false }

      it do
        expect(subject.unsigned?).to be(true)
      end
    end
  end

  describe Hexdump::Type::Int32 do
    it { expect(subject).is_a?(Hexdump::Type::Int) }

    describe "#initialize" do
      it "must default #signed? to true" do
        expect(subject.signed?).to be(true)
      end

      it "must default #size to 4" do
        expect(subject.size).to be(4)
      end

      it "must default #endian to NATIVE_ENDIAN" do
        expect(subject.endian).to eq(Hexdump::Type::NATIVE_ENDIAN)
      end
    end
  end
end

Error

$ crystal spec spec/type_spec.cr --error-trace
error in line 1
Error: while requiring "./spec/type_spec.cr"


In spec/type_spec.cr:4:11

 4 | Spectator.describe Hexdump::Type do
               ^-------
Error: expanding macro


In spec/type_spec.cr:4:1

 4 | Spectator.describe Hexdump::Type do
     ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in macro 'macro_140623426311824'

 46 | macro describe(description, *tags, **metadata, &block)

Which expanded to:

 >   1 |         class ::SpectatorTestContext
 >   2 |           describe(Hexdump::Type,  ) do
 >   3 |   describe("#initialize") do
 >   4 |     let(size) do
 >   5 |       4
 >   6 |     end
 >   7 |     let(signed) do
 >   8 |       true
 >   9 |     end
 >  10 |     subject do
 >  11 |       described_class.new(size: size, signed: signed)
 >  12 |     end
 >  13 |     it("must set #size") do
 >  14 |       (expect(subject.size)).to(eq(size))
 >  15 |     end
 >  16 |     it("must not set #endian") do
 >  17 |       (expect(subject.endian)).to(be(nil))
 >  18 |     end
 >  19 |     it("must set #signed?") do
 >  20 |       (expect(subject.signed?)).to(eq(signed))
 >  21 |     end
 >  22 |     context("when given endian:") do
 >  23 |       let(endian) do
 >  24 |         Hexdump::Type::Endian::BIG
 >  25 |       end
 >  26 |       subject do
 >  27 |         described_class.new(size: size, signed: signed, endian: endian)
 >  28 |       end
 >  29 |       it("must set #endian") do
 >  30 |         (expect(subject.endian)).to(eq(endian))
 >  31 |       end
 >  32 |     end
 >  33 |   end
 >  34 |   describe("#signed?") do
 >  35 |     let(size) do
 >  36 |       4
 >  37 |     end
 >  38 |     subject do
 >  39 |       described_class.new(size: size, signed: signed)
 >  40 |     end
 >  41 |     context("when initialized with signed: true") do
 >  42 |       let(signed) do
 >  43 |         true
 >  44 |       end
 >  45 |       it do
 >  46 |         (expect(subject.signed?)).to(be(true))
 >  47 |       end
 >  48 |     end
 >  49 |     context("when initialized with signed: false") do
 >  50 |       let(signed) do
 >  51 |         false
 >  52 |       end
 >  53 |       it do
 >  54 |         (expect(subject.signed?)).to(be(false))
 >  55 |       end
 >  56 |     end
 >  57 |   end
 >  58 |   describe("#unsigned?") do
 >  59 |     let(size) do
 >  60 |       4
 >  61 |     end
 >  62 |     subject do
 >  63 |       described_class.new(size: size, signed: signed)
 >  64 |     end
 >  65 |     context("when initialized with signed: true") do
 >  66 |       let(signed) do
 >  67 |         true
 >  68 |       end
 >  69 |       it do
 >  70 |         (expect(subject.unsigned?)).to(be(false))
 >  71 |       end
 >  72 |     end
 >  73 |     context("when initialized with signed: false") do
 >  74 |       let(signed) do
 >  75 |         false
 >  76 |       end
 >  77 |       it do
 >  78 |         (expect(subject.unsigned?)).to(be(true))
 >  79 |       end
 >  80 |     end
 >  81 |   end
 >  82 |   describe(Hexdump::Type::Int32) do
 >  83 |     it do
 >  84 |       expect(subject).is_a?(Hexdump::Type::Int)
 >  85 |     end
 >  86 |     describe("#initialize") do
 >  87 |       it("must default #signed? to true") do
 >  88 |         (expect(subject.signed?)).to(be(true))
 >  89 |       end
 >  90 |       it("must default #size to 4") do
 >  91 |         (expect(subject.size)).to(be(4))
 >  92 |       end
 >  93 |       it("must default #endian to NATIVE_ENDIAN") do
 >  94 |         (expect(subject.endian)).to(eq(Hexdump::Type::NATIVE_ENDIAN))
 >  95 |       end
 >  96 |     end
 >  97 |   end
 >  98 | end
 >  99 |         end
 > 100 |       
Error: expanding macro


In spec/type_spec.cr:15:11

 15 | it "must not set #endian" do
            ^-------
Error: expanding macro


In spec/type_spec.cr:14:1

 14 | 
      ^-
Error: expanding macro


In spec/type_spec.cr:14:1

 14 | 
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_65, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_66() : Nil
 > 11 |             (expect(subject.endian)).to(be(nil))
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("must not set #endian"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/hexdump.cr/spec/type_spec.cr", 15, 17),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_65
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 21 |               
 > 22 |                 __temp_66
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_55.class)'


In spec/type_spec.cr:14:1

 14 | 
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_65, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_66() : Nil
 > 11 |             (expect(subject.endian)).to(be(nil))
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("must not set #endian"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/hexdump.cr/spec/type_spec.cr", 15, 17),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_65
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 21 |               
 > 22 |                 __temp_66
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_55.class)'


In spec/type_spec.cr:14:1

 14 | 
      ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_65, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_66() : Nil
 > 11 |             (expect(subject.endian)).to(be(nil))
 > 12 |           end
 > 13 | 
 > 14 |           ::Spectator::DSL::Builder.add_example(
 > 15 |             _spectator_example_name("must not set #endian"),
 > 16 |             ::Spectator::Location.new("/data/home/postmodern/code/crystal/hexdump.cr/spec/type_spec.cr", 15, 17),
 > 17 |             -> { new.as(::Spectator::Context) },
 > 18 |             __temp_65
 > 19 |           ) do |example|
 > 20 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 21 |               
 > 22 |                 __temp_66
 > 23 |               
 > 24 |             end
 > 25 |           end
 > 26 | 
 > 27 |         
 > 28 |       
Error: instantiating '__temp_66()'


In spec/type_spec.cr:16:31

 16 | expect(subject.endian).to be(nil)
                              ^-
Error: instantiating 'Spectator::Expectation::Target(Hexdump::Type::Endian | Nil)#to(Spectator::Matchers::ReferenceMatcher(Nil))'


In lib/spectator/src/spectator/expectation.cr:104:7

 104 | def to(matcher, message = nil) : Nil
       ^-
Error: instantiating 'to(Spectator::Matchers::ReferenceMatcher(Nil), Nil)'


In lib/spectator/src/spectator/expectation.cr:105:30

 105 | match_data = matcher.match(@expression)
                            ^----
Error: instantiating 'Spectator::Matchers::ReferenceMatcher(Nil)#match(Spectator::Expression(Hexdump::Type::Endian | Nil)+)'


In lib/spectator/src/spectator/matchers/standard_matcher.cr:27:10

 27 | if match?(actual)
         ^-----
Error: instantiating 'match?(Spectator::Expression(Hexdump::Type::Endian | Nil)+)'


In lib/spectator/src/spectator/matchers/reference_matcher.cr:21:28

 21 | actual.value.class == value.class && actual.value == value
                         ^
Error: instantiating '(Hexdump::Type::Endian.class | Nil.class)#==(Nil.class)'


In /var/lib/snapd/snap/crystal/793/share/crystal/src/class.cr:18:5

 18 | crystal_type_id == other.crystal_type_id
      ^--------------
Error: instantiating 'crystal_type_id()'


In /var/lib/snapd/snap/crystal/793/share/crystal/src/primitives.cr:32:25

 32 | def crystal_type_id : Int32
                            ^----
Error: method Hexdump::Type::Endian.crystal_type_id must return Int32 but it is returning Hexdump::Type::Int32

Hexdump::Type::Int32 trace:

  /var/lib/snapd/snap/crystal/793/share/crystal/src/primitives.cr:32

      def crystal_type_id : Int32

GitLab or GitHub

It seems like you keep this repo and your GitLab repo in sync commit-wise, but there are different open issues in both. I'm about to open a GitHub PR but I have no idea if I should actually make it on GitLab instead.

Which repo is/should be the main one and which is a mirror? Could you add something to the README to help us understand?

Add allow and receive

It would be great, although I don't know if it's quite possible, to have allow and receive which tend to work closely together. This would allow for us to make sure that certain methods are being called as the result of another method being called. For instance:

allow(model).to receive(:fit_corpus).with(text)

it "calls its internal methods #fit_corpus to build the corpus obj" do
  expect(model).to receive(:fit_corpus).with(text)
  model.fit(text)
end

https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/method-stubs

Capturing and reusing STDOUT / STDERR of the test subject

Hi there,

First of all, thank you so much for this project! I was never a great fun of the built in should syntax or the way it mutates test objects, so it was awesome to find an option of using expect(โ€ฆ).to โ€ฆ. The fact that it comes with all the familiar rspec-like goodies (eg: subject, described_class, let, mocks & doubles, spies) is the icing on top of the cake! ๐Ÿ‘

The only shortcoming I found so far is that there is no way to capture STDOUT / STDERR of the app that is being tested. This is not much of an issue when dealing with web apps, but it does become a problem when testing CLI apps:

  1. the output of the test run becomes mixed-in with the output of the app
  2. it becomes difficult to validate the output of the app, eg: to ensure that all errors start_with("Error: ")

I've built a simple "Hello World" to demonstrate the problem. If you checkout the project and run make tests you'll see that the output produced by Spectator is:

$ crystal spec --progress --order rand
=> STDOUT Hello World                               
=> STDERR Hello World
.

Finished in 38 microseconds
1 examples, 0 failures, 0 errors, 0 pending

Randomized with seed 13940

Obviously, lines 2 & 3 of this output come from my hello-world app and not from Spectator itself.

Would it be possible to update Spectator to support a configuration that allows us to redirect STOUT of the test to one custom IO stream, and STDERR to another? Something like:

Spectator.configure do |config|
  config.capture_stdout
  config.capture_stderr
end

Spectator would then create a custom IO stream during a test run. This streams would capture the output of the app during the test. I guess it would be best if they were initialised anew for each test and destroyed when the test is done. This would allow us to test stdout and stderr outputs of our scripts using existing matchers, for example:

subject { MyApp.show_error_with_help }
it { expect(subject.stdout).to start_with("Usage: ") }
it { expect(subject.stderr).to start_with("Error: ") }

Allow matching any line number of example in filter

The file and line filter should be able to match any line of the example block, not just the first. For instance:

it "does a thing" do # Line 1
  expect(true).to be_true # Line 2
end # Line 3

Running any of the following the match and run the example.

crystal spec spec/example_spec.cr:1
crystal spec spec/example_spec.cr:2
crystal spec spec/example_spec.cr:3

Implementing this involves adding a "end line" value to the Source type in addition to updating the example filter to match the line range.

Spawned from #18

Usage of keyword arguments as positional arguments in stubs

Allow positional arguments to be defined with keyword arguments. Currently, keyword arguments are treated separately from positional arguments.

class Original
  def foo(arg1, arg2)
    # ...
  end
end

mock Original

let(fake) { mock(Original) }

specify do
  expect(fake).to receive(:foo).with("arg1", arg2: "arg2")
  fake.foo("arg1", "arg2")
end

The expectation fails because it recorded the call #foo("arg1", "arg2") and not #foo("arg1", arg2: "arg2"). The two calls are technically the same to Crystal, but Spectator treats them differently.

Add HTML as a format option

Just another thing that rspec has that would be wonderful to have here is the --format html command line flag. Idk if you already have it planned, but it's not on the roadmap so I figured I'd put it here.

How to mock/stub IO::Memory#gets

I'm attempting to mock/stub IO::Memory#gets to verify that it is called multiple times until non-empty user input is given. However, Spectator seems to be looking for the original IO#gets method? Is this due to the numeric overloads of IO#gets?

Specs

require "./spec_helper"
require "../src/command_kit/interactive"

Spectator.describe CommandKit::Interactive do
  module TestInteractive
    class TestCommand
      include CommandKit::Interactive

      def initialize(**kwargs)
        super(**kwargs)
      end
    end
  end

  let(command_class) { TestInteractive::TestCommand }

  describe "#included" do
    subject { command_class }

    it { expect(subject).to be_a(CommandKit::Stdio) }
  end

  mock IO::Memory do
    stub print
    stub gets : String?
  end

  let(stdout) { IO::Memory.new }
  let(stdin)  { IO::Memory.new }
  let(stderr) { IO::Memory.new }

  subject do
    command_class.new(stdout: stdout, stdin: stdin, stderr: stderr)
  end

  let(prompt) { "Prompt" }

  describe "#ask" do
    let(input) { "foo" }

    it "must print a prompt, read input, and return the input" do
      expect(stdout).to receive(:print).with("#{prompt}: ")
      expect(stdin).to receive(:gets).and_return(input)

      expect(subject.ask(prompt)).to eq(input)
    end
  end
end

Error

$ crystal spec spec/interactive_spec.cr 
Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro 'stub'

Code in spec/interactive_spec.cr:25:1

 25 | stub gets : String?
  ^
Called macro defined in lib/spectator/src/spectator/mocks/stubs.cr:3:13

 3 | private macro stub(definition, *types, _file = __FILE__, _line = __LINE__, return_type = :undefined, &block)

Which expanded to:

 >  9 | __temp_70.mocks.record_call(self, __temp_72)
 > 10 | if (__temp_73 = __temp_70.mocks.find_stub(self, __temp_72))
 > 11 |   return __temp_73.call!(__temp_71) { previous_def }
                                              ^-----------
Error: there is no previous definition of 'gets'

Stubs for mocked types should use the spec scope

Currently, the scope of method stubs with a default scope is executed within the mocked type. This should be changed to execute in the scope of the spec.

struct MyStruct
  def stubbed_method
    answer
  end

  def answer
    1
  end
end

Spectator.describe MyStruct do
  let(answer) { 42 }

  mock MyStruct do
    stub stubbed_method { answer }
  end

  it "returns the answer" do
    expect(subject.stubbed_method).to eq(42) # Fails, reports 1
  end
end

Issue originally raised in #14

Allow mocking libs

Seems as though libs can't be mocked the way you can a class or module. I don't know if it's possible given the constraints of the language, but I thought I'd put it out there.

Stubbed methods with default arguments not working

It seems that default arguments are not working properly when the method in question is being stubbed.

require "spectator"

class Person
  def initialize(@dog = Dog.new)
  end

  def pet
    @dog.pet
  end
end

class Dog
  def initialize()
  end

  def pet(times = 3)
    "woof" * times
  end
end

Spectator.describe Person do
  mock Dog do
    stub pet(times)
  end

  describe "#pet" do
    it "pets the persons dog" do
      dog = Dog.new
      person = Person.new(dog)
      allow(dog).to receive(pet()).and_return("woof")

      result = person.pet

      expect(result).to be("woof")
      expect(dog).to have_received(pet())
    end
  end
end

The error is:
Screenshot 2022-02-23 at 17 48 05

Adding another stub to the mock (something like stub pet()) also does not work.

Allow contain(*elements)

I noticed that contain automatically wraps the given argument in { } as a Tuple. It would be useful to be able to call contain() with multiple splatted arguments.

Example

alias ArgsTuple = Tuple(String, Extractor::MetaType, Extractor::MetaFormat, String, Extractor::MetaData)

let(metadata_file) { File.join(fixtures_dir,"image-metadata.yml") }
let(expected_metadata) do
  Array(ArgsTuple).from_yaml(File.read(metadata_file))
end

it "should extract metadata from a file" do
  findings = [] of ArgsTuple

  subject.extract(data) do |plugin_name,type,format,mime_type,data|
    findings << {plugin_name, type, format, mime_type, data}
  end

  expect(findings).to contain(*expected_metadata)
end

Result

Code in spec/extractor_spec.cr:53:27

 53 | expect(findings).to contain(*expected_metadata)
                          ^
Called macro defined in lib/spectator/src/spectator/dsl/matchers.cr:452:5

 452 | macro contain(*expected)

Which expanded to:

 > 1 | __temp_1618 = ::Spectator::TestValue.new({*expected_metadata}, "*expected_metadata")
                                               ^
Error: unterminated call

Mocks on nameless splats

Hi! Thanks for your library! It makes me, an RSpec lover, feel more at home. :-)

After a while I tried to run my project specs (that used to work) and something is failing now. I don't really know what caused the failure; perhaps a newer Crystal version.

My tail of shards.lock:

  spectator:
    git: https://gitlab.com/arctic-fox/spectator.git
    version: 0.11.5
$ crystal --version
Crystal 1.7.2 (2023-01-23)

LLVM: 14.0.6
Default target: aarch64-apple-darwin22.1.0

$ git clone https://github.com/kaukas/crystal-cassandra
$ shards install
$ crystal spec spec/cassandra/dbapi_spec.cr --error-trace
There was a problem expanding macro 'default_stub'

Called macro defined in lib/spectator/src/spectator/mocks/stubbable.cr:108:13

 108 | private macro default_stub(method)

Which expanded to:

 >  1 |
 >  2 |
 >  3 |
 >  4 |
 >  5 |
 >  6 |        def should(
 >  7 |         expectation : BeAExpectation(T), failure_message : String | ::Nil = nil, *, file = __FILE__, line = __LINE__,
 >  8 |
 >  9 |
 > 10 |       ) : T forall T
 > 11 |         super(expectation, failure_message, file: file, line: line)
 > 12 |       end
 > 13 |
 > 14 |
 > 15 |
 > 16 |
 > 17 |        def should(
 > 18 |         expectation : BeAExpectation(T), failure_message : String | ::Nil = nil, *, file = __FILE__, line = __LINE__,
 > 19 |
 > 20 |
 > 21 |       ) : T forall T
 > 22 |
 > 23 |         # Capture information about the call.
 > 24 |         __temp_153 = ::Spectator::MethodCall.build(
 > 25 |           :should,
 > 26 |           ::NamedTuple.new(
 > 27 |             "expectation": expectation, "failure_message": failure_message,
 > 28 |           ),
 > 29 |           :"", ,
 > 30 |           ::NamedTuple.new(
 > 31 |             "file": file, "line": line,
 > 32 |           ).merge()
 > 33 |         )
 > 34 |         _spectator_record_call(__temp_153)
 > 35 |
 > 36 |         # Attempt to find a stub that satisfies the method call and arguments.
 > 37 |         # Finding a suitable stub is delegated to the type including the `Stubbable` module.
 > 38 |         if __temp_154 = _spectator_find_stub(__temp_153)
 > 39 |           # Cast the stub or return value to the expected type.
 > 40 |           # This is necessary to match the expected return type of the original method.
 > 41 |           _spectator_cast_stub_value(__temp_154, __temp_153, typeof(previous_def),
 > 42 |           :raise)
 > 43 |         else
 > 44 |           # Delegate missing stub behavior to concrete type.
 > 45 |           _spectator_stub_fallback(__temp_153, typeof(previous_def)) do
 > 46 |             # Use the default response for the method.
 > 47 |             previous_def
 > 48 |           end
 > 49 |         end
 > 50 |       end
 > 51 |
Error: unterminated call

From what I gather splat names are expected to either be non empty strings on nils. However, this particular should definition has an unnamed splat which ends up as an empty string when rendered in the macro (see :"", , on line 29).

Unfortunately, my quick attempt to reproduce it with a spec in spectator failed. Perhaps you'd have better luck?..

Thank you!

`let!` doesn't infer return types

require "spectator"

def four
  2 + 2
end

Spectator.describe Int32 do
  let!(:x) { four }
  it("returns 4") { expect(x).to eq(4) }
end

Does not compile because crystal can't infer what type four returns. Strangely though if I replace let! with let it works fine and passes as it should. While I get why it doesn't compile with naive implementation of let!, you seem to have solved it with let, so it would be nice if it worked with let! too.

Problem in macro expansion for be_between

Hi all, just a small problem in the matcher with crystal 0.33...
Given this file test_problem.cr

require "spectator"
Spectator.describe "A test" do
  it "should work" do
    expect(7).to be_between(1, 10)
  end
end

I get this:

crystal spec ./test_problem.cr
There was a problem expanding macro 'be_between'

Code in test_problem.cr:4:18

 4 | expect(7).to be_between(1, 10)
                  ^
Called macro defined in lib/spectator/src/spectator/dsl/matchers.cr:344:5

 344 | macro be_between(min, max)

Which expanded to:

 > 1 | __temp_1031 = Range.new(1, 10))
                                     ^
Error: unexpected token: )

Running with --error-trace gives this trace:

In test_problem.cr:3:3

 3 | it "should work" do
     ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in lib/spectator/src/spectator/dsl/examples.cr:6:5

 6 | macro it(description = nil, _source_file = __FILE__, _source_line = __LINE__, &block)

Which expanded to:

 >  1 |
 >  2 |         def __temp_35
 >  3 |           (expect(7)).to(be_between(1, 10))
 >  4 |         end
 >  5 |
 >  6 |
 >  7 |       __temp_36 = ::Spectator::Source.new("/Users/tuad/work/slack/voronoi_stuff/crystal_voro/voronoi_geometry/test_problem.cr", 3)
 >  8 |       ::Spectator::SpecBuilder.add_example(
 >  9 |         "should work",
 > 10 |         __temp_36,
 > 11 |         SpectatorTest::Context__temp_33
 > 12 |       ) { |test| test.as(SpectatorTest::Context__temp_33).__temp_35 }
 > 13 |
Error: instantiating 'SpectatorTest::Context__temp_33#__temp_35()'


There was a problem expanding macro 'be_between'

Called macro defined in lib/spectator/src/spectator/dsl/matchers.cr:344:5

 344 | macro be_between(min, max)

Which expanded to:

 > 1 |       __temp_1031 = Range.new(1, 10))
 > 2 |       __temp_1032 = ["1", "10"].join(" to ")
 > 3 |       __temp_1033 = ::Spectator::TestValue.new(__temp_1031, __temp_1032)
 > 4 |       :Spectator::Matchers::RangeMatcher.new(__temp_1033)
 > 5 |
Error: unexpected token: )

Cannot stub methods on subject

I'm trying to simply test when an "aliased" method simply calls another method. Instead of testing the return value, I thought I
would use expect(subject).to receive..., however I receive an odd error message. Not sure whether I'm using the correct syntax?

Example Code

class Test
  def method2
  end

  def method1
    method2
  end
end
require "./spec_helper"

Spectator.describe Test do
  describe "#method1" do
    it do
      expect(subject).to receive(:method2)

      subject.method1
    end
  end
end

Actual Results

Failures:

  1) Test#method1 received message #method2 : Nil at spec/test_spec.cr:6 At least once with any arguments
     Failure: subject did not receive #method2 : Nil at spec/test_spec.cr:6 at least once with any arguments

       expected: At least once with any arguments
       received: 0 time(s)

Add DSL for compilation failing tests

When developing my Origin shard (https://github.com/pyrsmk/origin), I needed to test that the compilation is failing properly when using the shard wrongly. This is especially useful when coding a macro.

Here's what I came with in a helper:

class String
  def trail(str : String) : String
    return self + str if !(self =~ Regex.new("#{Regex.escape(str)}$"))
    self
  end
end

class SuccessfulCompileError < Exception; end

module CompileHelper
  def compile_fails(path : String) : String
    buffer = IO::Memory.new
    result = Process.run(
      "crystal",
      ["run", "--no-color", "--no-codegen", "spec/" + path.trail(".cr")],
      error: buffer,
    )
    raise SuccessfulCompileError.new if result.success?
    output = buffer.to_s
    buffer.close
    output
  end
end

Here, we're defining a wire/wrong_typing.cr with a wrong implementation of the macro. We can then test it in an example:

it "does not compile when mismatching type definition" do
  expect(compile_fails("wire/wrong_typing")).to match(
    /method must return Int32 but it is returning Float64/i
  )
end

What are your thoughts?

Properly document pending syntax

Based on the example in the wiki, I cannot figure out how to use the pending syntax, but nothing seem to compile correctly.

Examples

  pending "not implemented yet" do
    it "should do something" do
      expect(1).to be(1)
    end
  end
  it "should do something" do
    pending "not implemented yet" do
      expect(1).to be(1)
    end
  end
  it "should do something" do
    pending "not implemented yet"
    expect(1).to be(1)
  end

Error

Code in spec/test_spec.cr:5:5

 5 | it "should do something" do
     ^
Called macro defined in lib/spectator/src/spectator/dsl/examples.cr:6:5

 6 | macro it(description = nil, _source_file = __FILE__, _source_line = __LINE__, &block)

Which expanded to:

 > 1 |       
 > 2 |         def __temp_1095
               ^
Error: can't define def inside def

Error on upgrading to Crystal 0.36.0

I was updating the Shrine shard and found that it had specs like it is_expected.to be_nil. https://github.com/jetrockets/shrine.cr/blob/f897acbdc7efe4269442854bd6db1bda85fe3fdc/spec/shrine/uploaded_file_spec.cr#L73

I'm not sure if this worked on Crystal 0.35 but on 0.36 it produces an error like

In lib/spectator/src/spectator/source.cr:11:20

 11 | def initialize(@file, @line)
                     ^----
Error: instance variable '@file' of Spectator::Source must be String, not Nil

It ultimately is coming from this line returning nil instead of the filename string

%source = ::Spectator::Source.new({{block.filename}}, {{block.line_number}})

After changing the specs to have the expectation wrapped in curly braces the error went away. I believe this is an invalid spec call so maybe special handling should be added to catch it and raise a friendly error?

Cannot use be_kind_of() with described_class

Ran into this odd error:

 256 | expect(super_set).to be_kind_of(described_class)
                                       ^
Error: unexpected token: described_class

The entire spec block:

  describe "#|" do
    it "should be able to be unioned with another set of chars" do
      super_set = (subject | described_class['0'])

      expect(super_set).to be_kind_of(described_class)
      expect(super_set.includes_char?('0')).to be(true)
    end
  end

Clearly I'm using described_class before the offending line, but for some reason the compiler does not like described_class being passed/expanded within be_kind_of.

Support symbols as stub names

It would be great to be able to use symbols as stub names for mocks.
But I know this a complicated edge case ^^

My specific need comes from my Origin shard (https://github.com/pyrsmk/origin) which allows the delegation of methods with complex names like []? or <<. When implementing it in my code, I want to test that the delegated methods are well-delegated ^^

Here's a concrete and simplified example:

# A node with delegated methods from XML::Node.
struct Node
  autowire text : String, :[], :[]?

  def initialize(@origin : XML::Node); end
end
Spectator.describe Node do
  let(text_value) { Random::Secure.hex }
  let(array_get_value) { Random.rand(Int32) }
  let(array_get_or_nil_value) { [Random.rand(Int32), nil].sample }

  mock XML::Node do
    stub text
    # This will raise a syntax error at compile-time.
    # stub :[]
    # stub :[]?
  end

  let(html) { "<!doctype html><html></html>" }
  let(xml_node) { XML.parse_html(html) }
  subject { described_class.new(xml_node) }

  before_each do
    allow(xml_node).to receive(text).and_return(text_value)
    # allow(xml_node).to receive(:[]).and_return(array_get_value)
    # allow(xml_node).to receive(:[]?).and_return(array_get_or_nil_value)
  end

  it "wires #text method" do
    expect(subject.text).to eq text_value
  end

  # it "wires #[] method" do
  #   expect(subject[0]).to eq array_get_value
  # end

  # it "wires #[]? method" do
  #   expect(subject[0]?).to eq array_get_value_or_nil
  # end
end

There is a strong limitation (coming from Crystal): we cannot define those methods with a return type. So the only case of such implementation would be with mocks but not doubles (or only partially).

Default subject to described_class when describing a Module

Noticed that subject defaults to described_class.new. This should switch to just described_class when described_class.is_a?(Module).

Showing last frame. Use --error-trace for full trace.

There was a problem expanding macro 'let'

Code in macro 'subject'

 2 | let(:subject) do
     ^
Called macro defined in lib/spectator/src/spectator/dsl/values.cr:3:5

 3 | macro let(name, &block)

Which expanded to:

 > 2 | 
 > 3 |       def subject
 > 4 |         described_class.new
                               ^--

How to test when exit() is called?

Attempting to port some RSpec specs to Crystal which tests when the top-level exit method is called.

expect(subject).to receive(:exit).with(0)

Unfortunately, the Spectator method hook doesn't intercept the exit method call, and the process exits prematurely.

Code is untestable if method name is `description`

A good example is worth 1000 words, so here is how to recreate the problem:

main.cr:

module A
  def description
    "test method"
  end
end

main_spec.cr:

require "spectator"
require "../src/*"

Spectator.describe A do
  it { is_expected.to respond_to(:description) }
end

Running crystal spec with spectator 0.9.24 throws the following error:

Showing last frame. Use --error-trace for full trace.

In lib/spectator/src/spectator/matchers/respond_matcher.cr:22:25

 22 | FailedMatchData.new(description, "#{actual.label} does not respond to #{label}", **values(snapshot))
                      ^--
Error: no overload matches 'Spectator::Matchers::FailedMatchData.new' with types String, String, description: String

Overloads are:
 - Spectator::Matchers::FailedMatchData.new(description, failure_message, values)
 - Spectator::Matchers::FailedMatchData.new(description, failure_message, **values)

Based on a very brief look, I think the problem is in failed_match_data.cr#L23:

def initialize(description, @failure_message, **values)

From what I can gather, when we test a method that happens to be called description then the key supplied as part of **values overrides identically named method parameter. Unfortunately, I'm not familiar enough with the codebase to propose a solution or help with a PR.

Strange error when defining let(size) and calling subject.size

Source Code

module Hexdump
  class Type

    enum Endian
      LITTLE
      BIG
      NETWORK = BIG
    end

    getter endian : Endian?

    getter size : Int32

    getter? signed

    def initialize(@size : Int32, @signed : Bool, @endian : Endian? = nil)
    end

    def unsigned?
      !signed?
    end

  end
end

Specs

require "spectator"

Spectator.describe Hexdump::Type do
  describe "#initialize" do
    let(size) { 4 }
    let(signed) { true }

    subject { described_class.new(size: size, signed: signed) }

    it "must set #size" do
      expect(subject.size).to eq(size)
    end

    it "must not set #endian" do
      expect(subject.endian).to be(nil)
    end

    it "must set #signed?" do
      expect(subject.signed?).to eq(signed)
    end

    context "when given endian:" do
      let(endian) { Hexdump::Type::Endian::BIG }

      subject do
        described_class.new(size: size signed: signed, endian: endian)
      end

      it "must set #endian" do
        expect(subject.endian).to eq(endian)
      end
    end
  end

  describe "#signed?" do
    let(size) { 4 }

    subject { described_class.new(size: size, signed: signed) }

    context "when initialized with signed: true" do
      let(signed) { true }

      it do
        expect(subject.signed?).to be(true)
      end
    end

    context "when initialized with signed: false" do
      let(signed) { false }

      it do
        expect(subject.signed?).to be(false)
      end
    end
  end

  describe "#unsigned?" do
    let(size) { 4 }

    subject { described_class.new(size: size, signed: signed) }

    context "when initialized with signed: true" do
      let(signed) { true }

      it do
        expect(subject.unsigned?).to be(false)
      end
    end

    context "when initialized with signed: false" do
      let(signed) { false }

      it do
        expect(subject.unsigned?).to be(true)
      end
    end
  end
end

Error Message

$ crystal spec spec/type_spec.cr --error-trace
error in line 1
Error: while requiring "./spec/type_spec.cr"


In spec/type_spec.cr:28:11

 28 | Spectator.describe Hexdump::Type do
                ^-------
Error: expanding macro


In spec/type_spec.cr:28:1

 28 | Spectator.describe Hexdump::Type do
      ^
Error: expanding macro


There was a problem expanding macro 'describe'

Called macro defined in macro 'macro_139859134662512'

 46 | macro describe(description, *tags, **metadata, &block)

Which expanded to:

 >  1 |         class ::SpectatorTestContext
 >  2 |           describe(Hexdump::Type,  ) do
 >  3 |   describe("#initialize") do
 >  4 |     let(size) do
 >  5 |       4
 >  6 |     end
 >  7 |     let(signed) do
 >  8 |       true
 >  9 |     end
 > 10 |     subject do
 > 11 |       described_class.new(size: size, signed: signed)
 > 12 |     end
 > 13 |     it("must set #size") do
 > 14 |       p(size)
 > 15 |       (expect(subject.size)).to(eq(size))
 > 16 |     end
 > 17 |     it("must not set #endian") do
 > 18 |       (expect(subject.endian)).to(be(nil))
 > 19 |     end
 > 20 |     it("must set #signed?") do
 > 21 |       (expect(subject.signed?)).to(eq(signed))
 > 22 |     end
 > 23 |     context("when given endian:") do
 > 24 |       let(endian) do
 > 25 |         Hexdump::Type::Endian::BIG
 > 26 |       end
 > 27 |       subject do
 > 28 |         described_class.new(size: size(signed: signed, endian: endian))
 > 29 |       end
 > 30 |       it("must set #endian") do
 > 31 |         (expect(subject.endian)).to(eq(endian))
 > 32 |       end
 > 33 |     end
 > 34 |   end
 > 35 |   describe("#signed?") do
 > 36 |     let(size) do
 > 37 |       4
 > 38 |     end
 > 39 |     subject do
 > 40 |       described_class.new(size: size, signed: signed)
 > 41 |     end
 > 42 |     context("when initialized with signed: true") do
 > 43 |       let(signed) do
 > 44 |         true
 > 45 |       end
 > 46 |       it do
 > 47 |         (expect(subject.signed?)).to(be(true))
 > 48 |       end
 > 49 |     end
 > 50 |     context("when initialized with signed: false") do
 > 51 |       let(signed) do
 > 52 |         false
 > 53 |       end
 > 54 |       it do
 > 55 |         (expect(subject.signed?)).to(be(false))
 > 56 |       end
 > 57 |     end
 > 58 |   end
 > 59 |   describe("#unsigned?") do
 > 60 |     let(size) do
 > 61 |       4
 > 62 |     end
 > 63 |     subject do
 > 64 |       described_class.new(size: size, signed: signed)
 > 65 |     end
 > 66 |     context("when initialized with signed: true") do
 > 67 |       let(signed) do
 > 68 |         true
 > 69 |       end
 > 70 |       it do
 > 71 |         (expect(subject.unsigned?)).to(be(false))
 > 72 |       end
 > 73 |     end
 > 74 |     context("when initialized with signed: false") do
 > 75 |       let(signed) do
 > 76 |         false
 > 77 |       end
 > 78 |       it do
 > 79 |         (expect(subject.unsigned?)).to(be(true))
 > 80 |       end
 > 81 |     end
 > 82 |   end
 > 83 | end
 > 84 |         end
 > 85 |       
Error: expanding macro


In spec/type_spec.cr:15:11

 15 | 
                ^-------
Error: expanding macro


In spec/type_spec.cr:35:1

 35 | it "must set #size" do
  ^-
Error: expanding macro


In spec/type_spec.cr:35:1

 35 | it "must set #size" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_61, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_62() : Nil
 > 11 |             p(size)
 > 12 | (expect(subject.size)).to(eq(size))
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must set #size"),
 > 18 |             ::Spectator::Location.new("/home/postmodern/test/crystal/spectator/spec/type_spec.cr", 35, 39),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_61
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 23 |               
 > 24 |                 __temp_62
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_55.class)'


In spec/type_spec.cr:35:1

 35 | it "must set #size" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_61, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_62() : Nil
 > 11 |             p(size)
 > 12 | (expect(subject.size)).to(eq(size))
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must set #size"),
 > 18 |             ::Spectator::Location.new("/home/postmodern/test/crystal/spectator/spec/type_spec.cr", 35, 39),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_61
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 23 |               
 > 24 |                 __temp_62
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating 'Spectator::Example#with_context(SpectatorTestContext::Group__temp_51::Group__temp_55.class)'


In spec/type_spec.cr:35:1

 35 | it "must set #size" do
  ^
Error: expanding macro


There was a problem expanding macro 'it'

Called macro defined in macro 'define_example'

 17 | macro it(what = nil, *tags, **metadata, &block)

Which expanded to:

 >  1 |         
 >  2 |         
 >  3 | 
 >  4 |         _spectator_metadata(__temp_36, :metadata,  )
 >  5 |         _spectator_metadata(__temp_61, __temp_36,  )
 >  6 | 
 >  7 |         
 >  8 |           
 >  9 | 
 > 10 |           private def __temp_62() : Nil
 > 11 |             p(size)
 > 12 | (expect(subject.size)).to(eq(size))
 > 13 | 
 > 14 |           end
 > 15 | 
 > 16 |           ::Spectator::DSL::Builder.add_example(
 > 17 |             _spectator_example_name("must set #size"),
 > 18 |             ::Spectator::Location.new("/home/postmodern/test/crystal/spectator/spec/type_spec.cr", 35, 39),
 > 19 |             -> { new.as(::Spectator::Context) },
 > 20 |             __temp_61
 > 21 |           ) do |example|
 > 22 |             example.with_context(SpectatorTestContext::Group__temp_51::Group__temp_55) do
 > 23 |               
 > 24 |                 __temp_62
 > 25 |               
 > 26 |             end
 > 27 |           end
 > 28 | 
 > 29 |         
 > 30 |       
Error: instantiating '__temp_62()'


In spec/type_spec.cr:38:14

 38 | expect(subject.size).to eq(size)
             ^------
Error: Undefined local variable or method 'size(signed: _named_arg0, endian: _named_arg1)'

shard.yml

development_dependencies:
  spectator:
    gitlab: arctic-fox/spectator
    version: "~> 0.10"

Attempting to mock File results in error

Not sure if this is part of the intended purpose of mocks or not, but I'm attempting to mock the File class and I get the following error:

...
 > 38 |         # Attempt to find a stub that satisfies the method call and arguments.
 > 39 |         # Finding a suitable stub is delegated to the type including the `Stubbable` module.
 > 40 |         if __temp_270 = _spectator_find_stub(__temp_269)
 > 41 |           # Cast the stub or return value to the expected type.
 > 42 |           # This is necessary to match the expected return type of the original method.
 > 43 |           _spectator_cast_stub_value(__temp_270, __temp_269, typeof(previous_def(prefix, suffix, *dir: direncoding: encodinginvalid: invalid) { |*_spectator_yargs| yield *_spectator_yargs }),
 > 44 |           :raise)
 > 45 |         else
 > 46 |           # Delegate missing stub behavior to concrete type.
 > 47 |           _spectator_stub_fallback(__temp_269, typeof(previous_def(prefix, suffix, *dir: direncoding: encodinginvalid: invalid) { |*_spectator_yargs| yield *_spectator_yargs })) do
 > 48 |             # Use the default response for the method.
 > 49 |             previous_def(prefix, suffix, *dir: direncoding: encodinginvalid: invalid) { |*_spectator_yargs| yield *_spectator_yargs }
 > 50 |           end
 > 51 |         end
 > 52 |       end
 > 53 |     
Error: unexpected token: "direncoding"

expect().to be === does not actually call subject#===

Ran into this when testing custom #=== methods.

Example Code

module Test
  VERSION = "0.1.0"

  class Divisible

    def initialize(@div : Int32)
    end

    def ===(other : Int32)
      (other % @div) == 0
    end

  end
end
require "./spec_helper"
require "../src/test"

Spectator.describe Test::Divisible do
  let(i) { 3 }
  subject { described_class.new(i) }

  describe "#===" do
    it "must call === and compare the other object" do
      expect(subject).to be === 6
    end
  end
end

Output

$ shards install
Resolving dependencies
Fetching https://gitlab.com/arctic-fox/spectator.git
Using spectator (0.9.36)
$ crystal spec
Test::Divisible
  #===
    must call === and compare the other object

Failures:

  1) Test::Divisible#=== must call === and compare the other object
     Failure: subject does not equal 6

         actual: #<Test::Divisible:0x7f2fbab6abd0 @div=3>
       expected: 6

     # spec/test_spec.cr:9

Finished in 341 microseconds
1 examples, 1 failures, 0 errors, 0 pending

Workaround

However, if we explicitly call subject#=== within expect(), it works:

    it "must call === and compare the other object" do
      expect(subject === 6).to be(true)
    end
$ crystal spec
Test::Divisible
  #===
    must call === and compare the other object

Finished in 104 microseconds
1 examples, 0 failures, 0 errors, 0 pending

How to use macros in example descriptions?

I ran into a weird issue, where macros are not being properly expanded within the description text of describe or it blocks.

Example

require "./spec_helper"

Spectator.describe Test do
  {% var = :foo %}

  it "#{{ var.id }}" do
  end
end

Output

Test
  "#{{var.id}}"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.