|
| 1 | +# Writing Signatures Guide |
| 2 | + |
| 3 | +You can write the signature of your applications and libraries. |
| 4 | +Signature of your Ruby program would help: |
| 5 | + |
| 6 | +1. Understanding the code structure |
| 7 | +2. Finding APIs |
| 8 | + |
| 9 | +And if you ship your gem with signature, the gem users can type check their applications! |
| 10 | + |
| 11 | +## Writing signatures |
| 12 | + |
| 13 | +You first need to write your program's signature. |
| 14 | +See [syntax guide](syntax.md). |
| 15 | + |
| 16 | +## Testing signatures |
| 17 | + |
| 18 | +When you finish writing signature, you may want to test the signature. |
| 19 | +ruby-signature provides a feature to test your signature. |
| 20 | + |
| 21 | +``` |
| 22 | +$ RBS_TEST_TARGET='Foo::*' bundle exec ruby -r ruby/signature/test/setup test/foo_test.rb |
| 23 | +``` |
| 24 | + |
| 25 | +The test installs instrumentations to spy the method calls and check if arguments/return values are correct with respect to the type of the method in signature. |
| 26 | +If errors are reported by the test, you will fix the signature. |
| 27 | +You will be sure that you ship a correct signature finally. |
| 28 | + |
| 29 | +The instrumentations are implemneted using `Module#prepend`. |
| 30 | +It defines a module with same name of methods, which asserts the type of arguments/return values and calls `super`. |
| 31 | + |
| 32 | +## Type errors |
| 33 | + |
| 34 | +If the test detects type errors, it will print error messages. |
| 35 | + |
| 36 | +### ArgumentTypeError, BlockArgumentTypeError |
| 37 | + |
| 38 | +The message means there is an unexpected type of argument or block argument. |
| 39 | + |
| 40 | +``` |
| 41 | +ERROR -- : [Kaigi::Speaker.new] ArgumentTypeError: expected `::String` (email) but given `:"matsumoto@soutaro.com"` |
| 42 | +``` |
| 43 | + |
| 44 | +### ArgumentError, BlockArgumentError |
| 45 | + |
| 46 | +The message means there is an unexpected argument or missing argument. |
| 47 | + |
| 48 | +``` |
| 49 | +[Kaigi::Speaker.new] ArgumentError: expected method type (size: ::Symbol, email: ::String, name: ::String) -> ::Kaigi::Speaker |
| 50 | +``` |
| 51 | + |
| 52 | +### ReturnTypeError, BlockReturnTypeError |
| 53 | + |
| 54 | +The message means the return value from method or block is incorrect. |
| 55 | + |
| 56 | +``` |
| 57 | +ERROR -- : [Kaigi::Conference#each_speaker] ReturnTypeError: expected `self` but returns `[#<Kaigi::Speaker:0x00007fb2b249e5a0 @name="Soutaro Matsumoto", @email=:"matsumoto@soutaro.com">]` |
| 58 | +``` |
| 59 | + |
| 60 | +### UnexpectedBlockError, MissingBlockError |
| 61 | + |
| 62 | +The errors are reported when required block is not given or unused block is given. |
| 63 | + |
| 64 | +``` |
| 65 | +ERROR -- : [Kaigi::Conference#speakers] UnexpectedBlockError: unexpected block is given for `() -> ::Array[::Kaigi::Speaker]` |
| 66 | +``` |
| 67 | + |
| 68 | +### UnresolvedOverloadingError |
| 69 | + |
| 70 | +The error means there is a type error on overloaded methods. |
| 71 | +The `ruby-signature` test framework tries to the best error message for overloaded methods too, but it reports the `UnresolvedOverloadingError` when it fails. |
| 72 | + |
| 73 | +## Setting up the test |
| 74 | + |
| 75 | +The design of the signature testing aims to be non-intrusive. The setup is done in two steps: |
| 76 | + |
| 77 | +1. Loading the testing library |
| 78 | +2. Setting up the test through environment variables |
| 79 | + |
| 80 | +### Loading the library |
| 81 | + |
| 82 | +You need to require `ruby/signature/test/setup` for signature testing. |
| 83 | +You can do it using `-r` option through command line argument or the `RUBYOPT` environment variable. |
| 84 | + |
| 85 | +``` |
| 86 | +$ ruby -r ruby/signature/test/setup run_tests.rb |
| 87 | +$ RUBYOPT='-rruby/signature/test/setup' rake test |
| 88 | +``` |
| 89 | + |
| 90 | +When you are using Bundler, you may need to require `bundler/setup` explicitly. |
| 91 | + |
| 92 | +``` |
| 93 | +$ RUBYOPT='-rbundler/setup -rruby/signature/test/setup' bundle exec rake test |
| 94 | +``` |
| 95 | + |
| 96 | +### Environment variables |
| 97 | + |
| 98 | +You need to specify `RBS_TEST_TARGET` to run the test, and you can customize the test with the following environment variables. |
| 99 | + |
| 100 | +- `RBS_TEST_SKIP` (optional) |
| 101 | +- `RBS_TEST_OPT` (optional) |
| 102 | +- `RBS_TEST_LOGLEVEL` (optional) |
| 103 | + |
| 104 | +`RBS_TEST_TARGET` is to specify the classes you want to test. `RBS_TEST_TARGET` can contain comma-separated class name pattern, which is one of an exact class name or with wildcard `*`. |
| 105 | + |
| 106 | +- `RBS_TEST_TARGET=Foo::Bar,Foo::Baz` comma separated exact class names |
| 107 | +- `RBS_TEST_TARGET=Foo::*` using wildcard |
| 108 | + |
| 109 | +`RBS_TEST_SKIP` is to skip some of the classes which matches with `RBS_TEST_TARGET`. |
| 110 | + |
| 111 | +`RBS_TEST_OPT` is to pass the options for ruby signature handling. |
| 112 | +You may need to specify `-r` or `-I` to load signatures. |
| 113 | +The default is `-I sig`. |
| 114 | + |
| 115 | +``` |
| 116 | +RBS_TEST_OPT='-r set -r pathname -I sig' |
| 117 | +``` |
| 118 | + |
| 119 | +`RBS_TEST_LOGLEVEL` can be used to configure log level. Defaults to `info`. |
| 120 | + |
| 121 | +So, a typical command line to start the test would look like the following: |
| 122 | + |
| 123 | +``` |
| 124 | +$ RBS_TEST_LOGLEVEL=error \ |
| 125 | + RBS_TEST_TARGET='Kaigi::*' \ |
| 126 | + RBS_TEST_SKIP='Kaigi::MonkeyPatch' \ |
| 127 | + RBS_TEST_OPT='-rset -rpathname -Isig -Iprivate' \ |
| 128 | + RUBY_OPT='-rbundler/setup -rruby/signature/test/setup' \ |
| 129 | + bundle exec rake test |
| 130 | +``` |
| 131 | + |
| 132 | +## Testing tips |
| 133 | + |
| 134 | +### Skipping a method |
| 135 | + |
| 136 | +You can skip installing the instrumentation per-method basis using `rbs:test:skip` annotation. |
| 137 | + |
| 138 | +``` |
| 139 | +class String |
| 140 | + %a{rbs:test:skip} def =~: (Regexp) -> Integer? |
| 141 | +end |
| 142 | +``` |
0 commit comments