contracts-0.17.2/ 0000755 0000041 0000041 00000000000 14717736077 013654 5 ustar www-data www-data contracts-0.17.2/cucumber.yml 0000644 0000041 0000041 00000000034 14717736077 016201 0 ustar www-data www-data default: --require features
contracts-0.17.2/.gitignore 0000644 0000041 0000041 00000000057 14717736077 015646 0 ustar www-data www-data *.swp
*.swo
.*
!.github
Gemfile.lock
tmp/aruba
contracts-0.17.2/dependabot.yml 0000644 0000041 0000041 00000000671 14717736077 016510 0 ustar www-data www-data version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: monthly
time: "06:00"
timezone: Asia/Hong_Kong
open-pull-requests-limit: 10
labels:
- "dependencies"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
time: "06:00"
timezone: Asia/Hong_Kong
open-pull-requests-limit: 10
labels:
- "dependencies"
contracts-0.17.2/TODO.markdown 0000644 0000041 0000041 00000000727 14717736077 016173 0 ustar www-data www-data - maybe make some screencasts
- you can now do something like Haskell's quickcheck. Every contract has a method 'test_data' or something. You can use that data to automatically check methods with contracts to make sure they are correct.
- http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html
- for stuff like the Not contract, should I make a standard set of classes to check those functions with? Would that be useful at all?
- also write specs for this stuff
contracts-0.17.2/benchmarks/ 0000755 0000041 0000041 00000000000 14717736077 015771 5 ustar www-data www-data contracts-0.17.2/benchmarks/hash.rb 0000644 0000041 0000041 00000002755 14717736077 017252 0 ustar www-data www-data require "./lib/contracts"
require "benchmark"
require "rubygems"
require "method_profiler"
require "ruby-prof"
include Contracts
def add opts
opts[:a] + opts[:b]
end
Contract ({ :a => Num, :b => Num}) => Num
def contracts_add opts
opts[:a] + opts[:b]
end
def explicit_add opts
a = opts[:a]
b = opts[:b]
fail unless a.is_a?(Numeric)
fail unless b.is_a?(Numeric)
c = a + b
fail unless c.is_a?(Numeric)
c
end
def benchmark
Benchmark.bm 30 do |x|
x.report "testing add" do
1_000_000.times do |_|
add(:a => rand(1000), :b => rand(1000))
end
end
x.report "testing contracts add" do
1_000_000.times do |_|
contracts_add(:a => rand(1000), :b => rand(1000))
end
end
end
end
def profile
profilers = []
profilers << MethodProfiler.observe(Contract)
profilers << MethodProfiler.observe(Object)
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
profilers << MethodProfiler.observe(Contracts::Decorator)
profilers << MethodProfiler.observe(Contracts::Support)
profilers << MethodProfiler.observe(UnboundMethod)
10_000.times do |_|
contracts_add(:a => rand(1000), :b => rand(1000))
end
profilers.each { |p| puts p.report }
end
def ruby_prof
RubyProf.start
100_000.times do |_|
contracts_add(:a => rand(1000), :b => rand(1000))
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
benchmark
profile
ruby_prof if ENV["FULL_BENCH"] # takes some time
contracts-0.17.2/benchmarks/io.rb 0000644 0000041 0000041 00000002473 14717736077 016733 0 ustar www-data www-data require "./lib/contracts"
require "benchmark"
require "rubygems"
require "method_profiler"
require "ruby-prof"
require "open-uri"
include Contracts
def download url
open("http://www.#{url}/").read
end
Contract String => String
def contracts_download url
open("http://www.#{url}").read
end
@urls = %w{google.com bing.com}
def benchmark
Benchmark.bm 30 do |x|
x.report "testing download" do
100.times do |_|
download(@urls.sample)
end
end
x.report "testing contracts download" do
100.times do |_|
contracts_download(@urls.sample)
end
end
end
end
def profile
profilers = []
profilers << MethodProfiler.observe(Contract)
profilers << MethodProfiler.observe(Object)
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
profilers << MethodProfiler.observe(Contracts::Decorator)
profilers << MethodProfiler.observe(Contracts::Support)
profilers << MethodProfiler.observe(UnboundMethod)
10.times do |_|
contracts_download(@urls.sample)
end
profilers.each { |p| puts p.report }
end
def ruby_prof
RubyProf.start
10.times do |_|
contracts_download(@urls.sample)
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
benchmark
profile
ruby_prof if ENV["FULL_BENCH"] # takes some time
contracts-0.17.2/benchmarks/bench.rb 0000644 0000041 0000041 00000002562 14717736077 017402 0 ustar www-data www-data require "./lib/contracts"
require "benchmark"
require "rubygems"
require "method_profiler"
require "ruby-prof"
include Contracts
def add a, b
a + b
end
Contract Num, Num => Num
def contracts_add a, b
a + b
end
def explicit_add a, b
fail unless a.is_a?(Numeric)
fail unless b.is_a?(Numeric)
c = a + b
fail unless c.is_a?(Numeric)
c
end
def benchmark
Benchmark.bm 30 do |x|
x.report "testing add" do
1_000_000.times do |_|
add(rand(1000), rand(1000))
end
end
x.report "testing contracts add" do
1_000_000.times do |_|
contracts_add(rand(1000), rand(1000))
end
end
end
end
def profile
profilers = []
profilers << MethodProfiler.observe(Contract)
profilers << MethodProfiler.observe(Object)
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
profilers << MethodProfiler.observe(Contracts::Decorator)
profilers << MethodProfiler.observe(Contracts::Support)
profilers << MethodProfiler.observe(UnboundMethod)
10_000.times do |_|
contracts_add(rand(1000), rand(1000))
end
profilers.each { |p| puts p.report }
end
def ruby_prof
RubyProf.start
100_000.times do |_|
contracts_add(rand(1000), rand(1000))
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
benchmark
profile
ruby_prof if ENV["FULL_BENCH"] # takes some time
contracts-0.17.2/benchmarks/invariants.rb 0000644 0000041 0000041 00000003624 14717736077 020501 0 ustar www-data www-data require "./lib/contracts"
require "benchmark"
require "rubygems"
require "method_profiler"
require "ruby-prof"
class Obj
include Contracts
attr_accessor :value
def initialize value
@value = value
end
Contract Num, Num => Num
def contracts_add a, b
a + b
end
end
class ObjWithInvariants
include Contracts
include Contracts::Invariants
invariant(:value_not_nil) { value != nil }
invariant(:value_not_string) { !value.is_a?(String) }
attr_accessor :value
def initialize value
@value = value
end
Contract Num, Num => Num
def contracts_add a, b
a + b
end
end
def benchmark
obj = Obj.new(3)
obj_with_invariants = ObjWithInvariants.new(3)
Benchmark.bm 30 do |x|
x.report "testing contracts add" do
1_000_000.times do |_|
obj.contracts_add(rand(1000), rand(1000))
end
end
x.report "testing contracts add with invariants" do
1_000_000.times do |_|
obj_with_invariants.contracts_add(rand(1000), rand(1000))
end
end
end
end
def profile
obj_with_invariants = ObjWithInvariants.new(3)
profilers = []
profilers << MethodProfiler.observe(Contract)
profilers << MethodProfiler.observe(Object)
profilers << MethodProfiler.observe(Contracts::Support)
profilers << MethodProfiler.observe(Contracts::Invariants)
profilers << MethodProfiler.observe(Contracts::Invariants::InvariantExtension)
profilers << MethodProfiler.observe(UnboundMethod)
10_000.times do |_|
obj_with_invariants.contracts_add(rand(1000), rand(1000))
end
profilers.each { |p| puts p.report }
end
def ruby_prof
RubyProf.start
obj_with_invariants = ObjWithInvariants.new(3)
100_000.times do |_|
obj_with_invariants.contracts_add(rand(1000), rand(1000))
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
benchmark
profile
ruby_prof if ENV["FULL_BENCH"] # takes some time
contracts-0.17.2/benchmarks/wrap_test.rb 0000644 0000041 0000041 00000001725 14717736077 020333 0 ustar www-data www-data require "benchmark"
module Wrapper
def self.extended(klass)
klass.class_eval do
@@methods = {}
def self.methods
@@methods
end
def self.set_method k, v
@@methods[k] = v
end
end
end
def method_added name
return if methods.include?(name)
puts "#{name} added"
set_method(name, instance_method(name))
class_eval %{
def #{name}(*args)
self.class.methods[#{name.inspect}].bind(self).call(*args)
end
}, __FILE__, __LINE__ + 1
end
end
class NotWrapped
def add a, b
a + b
end
end
class Wrapped
extend ::Wrapper
def add a, b
a + b
end
end
w = Wrapped.new
nw = NotWrapped.new
# p w.add(1, 4)
# exit
# 30 is the width of the output column
Benchmark.bm 30 do |x|
x.report "wrapped" do
100_000.times do |_|
w.add(rand(1000), rand(1000))
end
end
x.report "not wrapped" do
100_000.times do |_|
nw.add(rand(1000), rand(1000))
end
end
end
contracts-0.17.2/CHANGELOG.markdown 0000644 0000041 0000041 00000023461 14717736077 016715 0 ustar www-data www-data
## [v0.17.1] - 2024-10-06
[v0.17.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17...v0.17.1
- Bugfix: Fix keyword arguments contract when used with optional positional arguments - [PikachuEXE](https://github.com/PikachuEXE) [#305](https://github.com/egonSchiele/contracts.ruby/pull/305)
- Enhancement: Always load version.rb, suppress legacy deprecation warning - [Vlad Pisanov](https://github.com/vlad-pisanov) [#301](https://github.com/egonSchiele/contracts.ruby/pull/306)
- Enhancement: Update doc & spec about deprecated `Fixnum` to `Integer` - [PikachuEXE](https://github.com/PikachuEXE) [#301](https://github.com/egonSchiele/contracts.ruby/pull/301)
## [v0.17] - 2021-09-28
[v0.17]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.1...v0.17
- Update implementation & spec to be 3.0 compatible **Support for Ruby 2 has been discontinued** - [PikachuEXE](https://github.com/PikachuEXE) [#295](https://github.com/egonSchiele/contracts.ruby/pull/295)
## [v0.16.1] - 2021-04-17
[v0.16.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.0...v0.16.1
- Enhancement: Pretty-print contracts in error messages - [Corey Farwell](https://github.com/frewsxcv) [#289](https://github.com/egonSchiele/contracts.ruby/pull/289)
- Bugfix: Fix `attr_accessor_with_contract` with multiple attribute names input - [Kevin Yeh](https://github.com/kyeah) [#259](https://github.com/egonSchiele/contracts.ruby/pull/259)
- Bugfix: Fix "stack level too deep" in CI builds - [md-work](https://github.com/md-work) [#283](https://github.com/egonSchiele/contracts.ruby/pull/283)
## [v0.16.0] - 2017-04-24
[v0.16.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.15.0...v0.16.0
- **Support for Ruby 1.8 has been discontinued** - [Corey Farwell](https://github.com/frewsxcv) [#256](https://github.com/egonSchiele/contracts.ruby/pull/256)
- Enhancement: Add a `Contracts::Attrs` module containing attribute w/ contracts utilities - [Corey Farwell](https://github.com/frewsxcv) [#255](https://github.com/egonSchiele/contracts.ruby/pull/255)
- Bugfix: Fix StrictHash contract for extra keys - [Maciej Malecki](https://github.com/smt116) [#254](https://github.com/egonSchiele/contracts.ruby/pull/254)
## [v0.15.0] - 2017-02-24
[v0.15.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.14.0...v0.15.0
- Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251)
- Bugfix: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237)
## [v0.14.0] - 2016-04-26
[v0.14.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.13.0...v0.14.0
- Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236)
- Bugfix: dont fail if something other than a hash is passed to a KeywordArgs - [Dan Padilha](https://github.com/dpad) [#234](https://github.com/egonSchiele/contracts.ruby/pull/234)
- LICENSE ADDED: Simplified BSD (same as what is specified in the readme) - [Charles Dale](https://github.com/chuckd) [#233](https://github.com/egonSchiele/contracts.ruby/pull/233)
- Bugfix: fix constant looking when including a module that includes contracts (requires removing the check to see if contracts is already included) - [Aditya Bhargava](https://github.com/egonSchiele) [#232](https://github.com/egonSchiele/contracts.ruby/pull/232)
- Bugfix for err case when KeywordArgs and Proc are used together - [Aditya Bhargava](https://github.com/egonSchiele) [#230](https://github.com/egonSchiele/contracts.ruby/pull/230)
- Enhancement: Add DescendantOf contract - [Miguel Palhas](https://github.com/naps62) [#227](https://github.com/egonSchiele/contracts.ruby/pull/227)
## [v0.13.0] - 2016-01-25
[v0.13.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.12.0...v0.13.0
- Enhancement: Add support for Ruby 2.3 - [Oleksii Fedorov](https://github.com/waterlink) [#216](https://github.com/egonSchiele/contracts.ruby/pull/216)
- Enhancement: Added Int, Nat and NatPos builtin contracts - [Simon George](https://github.com/sfcgeorge) [#212](https://github.com/egonSchiele/contracts.ruby/pull/212)
- Bugfix: Allow contracts on singleton of subclass - [Oleksii Federov](https://github.com/waterlink) [#211](https://github.com/egonSchiele/contracts.ruby/pull/211)
## [v0.12.0] - 2015-09-15
[v0.12.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.11.0...v0.12.0
- Feature: add `Regexp` validator - [Gert Goet](https://github.com/eval) [#196](https://github.com/egonSchiele/contracts.ruby/pull/196)
- Docs: bootstrap cucumber/aruba/relish setup - [Oleksii Fedorov](https://github.com/waterlink) [#195](https://github.com/egonSchiele/contracts.ruby/pull/195)
- Bugfix: allow to `extend` module, that has `Contracts` or `Contracts::Core` included without harming current module/class `Contracts` functionality, see: [#176](https://github.com/egonSchiele/contracts.ruby/issues/176) - [Oleksii Fedorov](https://github.com/waterlink) [#198](https://github.com/egonSchiele/contracts.ruby/pull/198)
- Enhancement: add `include Contracts::Builtin` to allow users to use builtin contracts without `Contracts::` prefix together with `include Contracts::Core` - [PikachuEXE](https://github.com/PikachuEXE) [#199](https://github.com/egonSchiele/contracts.ruby/pull/199)
## [v0.11.0] - 2015-07-30
[v0.11.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.10.1...v0.11.0
- Enhancement: add `include Contracts::Core` that doesn't pollute the namespace as much as `include Contracts` - [Oleksii Federov](https://github.com/waterlink) [#185](https://github.com/egonSchiele/contracts.ruby/pull/185)
- Bugfix: fail if a non-hash is provided to a `HashOf` contract - [Abe Voelker](https://github.com/abevoelker) [#190](https://github.com/egonSchiele/contracts.ruby/pull/190)
- Bugfix: bugfix for using varargs and `Maybe[Proc]` together - [Adit Bhargava](https://github.com/egonSchiele) [#188](https://github.com/egonSchiele/contracts.ruby/pull/188)
- Bugfix: make KeywordArgs fail if unexpected keys are passed in - [Abe Voelker](https://github.com/abevoelker) [#187](https://github.com/egonSchiele/contracts.ruby/pull/187)
- Feature: range contract added - [Oleksii Fedorov](https://github.com/waterlink) [#184](https://github.com/egonSchiele/contracts.ruby/pull/184)
- Feature: enum contract added - [Dennis Günnewig](https://github.com/dg-ratiodata) [#181](https://github.com/egonSchiele/contracts.ruby/pull/181)
## [v0.10.1] - 2015-07-16
[v0.10.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.10...v0.10.1
- Enhancement: make `@pattern_match` instance variable not render ruby warning. Required to use new aruba versions in rspec tests - [Dennis Günnewig](https://github.com/dg-ratiodata) [#179](https://github.com/egonSchiele/contracts.ruby/pull/179)
## [v0.10] - 2015-07-07
[v0.10]: https://github.com/egonSchiele/contracts.ruby/compare/v0.9...v0.10
- Bugfix: make `Maybe[Proc]` work correctly - [Simon George](https://github.com/sfcgeorge) [#142](https://github.com/egonSchiele/contracts.ruby/pull/142)
- Bugfix: make `Func` contract verified when used as return contract - [Rob Rosenbaum](https://github.com/robnormal) [#145](https://github.com/egonSchiele/contracts.ruby/pull/145)
- Bugfix: make `Pos`, `Neg` and `Nat` contracts handle non-numeric values correctly - [Matt Griffin](https://github.com/betamatt) and [Gavin Sinclair](https://github.com/gsinclair) [#147](https://github.com/egonSchiele/contracts.ruby/pull/147) [#173](https://github.com/egonSchiele/contracts.ruby/pull/173)
- Enhancement: reduce user class pollution through introduction of contracts engine - [Oleksii Fedorov](https://github.com/waterlink) [#141](https://github.com/egonSchiele/contracts.ruby/pull/141)
- Feature: add builtin `KeywordArgs` and `Optional` contracts for keyword arguments handling - [Oleksii Fedorov](https://github.com/waterlink) [#151](https://github.com/egonSchiele/contracts.ruby/pull/151)
- Feature: recognize module as a class contract - [Oleksii Fedorov](https://github.com/waterlink) [#153](https://github.com/egonSchiele/contracts.ruby/pull/153)
- Feature: custom validators with `Contract.override_validator` - [Oleksii Fedorov](https://github.com/waterlink) [#159](https://github.com/egonSchiele/contracts.ruby/pull/159)
- Feature: add builtin `RangeOf[...]` contract - [Gavin Sinclair](https://github.com/gsinclair) [#171](https://github.com/egonSchiele/contracts.ruby/pull/171)
## [v0.9] - 2015-04-24
[v0.9]: https://github.com/egonSchiele/contracts.ruby/compare/0.8...v0.9
- MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values.
- raise an error if multiple defns have the same contract for pattern matching.
- New syntax for functions with no input params (the old style still works)
Old way:
```ruby
Contract nil => 1
def one
```
New way:
```
Contract 1
def one
```
- Prettier HashOf contract can now be written like this: `HashOf[Num => String]`
- Add `SetOf` contract
- various small fixes
## v0.8 - 2015-04-03
- code refactored (very slight loss of performance, big increase in readability)
- fail when defining a contract on a module without `include Contracts::Modules`
- fixed several bugs in argument parsing, functions with complex params get contracts applied correctly now.
- added rubocop to ci.
- if a contract is set on a protected method, it should not become public.
- fixed pattern matching when the multiple definitions of functions have different arities.
- couple of new built-in contracts: Nat, Eq.
- changed `Invariant` to `invariant`: `invariant(:day) { 1 <= day && day <= 31 }`
- prettier error messages (`Contracts::Num` is now just `Num`, for example)
- support for yard-contracts
contracts-0.17.2/.github/ 0000755 0000041 0000041 00000000000 14717736077 015214 5 ustar www-data www-data contracts-0.17.2/.github/workflows/ 0000755 0000041 0000041 00000000000 14717736077 017251 5 ustar www-data www-data contracts-0.17.2/.github/workflows/code_style_checks.yaml 0000644 0000041 0000041 00000001412 14717736077 023605 0 ustar www-data www-data name: Code Style Checks
on:
pull_request:
branches:
- master
paths-ignore:
- 'README.md'
- 'CHANGELOG.markdown'
push:
branches:
- master
paths-ignore:
- 'README.md'
- 'CHANGELOG.markdown'
jobs:
rubocop:
name: Rubocop
if: "contains(github.event.commits[0].message, '[ci skip]') == false"
strategy:
fail-fast: false
matrix:
os:
- ubuntu
ruby:
- "3.3"
runs-on: ${{ matrix.os }}-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run Rubocop
run: bundle exec rubocop
contracts-0.17.2/.github/workflows/tests.yaml 0000644 0000041 0000041 00000001610 14717736077 021275 0 ustar www-data www-data name: Tests
on:
pull_request:
branches:
- master
paths-ignore:
- 'README.md'
- 'CHANGELOG.markdown'
push:
branches:
- master
paths-ignore:
- 'README.md'
- 'CHANGELOG.markdown'
jobs:
unit_tests:
name: Unit Tests
if: "contains(github.event.commits[0].message, '[ci skip]') == false"
strategy:
fail-fast: false
matrix:
os:
- ubuntu
ruby:
- "3.3"
- "3.2"
- "3.1"
- "3.0"
test_command:
- "bundle exec rspec && bundle exec cucumber"
runs-on: ${{ matrix.os }}-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Test
run: ${{ matrix.test_command }}
contracts-0.17.2/lib/ 0000755 0000041 0000041 00000000000 14717736077 014422 5 ustar www-data www-data contracts-0.17.2/lib/contracts.rb 0000644 0000041 0000041 00000017606 14717736077 016761 0 ustar www-data www-data # frozen_string_literal: true
require "contracts/version"
require "contracts/attrs"
require "contracts/builtin_contracts"
require "contracts/decorators"
require "contracts/errors"
require "contracts/formatters"
require "contracts/invariants"
require "contracts/method_reference"
require "contracts/support"
require "contracts/engine"
require "contracts/method_handler"
require "contracts/validators"
require "contracts/call_with"
require "contracts/core"
module Contracts
def self.included(base)
base.send(:include, Core)
end
def self.extended(base)
base.send(:extend, Core)
end
end
# This is the main Contract class. When you write a new contract, you'll
# write it as:
#
# Contract [contract names] => return_value
#
# This class also provides useful callbacks and a validation method.
#
# For #make_validator and related logic see file
# lib/contracts/validators.rb
# For #call_with and related logic see file
# lib/contracts/call_with.rb
class Contract < Contracts::Decorator
extend Contracts::Validators
include Contracts::CallWith
# Default implementation of failure_callback. Provided as a block to be able
# to monkey patch #failure_callback only temporary and then switch it back.
# First important usage - for specs.
DEFAULT_FAILURE_CALLBACK = proc do |data|
if data[:return_value]
# this failed on the return contract
fail ReturnContractError.new(failure_msg(data), data)
else
# this failed for a param contract
fail data[:contracts].failure_exception.new(failure_msg(data), data)
end
end
attr_reader :args_contracts, :kargs_contract, :ret_contract, :klass, :method
def initialize(klass, method, *contracts)
super(klass, method)
unless contracts.last.is_a?(Hash)
unless contracts.one?
fail %{
It looks like your contract for #{method.name} doesn't have a return
value. A contract should be written as `Contract arg1, arg2 =>
return_value`.
}.strip
end
contracts = [nil => contracts[-1]]
end
# internally we just convert that return value syntax back to an array
@args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
# Extract contract for keyword arguments
@kargs_contract = args_contracts.find { |c| c.is_a?(Contracts::Builtin::KeywordArgs) }
args_contracts.delete(kargs_contract) if kargs_contract
@ret_contract = contracts[-1].values[0]
@args_validators = args_contracts.map do |contract|
Contract.make_validator(contract)
end
@kargs_validator = kargs_contract ? Contract.make_validator(kargs_contract) : nil
@args_contract_index = args_contracts.index do |contract|
contract.is_a? Contracts::Args
end
@ret_validator = Contract.make_validator(ret_contract)
@pattern_match = false
# == @has_proc_contract
last_contract = args_contracts.last
is_a_proc = last_contract.is_a?(Class) && (last_contract <= Proc || last_contract <= Method)
maybe_a_proc = last_contract.is_a?(Contracts::Maybe) && last_contract.include_proc?
@has_proc_contract = is_a_proc || maybe_a_proc || last_contract.is_a?(Contracts::Func)
# ====
@klass, @method = klass, method
end
def pretty_contract contract
contract.is_a?(Class) ? contract.name : contract.class.name
end
def to_s
args = args_contracts.map { |c| pretty_contract(c) }.join(", ")
ret = pretty_contract(ret_contract)
("#{args} => #{ret}").gsub("Contracts::Builtin::", "")
end
# Given a hash, prints out a failure message.
# This function is used by the default #failure_callback method
# and uses the hash passed into the failure_callback method.
def self.failure_msg(data)
indent_amount = 8
method_name = Contracts::Support.method_name(data[:method])
# Header
header = if data[:return_value]
"Contract violation for return value:"
else
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
end
# Expected
expected_prefix = "Expected: "
expected_value = Contracts::Support.indent_string(
Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect,
expected_prefix.length,
).strip
expected_line = "#{expected_prefix}#{expected_value},"
# Actual
actual_prefix = "Actual: "
actual_value = Contracts::Support.indent_string(
data[:arg].pretty_inspect,
actual_prefix.length,
).strip
actual_line = actual_prefix + actual_value
# Value guarded in
value_prefix = "Value guarded in: "
value_value = "#{data[:class]}::#{method_name}"
value_line = value_prefix + value_value
# Contract
contract_prefix = "With Contract: "
contract_value = data[:contracts].to_s
contract_line = contract_prefix + contract_value
# Position
position_prefix = "At: "
position_value = Contracts::Support.method_position(data[:method])
position_line = position_prefix + position_value
[
header,
Contracts::Support.indent_string(
[
expected_line,
actual_line,
value_line,
contract_line,
position_line,
].join("\n"),
indent_amount,
),
].join("\n")
end
# Callback for when a contract fails. By default it raises
# an error and prints detailed info about the contract that
# failed. You can also monkeypatch this callback to do whatever
# you want...log the error, send you an email, print an error
# message, etc.
#
# Example of monkeypatching:
#
# def Contract.failure_callback(data)
# puts "You had an error!"
# puts failure_msg(data)
# exit
# end
def self.failure_callback(data, use_pattern_matching: true)
if data[:contracts].pattern_match? && use_pattern_matching
return DEFAULT_FAILURE_CALLBACK.call(data)
end
fetch_failure_callback.call(data)
end
# Used to override failure_callback without monkeypatching.
#
# Takes: block parameter, that should accept one argument - data.
#
# Example usage:
#
# Contract.override_failure_callback do |data|
# puts "You had an error"
# puts failure_msg(data)
# exit
# end
def self.override_failure_callback(&blk)
@failure_callback = blk
end
# Used to restore default failure callback
def self.restore_failure_callback
@failure_callback = DEFAULT_FAILURE_CALLBACK
end
def self.fetch_failure_callback
@failure_callback ||= DEFAULT_FAILURE_CALLBACK
end
# Used to verify if an argument satisfies a contract.
#
# Takes: an argument and a contract.
#
# Returns: a tuple: [Boolean, metadata]. The boolean indicates
# whether the contract was valid or not. If it wasn't, metadata
# contains some useful information about the failure.
def self.valid?(arg, contract)
make_validator(contract)[arg]
end
def [](*args, &blk)
call(*args, &blk)
end
def call(*args, &blk)
call_with(nil, *args, &blk)
end
# if we specified a proc in the contract but didn't pass one in,
# it's possible we are going to pass in a block instead. So lets
# append a nil to the list of args just so it doesn't fail.
# a better way to handle this might be to take this into account
# before throwing a "mismatched # of args" error.
# returns true if it appended nil
def maybe_append_block! args, blk
return false unless @has_proc_contract && !blk &&
(@args_contract_index || args.size < args_contracts.size)
args << nil
true
end
# Used to determine type of failure exception this contract should raise in case of failure
def failure_exception
if pattern_match?
PatternMatchingError
else
ParamContractError
end
end
# @private
# Used internally to mark contract as pattern matching contract
def pattern_match!
@pattern_match = true
end
# Used to determine if contract is a pattern matching contract
def pattern_match?
@pattern_match == true
end
end
contracts-0.17.2/lib/contracts/ 0000755 0000041 0000041 00000000000 14717736077 016422 5 ustar www-data www-data contracts-0.17.2/lib/contracts/method_handler.rb 0000644 0000041 0000041 00000013715 14717736077 021733 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
# Handles class and instance methods addition
# Represents single such method
class MethodHandler
METHOD_REFERENCE_FACTORY = {
:class_methods => SingletonMethodReference,
:instance_methods => MethodReference,
}
RAW_METHOD_STRATEGY = {
:class_methods => lambda { |target, name| target.method(name) },
:instance_methods => lambda { |target, name| target.instance_method(name) },
}
# Creates new instance of MethodHandler
#
# @param [Symbol] method_name
# @param [Bool] is_class_method
# @param [Class] target - class that method got added to
def initialize(method_name, is_class_method, target)
@method_name = method_name
@is_class_method = is_class_method
@target = target
end
# Handles method addition
def handle
return unless engine?
return if decorators.empty?
validate_decorators!
validate_pattern_matching!
engine.add_method_decorator(method_type, method_name, decorator)
mark_pattern_matching_decorators
method_reference.make_alias(target)
redefine_method
end
private
attr_reader :method_name, :is_class_method, :target
def engine?
Engine.applied?(target)
end
def engine
Engine.fetch_from(target)
end
def decorators
@_decorators ||= engine.all_decorators
end
def method_type
@_method_type ||= is_class_method ? :class_methods : :instance_methods
end
# _method_type is required for assigning it to local variable with
# the same name. See: #redefine_method
alias_method :_method_type, :method_type
def method_reference
@_method_reference ||= METHOD_REFERENCE_FACTORY[method_type].new(method_name, raw_method)
end
def raw_method
RAW_METHOD_STRATEGY[method_type].call(target, method_name)
end
def ignore_decorators?
ENV["NO_CONTRACTS"] && !pattern_matching?
end
def decorated_methods
@_decorated_methods ||= engine.decorated_methods_for(method_type, method_name)
end
def pattern_matching?
return @_pattern_matching if defined?(@_pattern_matching)
@_pattern_matching = decorated_methods.any? { |x| x.method != method_reference }
end
def mark_pattern_matching_decorators
return unless pattern_matching?
decorated_methods.each(&:pattern_match!)
end
def decorator
@_decorator ||= decorator_class.new(target, method_reference, *decorator_args)
end
def decorator_class
decorators.first[0]
end
def decorator_args
decorators.first[1]
end
def redefine_method
return if ignore_decorators?
# Those are required for instance_eval to be able to refer them
name = method_name
method_type = _method_type
current_engine = engine
# We are gonna redefine original method here
method_reference.make_definition(target) do |*args, **kargs, &blk|
engine = current_engine.nearest_decorated_ancestor
# If we weren't able to find any ancestor that has decorated methods
# FIXME : this looks like untested code (commenting it out doesn't make specs red)
unless engine
fail "Couldn't find decorator for method #{self.class.name}:#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
end
# Fetch decorated methods out of the contracts engine
decorated_methods = engine.decorated_methods_for(method_type, name)
# This adds support for overloading methods. Here we go
# through each method and call it with the arguments.
# If we get a failure_exception, we move to the next
# function. Otherwise we return the result.
# If we run out of functions, we raise the last error, but
# convert it to_contract_error.
expected_error = decorated_methods[0].failure_exception
last_error = nil
decorated_methods.each do |decorated_method|
result = decorated_method.call_with_inner(true, self, *args, **kargs, &blk)
return result unless result.is_a?(ParamContractError)
last_error = result
end
begin
if ::Contract.failure_callback(last_error&.data, use_pattern_matching: false)
decorated_methods.last.call_with_inner(false, self, *args, **kargs, &blk)
end
# rubocop:disable Naming/RescuedExceptionsVariableName
rescue expected_error => final_error
raise final_error.to_contract_error
# rubocop:enable Naming/RescuedExceptionsVariableName
end
end
end
def validate_decorators!
return if decorators.size == 1
fail %{
Oops, it looks like method '#{method_name}' has multiple contracts:
#{decorators.map { |x| x[1][0].inspect }.join("\n")}
Did you accidentally put more than one contract on a single function, like so?
Contract String => String
Contract Num => String
def foo x
end
If you did NOT, then you have probably discovered a bug in this library.
Please file it along with the relevant code at:
https://github.com/egonSchiele/contracts.ruby/issues
}
end
def validate_pattern_matching!
new_args_contract = decorator.args_contracts
new_kargs_contract = decorator.kargs_contract
matched = decorated_methods.select do |contract|
contract.args_contracts == new_args_contract &&
contract.kargs_contract == new_kargs_contract
end
return if matched.empty?
fail ContractError.new(
%{
It looks like you are trying to use pattern-matching, but
multiple definitions for function '#{method_name}' have the same
contract for input parameters:
#{(matched + [decorator]).map(&:to_s).join("\n")}
Each definition needs to have a different contract for the parameters.
},
{},
)
end
end
end
contracts-0.17.2/lib/contracts/decorators.rb 0000644 0000041 0000041 00000002363 14717736077 021120 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module MethodDecorators
def self.extended(klass)
Engine.apply(klass)
end
def inherited(subclass)
Engine.fetch_from(subclass).set_eigenclass_owner
super
end
def method_added(name)
MethodHandler.new(name, false, self).handle
super
end
def singleton_method_added(name)
MethodHandler.new(name, true, self).handle
super
end
end
class Decorator
# an attr_accessor for a class variable:
class << self; attr_accessor :decorators; end
def self.inherited(klass)
super
name = klass.name.gsub(/^./) { |m| m.downcase }
return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
# the file and line parameters set the text for error messages
# make a new method that is the name of your decorator.
# that method accepts random args and a block.
# inside, `decorate` is called with those params.
MethodDecorators.module_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{klass}(*args, &blk)
::Contracts::Engine.fetch_from(self).decorate(#{klass}, *args, &blk)
end
RUBY_EVAL
end
def initialize(klass, method)
@method = method
end
end
end
contracts-0.17.2/lib/contracts/call_with.rb 0000644 0000041 0000041 00000010752 14717736077 020722 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module CallWith
def call_with(this, *args, **kargs, &blk)
call_with_inner(false, this, *args, **kargs, &blk)
end
def call_with_inner(returns, this, *args, **kargs, &blk)
args << blk if blk
# Explicitly append blk=nil if nil != Proc contract violation anticipated
nil_block_appended = maybe_append_block!(args, blk)
if @kargs_validator && !@kargs_validator[kargs]
data = {
arg: kargs,
contract: kargs_contract,
class: klass,
method: method,
contracts: self,
arg_pos: :keyword,
total_args: args.size,
return_value: false,
}
return ParamContractError.new("as return value", data) if returns
return unless Contract.failure_callback(data)
end
# Loop forward validating the arguments up to the splat (if there is one)
(@args_contract_index || args.size).times do |i|
contract = args_contracts[i]
arg = args[i]
validator = @args_validators[i]
unless validator && validator[arg]
data = {
arg: arg,
contract: contract,
class: klass,
method: method,
contracts: self,
arg_pos: i+1,
total_args: args.size,
return_value: false,
}
return ParamContractError.new("as return value", data) if returns
return unless Contract.failure_callback(data)
end
if contract.is_a?(Contracts::Func) && blk && !nil_block_appended
blk = Contract.new(klass, arg, *contract.contracts)
elsif contract.is_a?(Contracts::Func)
args[i] = Contract.new(klass, arg, *contract.contracts)
end
end
# If there is a splat loop backwards to the lower index of the splat
# Once we hit the splat in this direction set its upper index
# Keep validating but use this upper index to get the splat validator.
if @args_contract_index
splat_upper_index = @args_contract_index
(args.size - @args_contract_index).times do |i|
arg = args[args.size - 1 - i]
if args_contracts[args_contracts.size - 1 - i].is_a?(Contracts::Args)
splat_upper_index = i
end
# Each arg after the spat is found must use the splat validator
j = i < splat_upper_index ? i : splat_upper_index
contract = args_contracts[args_contracts.size - 1 - j]
validator = @args_validators[args_contracts.size - 1 - j]
unless validator && validator[arg]
# rubocop:disable Style/SoleNestedConditional
return unless Contract.failure_callback({
:arg => arg,
:contract => contract,
:class => klass,
:method => method,
:contracts => self,
:arg_pos => i - 1,
:total_args => args.size,
:return_value => false,
})
# rubocop:enable Style/SoleNestedConditional
end
if contract.is_a?(Contracts::Func)
args[args.size - 1 - i] = Contract.new(klass, arg, *contract.contracts)
end
end
end
# If we put the block into args for validating, restore the args
# OR if we added a fake nil at the end because a block wasn't passed in.
args.slice!(-1) if blk || nil_block_appended
result = if method.respond_to?(:call)
# proc, block, lambda, etc
method.call(*args, **kargs, &blk)
else
# original method name reference
# Don't reassign blk, else Travis CI shows "stack level too deep".
target_blk = blk
target_blk = lambda { |*params| blk.call(*params) } if blk.is_a?(Contract)
method.send_to(this, *args, **kargs, &target_blk)
end
unless @ret_validator[result]
Contract.failure_callback({
arg: result,
contract: ret_contract,
class: klass,
method: method,
contracts: self,
return_value: true,
})
end
this.verify_invariants!(method) if this.respond_to?(:verify_invariants!)
if ret_contract.is_a?(Contracts::Func)
result = Contract.new(klass, result, *ret_contract.contracts)
end
result
end
end
end
contracts-0.17.2/lib/contracts/formatters.rb 0000644 0000041 0000041 00000007504 14717736077 021143 0 ustar www-data www-data # frozen_string_literal: true
# rubocop:disable Lint/RedundantRequireStatement
# If removed, spec/module_spec fails
require "pp"
# rubocop:enable Lint/RedundantRequireStatement
module Contracts
# A namespace for classes related to formatting.
module Formatters
# Used to format contracts for the `Expected:` field of error output.
class Expected
# @param full [Boolean] if false only unique `to_s` values will be output,
# non unique values become empty string.
def initialize(contract, full: true)
@contract, @full = contract, full
end
# Formats any type of Contract.
def contract(contract = @contract)
case contract
when Hash
hash_contract(contract)
when Array
array_contract(contract)
else
InspectWrapper.create(contract, full: @full)
end
end
# Formats Hash contracts.
def hash_contract(hash)
@full = true # Complex values output completely, overriding @full
hash.inject({}) do |repr, (k, v)|
repr.merge(k => InspectWrapper.create(contract(v), full: @full))
end
end
# Formats Array contracts.
def array_contract(array)
@full = true
array.map { |v| InspectWrapper.create(contract(v), full: @full) }
end
end
# A wrapper class to produce correct inspect behaviour for different
# contract values - constants, Class contracts, instance contracts etc.
module InspectWrapper
# InspectWrapper is a factory, will never be an instance
# @return [ClassInspectWrapper, ObjectInspectWrapper]
def self.create(value, full: true)
if value.instance_of?(Class)
ClassInspectWrapper
else
ObjectInspectWrapper
end.new(value, full)
end
# @param full [Boolean] if false only unique `to_s` values will be output,
# non unique values become empty string.
def initialize(value, full)
@value, @full = value, full
end
# Inspect different types of contract values.
# Contracts module prefix will be removed from classes.
# Custom to_s messages will be wrapped in round brackets to differentiate
# from standard Strings.
# Primitive values e.g. 42, true, nil will be left alone.
def inspect
return "" unless full?
return @value.inspect if empty_val?
return @value.to_s if plain?
return delim(@value.to_s) if useful_to_s?
useful_inspect
end
def delim(value)
@full ? "(#{value})" : "#{value}"
end
# Eliminates eronious quotes in output that plain inspect includes.
def to_s
inspect
end
private
def empty_val?
@value.nil? || @value == ""
end
def full?
@full ||
@value.is_a?(Hash) || @value.is_a?(Array) ||
(!plain? && useful_to_s?)
end
def plain?
# Not a type of contract that can have a custom to_s defined
!@value.is_a?(Builtin::CallableClass) && @value.class != Class
end
def useful_to_s?
# Useless to_s value or no custom to_s behaviour defined
!empty_to_s? && custom_to_s?
end
def empty_to_s?
@value.to_s.empty?
end
def strip_prefix(val)
val.gsub(/^Contracts::Builtin::/, "")
end
end
class ClassInspectWrapper
include InspectWrapper
def custom_to_s?
@value.to_s != @value.name
end
def useful_inspect
strip_prefix(empty_to_s? ? @value.name : @value.inspect)
end
end
class ObjectInspectWrapper
include InspectWrapper
def custom_to_s?
!@value.to_s.match(/#<\w+:.+>/)
end
def useful_inspect
strip_prefix(empty_to_s? ? @value.class.name : @value.inspect)
end
end
end
end
contracts-0.17.2/lib/contracts/engine.rb 0000644 0000041 0000041 00000001235 14717736077 020215 0 ustar www-data www-data # frozen_string_literal: true
require "contracts/engine/base"
require "contracts/engine/target"
require "contracts/engine/eigenclass"
require "forwardable"
module Contracts
# Engine facade, normally you shouldn't refer internals of Engine
# module directly.
module Engine
class << self
extend Forwardable
# .apply(klass) - enables contracts engine on klass
# .applied?(klass) - returns true if klass has contracts engine
# .fetch_from(klass) - returns contracts engine for klass
def_delegators :base_engine, :apply, :applied?, :fetch_from
private
def base_engine
Base
end
end
end
end
contracts-0.17.2/lib/contracts/attrs.rb 0000644 0000041 0000041 00000001107 14717736077 020103 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Attrs
def attr_reader_with_contract(*names, contract)
names.each do |name|
Contract Contracts::None => contract
attr_reader(name)
end
end
def attr_writer_with_contract(*names, contract)
names.each do |name|
Contract contract => contract
attr_writer(name)
end
end
def attr_accessor_with_contract(*names, contract)
attr_reader_with_contract(*names, contract)
attr_writer_with_contract(*names, contract)
end
end
include Attrs
end
contracts-0.17.2/lib/contracts/core.rb 0000644 0000041 0000041 00000002571 14717736077 017704 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Core
def self.included(base)
common(base)
end
def self.extended(base)
common(base)
end
def self.common(base)
base.extend(MethodDecorators)
base.instance_eval do
def functype(funcname)
contracts = Engine.fetch_from(self).decorated_methods_for(:class_methods, funcname)
if contracts.nil?
"No contract for #{self}.#{funcname}"
else
"#{funcname} :: #{contracts[0]}"
end
end
end
# NOTE: Workaround for `defined?(super)` bug in ruby 1.9.2
# source: http://stackoverflow.com/a/11181685
# bug: https://bugs.ruby-lang.org/issues/6644
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
# TODO: deprecate
# Required when contracts are included in global scope
def Contract(*args)
if defined?(super)
super
else
self.class.Contract(*args)
end
end
RUBY
base.class_eval do
def functype(funcname)
contracts = Engine.fetch_from(self.class).decorated_methods_for(:instance_methods, funcname)
if contracts.nil?
"No contract for #{self.class}.#{funcname}"
else
"#{funcname} :: #{contracts[0]}"
end
end
end
end
end
end
contracts-0.17.2/lib/contracts/method_reference.rb 0000644 0000041 0000041 00000005154 14717736077 022252 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
# MethodReference represents original method reference that was
# decorated by contracts.ruby. Used for instance methods.
class MethodReference
attr_reader :name
# name - name of the method
# method - method object
def initialize(name, method)
@name = name
@method = method
end
# Returns method_position, delegates to Support.method_position
def method_position
Support.method_position(@method)
end
# Makes a method re-definition in proper way
def make_definition(this, &blk)
is_private = private?(this)
is_protected = protected?(this)
alias_target(this).send(:define_method, name, &blk)
make_private(this) if is_private
make_protected(this) if is_protected
end
# Aliases original method to a special unique name, which is known
# only to this class. Usually done right before re-defining the
# method.
def make_alias(this)
_aliased_name = aliased_name
original_name = name
alias_target(this).class_eval do
alias_method _aliased_name, original_name
end
end
# Calls original method on specified `this` argument with
# specified arguments `args` and block `&blk`.
def send_to(this, *args, **kargs, &blk)
this.send(aliased_name, *args, **kargs, &blk)
end
private
# Makes a method private
def make_private(this)
original_name = name
alias_target(this).class_eval { private original_name }
end
def private?(this)
this.private_instance_methods.map(&:to_sym).include?(name)
end
def protected?(this)
this.protected_instance_methods.map(&:to_sym).include?(name)
end
# Makes a method protected
def make_protected(this)
original_name = name
alias_target(this).class_eval { protected original_name }
end
# Returns alias target for instance methods, subject to be
# overriden in subclasses.
def alias_target(this)
this
end
def aliased_name
@_original_name ||= construct_unique_name
end
def construct_unique_name
:"__contracts_ruby_original_#{name}_#{Support.unique_id}"
end
end
# The same as MethodReference, but used for singleton methods.
class SingletonMethodReference < MethodReference
private
def private?(this)
this.private_methods.map(&:to_sym).include?(name)
end
def protected?(this)
this.protected_methods.map(&:to_sym).include?(name)
end
# Return alias target for singleton methods.
def alias_target(this)
Support.eigenclass_of this
end
end
end
contracts-0.17.2/lib/contracts/engine/ 0000755 0000041 0000041 00000000000 14717736077 017667 5 ustar www-data www-data contracts-0.17.2/lib/contracts/engine/target.rb 0000644 0000041 0000041 00000003451 14717736077 021505 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Engine
# Represents class in question
class Target
# Creates new instance of Target
#
# @param [Class] target - class in question
def initialize(target)
@target = target
end
# Enable contracts engine for target
# - it is no-op if contracts engine is already enabled
# - it automatically enables contracts engine for its eigenclass
# - it sets owner class to target for its eigenclass
#
# @param [Engine::Base:Class] engine_class - type of engine to
# enable (Base or Eigenclass)
def apply(engine_class = Base)
return if applied?
apply_to_eigenclass
eigenclass.class_eval do
define_method(:__contracts_engine) do
@__contracts_engine ||= engine_class.new(self)
end
end
engine.set_eigenclass_owner
end
# Returns true if target has contracts engine already
#
# @return [Bool]
def applied?
target.respond_to?(:__contracts_engine)
end
# Returns contracts engine of target
#
# @return [Engine::Base or Engine::Eigenclass]
def engine
applied? && target.__contracts_engine
end
private
attr_reader :target
def apply_to_eigenclass
return unless meaningless_eigenclass?
self.class.new(eigenclass).apply(Eigenclass)
eigenclass.extend(MethodDecorators)
# FIXME; this should detect what user uses `include Contracts` or
# `include Contracts;;Core`
eigenclass.send(:include, Contracts)
end
def eigenclass
Support.eigenclass_of(target)
end
def meaningless_eigenclass?
!Support.eigenclass?(target)
end
end
end
end
contracts-0.17.2/lib/contracts/engine/base.rb 0000644 0000041 0000041 00000007204 14717736077 021131 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Engine
# Contracts engine
class Base
# Enable contracts engine for klass
#
# @param [Class] klass - target class
def self.apply(klass)
Engine::Target.new(klass).apply
end
# Returns true if klass has contracts engine
#
# @param [Class] klass - target class
# @return [Bool]
def self.applied?(klass)
Engine::Target.new(klass).applied?
end
# Fetches contracts engine out of klass
#
# @param [Class] klass - target class
# @return [Engine::Base or Engine::Eigenclass]
def self.fetch_from(klass)
Engine::Target.new(klass).engine
end
# Creates new instance of contracts engine
#
# @param [Class] klass - class that owns this engine
def initialize(klass)
@klass = klass
end
# Adds provided decorator to the engine
# It validates that decorator can be added to this engine at the
# moment
#
# @param [Decorator:Class] decorator_class
# @param args - arguments for decorator
def decorate(decorator_class, *args)
validate!
decorators << [decorator_class, args]
end
# Sets eigenclass' owner to klass
def set_eigenclass_owner
eigenclass_engine.owner_class = klass
end
# Fetches all accumulated decorators (both this engine and
# corresponding eigenclass' engine)
# It clears all accumulated decorators
#
# @return [ArrayOf[Decorator]]
def all_decorators
pop_decorators + eigenclass_engine.all_decorators
end
# Fetches decorators of specified type for method with name
#
# @param [Or[:class_methods, :instance_methods]] type - method type
# @param [Symbol] name - method name
# @return [ArrayOf[Decorator]]
def decorated_methods_for(type, name)
Array(decorated_methods[type][name])
end
# Returns true if there are any decorated methods
#
# @return [Bool]
def decorated_methods?
!decorated_methods[:class_methods].empty? ||
!decorated_methods[:instance_methods].empty?
end
# Adds method decorator
#
# @param [Or[:class_methods, :instance_methods]] type - method type
# @param [Symbol] name - method name
# @param [Decorator] decorator - method decorator
def add_method_decorator(type, name, decorator)
decorated_methods[type][name] ||= []
decorated_methods[type][name] << decorator
end
# Returns nearest ancestor's engine that has decorated methods
#
# @return [Engine::Base or Engine::Eigenclass]
def nearest_decorated_ancestor
current = klass
current_engine = self
ancestors = current.ancestors[1..]
while current && current_engine && !current_engine.decorated_methods?
current = ancestors.shift
current_engine = Engine.fetch_from(current)
end
current_engine
end
private
attr_reader :klass
def decorated_methods
@_decorated_methods ||= { :class_methods => {}, :instance_methods => {} }
end
# No-op because it is safe to add decorators to normal classes
def validate!; end
def pop_decorators
decorators.tap { clear_decorators }
end
def eigenclass
Support.eigenclass_of(klass)
end
def eigenclass_engine
Eigenclass.lift(eigenclass, klass)
end
def decorators
@_decorators ||= []
end
def clear_decorators
@_decorators = []
end
end
end
end
contracts-0.17.2/lib/contracts/engine/eigenclass.rb 0000644 0000041 0000041 00000002731 14717736077 022334 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Engine
# Special case of contracts engine for eigenclasses
# We don't care about eigenclass of eigenclass at this point
class Eigenclass < Base
# Class that owns this eigenclass
attr_accessor :owner_class
# Automatically enables eigenclass engine if it is not
# Returns its engine
# NOTE: Required by jruby in 1.9 mode. Otherwise inherited
# eigenclasses don't have their engines
#
# @param [Class] eigenclass - class in question
# @param [Class] owner - owner of eigenclass
# @return [Engine::Eigenclass]
def self.lift(eigenclass, owner)
return Engine.fetch_from(eigenclass) if Engine.applied?(eigenclass)
Target.new(eigenclass).apply(Eigenclass)
eigenclass.extend(MethodDecorators)
# FIXME; this should detect what user uses `include Contracts` or
# `include Contracts;;Core`
eigenclass.send(:include, Contracts)
Engine.fetch_from(owner).set_eigenclass_owner
Engine.fetch_from(eigenclass)
end
# No-op for eigenclasses
def set_eigenclass_owner; end
# Fetches just eigenclasses decorators
def all_decorators
pop_decorators
end
private
# Fails when contracts are not included in owner class
def validate!
fail ContractsNotIncluded unless owner?
end
def owner?
!!owner_class
end
end
end
end
contracts-0.17.2/lib/contracts/version.rb 0000644 0000041 0000041 00000000111 14717736077 020425 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
VERSION = "0.17.2"
end
contracts-0.17.2/lib/contracts/invariants.rb 0000644 0000041 0000041 00000003132 14717736077 021124 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Invariants
def self.included(base)
common base
end
def self.extended(base)
common base
end
def self.common(base)
return if base.respond_to?(:Invariant)
base.extend(InvariantExtension)
end
def verify_invariants!(method)
return unless self.class.respond_to?(:invariants)
self.class.invariants.each do |invariant|
invariant.check_on(self, method)
end
end
module InvariantExtension
def invariant(name, &condition)
return if ENV["NO_CONTRACTS"]
invariants << Invariant.new(self, name, &condition)
end
def invariants
@invariants ||= []
end
end
class Invariant
def initialize(klass, name, &condition)
@klass, @name, @condition = klass, name, condition
end
def expected
"#{@name} condition to be true"
end
def check_on(target, method)
return if target.instance_eval(&@condition)
self.class.failure_callback({
expected: expected,
actual: false,
target: target,
method: method,
})
end
def self.failure_callback(data)
fail InvariantError, failure_msg(data)
end
def self.failure_msg(data)
%{Invariant violation:
Expected: #{data[:expected]}
Actual: #{data[:actual]}
Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
At: #{Support.method_position(data[:method])}}
end
end
end
end
contracts-0.17.2/lib/contracts/builtin_contracts.rb 0000644 0000041 0000041 00000033721 14717736077 022503 0 ustar www-data www-data # frozen_string_literal: true
require "contracts/formatters"
require "set"
# rdoc
# This module contains all the builtin contracts.
# If you want to use them, first:
#
# import Contracts
#
# And then use these or write your own!
#
# A simple example:
#
# Contract Num, Num => Num
# def add(a, b)
# a + b
# end
#
# The contract is Contract Num, Num, Num.
# That says that the +add+ function takes two numbers and returns a number.
module Contracts
module Builtin
# Check that an argument is +Numeric+.
class Num
def self.valid? val
val.is_a? Numeric
end
end
# Check that an argument is a positive number.
class Pos
def self.valid? val
val.is_a?(Numeric) && val.positive?
end
end
# Check that an argument is a negative number.
class Neg
def self.valid? val
val.is_a?(Numeric) && val.negative?
end
end
# Check that an argument is an +Integer+.
class Int
def self.valid? val
val.is_a?(Integer)
end
end
# Check that an argument is a natural number (includes zero).
class Nat
def self.valid? val
val.is_a?(Integer) && val >= 0
end
end
# Check that an argument is a positive natural number (excludes zero).
class NatPos
def self.valid? val
val.is_a?(Integer) && val.positive?
end
end
# Passes for any argument.
class Any
def self.valid? val
true
end
end
# Fails for any argument.
class None
def self.valid? val
false
end
end
# Use this when you are writing your own contract classes.
# Allows your contract to be called with [] instead of .new:
#
# Old: Or.new(param1, param2)
#
# New: Or[param1, param2]
#
# Of course, .new still works.
class CallableClass
include ::Contracts::Formatters
def self.[](*vals)
new(*vals)
end
end
# Takes a variable number of contracts.
# The contract passes if any of the contracts pass.
# Example: Or[Integer, Float]
class Or < CallableClass
def initialize(*vals)
super()
@vals = vals
end
def valid?(val)
@vals.any? do |contract|
res, _ = Contract.valid?(val, contract)
res
end
end
def to_s
# rubocop:disable Style/StringConcatenation
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
# rubocop:enable Style/StringConcatenation
end
end
# Takes a variable number of contracts.
# The contract passes if exactly one of those contracts pass.
# Example: Xor[Integer, Float]
class Xor < CallableClass
def initialize(*vals)
super()
@vals = vals
end
def valid?(val)
results = @vals.map do |contract|
res, _ = Contract.valid?(val, contract)
res
end
results.count(true) == 1
end
def to_s
# rubocop:disable Style/StringConcatenation
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
# rubocop:enable Style/StringConcatenation
end
end
# Takes a variable number of contracts.
# The contract passes if all contracts pass.
# Example: And[Integer, Float]
class And < CallableClass
def initialize(*vals)
super()
@vals = vals
end
def valid?(val)
@vals.all? do |contract|
res, _ = Contract.valid?(val, contract)
res
end
end
def to_s
# rubocop:disable Style/StringConcatenation
@vals[0, @vals.size-1].map do |x|
InspectWrapper.create(x)
end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
# rubocop:enable Style/StringConcatenation
end
end
# Takes a variable number of method names as symbols.
# The contract passes if the argument responds to all
# of those methods.
# Example: RespondTo[:password, :credit_card]
class RespondTo < CallableClass
def initialize(*meths)
super()
@meths = meths
end
def valid?(val)
@meths.all? do |meth|
val.respond_to? meth
end
end
def to_s
"a value that responds to #{@meths.inspect}"
end
end
# Takes a variable number of method names as symbols.
# Given an argument, all of those methods are called
# on the argument one by one. If they all return true,
# the contract passes.
# Example: Send[:valid?]
class Send < CallableClass
def initialize(*meths)
super()
@meths = meths
end
def valid?(val)
@meths.all? do |meth|
val.send(meth)
end
end
def to_s
"a value that returns true for all of #{@meths.inspect}"
end
end
# Takes a class +A+. If argument is object of type +A+, the contract passes.
# If it is a subclass of A (or not related to A in any way), it fails.
# Example: Exactly[Numeric]
class Exactly < CallableClass
def initialize(cls)
super()
@cls = cls
end
def valid?(val)
val.instance_of?(@cls)
end
def to_s
"exactly #{@cls.inspect}"
end
end
# Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
# the list, the contract passes.
#
# Example: Enum[:a, :b, :c]?
class Enum < CallableClass
def initialize(*vals)
super()
@vals = vals
end
def valid?(val)
@vals.include? val
end
end
# Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
# otherwise the contract fails.
# Example: Eq[Class]
class Eq < CallableClass
def initialize(value)
super()
@value = value
end
def valid?(val)
@value.equal?(val)
end
def to_s
"to be equal to #{@value.inspect}"
end
end
# Takes a variable number of contracts. The contract
# passes if all of those contracts fail for the given argument.
# Example: Not[nil]
class Not < CallableClass
def initialize(*vals)
super()
@vals = vals
end
def valid?(val)
@vals.all? do |contract|
res, _ = Contract.valid?(val, contract)
!res
end
end
def to_s
"a value that is none of #{@vals.inspect}"
end
end
# @private
# Takes a collection(responds to :each) type and a contract.
# The related argument must be of specified collection type.
# Checks the contract against every element of the collection.
# If it passes for all elements, the contract passes.
# Example: CollectionOf[Array, Num]
class CollectionOf < CallableClass
def initialize(collection_class, contract)
super()
@collection_class = collection_class
@contract = contract
end
def valid?(vals)
return false unless vals.is_a?(@collection_class)
vals.all? do |val|
res, _ = Contract.valid?(val, @contract)
res
end
end
def to_s
"a collection #{@collection_class} of #{@contract}"
end
class Factory
def initialize(collection_class, &before_new)
@collection_class = collection_class
@before_new = before_new
end
def new(contract)
@before_new&.call
CollectionOf.new(@collection_class, contract)
end
alias_method :[], :new
end
end
# Takes a contract. The related argument must be an array.
# Checks the contract against every element of the array.
# If it passes for all elements, the contract passes.
# Example: ArrayOf[Num]
ArrayOf = CollectionOf::Factory.new(Array)
# Takes a contract. The related argument must be a set.
# Checks the contract against every element of the set.
# If it passes for all elements, the contract passes.
# Example: SetOf[Num]
SetOf = CollectionOf::Factory.new(Set)
# Used for *args (variadic functions). Takes a contract
# and uses it to validate every element passed in
# through *args.
# Example: Args[Or[String, Num]]
class Args < CallableClass
attr_reader :contract
def initialize(contract)
super()
@contract = contract
end
def to_s
"Args[#{@contract}]"
end
end
class Bool
def self.valid? val
val.is_a?(TrueClass) || val.is_a?(FalseClass)
end
end
# Use this to specify a Range object of a particular datatype.
# Example: RangeOf[Nat], RangeOf[Date], ...
class RangeOf < CallableClass
def initialize(contract)
super()
@contract = contract
end
def valid?(val)
val.is_a?(Range) &&
Contract.valid?(val.first, @contract) &&
Contract.valid?(val.last, @contract)
end
def to_s
"a range of #{@contract}"
end
end
# Use this to specify the Hash characteristics. Takes two contracts,
# one for hash keys and one for hash values.
# Example: HashOf[Symbol, String]
class HashOf < CallableClass
INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
def initialize(key, value = nil)
super()
if value
@key = key
@value = value
else
validate_hash(key)
@key = key.keys.first
@value = key[@key]
end
end
def valid?(hash)
return false unless hash.is_a?(Hash)
keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
[keys_match, vals_match].all?
end
def to_s
"Hash<#{@key}, #{@value}>"
end
private
def validate_hash(hash)
fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
end
end
# Use this to specify the Hash characteristics. This contracts fails
# if there are any extra keys that don't have contracts on them.
# Example: StrictHash[{ a: String, b: Bool }]
class StrictHash < CallableClass
attr_reader :contract_hash
def initialize(contract_hash)
super()
@contract_hash = contract_hash
end
def valid?(arg)
return false unless arg.is_a?(Hash)
return false unless arg.keys.sort.eql?(contract_hash.keys.sort)
contract_hash.all? do |key, contract|
contract_hash.key?(key) && Contract.valid?(arg[key], contract)
end
end
end
# Use this for specifying contracts for keyword arguments
# Example: KeywordArgs[ e: Range, f: Optional[Num] ]
class KeywordArgs < CallableClass
def initialize(options)
super()
@options = options
end
def valid?(hash)
return false unless hash.is_a?(Hash)
return false unless hash.keys - options.keys == []
options.all? do |key, contract|
Optional._valid?(hash, key, contract)
end
end
def to_s
"KeywordArgs[#{options}]"
end
def inspect
to_s
end
private
attr_reader :options
end
# Use this for specifying contracts for class arguments
# Example: DescendantOf[ e: Range, f: Optional[Num] ]
class DescendantOf < CallableClass
def initialize(parent_class)
super()
@parent_class = parent_class
end
def valid?(given_class)
given_class.is_a?(Class) && given_class.ancestors.include?(parent_class)
end
def to_s
"DescendantOf[#{parent_class}]"
end
def inspect
to_s
end
private
attr_reader :parent_class
end
# Use this for specifying optional keyword argument
# Example: Optional[Num]
class Optional < CallableClass
UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
"Unable to use Optional contract outside of KeywordArgs contract"
def self._valid?(hash, key, contract)
return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
contract.within_opt_hash!
!hash.key?(key) || Contract.valid?(hash[key], contract)
end
def initialize(contract)
super()
@contract = contract
@within_opt_hash = false
end
def within_opt_hash!
@within_opt_hash = true
self
end
def valid?(value)
ensure_within_opt_hash
Contract.valid?(value, contract)
end
def to_s
"Optional[#{formatted_contract}]"
end
def inspect
to_s
end
private
attr_reader :contract, :within_opt_hash
def ensure_within_opt_hash
return if within_opt_hash
fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
end
def formatted_contract
Formatters::InspectWrapper.create(contract)
end
end
# Takes a Contract.
# The contract passes if the contract passes or the given value is nil.
# Maybe(foo) is equivalent to Or[foo, nil].
class Maybe < Or
def initialize(*vals)
super(*(vals + [nil]))
end
def include_proc?
@vals.include? Proc
end
end
# Used to define contracts on functions passed in as arguments.
# Example: Func[Num => Num] # the function should take a number and return a number
class Func < CallableClass
attr_reader :contracts
def initialize(*contracts)
super()
@contracts = contracts
end
end
end
# Users can still include `Contracts::Core` & `Contracts::Builtin`
include Builtin
end
contracts-0.17.2/lib/contracts/errors.rb 0000644 0000041 0000041 00000003357 14717736077 020273 0 ustar www-data www-data # frozen_string_literal: true
# @private
# Base class for Contract errors
#
# If default failure callback is used it stores failure data
class ContractBaseError < ArgumentError
attr_reader :data
def initialize(message, data)
super(message)
@data = data
end
# Used to convert to simple ContractError from other contract errors
def to_contract_error
self
end
end
# Default contract error
#
# If default failure callback is used, users normally see only these contract errors
class ContractError < ContractBaseError
end
class ParamContractError < ContractError
end
class ReturnContractError < ContractError
end
# @private
# Special contract error used internally to detect pattern failure during pattern matching
class PatternMatchingError < ContractBaseError
# Used to convert to ContractError from PatternMatchingError
def to_contract_error
ContractError.new(to_s, data)
end
end
# Base invariant violation error
class InvariantError < StandardError
def to_contract_error
self
end
end
module Contracts
# Error issued when user haven't included Contracts in original class but used Contract definition in singleton class
#
# Provides useful description for user of the gem and an example of correct usage.
class ContractsNotIncluded < TypeError
DEFAULT_MESSAGE = %{In order to use contracts in singleton class, please include Contracts module in original class
Example:
```ruby
class Example
include Contracts # this line is required
class << self
# you can use `Contract` definition here now
end
end
```}
attr_reader :message
alias_method :to_s, :message
def initialize(message = DEFAULT_MESSAGE)
super
@message = message
end
end
end
contracts-0.17.2/lib/contracts/validators.rb 0000644 0000041 0000041 00000006743 14717736077 021131 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Validators
DEFAULT_VALIDATOR_STRATEGIES = {
# e.g. lambda {true}
Proc => lambda { |contract| contract },
# e.g. [Num, String]
# TODO: account for these errors too
Array => lambda do |contract|
lambda do |arg|
return false unless arg.is_a?(Array) && arg.length == contract.length
arg.zip(contract).all? do |_arg, _contract|
Contract.valid?(_arg, _contract)
end
end
end,
# e.g. { :a => Num, :b => String }
Hash => lambda do |contract|
lambda do |arg|
return false unless arg.is_a?(Hash)
contract.keys.all? do |k|
Contract.valid?(arg[k], contract[k])
end
end
end,
Range => lambda do |contract|
lambda do |arg|
contract.include?(arg)
end
end,
Regexp => lambda do |contract|
lambda do |arg|
arg =~ contract
end
end,
Contracts::Args => lambda do |contract|
lambda do |arg|
Contract.valid?(arg, contract.contract)
end
end,
Contracts::Func => lambda do |_|
lambda do |arg|
arg.is_a?(Method) || arg.is_a?(Proc)
end
end,
:valid => lambda do |contract|
lambda { |arg| contract.valid?(arg) }
end,
:class => lambda do |contract|
lambda { |arg| arg.is_a?(contract) }
end,
:default => lambda do |contract|
lambda { |arg| contract == arg }
end,
}.freeze
# Allows to override validator with custom one.
# Example:
# Contract.override_validator(Array) do |contract|
# lambda do |arg|
# # .. implementation for Array contract ..
# end
# end
#
# Contract.override_validator(:class) do |contract|
# lambda do |arg|
# arg.is_a?(contract) || arg.is_a?(RSpec::Mocks::Double)
# end
# end
def override_validator(name, &block)
validator_strategies[name] = block
end
# This is a little weird. For each contract
# we pre-make a proc to validate it so we
# don't have to go through this decision tree every time.
# Seems silly but it saves us a bunch of time (4.3sec vs 5.2sec)
def make_validator!(contract)
klass = contract.class
key = if validator_strategies.key?(klass)
klass
else
if contract.respond_to? :valid?
:valid
elsif [Class, Module].include?(klass)
:class
else
:default
end
end
validator_strategies[key].call(contract)
end
def make_validator(contract)
contract_id = Support.contract_id(contract)
if memoized_validators.key?(contract_id)
return memoized_validators[contract_id]
end
memoized_validators[contract_id] = make_validator!(contract)
end
# @private
def reset_validators
clean_memoized_validators
restore_validators
end
# @private
def validator_strategies
@_validator_strategies ||= restore_validators
end
# @private
def restore_validators
@_validator_strategies = DEFAULT_VALIDATOR_STRATEGIES.dup
end
# @private
def memoized_validators
@_memoized_validators ||= clean_memoized_validators
end
# @private
def clean_memoized_validators
@_memoized_validators = {}
end
end
end
contracts-0.17.2/lib/contracts/support.rb 0000644 0000041 0000041 00000002740 14717736077 020466 0 ustar www-data www-data # frozen_string_literal: true
module Contracts
module Support
class << self
def method_position(method)
return method.method_position if method.is_a?(MethodReference)
file, line = method.source_location
if file.nil? || line.nil?
""
else
"#{file}:#{line}"
end
end
def method_name(method)
method.is_a?(Proc) ? "Proc" : method.name
end
# Generates unique id, which can be used as a part of identifier
#
# Example:
# Contracts::Support.unique_id # => "i53u6tiw5hbo"
def unique_id
# Consider using SecureRandom.hex here, and benchmark which one is better
(Time.now.to_f * 1000).to_i.to_s(36) + rand(1_000_000).to_s(36)
end
def contract_id(contract)
contract.object_id
end
def eigenclass_hierarchy_supported?
RUBY_PLATFORM != "java" || RUBY_VERSION.to_f >= 2.0
end
def eigenclass_of(target)
class << target; self; end
end
def eigenclass?(target)
module_eigenclass?(target) ||
target <= eigenclass_of(Object)
end
def indent_string(string, amount)
string.gsub(
/^(?!$)/,
(string[/^[ \t]/] || " ") * amount,
)
end
private
# Module eigenclass can be detected by its ancestor chain
# containing a Module
def module_eigenclass?(target)
target < Module
end
end
end
end
contracts-0.17.2/contracts.gemspec 0000644 0000041 0000041 00000001274 14717736077 017225 0 ustar www-data www-data # frozen_string_literal: true
require File.expand_path(File.join(__FILE__, "../lib/contracts/version"))
Gem::Specification.new do |s|
s.name = "contracts"
s.version = Contracts::VERSION
s.summary = "Contracts for Ruby."
s.description = "This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code."
s.author = "Aditya Bhargava"
s.email = "bluemangroupie@gmail.com"
s.files = `git ls-files`.split("\n")
s.homepage = "https://github.com/egonSchiele/contracts.ruby"
s.license = "BSD-2-Clause"
s.required_ruby_version = [">= 3.0", "< 4"]
end
contracts-0.17.2/TUTORIAL.md 0000644 0000041 0000041 00000064717 14717736077 015460 0 ustar www-data www-data # The contracts.ruby tutorial
## Introduction
contracts.ruby brings code contracts to the Ruby language. Code contracts allow you make some assertions about your code, and then checks them to make sure they hold. This lets you
- catch bugs faster
- make it very easy to catch certain types of bugs
- make sure that the user gets proper messaging when a bug occurs.
## Installation
gem install contracts
## Basics
A simple example:
```ruby
Contract Contracts::Num, Contracts::Num => Contracts::Num
def add(a, b)
a + b
end
```
Here, the contract is `Contract Num, Num => Num`. This says that the `add` function takes two numbers and returns a number.
Copy this code into a file and run it:
```ruby
require 'contracts'
class Math
include Contracts::Core
Contract Contracts::Num, Contracts::Num => Contracts::Num
def self.add(a, b)
a + b
end
end
puts Math.add(1, "foo")
```
You'll see a detailed error message like so:
./contracts.rb:60:in `failure_callback': Contract violation: (RuntimeError)
Expected: Contracts::Num,
Actual: "foo"
Value guarded in: Object::add
With Contract: Contracts::Num, Contracts::Num
At: foo.rb:6
That tells you that your contract was violated! `add` expected a `Num`, and got a string (`"foo"`) instead.
By default, an exception is thrown when a contract fails. This can be changed to do whatever you want. More on this later.
You can also see the contract for a function with the `functype` method:
functype(:add)
=> "add :: Num, Num => Num"
This can be useful if you're in a REPL and want to figure out how a function should be used.
## Built-in Contracts
`Num` is one of the built-in contracts that contracts.ruby comes with. The built-in contracts are in the `Contracts` namespace. The easiest way to use them is to include the `Contracts::Builtin` module in your class/module.
contracts.ruby comes with a lot of built-in contracts, including the following:
* Basic types
* [`Num`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Num) – checks that the argument is `Numeric`
* [`Pos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Pos) – checks that the argument is a positive number
* [`Neg`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Neg) – checks that the argument is a negative number
* [`Int`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Int) – checks that the argument is an integer
* [`Nat`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Nat) – checks that the argument is a natural number (>= 0)
* [`NatPos`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/NatPos) – checks that the argument is a positive natural number (> 0)
* [`Bool`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Bool) – checks that the argument is `true` or `false`
* [`Any`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Any) – Passes for any argument. Use when the argument has no constraints.
* [`None`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/None) – Fails for any argument. Use when the method takes no arguments.
* Logical combinations
* [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`)
* [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Integer, Float]`
* [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Integer, Float]`
* [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]`
* [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
* Collections
* [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]`
* [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Integer }]`
* [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
* [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]`
* Keyword arguments
* [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]`
* [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]`
* Duck typing
* [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
* [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Send) – checks that all named methods return a truthy value, e.g. `Send[:valid?]`
* Miscellaneous
* [`Exactly`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Exactly) – checks that the argument has the given type, not accepting sub-classes, e.g. `Exactly[Numeric]`.
* [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance.
* [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions".
To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts/Builtin).
It is recommended to use shortcut for referring builtin contracts:
```ruby
# define shortcut somewhere at the top level of your codebase:
C = Contracts
# and use it:
Contract C::Maybe[C::Num], String => C::Num
```
Shortcut name should not be necessary `C`, can be anything that you are comfort
with while typing and anything that does not conflict with libraries you use.
All examples after this point assume you have chosen a shortcut as `C::`.
If you are sure, that builtin contracts will not nameclash with your own code
and libraries you may use, then you can include all builtin contracts in your
class/module:
```ruby
class Example
include Contracts::Core
include Contracts::Builtin
Contract Maybe[Num], Or[Float, String] => Bool
def complicated_algorithm(a, b)
# ...
end
end
```
## More Examples
### Hello, World
```ruby
Contract String => nil
def hello(name)
puts "hello, #{name}!"
end
```
You always need to specify a contract for the return value. In this example, `hello` doesn't return anything, so the contract is `nil`. Now you know that you can use a constant like `nil` as the end of a contract. Valid values for a contract are:
- the name of a class (like `String` or `Integer`)
- a constant (like `nil` or `1`)
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
- a class that responds to the `valid?` class method (more on this later)
- an instance of a class that responds to the `valid?` method (more on this later)
### A Double Function
```ruby
Contract C::Or[Integer, Float] => C::Or[Integer, Float]
def double(x)
2 * x
end
```
Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes.
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Integer, Float]` is. The longer way to write it would have been:
```ruby
Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float)
```
All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
```ruby
Contract C::Or[Integer, Float] => C::Or[Integer, Float]
```
or
```ruby
Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float)
```
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Integer` and `Float`. Use that instance to validate the argument.
### A Product Function
```ruby
Contract C::ArrayOf[C::Num] => C::Num
def product(vals)
total = 1
vals.each do |val|
total *= val
end
total
end
```
This contract uses the `ArrayOf` contract. Here's how `ArrayOf` works: it takes a contract. It expects the argument to be a list. Then it checks every value in that list to see if it satisfies that contract.
```ruby
# passes
product([1, 2, 3, 4])
# fails
product([1, 2, 3, "foo"])
```
### Another Product Function
```ruby
Contract C::Args[C::Num] => C::Num
def product(*vals)
total = 1
vals.each do |val|
total *= val
end
total
end
```
This function uses varargs (`*args`) instead of an array. To make a contract on varargs, use the `Args` contract. It takes one contract as an argument and uses it to validate every element passed in through `*args`. So for example,
`Args[Num]` means they should all be numbers.
`Args[Or[Num, String]]` means they should all be numbers or strings.
`Args[Any]` means all arguments are allowed (`Any` is a contract that passes for any argument).
### Contracts On Arrays
If an array is one of the arguments and you know how many elements it's going to have, you can put a contract on it:
```ruby
# a function that takes an array of two elements...a person's age and a person's name.
Contract [C::Num, String] => nil
def person(data)
p data
end
```
If you don't know how many elements it's going to have, use `ArrayOf`.
### Contracts On Hashes
Here's a contract that requires a Hash. We can put contracts on each of the keys:
```ruby
# note the parentheses around the hash; without those you would get a syntax error
Contract ({ :age => C::Num, :name => String }) => nil
def person(data)
p data
end
```
Then if someone tries to call the function with bad data, it will fail:
```ruby
# error: age can't be nil!
person({:name => "Adit", :age => nil})
```
You don't need to put a contract on every key. So this call would succeed:
```ruby
person({:name => "Adit", :age => 42, :foo => "bar"})
```
even though we don't specify a type for `:foo`. If you need this check though, use `StrictHash` instead.
Peruse this contract on the keys and values of a Hash.
```ruby
Contract C::HashOf[Symbol, C::Num] => C::Num
def give_largest_value(hsh)
hsh.values.max
end
```
Which you use like so:
```ruby
# succeeds
give_largest_value(a: 1, b: 2, c: 3) # returns 3
# fails
give_largest_value("a" => 1, 2 => 2, c: 3)
```
### Contracts On Strings
When you want a contract to match not just any string (i.e. `Contract String => nil`), you can use regular expressions:
```ruby
Contract /World|Mars/i => nil
def greet(name)
puts "Hello #{name}!"
end
```
Using logical combinations you can combine existing definitions, instead of writing 1 big regular expression:
```ruby
Contract C::And[default_mail_regexp, /#{AppConfig.domain}\z/] => nil
def send_admin_invite(email)
```
### Contracts On Keyword Arguments
ruby 2.0+, but can be used for normal hashes too, when keyword arguments are
not available
Lets say you are writing a simple function and require a bunch of keyword arguments:
```ruby
def connect(host, port:, user:, password:)
```
You can of course put `Hash` contract on it:
```ruby
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
def connect(host, port:, user:, password:)
```
But this will not quite work if you want to have a default values:
```ruby
Contract String, { :port => C::Num, :user => String, :password => String } => Connection
def connect(host, port: 5000, user:, password:)
# ...
end
# No value is passed for port
connect("example.org", user: "me", password: "none")
```
Results in:
```
ContractError: Contract violation for argument 2 of 2:
Expected: {:port=>Num, :user=>String, :password=>String},
Actual: {:user=>"me", :password=>"none"}
Value guarded in: Object::connect
With Contract: String, Hash => Connection
At: (irb):12
```
This can be fixed with contract `{ :port => C::Maybe[C::Num], ... }`, but that will
allow `nil` to be passed in, which is not the original intent.
So that is where `KeywordArgs` and `Optional` contracts jump in:
```ruby
Contract String, C::KeywordArgs[ :port => C::Optional[C::Num], :user => String, :password => String ] => Connection
def connect(host, port: 5000, user:, password:)
```
It looks just like the hash contract, but wrapped in `KeywordArgs` contract. Notice the usage of `Optional` contract - this way you specify that `:port` argument is optional. And it will not fail, when you omit this argument, but it will fail when you pass in `nil`.
### Contracts On Functions
Lets say you are writing a simple map function:
```ruby
def map(arr, func)
```
`map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this:
```ruby
Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
def map(arr, func)
```
This says that the second argument should be a `Proc`. You can call the function like so:
```ruby
p map([1, 2, 3], lambda { |x| x + 1 }) # works
```
But suppose you want to have a contract on the Proc too! Suppose you want to make sure that the Proc returns a number. Use the `Func` contract. `Func` takes a contract as its argument, and uses that contract on the function that you pass in.
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
```ruby
Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
def map(arr, func)
ret = []
arr.each do |x|
ret << func[x]
end
ret
end
```
Earlier, we used `Proc`, which just says "make sure the second variable is a Proc". Now we are using `Func[Num => Num]`, which says "make sure the second variable is a Proc that takes a number and returns a number". Better!
Try this map function with these two examples:
```ruby
p map([1, 2, 3], lambda { |x| x + 1 }) # works
p map([1, 2, 3], lambda { |x| "oops" }) # fails, the lambda returns a string.
```
The above examples showed a method accepting a `Proc` as the last argument, but the same contract works on methods that accept a block:
```ruby
def map(arr, &block)
```
NOTE: This is not valid:
```ruby
Contract C::ArrayOf[C::Num], C::Func => C::ArrayOf[C::Num]
def map(arr, &func)
```
Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. That's not a legal contract. If you just want to validate that the second argument is a proc, use `Proc`.
### Returning Multiple Values
Treat the return value as an array. For example, here's a function that returns two numbers:
```ruby
Contract C::Num => [C::Num, C::Num]
def mult(x)
return x, x+1
end
```
## Synonyms For Contracts
If you use a contract a lot, it's a good idea to give it a meaningful synonym that tells the reader more about what your code returns. For example, suppose you have many functions that return a `Hash` or `nil`. If a `Hash` is returned, it contains information about a person. Your contact might look like this:
```ruby
Contract String => C::Or[Hash, nil]
def some_func(str)
```
You can make your contract more meaningful with a synonym:
```ruby
# the synonym
Person = Or[Hash, nil]
# use the synonym here
Contract String => Person
def some_func(str)
```
Now you can use `Person` wherever you would have used `Or[Hash, nil]`. Your code is now cleaner and more clearly says what the function is doing.
## Defining Your Own Contracts
Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts:
- the name of a class (like `String` or `Integer`)
- a constant (like `nil` or `1`)
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
- a class that responds to the `valid?` class method (more on this later)
- an instance of a class that responds to the `valid?` method (more on this later)
The first two don't need any extra work to define: you can just use any constant or class name in your contract and it should just work. Here are examples for the rest:
### A Proc
```ruby
Contract lambda { |x| x.is_a? Numeric } => C::Num
def double(x)
```
The lambda takes one parameter: the argument that is getting passed to the function. It checks to see if it's a `Numeric`. If it is, it returns true. Otherwise it returns false.
It's not good practice to write a lambda right in your contract...if you find yourself doing it often, write it as a class instead:
### A Class With `valid?` As a Class Method
Here's how the `Num` class is defined. It does exactly what the `lambda` did in the previous example:
```ruby
class Num
def self.valid? val
val.is_a? Numeric
end
end
```
The `valid?` class method takes one parameter: the argument that is getting passed to the function. It returns true or false.
### A Class With `valid?` As an Instance Method
Here's how the `Or` class is defined:
```ruby
class Or < CallableClass
def initialize(*vals)
@vals = vals
end
def valid?(val)
@vals.any? do |contract|
res, _ = Contract.valid?(val, contract)
res
end
end
end
```
The `Or` contract takes a sequence of contracts, and passes if any of them pass. It uses `Contract.valid?` to validate the value against the contracts.
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
```ruby
Contract C::Or[Integer, Float] => C::Num
def double(x)
2 * x
end
```
Without `CallableClass`, we would have to use `.new` instead:
```ruby
Contract C::Or.new(Integer, Float) => C::Num
def double(x)
# etc
```
You can use `CallableClass` in your own contracts to make them callable using `[]`.
## Customizing Error Messages
When a contract fails, part of the error message prints the contract:
...
Expected: Contracts::Num,
...
You can customize this message by overriding the `to_s` method on your class or proc. For example, suppose we overrode `Num`'s `to_s` method:
```ruby
def Num.to_s
"a number please"
end
```
Now the error says:
...
Expected: a number please,
...
## Failure Callbacks
Supposing you don't want contract failures to become exceptions. You run a popular website, and when there's a contract exception you would rather log it and continue than throw an exception and break your site.
contracts.ruby provides a failure callback that gets called when a contract fails. For example, here we log every failure instead of raising an error:
```ruby
Contract.override_failure_callback do |data|
puts "You had an error"
puts failure_msg(data)
end
```
`failure_msg` is a function that prints out information about the failure. Your failure callback gets a hash with the following values:
{
:arg => the argument to the method,
:contract => the contract that got violated,
:class => the method's class,
:method => the method,
:contracts => the contract object
}
If your failure callback returns `false`, the method that the contract is guarding will not be called (the default behaviour).
## Providing your own custom validators
This can be done with `Contract.override_validator`:
```ruby
# Make contracts accept all RSpec doubles
Contract.override_validator(:class) do |contract|
lambda do |arg|
arg.is_a?(RSpec::Mocks::Double) ||
arg.is_a?(contract)
end
end
```
The block you provide should always return lambda accepting one argument - validated argument. Block itself accepts contract as an argument.
Possible validator overrides:
- `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts,
- `override_validator(Proc)` - e.g. `lambda { true }`,
- `override_validator(Array)` - e.g. `[C::Num, String]`,
- `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`,
- `override_validator(Range)` - e.g. `(1..10)`,
- `override_validator(Regexp)` - e.g. `/foo/`,
- `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`,
- `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`,
- `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
- `override_validator(:class)` - allows to override how class/module contract constants are handled,
- `override_validator(:default)` - otherwise, raw value contracts.
Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb).
## Contracts with attributes
You can include the `Contracts::Attrs` module in your class/module to get access to attribute utilities:
- `attr_reader_with_contract ..., `
- Wraps `attr_reader`, validates contract upon 'getting'
- `attr_writer_with_contract ..., `
- Wraps `attr_writer`, validates contract upon 'setting'
- `attr_accessor_with_contract ..., `
- Wraps `attr_accessor`, validates contract upon 'getting' or 'setting'
### Example
```ruby
class Person
include Contracts::Core
include Contracts::Attrs
attr_accessor_with_contract :name, String
end
person = Person.new
person.name = 'Jane'
person.name = 1.4 # This results in a contract error!
```
## Disabling contracts
If you want to disable contracts, set the `NO_CONTRACTS` environment variable. This will disable contracts and you won't have a performance hit. Pattern matching will still work if you disable contracts in this way! With NO_CONTRACTS only pattern-matching contracts are defined.
## Method overloading
You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
For example, here's a factorial function without method overloading:
```ruby
Contract C::Num => C::Num
def fact x
if x == 1
x
else
x * fact(x - 1)
end
end
```
Here it is again, re-written with method overloading:
```ruby
Contract 1 => 1
def fact x
x
end
Contract C::Num => C::Num
def fact x
x * fact(x - 1)
end
```
For an argument, each function will be tried in order. The first function that doesn't raise a `ContractError` will be used. So in this case, if x == 1, the first function will be used. For all other values, the second function will be used.
This allows you write methods more declaratively, rather than using conditional branching. This feature is not only useful for recursion; you can use it to keep parallel use cases separate:
```ruby
Contract lambda{|n| n < 12 } => Ticket
def get_ticket(age)
ChildTicket.new(age: age)
end
Contract lambda{|n| n >= 12 } => Ticket
def get_ticket(age)
AdultTicket.new(age: age)
end
```
Note that the second `get_ticket` contract above could have been simplified to:
```ruby
Contract C::Num => Ticket
```
This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts.
## Contracts in modules
Usage is the same as contracts in classes:
```ruby
module M
include Contracts::Core
Contract String => String
def self.parse
# do some hard parsing
end
end
```
## Invariants
Invariants are conditions on objects that should always hold. If after any method call on given object, any of the Invariants fails, then Invariant violation error will be generated.
**NOTE**: Only methods with contracts will be affected.
A simple example:
```ruby
class MyBirthday < Struct.new(:day, :month)
include Contracts::Core
include Contracts::Invariants
invariant(:day) { 1 <= day && day <= 31 }
invariant(:month) { 1 <= month && month <= 12 }
Contract C::None => Integer
def silly_next_day!
self.day += 1
end
end
birthday = MyBirthday.new(31, 12)
birthday.silly_next_day!
```
If you run it, last line will generate invariant violation:
```ruby
./invariant.rb:38:in `failure_callback': Invariant violation: (RuntimeError)
Expected: day condition to be true
Actual: false
Value guarded in: MyBirthday::silly_next_day!
At: main.rb:9
```
Which means, that after `#silly_next_day!` all checks specified in `invariant` statement will be verified, and if at least one fail, then invariant violation error will be raised.
## Using contracts within your own code
contracts.ruby is obviously designed to check method parameters and return values. But if you want to check whether some other data obeys a contract, you can use `Contract.valid?(value, contract)`. For instance:
```ruby
data = parse(user_input)
unless Contract.valid?(data, HashOf[String,Nat])
raise UserInputError.new(user_input)
end
```
## Auto-generate documentation using contracts
If you are generating documentation for your code with [YARD](http://yardoc.org/), check out [yard-contracts](https://github.com/sfcgeorge/yard-contracts). It will automatically annotate your functions with contracts information. Instead of documenting each parameter for a function yourself, you can just add a contract and yard-contracts will generate the documentation for you!
## Misc
Please submit any bugs [here](https://github.com/egonSchiele/contracts.ruby/issues) and I'll try to get them resolved ASAP!
See any mistakes in this tutorial? I try to make it bug-free, but they can creep in. [File an issue](https://github.com/egonSchiele/contracts.ruby/issues).
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
See the [wiki](https://github.com/egonSchiele/contracts.ruby/wiki) for more info.
Happy Coding!
contracts-0.17.2/spec/ 0000755 0000041 0000041 00000000000 14717736077 014606 5 ustar www-data www-data contracts-0.17.2/spec/attrs_spec.rb 0000644 0000041 0000041 00000006105 14717736077 017304 0 ustar www-data www-data RSpec.describe "Contracts:" do
describe "Attrs:" do
class Person
include Contracts::Core
include Contracts::Attrs
include Contracts::Builtin
def initialize(name)
@name_r = name
@name_w = name
@name_rw = name
@name_r_2 = name
@name_w_2 = name
@name_rw_2 = name
end
attr_reader_with_contract :name_r, :name_r_2, String
attr_writer_with_contract :name_w, :name_w_2, String
attr_accessor_with_contract :name_rw, :name_rw_2, String
end
context "attr_reader_with_contract" do
it "getting valid type" do
expect(Person.new("bob").name_r)
.to(eq("bob"))
end
it "getting invalid type" do
expect { Person.new(1.3).name_r }
.to(raise_error(ReturnContractError))
end
it "getting valid type for second val" do
expect(Person.new("bob").name_r_2)
.to(eq("bob"))
end
it "getting invalid type for second val" do
expect { Person.new(1.3).name_r_2 }
.to(raise_error(ReturnContractError))
end
it "setting" do
expect { Person.new("bob").name_r = "alice" }
.to(raise_error(NoMethodError))
end
end
context "attr_writer_with_contract" do
it "getting" do
expect { Person.new("bob").name_w }
.to(raise_error(NoMethodError))
end
it "setting valid type" do
expect(Person.new("bob").name_w = "alice")
.to(eq("alice"))
end
it "setting invalid type" do
expect { Person.new("bob").name_w = 1.2 }
.to(raise_error(ParamContractError))
end
it "setting valid type for second val" do
expect(Person.new("bob").name_w_2 = "alice")
.to(eq("alice"))
end
it "setting invalid type for second val" do
expect { Person.new("bob").name_w_2 = 1.2 }
.to(raise_error(ParamContractError))
end
end
context "attr_accessor_with_contract" do
it "getting valid type" do
expect(Person.new("bob").name_rw)
.to(eq("bob"))
end
it "getting invalid type" do
expect { Person.new(1.2).name_rw }
.to(raise_error(ReturnContractError))
end
it "setting valid type" do
expect(Person.new("bob").name_rw = "alice")
.to(eq("alice"))
end
it "setting invalid type" do
expect { Person.new("bob").name_rw = 1.2 }
.to(raise_error(ParamContractError))
end
it "getting valid type for second val" do
expect(Person.new("bob").name_rw_2)
.to(eq("bob"))
end
it "getting invalid type for second val" do
expect { Person.new(1.2).name_rw_2 }
.to(raise_error(ReturnContractError))
end
it "setting valid type for second val" do
expect(Person.new("bob").name_rw_2 = "alice")
.to(eq("alice"))
end
it "setting invalid type for second val" do
expect { Person.new("bob").name_rw_2 = 1.2 }
.to(raise_error(ParamContractError))
end
end
end
end
contracts-0.17.2/spec/spec_helper.rb 0000644 0000041 0000041 00000010703 14717736077 017425 0 ustar www-data www-data require "contracts"
require File.expand_path(File.join(__FILE__, "../support"))
require File.expand_path(File.join(__FILE__, "../fixtures/fixtures"))
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause this
# file to always be loaded, without a need to explicitly require it in any files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
config.pattern = "*.rb"
# Only load tests who's syntax is valid in the current Ruby
[1.9, 2.0, 2.1].each do |ver|
config.pattern << ",ruby_version_specific/*#{ver}.rb" if ruby_version >= ver
end
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Limits the available syntax to the non-monkey patched syntax that is recommended.
# For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
# config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
# Unable to use it now
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
# Callbacks
config.after :each do
::Contract.restore_failure_callback
end
end
contracts-0.17.2/spec/invariants_spec.rb 0000644 0000041 0000041 00000001143 14717736077 020322 0 ustar www-data www-data module Contracts
RSpec.describe Invariants do
def new_subject
MyBirthday.new(31, 12)
end
it "works when all invariants are holding" do
expect { new_subject.clever_next_day! }.not_to raise_error
expect { new_subject.clever_next_month! }.not_to raise_error
end
it "raises invariant violation error when any of invariants are not holding" do
expect { new_subject.silly_next_day! }.to raise_error(InvariantError, /day condition to be true/)
expect { new_subject.silly_next_month! }.to raise_error(InvariantError, /month condition to be true/)
end
end
end
contracts-0.17.2/spec/contracts_spec.rb 0000644 0000041 0000041 00000054132 14717736077 020152 0 ustar www-data www-data RSpec.describe "Contracts:" do
before :all do
@o = GenericExample.new
end
describe "basic" do
it "should fail for insufficient arguments" do
expect do
@o.hello
end.to raise_error ArgumentError
end
it "should fail for insufficient contracts" do
expect { @o.bad_double(2) }.to raise_error(ContractError)
end
end
describe "contracts for functions with no arguments" do
it "should work for functions with no args" do
expect { @o.no_args }.to_not raise_error
end
it "should still work for old-style contracts for functions with no args" do
expect { @o.old_style_no_args }.to_not raise_error
end
it "should not work for a function with a bad contract" do
expect do
Class.new(GenericExample) do
Contract Num, Num
def no_args_bad_contract
1
end
end
end.to raise_error NameError
end
end
describe "pattern matching" do
let(:string_with_hello) { "Hello, world" }
let(:string_without_hello) { "Hi, world" }
let(:expected_decorated_string) { "Hello, world!" }
subject { PatternMatchingExample.new }
it "should work as expected when there is no contract violation" do
expect(
subject.process_request(PatternMatchingExample::Success.new(string_with_hello))
).to eq(PatternMatchingExample::Success.new(expected_decorated_string))
expect(
subject.process_request(PatternMatchingExample::Failure.new)
).to be_a(PatternMatchingExample::Failure)
end
it "should not fall through to next pattern when there is a deep contract violation" do
expect(PatternMatchingExample::Failure).not_to receive(:is_a?)
expect do
subject.process_request(PatternMatchingExample::Success.new(string_without_hello))
end.to raise_error(ContractError)
end
it "should fail when the pattern-matched method's contract fails" do
expect do
subject.process_request("bad input")
end.to raise_error(ContractError)
end
it "should work for differing arities" do
expect(
subject.do_stuff(1, "abc", 2)
).to eq("bar")
expect(
subject.do_stuff(3, "def")
).to eq("foo")
end
it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match" do
expect do
subject.double(1)
end.to raise_error(ContractError)
end
it "should fail if multiple methods are defined with the same contract (for pattern-matching)" do
expect do
Class.new(GenericExample) do
Contract Contracts::Num => Contracts::Num
def same_param_contract x
x + 2
end
Contract Contracts::Num => String
def same_param_contract x
"sdf"
end
end
end.to raise_error(ContractError)
end
context "when failure_callback was overriden" do
before do
::Contract.override_failure_callback do |_data|
fail "contract violation"
end
end
it "calls a method when first pattern matches" do
expect(
subject.process_request(PatternMatchingExample::Success.new(string_with_hello))
).to eq(PatternMatchingExample::Success.new(expected_decorated_string))
end
it "falls through to 2nd pattern when first pattern does not match" do
expect(
subject.process_request(PatternMatchingExample::Failure.new)
).to be_a(PatternMatchingExample::Failure)
end
it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match, even with the failure callback" do
expect do
subject.double(1)
end.to raise_error(ContractError)
end
it "uses overriden failure_callback when pattern matching fails" do
expect do
subject.process_request("hello")
end.to raise_error(RuntimeError, /contract violation/)
end
end
end
describe "usage in singleton class" do
it "should work normally when there is no contract violation" do
expect(SingletonClassExample.hoge("hoge")).to eq("superhoge")
end
it "should fail with proper error when there is contract violation" do
expect do
SingletonClassExample.hoge(3)
end.to raise_error(ContractError, /Expected: String/)
end
describe "builtin contracts usage" do
it "allows to use builtin contracts without namespacing and redundant Contracts inclusion" do
expect do
SingletonClassExample.add("55", 5.6)
end.to raise_error(ContractError, /Expected: Num/)
end
end
end
describe "usage in the singleton class of a subclass" do
subject { SingletonInheritanceExampleSubclass }
it "should work with a valid contract on a singleton method" do
expect(subject.num(1)).to eq(1)
end
end
describe "no contracts feature" do
it "disables normal contract checks" do
object = NoContractsSimpleExample.new
expect { object.some_method(3) }.not_to raise_error
end
it "disables invariants" do
object = NoContractsInvariantsExample.new
object.day = 7
expect { object.next_day }.not_to raise_error
end
it "does not disable pattern matching" do
object = NoContractsPatternMatchingExample.new
expect(object.on_response(200, "hello")).to eq("hello!")
expect(object.on_response(404, "Not found")).to eq("error 404: Not found")
expect { object.on_response(nil, "junk response") }.to raise_error(ContractError)
end
end
describe "module usage" do
context "with instance methods" do
it "should check contract" do
expect { KlassWithModuleExample.new.plus(3, nil) }.to raise_error(ContractError)
end
end
context "with singleton methods" do
it "should check contract" do
expect { ModuleExample.hoge(nil) }.to raise_error(ContractError)
end
end
context "with singleton class methods" do
it "should check contract" do
expect { ModuleExample.eat(:food) }.to raise_error(ContractError)
end
end
end
describe "singleton methods self in inherited methods" do
it "should be a proper self" do
expect(SingletonInheritanceExampleSubclass.a_contracted_self).to eq(SingletonInheritanceExampleSubclass)
end
end
describe "anonymous classes" do
let(:klass) do
Class.new do
include Contracts::Core
Contract String => String
def greeting(name)
"hello, #{name}"
end
end
end
let(:obj) { klass.new }
it "does not fail when contract is satisfied" do
expect(obj.greeting("world")).to eq("hello, world")
end
it "fails with error when contract is violated" do
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
end
end
describe "anonymous modules" do
let(:mod) do
Module.new do
include Contracts::Core
Contract String => String
def greeting(name)
"hello, #{name}"
end
Contract String => String
def self.greeting(name)
"hello, #{name}"
end
end
end
let(:klass) do
Class.new.tap { |klass| klass.send(:include, mod) }
end
let(:obj) { klass.new }
it "does not fail when contract is satisfied" do
expect(obj.greeting("world")).to eq("hello, world")
end
it "fails with error when contract is violated" do
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
end
context "when called on module itself" do
let(:obj) { mod }
it "does not fail when contract is satisfied" do
expect(obj.greeting("world")).to eq("hello, world")
end
it "fails with error when contract is violated" do
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
end
end
end
describe "instance methods" do
it "should allow two classes to have the same method with different contracts" do
a = A.new
b = B.new
expect do
a.triple(5)
b.triple("a string")
end.to_not raise_error
end
end
describe "instance and class methods" do
it "should allow a class to have an instance method and a class method with the same name" do
a = A.new
expect do
a.instance_and_class_method(5)
A.instance_and_class_method("a string")
end.to_not raise_error
end
end
describe "class methods" do
it "should pass for correct input" do
expect { GenericExample.a_class_method(2) }.to_not raise_error
end
it "should fail for incorrect input" do
expect { GenericExample.a_class_method("bad") }.to raise_error(ContractError)
end
end
describe "classes" do
it "should pass for correct input" do
expect { @o.hello("calvin") }.to_not raise_error
end
it "should fail for incorrect input" do
expect { @o.hello(1) }.to raise_error(ContractError)
end
end
describe "classes with a valid? class method" do
it "should pass for correct input" do
expect { @o.double(2) }.to_not raise_error
end
it "should fail for incorrect input" do
expect { @o.double("bad") }.to raise_error(ContractError)
end
end
describe "Procs" do
it "should pass for correct input" do
expect { @o.square(2) }.to_not raise_error
end
it "should fail for incorrect input" do
expect { @o.square("bad") }.to raise_error(ContractError)
end
end
describe "Arrays" do
it "should pass for correct input" do
expect { @o.sum_three([1, 2, 3]) }.to_not raise_error
end
it "should fail for insufficient items" do
expect { @o.square([1, 2]) }.to raise_error(ContractError)
end
it "should fail for some incorrect elements" do
expect { @o.sum_three([1, 2, "three"]) }.to raise_error(ContractError)
end
end
describe "Hashes" do
it "should pass for exact correct input" do
expect { @o.person({ :name => "calvin", :age => 10 }) }.to_not raise_error
end
it "should pass even if some keys don't have contracts" do
expect { @o.person({ :name => "calvin", :age => 10, :foo => "bar" }) }.to_not raise_error
end
it "should fail if a key with a contract on it isn't provided" do
expect { @o.person({ :name => "calvin" }) }.to raise_error(ContractError)
end
it "should fail for incorrect input" do
expect { @o.person({ :name => 50, :age => 10 }) }.to raise_error(ContractError)
end
end
describe "blocks" do
it "should pass for correct input" do
expect do
@o.do_call do
2 + 2
end
end.to_not raise_error
end
it "should fail for incorrect input" do
expect do
@o.do_call(nil)
end.to raise_error(ContractError)
end
it "should handle properly lack of block when there are other arguments" do
expect do
@o.double_with_proc(4)
end.to raise_error(ContractError, /Actual: nil/)
end
it "should succeed for maybe proc with no proc" do
expect do
@o.maybe_call(5)
end.to_not raise_error
end
it "should succeed for maybe proc with proc" do
expect do
@o.maybe_call(5) do
2 + 2
end
end.to_not raise_error
end
it "should fail for maybe proc with invalid input" do
expect do
@o.maybe_call("bad")
end.to raise_error(ContractError)
end
describe "varargs are given with a maybe block" do
it "when a block is passed in, varargs should be correct" do
expect(@o.maybe_call(1, 2, 3) { 1 + 1 }).to eq([1, 2, 3])
end
it "when a block is NOT passed in, varargs should still be correct" do
expect(@o.maybe_call(1, 2, 3)).to eq([1, 2, 3])
end
end
end
describe "varargs" do
it "should pass for correct input" do
expect do
@o.sum(1, 2, 3)
end.to_not raise_error
end
it "should fail for incorrect input" do
expect do
@o.sum(1, 2, "bad")
end.to raise_error(ContractError)
end
it "should work with arg before splat" do
expect do
@o.arg_then_splat(3, "hello", "world")
end.to_not raise_error
end
end
describe "varargs with block" do
it "should pass for correct input" do
expect do
@o.with_partial_sums(1, 2, 3) do |partial_sum|
2 * partial_sum + 1
end
end.not_to raise_error
expect do
@o.with_partial_sums_contracted(1, 2, 3) do |partial_sum|
2 * partial_sum + 1
end
end.not_to raise_error
end
it "should fail for incorrect input" do
expect do
@o.with_partial_sums(1, 2, "bad") do |partial_sum|
2 * partial_sum + 1
end
end.to raise_error(ContractError, /Actual: "bad"/)
expect do
@o.with_partial_sums(1, 2, 3)
end.to raise_error(ContractError, /Actual: nil/)
expect do
@o.with_partial_sums(1, 2, 3, lambda { |x| x })
end.to raise_error(ContractError, /Actual: nil/)
end
context "when block has Func contract" do
it "should fail for incorrect input" do
expect do
@o.with_partial_sums_contracted(1, 2, "bad") { |partial_sum| 2 * partial_sum + 1 }
end.to raise_error(ContractError, /Actual: "bad"/)
expect do
@o.with_partial_sums_contracted(1, 2, 3)
end.to raise_error(ContractError, /Actual: nil/)
end
end
end
describe "contracts on functions" do
it "should pass for a function that passes the contract" do
expect { @o.map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error
end
it "should pass for a function that passes the contract as in tutorial" do
expect { @o.tutorial_map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error
end
it "should fail for a function that doesn't pass the contract" do
expect { @o.map([1, 2, 3], lambda { |_| "bad return value" }) }.to raise_error(ContractError)
end
it "should pass for a function that passes the contract with weak other args" do
expect { @o.map_plain(["hello", "joe"], lambda { |x| x.size }) }.to_not raise_error
end
it "should fail for a function that doesn't pass the contract with weak other args" do
expect { @o.map_plain(["hello", "joe"], lambda { |_| nil }) }.to raise_error(ContractError)
end
it "should fail for a returned function that doesn't pass the contract" do
expect { @o.lambda_with_wrong_return.call("hello") }.to raise_error(ContractError)
end
it "should fail for a returned function that receives the wrong argument type" do
expect { @o.lambda_with_correct_return.call(123) }.to raise_error(ContractError)
end
it "should not fail for a returned function that passes the contract" do
expect { @o.lambda_with_correct_return.call("hello") }.to_not raise_error
end
end
describe "default args to functions" do
it "should work for a function call that relies on default args" do
expect { @o.default_args }.to_not raise_error
expect { @o.default_args("foo") }.to raise_error(ContractError)
end
end
describe "classes" do
it "should not fail for an object that is the exact type as the contract" do
p = Parent.new
expect { @o.id_(p) }.to_not raise_error
end
it "should not fail for an object that is a subclass of the type in the contract" do
c = Child.new
expect { @o.id_(c) }.to_not raise_error
end
end
describe "failure callbacks" do
before :each do
::Contract.override_failure_callback do |_data|
should_call
end
end
context "when failure_callback returns false" do
let(:should_call) { false }
it "does not call a function for which the contract fails" do
res = @o.double("bad")
expect(res).to eq(nil)
end
end
context "when failure_callback returns true" do
let(:should_call) { true }
it "calls a function for which the contract fails" do
res = @o.double("bad")
expect(res).to eq("badbad")
end
end
end
describe "module contracts" do
it "passes for instance of class including module" do
expect(
ModuleContractExample.hello(ModuleContractExample::AClassWithModule.new)
).to eq(:world)
end
it "passes for instance of class including inherited module" do
expect(
ModuleContractExample.hello(ModuleContractExample::AClassWithInheritedModule.new)
).to eq(:world)
end
it "does not pass for instance of class not including module" do
expect do
ModuleContractExample.hello(ModuleContractExample::AClassWithoutModule.new)
end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/)
end
it "does not pass for instance of class including another module" do
expect do
ModuleContractExample.hello(ModuleContractExample::AClassWithAnotherModule.new)
end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/)
end
it "passes for instance of class including both modules" do
expect(
ModuleContractExample.hello(ModuleContractExample::AClassWithBothModules.new)
).to eq(:world)
end
end
describe "Contracts to_s formatting in expected" do
def not_s(match)
Regexp.new "[^\"\']#{match}[^\"\']"
end
def delim(match)
"(#{match})"
end
it "should not stringify native types" do
expect do
@o.constanty("bad", nil)
end.to raise_error(ContractError, not_s(123))
expect do
@o.constanty(123, "bad")
end.to raise_error(ContractError, not_s(nil))
end
it "should contain to_s representation within a Hash contract" do
expect do
@o.hash_complex_contracts({ :rigged => "bad" })
end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass"))
end
it "should contain to_s representation within a nested Hash contract" do
expect do
@o.nested_hash_complex_contracts({
:rigged => true,
:contents => {
:kind => 0,
:total => 42,
},
})
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
end
it "should contain to_s representation within an Array contract" do
expect do
@o.array_complex_contracts(["bad"])
end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass"))
end
it "should contain to_s representation within a nested Array contract" do
expect do
@o.nested_array_complex_contracts([true, [0]])
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
end
it "should wrap and pretty print for long param contracts" do
expect do
@o.long_array_param_contracts(true)
end.to(
raise_error(
ParamContractError,
/\[\(String or Symbol\),\n \(String or Symbol\),/
)
)
end
it "should wrap and pretty print for long return contracts" do
expect do
@o.long_array_return_contracts
end.to(
raise_error(
ReturnContractError,
/\[\(String or Symbol\),\n \(String or Symbol\),/
)
)
end
it "should not contain Contracts:: module prefix" do
expect do
@o.double("bad")
end.to raise_error(ContractError, /Expected: Num/)
end
it "should still show nils, not just blank space" do
expect do
@o.no_args("bad")
end.to raise_error(ContractError, /Expected: nil/)
end
it 'should show empty quotes as ""' do
expect do
@o.no_args("")
end.to raise_error(ContractError, /Actual: ""/)
end
it "should not use custom to_s if empty string" do
expect do
@o.using_empty_contract("bad")
end.to raise_error(ContractError, /Expected: EmptyCont/)
end
end
describe "functype" do
it "should correctly print out a instance method's type" do
expect(@o.functype(:double)).not_to eq("")
end
it "should correctly print out a class method's type" do
expect(A.functype(:a_class_method)).not_to eq("")
end
end
describe "private methods" do
it "should raise an error if you try to access a private method" do
expect { @o.a_private_method }.to raise_error(NoMethodError, /private/)
end
it "should raise an error if you try to access a private method" do
expect { @o.a_really_private_method }.to raise_error(NoMethodError, /private/)
end
end
describe "protected methods" do
it "should raise an error if you try to access a protected method" do
expect { @o.a_protected_method }.to raise_error(NoMethodError, /protected/)
end
it "should raise an error if you try to access a protected method" do
expect { @o.a_really_protected_method }.to raise_error(NoMethodError, /protected/)
end
end
describe "inherited methods" do
it "should apply the contract to an inherited method" do
c = Child.new
expect { c.double(2) }.to_not raise_error
expect { c.double("asd") }.to raise_error ParamContractError
end
end
describe "classes with extended modules" do
let(:klass) do
m = Module.new do
include Contracts::Core
end
Class.new do
include Contracts::Core
extend m
Contract String => nil
def foo(x)
end
end
end
it "is possible to define it" do
expect { klass }.not_to raise_error
end
it "works correctly with methods with passing contracts" do
expect { klass.new.foo("bar") }.not_to raise_error
end
it "works correctly with methods with passing contracts" do
expect { klass.new.foo(42) }.to raise_error(ContractError, /Expected: String/)
end
# See the discussion on this issue:
# https://github.com/egonSchiele/contracts.ruby/issues/229
it "should not fail with 'undefined method 'Contract''" do
expect do
class ModuleThenContracts
include ModuleWithContracts
include Contracts::Core
# fails on this line
Contract C::Num => C::Num
def double(x)
x * 2
end
end
end.to_not raise_error
end
end
end
contracts-0.17.2/spec/builtin_contracts_spec.rb 0000644 0000041 0000041 00000026625 14717736077 021706 0 ustar www-data www-data RSpec.describe "Contracts:" do
before :all do
@o = GenericExample.new
end
def fails(&some)
expect { some.call }.to raise_error(ContractError)
end
def passes(&some)
expect { some.call }.to_not raise_error
end
describe "DescendantOf:" do
it "should pass for Array" do
passes { @o.enumerable_descendant_test(Array) }
end
it "should pass for a hash" do
passes { @o.enumerable_descendant_test(Hash) }
end
it "should fail for a number class" do
fails { @o.enumerable_descendant_test(Integer) }
end
it "should fail for a non-class" do
fails { @o.enumerable_descendant_test(1) }
end
end
describe "Num:" do
it "should pass for Integers" do
passes { @o.double(2) }
end
it "should pass for Floats" do
passes { @o.double(2.2) }
end
it "should fail for nil and other data types" do
fails { @o.double(nil) }
fails { @o.double(:x) }
fails { @o.double("x") }
fails { @o.double(/x/) }
end
end
describe "Pos:" do
it "should pass for positive numbers" do
passes { @o.pos_test(1) }
passes { @o.pos_test(1.6) }
end
it "should fail for 0" do
fails { @o.pos_test(0) }
end
it "should fail for negative numbers" do
fails { @o.pos_test(-1) }
fails { @o.pos_test(-1.6) }
end
it "should fail for nil and other data types" do
fails { @o.pos_test(nil) }
fails { @o.pos_test(:x) }
fails { @o.pos_test("x") }
fails { @o.pos_test(/x/) }
end
end
describe "Neg:" do
it "should pass for negative numbers" do
passes { @o.neg_test(-1) }
passes { @o.neg_test(-1.6) }
end
it "should fail for 0" do
fails { @o.neg_test(0) }
end
it "should fail for positive numbers" do
fails { @o.neg_test(1) }
fails { @o.neg_test(1.6) }
end
it "should fail for nil and other data types" do
fails { @o.neg_test(nil) }
fails { @o.neg_test(:x) }
fails { @o.neg_test("x") }
fails { @o.neg_test(/x/) }
end
end
describe "Nat:" do
it "should pass for 0" do
passes { @o.nat_test(0) }
end
it "should pass for positive whole numbers" do
passes { @o.nat_test(1) }
end
it "should fail for positive non-whole numbers" do
fails { @o.nat_test(1.5) }
end
it "should fail for negative numbers" do
fails { @o.nat_test(-1) }
fails { @o.nat_test(-1.6) }
end
it "should fail for nil and other data types" do
fails { @o.nat_test(nil) }
fails { @o.nat_test(:x) }
fails { @o.nat_test("x") }
fails { @o.nat_test(/x/) }
end
end
describe "Any:" do
it "should pass for numbers" do
passes { @o.show(1) }
end
it "should pass for strings" do
passes { @o.show("bad") }
end
it "should pass for procs" do
passes { @o.show(lambda {}) }
end
it "should pass for nil" do
passes { @o.show(nil) }
end
end
describe "None:" do
it "should fail for numbers" do
fails { @o.fail_all(1) }
end
it "should fail for strings" do
fails { @o.fail_all("bad") }
end
it "should fail for procs" do
fails { @o.fail_all(lambda {}) }
end
it "should fail for nil" do
fails { @o.fail_all(nil) }
end
end
describe "Or:" do
it "should pass for nums" do
passes { @o.num_or_string(1) }
end
it "should pass for strings" do
passes { @o.num_or_string("bad") }
end
it "should fail for nil" do
fails { @o.num_or_string(nil) }
end
end
describe "Xor:" do
it "should pass for an object with a method :good" do
passes { @o.xor_test(A.new) }
end
it "should pass for an object with a method :bad" do
passes { @o.xor_test(B.new) }
end
it "should fail for an object with neither method" do
fails { @o.xor_test(1) }
end
it "should fail for an object with both methods :good and :bad" do
fails { @o.xor_test(F.new) }
end
end
describe "And:" do
it "should pass for an object of class A that has a method :good" do
passes { @o.and_test(A.new) }
end
it "should fail for an object that has a method :good but isn't of class A" do
fails { @o.and_test(F.new) }
end
end
describe "Enum:" do
it "should pass for an object that is included" do
passes { @o.enum_test(:a) }
end
it "should fail for an object that is not included" do
fails { @o.enum_test(:z) }
end
end
describe "RespondTo:" do
it "should pass for an object that responds to :good" do
passes { @o.responds_test(A.new) }
end
it "should fail for an object that doesn't respond to :good" do
fails { @o.responds_test(B.new) }
end
end
describe "Send:" do
it "should pass for an object that returns true for method :good" do
passes { @o.send_test(A.new) }
end
it "should fail for an object that returns false for method :good" do
fails { @o.send_test(F.new) }
end
end
describe "Exactly:" do
it "should pass for an object that is exactly a Parent" do
passes { @o.exactly_test(Parent.new) }
end
it "should fail for an object that inherits from Parent" do
fails { @o.exactly_test(Child.new) }
end
it "should fail for an object that is not related to Parent at all" do
fails { @o.exactly_test(A.new) }
end
end
describe "Eq:" do
it "should pass for a class" do
passes { @o.eq_class_test(Foo) }
end
it "should pass for a module" do
passes { @o.eq_module_test(Bar) }
end
it "should pass for other values" do
passes { @o.eq_value_test(Baz) }
end
it "should fail when not equal" do
fails { @o.eq_class_test(Bar) }
end
it "should fail when given instance of class" do
fails { @o.eq_class_test(Foo.new) }
end
end
describe "Not:" do
it "should pass for an argument that isn't nil" do
passes { @o.not_nil(1) }
end
it "should fail for nil" do
fails { @o.not_nil(nil) }
end
end
describe "ArrayOf:" do
it "should pass for an array of nums" do
passes { @o.product([1, 2, 3]) }
end
it "should fail for an array with one non-num" do
fails { @o.product([1, 2, 3, "bad"]) }
end
it "should fail for a non-array" do
fails { @o.product(1) }
end
end
describe "RangeOf:" do
require "date"
it "should pass for a range of nums" do
passes { @o.first_in_range_num(3..10) }
end
it "should pass for a range of dates" do
d1 = Date.today
d2 = d1 + 18
passes { @o.first_in_range_date(d1..d2) }
end
it "should fail for a non-range" do
fails { @o.first_in_range_num("foo") }
fails { @o.first_in_range_num(:foo) }
fails { @o.first_in_range_num(5) }
fails { @o.first_in_range_num(nil) }
end
it "should fail for a range with incorrect data type" do
fails { @o.first_in_range_num("a".."z") }
end
it "should fail for a badly-defined range" do
# For some reason, Ruby 2.0.0 allows (date .. number) as a range.
# Perhaps other Ruby versions do too.
# Note that (date .. string) gives ArgumentError.
# This test guards against ranges with inconsistent data types.
begin
d1 = Date.today
fails { @o.first_in_range_date(d1..10) }
fails { @o.first_in_range_num(d1..10) }
rescue ArgumentError
# If Ruby doesn't like the range, we ignore the test.
:nop
end
end
end
describe "SetOf:" do
it "should pass for a set of nums" do
passes { @o.product_from_set(Set.new([1, 2, 3])) }
end
it "should fail for an array with one non-num" do
fails { @o.product_from_set(Set.new([1, 2, 3, "bad"])) }
end
it "should fail for a non-array" do
fails { @o.product_from_set(1) }
end
end
describe "Bool:" do
it "should pass for an argument that is a boolean" do
passes { @o.bool_test(true) }
passes { @o.bool_test(false) }
end
it "should fail for nil" do
fails { @o.bool_test(nil) }
end
end
describe "Maybe:" do
it "should pass for nums" do
expect(@o.maybe_double(1)).to eq(2)
end
it "should pass for nils" do
expect(@o.maybe_double(nil)).to eq(nil)
end
it "should fail for strings" do
fails { @o.maybe_double("foo") }
end
end
describe "KeywordArgs:" do
it "should pass for exact correct input" do
passes { @o.person_keywordargs(:name => "calvin", :age => 10) }
end
it "should fail if some keys don't have contracts" do
fails { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") }
end
it "should fail if a key with a contract on it isn't provided" do
fails { @o.person_keywordargs(:name => "calvin") }
end
it "should fail for incorrect input" do
fails { @o.person_keywordargs(:name => 50, :age => 10) }
fails { @o.hash_keywordargs(:hash => nil) }
fails { @o.hash_keywordargs(:hash => 1) }
end
end
describe "Optional:" do
it "can't be used outside of KeywordArgs" do
expect do
BareOptionalContractUsed.new.something(3, 5)
end.to raise_error(ArgumentError, Contracts::Optional::UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH)
end
end
describe "HashOf:" do
it "doesn't allow to specify multiple key-value pairs with pretty syntax" do
expect do
Class.new do
include Contracts::Core
Contract Contracts::HashOf[Symbol => String, Contracts::Num => Contracts::Num] => nil
def something(hash)
# ...
end
end
end.to raise_error(ArgumentError, "You should provide only one key-value pair to HashOf contract")
end
context "given a fulfilled contract" do
it { expect(@o.gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) }
it { expect(@o.pretty_gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) }
end
context "given an unfulfilled contract" do
it { fails { @o.gives_max_value({ :panda => "1", :bamboo => "2" }) } }
it { fails { @o.gives_max_value(nil) } }
it { fails { @o.gives_max_value(1) } }
it { fails { @o.pretty_gives_max_value({ :panda => "1", :bamboo => "2" }) } }
end
describe "#to_s" do
context "given Symbol => String" do
it { expect(Contracts::HashOf[Symbol, String].to_s).to eq("Hash") }
end
context "given String => Num" do
it { expect(Contracts::HashOf[String, Contracts::Num].to_s).to eq("Hash") }
end
end
end
describe "StrictHash:" do
context "when given an exact correct input" do
it "does not raise an error" do
passes { @o.strict_person({ :name => "calvin", :age => 10 }) }
end
end
context "when given an input with correct keys but wrong types" do
it "raises an error" do
fails { @o.strict_person({ :name => "calvin", :age => "10" }) }
end
end
context "when given an input with missing keys" do
it "raises an error" do
fails { @o.strict_person({ :name => "calvin" }) }
end
end
context "when given an input with extra keys" do
it "raises an error" do
fails { @o.strict_person({ :name => "calvin", :age => 10, :soft => true }) }
end
end
context "when given not a hash" do
it "raises an error" do
fails { @o.strict_person(1337) }
end
end
end
end
contracts-0.17.2/spec/fixtures/ 0000755 0000041 0000041 00000000000 14717736077 016457 5 ustar www-data www-data contracts-0.17.2/spec/fixtures/fixtures.rb 0000644 0000041 0000041 00000027745 14717736077 020674 0 ustar www-data www-data require "date"
C = Contracts
class A
include Contracts::Core
Contract C::Num => C::Num
def self.a_class_method x
x + 1
end
def good
true
end
Contract C::Num => C::Num
def triple x
x * 3
end
Contract C::Num => C::Num
def instance_and_class_method x
x * 2
end
Contract String => String
def self.instance_and_class_method x
x * 2
end
end
class B
include Contracts::Core
def bad
false
end
Contract String => String
def triple x
x * 3
end
end
class F
include Contracts::Core
def good
false
end
def bad
true
end
end
class EmptyCont
def self.to_s
""
end
end
class GenericExample
include Contracts::Core
Contract C::Num => C::Num
def self.a_class_method x
x + 1
end
Contract C::Num => nil
def bad_double(x)
x * 2
end
Contract C::Num => C::Num
def double(x)
x * 2
end
Contract 123, nil => nil
def constanty(num, nul)
0
end
Contract String => nil
def hello(name)
end
Contract lambda { |x| x.is_a? Numeric } => C::Num
def square(x)
x ** 2
end
Contract [C::Num, C::Num, C::Num] => C::Num
def sum_three(vals)
vals.inject(0) do |acc, x|
acc + x
end
end
Contract ({ :name => String, :age => Integer }) => nil
def person(data)
end
Contract C::StrictHash[{ :name => String, :age => Integer }] => nil
def strict_person(data)
end
Contract ({ :rigged => C::Or[TrueClass, FalseClass] }) => nil
def hash_complex_contracts(data)
end
Contract ({ :rigged => C::Bool,
:contents => { :kind => C::Or[String, Symbol],
:total => C::Num }
}) => nil
def nested_hash_complex_contracts(data)
end
Contract C::KeywordArgs[:name => String, :age => Integer] => nil
def person_keywordargs(name: "name", age: 10)
end
Contract C::KeywordArgs[:hash => C::HashOf[Symbol, C::Num]] => nil
def hash_keywordargs(hash:)
end
Contract (/foo/) => nil
def should_contain_foo(s)
end
Contract ({ :host => /foo/ }) => nil
def hash_containing_foo(s)
end
Contract C::ArrayOf[/foo/] => nil
def array_containing_foo(s)
end
Contract [C::Or[TrueClass, FalseClass]] => nil
def array_complex_contracts(data)
end
Contract [C::Bool, [C::Or[String, Symbol]]] => nil
def nested_array_complex_contracts(data)
end
Contract [
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol]
] => nil
def long_array_param_contracts(data)
end
Contract C::None => [
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol],
C::Or[String, Symbol]
]
def long_array_return_contracts
end
Contract Proc => C::Any
def do_call(&block)
block.call
end
Contract C::Args[C::Num], C::Maybe[Proc] => C::Any
def maybe_call(*vals, &block)
block.call if block
vals
end
Contract C::Args[C::Num] => C::Num
def sum(*vals)
vals.inject(0) do |acc, val|
acc + val
end
end
Contract C::Args[C::Num], Proc => C::Num
def with_partial_sums(*vals, &blk)
sum = vals.inject(0) do |acc, val|
blk[acc]
acc + val
end
blk[sum]
end
Contract C::Args[C::Num], C::Func[C::Num => C::Num] => C::Num
def with_partial_sums_contracted(*vals, &blk)
sum = vals.inject(0) do |acc, val|
blk[acc]
acc + val
end
blk[sum]
end
# Important to use different arg types or it falsely passes
Contract C::Num, C::Args[String] => C::ArrayOf[String]
def arg_then_splat(n, *vals)
vals.map { |v| v * n }
end
Contract C::Num, Proc => nil
def double_with_proc(x, &blk)
blk.call(x * 2)
nil
end
Contract C::Pos => nil
def pos_test(x)
end
Contract C::Neg => nil
def neg_test(x)
end
Contract C::Nat => nil
def nat_test(x)
end
Contract C::Any => nil
def show(x)
end
Contract C::None => nil
def fail_all(x)
end
Contract C::Or[C::Num, String] => nil
def num_or_string(x)
end
Contract C::Xor[C::RespondTo[:good], C::RespondTo[:bad]] => nil
def xor_test(x)
end
Contract C::And[A, C::RespondTo[:good]] => nil
def and_test(x)
end
Contract C::Enum[:a, :b, :c] => nil
def enum_test(x)
end
Contract C::RespondTo[:good] => nil
def responds_test(x)
end
Contract C::Send[:good] => nil
def send_test(x)
end
Contract C::Not[nil] => nil
def not_nil(x)
end
Contract C::ArrayOf[C::Num] => C::Num
def product(vals)
vals.inject(1) do |acc, x|
acc * x
end
end
Contract C::SetOf[C::Num] => C::Num
def product_from_set(vals)
vals.inject(1) do |acc, x|
acc * x
end
end
Contract C::RangeOf[C::Num] => C::Num
def first_in_range_num(r)
r.first
end
Contract C::RangeOf[Date] => Date
def first_in_range_date(r)
r.first
end
Contract C::DescendantOf[Enumerable] => nil
def enumerable_descendant_test(enum)
end
Contract C::Bool => nil
def bool_test(x)
end
Contract C::Num
def no_args
1
end
# This function has a contract which says it has no args,
# but the function does have args.
Contract nil => C::Num
def old_style_no_args
2
end
Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
def map(arr, func)
ret = []
arr.each do |x|
ret << func[x]
end
ret
end
Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
def tutorial_map(arr, func)
ret = []
arr.each do |x|
ret << func[x]
end
ret
end
# Need to test Func with weak contracts for other args
# and changing type from input to output otherwise it falsely passes!
Contract Array, C::Func[String => C::Num] => Array
def map_plain(arr, func)
arr.map do |x|
func[x]
end
end
Contract C::None => C::Func[String => C::Num]
def lambda_with_wrong_return
lambda { |x| x }
end
Contract C::None => C::Func[String => C::Num]
def lambda_with_correct_return
lambda { |x| x.length }
end
Contract C::Num => C::Num
def default_args(x = 1)
2
end
Contract C::Maybe[C::Num] => C::Maybe[C::Num]
def maybe_double x
if x.nil?
nil
else
x * 2
end
end
Contract C::HashOf[Symbol, C::Num] => C::Num
def gives_max_value(hash)
hash.values.max
end
Contract C::HashOf[Symbol => C::Num] => C::Num
def pretty_gives_max_value(hash)
hash.values.max
end
Contract EmptyCont => C::Any
def using_empty_contract(a)
a
end
Contract (1..10) => nil
def method_with_range_contract(x)
end
Contract String
def a_private_method
"works"
end
private :a_private_method
Contract String
def a_protected_method
"works"
end
protected :a_protected_method
private
Contract String
def a_really_private_method
"works for sure"
end
protected
Contract String
def a_really_protected_method
"works for sure"
end
end
# for testing inheritance
class Parent
include Contracts::Core
Contract C::Num => C::Num
def double x
x * 2
end
end
class Child < Parent
end
class GenericExample
Contract Parent => Parent
def id_ a
a
end
Contract C::Exactly[Parent] => nil
def exactly_test(x)
end
end
# for testing equality
class Foo
end
module Bar
end
Baz = 1
class GenericExample
Contract C::Eq[Foo] => C::Any
def eq_class_test(x)
end
Contract C::Eq[Bar] => C::Any
def eq_module_test(x)
end
Contract C::Eq[Baz] => C::Any
def eq_value_test(x)
end
end
# pattern matching example with possible deep contract violation
class PatternMatchingExample
include Contracts::Core
class Success
attr_accessor :request
def initialize request
@request = request
end
def ==(other)
request == other.request
end
end
class Failure
end
Response = C::Or[Success, Failure]
class StringWithHello
def self.valid?(string)
string.is_a?(String) && !!string.match(/hello/i)
end
end
Contract Success => Response
def process_request(status)
Success.new(decorated_request(status.request))
end
Contract Failure => Response
def process_request(status)
Failure.new
end
Contract StringWithHello => String
def decorated_request(request)
request + "!"
end
Contract C::Num, String => String
def do_stuff(number, string)
"foo"
end
Contract C::Num, String, C::Num => String
def do_stuff(number, string, other_number)
"bar"
end
Contract C::Num => C::Num
def double x
"bad"
end
Contract String => String
def double x
x * 2
end
end
# invariant example (silliest implementation ever)
class MyBirthday
include Contracts::Core
include Contracts::Invariants
invariant(:day) { 1 <= day && day <= 31 }
invariant(:month) { 1 <= month && month <= 12 }
attr_accessor :day, :month
def initialize(day, month)
@day = day
@month = month
end
Contract C::None => Integer
def silly_next_day!
self.day += 1
end
Contract C::None => Integer
def silly_next_month!
self.month += 1
end
Contract C::None => Integer
def clever_next_day!
return clever_next_month! if day == 31
self.day += 1
end
Contract C::None => Integer
def clever_next_month!
return next_year! if month == 12
self.month += 1
self.day = 1
end
Contract C::None => Integer
def next_year!
self.month = 1
self.day = 1
end
end
class SingletonClassExample
# This turned out to be required line here to make singleton classes
# work properly under all platforms. Not sure if it worth trying to
# do something with it.
include Contracts::Core
class << self
Contract String => String
def hoge(str)
"super#{str}"
end
Contract C::Num, C::Num => C::Num
def add(a, b)
a + b
end
end
end
with_enabled_no_contracts do
class NoContractsSimpleExample
include Contracts::Core
Contract String => nil
def some_method(x)
nil
end
end
class NoContractsInvariantsExample
include Contracts::Core
include Contracts::Invariants
attr_accessor :day
invariant(:day_rule) { 1 <= day && day <= 7 }
Contract C::None => nil
def next_day
self.day += 1
end
end
class NoContractsPatternMatchingExample
include Contracts::Core
Contract 200, String => String
def on_response(status, body)
body + "!"
end
Contract Integer, String => String
def on_response(status, body)
"error #{status}: #{body}"
end
end
end
module ModuleExample
include Contracts::Core
Contract C::Num, C::Num => C::Num
def plus(a, b)
a + b
end
Contract String => String
def self.hoge(str)
"super#{str}"
end
class << self
Contract String => nil
def eat(food)
# yummy
nil
end
end
end
class KlassWithModuleExample
include ModuleExample
end
class SingletonInheritanceExample
include Contracts::Core
Contract C::Any => C::Any
def self.a_contracted_self
self
end
end
class SingletonInheritanceExampleSubclass < SingletonInheritanceExample
class << self
Contract Integer => Integer
def num(int)
int
end
end
end
class BareOptionalContractUsed
include Contracts::Core
Contract C::Num, C::Optional[C::Num] => nil
def something(a, b)
nil
end
end
module ModuleContractExample
include Contracts::Core
module AModule
end
module AnotherModule
end
module InheritedModule
include AModule
end
class AClassWithModule
include AModule
end
class AClassWithoutModule
end
class AClassWithAnotherModule
include AnotherModule
end
class AClassWithInheritedModule
include InheritedModule
end
class AClassWithBothModules
include AModule
include AnotherModule
end
Contract AModule => Symbol
def self.hello(thing)
:world
end
end
module ModuleWithContracts
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
include Contracts::Core
Contract C::None => String
def foo
"bar"
end
end
end
contracts-0.17.2/spec/validators_spec.rb 0000644 0000041 0000041 00000002437 14717736077 020323 0 ustar www-data www-data require "spec_helper"
RSpec.describe "Contract validators" do
subject(:o) { GenericExample.new }
describe "Range" do
it "passes when value is in range" do
expect do
o.method_with_range_contract(5)
end.not_to raise_error
end
it "fails when value is not in range" do
expect do
o.method_with_range_contract(300)
end.to raise_error(ContractError, /Expected: 1\.\.10/)
end
it "fails when value is incorrect" do
expect do
o.method_with_range_contract("hello world")
end.to raise_error(ContractError, /Expected: 1\.\.10.*Actual: "hello world"/m)
end
end
describe "Regexp" do
it "should pass for a matching string" do
expect { o.should_contain_foo("containing foo") }.to_not raise_error
end
it "should fail for a non-matching string" do
expect { o.should_contain_foo("that's not F00") }.to raise_error(ContractError)
end
describe "within a hash" do
it "should pass for a matching string" do
expect { o.hash_containing_foo({ :host => "foo.example.org" }) }.to_not raise_error
end
end
describe "within an array" do
it "should pass for a matching string" do
expect { o.array_containing_foo(["foo"]) }.to_not raise_error
end
end
end
end
contracts-0.17.2/spec/methods_spec.rb 0000644 0000041 0000041 00000002504 14717736077 017611 0 ustar www-data www-data RSpec.describe "Contracts:" do
describe "method called with blocks" do
module FuncTest
include Contracts::Core
include Contracts::Builtin
Contract Func[Num=>Num] => nil
def foo(&blk)
_ = blk.call(2)
nil
end
Contract Num, Func[Num=>Num] => nil
def foo2(a, &blk)
_ = blk.call(2)
nil
end
Contract Func[Num=>Num] => nil
def bar(blk)
_ = blk.call(2)
nil
end
Contract Num, Func[Num=>Num] => nil
def bar2(a, blk)
_ = blk.call(2)
nil
end
end
def obj
Object.new.tap do |o|
o.extend(FuncTest)
end
end
it "should enforce return value inside block with no other parameter" do
expect { obj.foo(&:to_s) }.to raise_error ReturnContractError
end
it "should enforce return value inside block with other parameter" do
expect { obj.foo2(2) { |x| x.to_s } }.to raise_error ReturnContractError
end
it "should enforce return value inside lambda with no other parameter" do
expect { obj.bar lambda { |x| x.to_s } }.to raise_error ReturnContractError
end
it "should enforce return value inside lambda with other parameter" do
expect { obj.bar2(2, lambda { |x| x.to_s }) }.to raise_error ReturnContractError
end
end
end
contracts-0.17.2/spec/override_validators_spec.rb 0000644 0000041 0000041 00000007533 14717736077 022224 0 ustar www-data www-data RSpec.describe Contract do
describe ".override_validator" do
around do |example|
Contract.reset_validators
example.run
Contract.reset_validators
end
it "allows to override simple validators" do
Contract.override_validator(Hash) do |contract|
lambda do |arg|
return false unless arg.is_a?(Hash)
# Any hash in my system should have :it_is_a_hash key!
return false unless arg.key?(:it_is_a_hash)
contract.keys.all? do |k|
Contract.valid?(arg[k], contract[k])
end
end
end
klass = Class.new do
include Contracts::Core
Contract ({ :a => Contracts::Num, :b => String }) => nil
def something(opts)
nil
end
end
obj = klass.new
expect do
obj.something({ :a => 35, :b => "hello" })
end.to raise_error(ContractError)
expect do
obj.something({
:a => 35,
:b => "hello",
:it_is_a_hash => true
})
end.not_to raise_error
end
it "allows to override valid contract" do
Contract.override_validator(:valid) do |contract|
if contract.respond_to?(:in_valid_state?)
lambda do |arg|
contract.in_valid_state? && contract.valid?(arg)
end
else
lambda { |arg| contract.valid?(arg) }
end
end
stateful_contract = Class.new(Contracts::CallableClass) do
def initialize(contract)
@contract = contract
@state = 0
end
def in_valid_state?
@state < 3
end
def valid?(arg)
@state += 1
Contract.valid?(arg, @contract)
end
end
klass = Class.new do
include Contracts::Core
Contract stateful_contract[Contracts::Num] => Contracts::Num
def only_three_times(x)
x * x
end
end
obj = klass.new
expect(obj.only_three_times(3)).to eq(9)
expect(obj.only_three_times(3)).to eq(9)
expect(obj.only_three_times(3)).to eq(9)
expect do
obj.only_three_times(3)
end.to raise_error(ContractError)
expect do
obj.only_three_times(3)
end.to raise_error(ContractError)
end
it "allows to override class validator" do
# Make contracts accept all rspec doubles
Contract.override_validator(:class) do |contract|
lambda do |arg|
arg.is_a?(RSpec::Mocks::Double) ||
arg.is_a?(contract)
end
end
klass = Class.new do
include Contracts::Core
Contract String => String
def greet(name)
"hello, #{name}"
end
end
obj = klass.new
expect(obj.greet("world")).to eq("hello, world")
expect do
obj.greet(4)
end.to raise_error(ContractError)
expect(obj.greet(double("name"))).to match(
/hello, #\[.*Double.*"name"\]/
)
end
it "allows to override default validator" do
spy = double("spy")
Contract.override_validator(:default) do |contract|
lambda do |arg|
spy.log("#{arg} == #{contract}")
arg == contract
end
end
klass = Class.new do
include Contracts::Core
Contract 1, Contracts::Num => Contracts::Num
def gcd(_, b)
b
end
Contract Contracts::Num, Contracts::Num => Contracts::Num
def gcd(a, b)
gcd(b % a, a)
end
end
obj = klass.new
expect(spy).to receive(:log).with("8 == 1").ordered.once
expect(spy).to receive(:log).with("5 == 1").ordered.once
expect(spy).to receive(:log).with("3 == 1").ordered.once
expect(spy).to receive(:log).with("2 == 1").ordered.once
expect(spy).to receive(:log).with("1 == 1").ordered.once
obj.gcd(8, 5)
end
end
end
contracts-0.17.2/spec/ruby_version_specific/ 0000755 0000041 0000041 00000000000 14717736077 021201 5 ustar www-data www-data contracts-0.17.2/spec/ruby_version_specific/contracts_spec_2.0.rb 0000644 0000041 0000041 00000003350 14717736077 025120 0 ustar www-data www-data class GenericExample
Contract C::Args[String], C::KeywordArgs[ repeat: C::Maybe[C::Num] ] => C::ArrayOf[String]
def splat_then_optional_named(*vals, repeat: 2)
vals.map { |v| v * repeat }
end
Contract C::KeywordArgs[ foo: C::Nat ] => nil
def nat_test_with_kwarg(foo: 10)
end
Contract C::KeywordArgs[name: C::Optional[String]], C::Func[String => String] => String
def keyword_args_hello(name: "Adit", &block)
"Hey, #{yield name}!"
end
end
RSpec.describe "Contracts:" do
before :all do
@o = GenericExample.new
end
describe "Optional named arguments" do
it "should work with optional named argument unfilled after splat" do
expect { @o.splat_then_optional_named("hello", "world") }.to_not raise_error
end
it "should work with optional named argument filled after splat" do
expect { @o.splat_then_optional_named("hello", "world", repeat: 3) }.to_not raise_error
end
end
describe "Nat:" do
it "should pass for keyword args with correct arg given" do
expect { @o.nat_test_with_kwarg(foo: 10) }.to_not raise_error
end
it "should fail with a ContractError for wrong keyword args input" do
expect { @o.nat_test_with_kwarg(foo: -10) }.to raise_error(ContractError)
end
it "should fail with a ContractError for no input" do
expect { @o.nat_test_with_kwarg }.to raise_error(ContractError)
end
end
describe "keyword args with defaults, with a block" do
it "should work when both keyword args and a block is given" do
expect(@o.keyword_args_hello(name: "maggie", &:upcase)).to eq("Hey, MAGGIE!")
end
it "should work even when keyword args aren't given" do
expect(@o.keyword_args_hello(&:upcase)).to eq("Hey, ADIT!")
end
end
end
contracts-0.17.2/spec/ruby_version_specific/contracts_spec_1.9.rb 0000644 0000041 0000041 00000001034 14717736077 025125 0 ustar www-data www-data class GenericExample
Contract C::Args[String], C::Num => C::ArrayOf[String]
def splat_then_arg(*vals, n)
vals.map { |v| v * n }
end
if ruby_version <= 1.9
Contract ({:foo => C::Nat}) => nil
def nat_test_with_kwarg(a_hash)
end
end
end
RSpec.describe "Contracts:" do
before :all do
@o = GenericExample.new
end
describe "Splat not last (or penultimate to block)" do
it "should work with arg after splat" do
expect { @o.splat_then_arg("hello", "world", 3) }.to_not raise_error
end
end
end
contracts-0.17.2/spec/ruby_version_specific/contracts_spec_2.1.rb 0000644 0000041 0000041 00000004061 14717736077 025121 0 ustar www-data www-data class GenericExample
Contract String, C::Bool, C::Args[Symbol], Float, C::KeywordArgs[e: Range, f: C::Optional[C::Num], g: Symbol], Proc =>
[Proc, Hash, C::Maybe[C::Num], Range, Float, C::ArrayOf[Symbol], C::Bool, String]
def complicated(a, b = true, *c, d, e:, f:2, **g, &h)
h.call [h, g, f, e, d, c, b, a]
end
end
RSpec.describe "Contracts:" do
before :all do
@o = GenericExample.new
end
describe "Required named arguments" do
describe "really complicated method signature" do
it "should work with default named args used" do
expect do
@o.complicated("a", false, :b, 2.0, e: (1..5), g: :d) { |x| x }
end.to_not raise_error
end
it "should work with all args filled manually, with extra splat and hash" do
expect do
@o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: 8.3, g: :d) do |x|
x
end
end.to_not raise_error
end
it "should fail when the return is invalid" do
expect do
@o.complicated("a", true, :b, 2.0, e: (1..5)) { |_x| "bad" }
end.to raise_error(ContractError)
end
it "should fail when args are invalid" do
expect do
@o.complicated("a", "bad", :b, 2.0, e: (1..5)) { |x| x }
end.to raise_error(ContractError)
end
it "should fail when splat is invalid" do
expect do
@o.complicated("a", true, "bad", 2.0, e: (1..5)) { |x| x }
end.to raise_error(ContractError)
end
it "should fail when named argument is invalid" do
expect do
@o.complicated("a", true, :b, 2.0, e: "bad") { |x| x }
end.to raise_error(ContractError)
end
it "should fail when passed nil to an optional argument which contract shouldn't accept nil" do
expect do
@o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: nil, g: :d) do |x|
x
end
end.to raise_error(ContractError, /Expected: \(KeywordArgs\[{:e=>Range, :f=>Optional\[Num\], :g=>Symbol}\]\)/)
end
end
end
end
contracts-0.17.2/spec/support_spec.rb 0000644 0000041 0000041 00000001261 14717736077 017661 0 ustar www-data www-data module Contracts
RSpec.describe Support do
describe "eigenclass?" do
it "is falsey for non-singleton classes" do
expect(Contracts::Support.eigenclass? String).to be_falsey
end
it "is truthy for singleton classes" do
singleton_class = String.instance_exec { class << self; self; end }
expect(Contracts::Support.eigenclass? singleton_class).to be_truthy
end
end
describe "eigenclass_of" do
it "returns the eigenclass of a given object" do
singleton_class = String.instance_exec { class << self; self; end }
expect(Contracts::Support.eigenclass_of String).to eq singleton_class
end
end
end
end
contracts-0.17.2/spec/support.rb 0000644 0000041 0000041 00000000322 14717736077 016644 0 ustar www-data www-data def with_enabled_no_contracts
no_contracts = ENV["NO_CONTRACTS"]
ENV["NO_CONTRACTS"] = "true"
yield
ENV["NO_CONTRACTS"] = no_contracts
end
def ruby_version
RUBY_VERSION.match(/\d+\.\d+/)[0].to_f
end
contracts-0.17.2/spec/module_spec.rb 0000644 0000041 0000041 00000000573 14717736077 017437 0 ustar www-data www-data module Mod
include Contracts::Core
Contract C::Num => C::Num
def self.a_module_method a
a + 1
end
end
RSpec.describe "module methods" do
it "should pass for correct input" do
expect { Mod.a_module_method(2) }.to_not raise_error
end
it "should fail for incorrect input" do
expect { Mod.a_module_method("bad") }.to raise_error(ContractError)
end
end
contracts-0.17.2/.rspec 0000644 0000041 0000041 00000000036 14717736077 014770 0 ustar www-data www-data --color
--require spec_helper
contracts-0.17.2/Rakefile 0000644 0000041 0000041 00000000321 14717736077 015315 0 ustar www-data www-data # frozen_string_literal: true
task :default => [:spec]
task :add_tag do
`git tag -a v#{Contracts::VERSION} -m 'v#{Contracts::VERSION}'`
end
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
contracts-0.17.2/features/ 0000755 0000041 0000041 00000000000 14717736077 015472 5 ustar www-data www-data contracts-0.17.2/features/builtin_contracts/ 0000755 0000041 0000041 00000000000 14717736077 021220 5 ustar www-data www-data contracts-0.17.2/features/builtin_contracts/nat.feature 0000644 0000041 0000041 00000005565 14717736077 023372 0 ustar www-data www-data Feature: Nat
Checks that an argument is a natural number.
```ruby
Contract C::Nat => C::Nat
```
Background:
Given a file named "nat_usage.rb" with:
"""ruby
require "contracts"
C = Contracts
class Natural
include Contracts::Core
Contract C::Nat => C::Nat
def prev(number)
number - 1
end
end
"""
Scenario: Accepts positive integers
Given a file named "accepts_positive_integers.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev(7)
"""
When I run `ruby accepts_positive_integers.rb`
Then output should contain:
"""
6
"""
Scenario: Accepts zero
Given a file named "accepts_zero.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev(1)
"""
When I run `ruby accepts_zero.rb`
Then output should contain:
"""
0
"""
Scenario: Rejects negative integers
Given a file named "rejects_negative_integers.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev(-1)
"""
When I run `ruby rejects_negative_integers.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: Nat,
Actual: -1
Value guarded in: Natural::prev
With Contract: Nat => Nat
"""
And output should contain "nat_usage.rb:8"
Scenario: Rejects negative integers as a return value
Given a file named "rejects_negative_integers.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev(0)
"""
When I run `ruby rejects_negative_integers.rb`
Then output should contain:
"""
: Contract violation for return value: (ReturnContractError)
Expected: Nat,
Actual: -1
Value guarded in: Natural::prev
With Contract: Nat => Nat
"""
And output should contain "nat_usage.rb:8"
Scenario: Rejects floats
Given a file named "rejects_floats.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev(3.43)
"""
When I run `ruby rejects_floats.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: Nat,
Actual: 3.43
Value guarded in: Natural::prev
With Contract: Nat => Nat
"""
And output should contain "nat_usage.rb:8"
Scenario: Rejects other values
Given a file named "rejects_others.rb" with:
"""ruby
require "./nat_usage"
puts Natural.new.prev("foo")
"""
When I run `ruby rejects_others.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: Nat,
Actual: "foo"
Value guarded in: Natural::prev
With Contract: Nat => Nat
"""
And output should contain "nat_usage.rb:8"
contracts-0.17.2/features/builtin_contracts/num.feature 0000644 0000041 0000041 00000002547 14717736077 023404 0 ustar www-data www-data Feature: Num
Checks that an argument is `Numeric`.
```ruby
Contract C::Num => C::Num
```
Background:
Given a file named "num_usage.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Num => C::Num
def increase(number)
number + 1
end
end
"""
Scenario: Accepts integers
Given a file named "accepts_integers.rb" with:
"""ruby
require "./num_usage"
puts Example.new.increase(7)
"""
When I run `ruby accepts_integers.rb`
Then output should contain:
"""
8
"""
Scenario: Accepts floats
Given a file named "accepts_floats.rb" with:
"""ruby
require "./num_usage"
puts Example.new.increase(7.5)
"""
When I run `ruby accepts_floats.rb`
Then output should contain:
"""
8.5
"""
Scenario: Rejects other values
Given a file named "rejects_others.rb" with:
"""ruby
require "./num_usage"
puts Example.new.increase("foo")
"""
When I run `ruby rejects_others.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: Num,
Actual: "foo"
Value guarded in: Example::increase
With Contract: Num => Num
"""
And output should contain "num_usage.rb:8"
contracts-0.17.2/features/builtin_contracts/or.feature 0000644 0000041 0000041 00000003751 14717736077 023223 0 ustar www-data www-data Feature: Or
Takes a variable number of contracts. The contract passes if any of the
contracts pass.
```ruby
Contract C::Or[Float, C::Nat] => String
```
This example will validate first argument of a method and accept either
`Float` or natural integer.
Background:
Given a file named "or_usage.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Or[Float, C::Nat] => String
def nat_string(number)
number.to_i.to_s
end
end
"""
Scenario: Accepts float
Given a file named "accepts_float.rb" with:
"""ruby
require "./or_usage"
puts Example.new.nat_string(3.7)
"""
When I run `ruby accepts_float.rb`
Then output should contain:
"""
3
"""
Scenario: Accepts natural
Given a file named "accepts_natural.rb" with:
"""ruby
require "./or_usage"
puts Example.new.nat_string(7)
"""
When I run `ruby accepts_natural.rb`
Then output should contain:
"""
7
"""
Scenario: Rejects negative integer
Given a file named "rejects_negative_integer.rb" with:
"""ruby
require "./or_usage"
puts Example.new.nat_string(-3)
"""
When I run `ruby rejects_negative_integer.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: (Float or Nat),
Actual: -3
Value guarded in: Example::nat_string
With Contract: Or => String
"""
Scenario: Rejects other values
Given a file named "rejects_other.rb" with:
"""ruby
require "./or_usage"
puts Example.new.nat_string(nil)
"""
When I run `ruby rejects_other.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: (Float or Nat),
Actual: nil
Value guarded in: Example::nat_string
With Contract: Or => String
"""
contracts-0.17.2/features/builtin_contracts/send.feature 0000644 0000041 0000041 00000006163 14717736077 023534 0 ustar www-data www-data Feature: Send
Takes a variable number of method names as symbols. Given an argument, all of
those methods are called on the argument one by one. If they all return true,
the contract passes.
```ruby
Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item]
```
This contract will pass only if:
`arg.valid? == true && arg.has_items? == true`,
where `arg` is the first argument.
Background:
Given a file named "item.rb" with:
"""ruby
Item = Struct.new(:name, :cost)
Item::DEFAULT = Item["default", 0]
"""
Given a file named "send_usage.rb" with:
"""ruby
require "contracts"
C = Contracts
require "./item"
class FetchItemCommand
include Contracts::Core
Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item]
def call(subject)
([Item::DEFAULT] + subject.items).uniq
end
end
"""
Scenario: All methods return `true`
Given a file named "box.rb" with:
"""ruby
class Box
def valid?
true
end
def has_items?
true
end
def items
[Item["cat", 599.99]]
end
end
require "./send_usage"
p FetchItemCommand.new.call(Box.new)
"""
When I run `ruby box.rb`
Then output should contain:
"""
[#, #]
"""
Scenario: When second method returns `false`
Given a file named "cat.rb" with:
"""ruby
class Cat
def valid?
true
end
def has_items?
false
end
end
require "./send_usage"
p FetchItemCommand.new.call(Cat.new)
"""
When I run `ruby cat.rb`
Then output should contain:
"""
: Contract violation for argument 1 of 1: (ParamContractError)
Expected: (a value that returns true for all of [:valid?, :has_items?]),
"""
And output should contain:
"""
Actual: #