contracts-lite 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.markdown +80 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +23 -0
  5. data/README.md +102 -0
  6. data/TODO.markdown +6 -0
  7. data/TUTORIAL.md +747 -0
  8. data/benchmarks/bench.rb +67 -0
  9. data/benchmarks/hash.rb +69 -0
  10. data/benchmarks/invariants.rb +91 -0
  11. data/benchmarks/io.rb +62 -0
  12. data/benchmarks/wrap_test.rb +57 -0
  13. data/contracts.gemspec +13 -0
  14. data/lib/contracts.rb +231 -0
  15. data/lib/contracts/builtin_contracts.rb +541 -0
  16. data/lib/contracts/call_with.rb +97 -0
  17. data/lib/contracts/core.rb +52 -0
  18. data/lib/contracts/decorators.rb +47 -0
  19. data/lib/contracts/engine.rb +26 -0
  20. data/lib/contracts/engine/base.rb +136 -0
  21. data/lib/contracts/engine/eigenclass.rb +50 -0
  22. data/lib/contracts/engine/target.rb +70 -0
  23. data/lib/contracts/error_formatter.rb +121 -0
  24. data/lib/contracts/errors.rb +71 -0
  25. data/lib/contracts/formatters.rb +134 -0
  26. data/lib/contracts/invariants.rb +68 -0
  27. data/lib/contracts/method_handler.rb +195 -0
  28. data/lib/contracts/method_reference.rb +100 -0
  29. data/lib/contracts/support.rb +59 -0
  30. data/lib/contracts/validators.rb +139 -0
  31. data/lib/contracts/version.rb +3 -0
  32. data/script/rubocop +7 -0
  33. data/spec/builtin_contracts_spec.rb +461 -0
  34. data/spec/contracts_spec.rb +748 -0
  35. data/spec/error_formatter_spec.rb +68 -0
  36. data/spec/fixtures/fixtures.rb +710 -0
  37. data/spec/invariants_spec.rb +17 -0
  38. data/spec/module_spec.rb +18 -0
  39. data/spec/override_validators_spec.rb +162 -0
  40. data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  41. data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  42. data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  43. data/spec/spec_helper.rb +102 -0
  44. data/spec/support.rb +10 -0
  45. data/spec/support_spec.rb +21 -0
  46. data/spec/validators_spec.rb +47 -0
  47. metadata +94 -0
@@ -0,0 +1,17 @@
1
+ module Contracts
2
+ RSpec.describe Invariants do
3
+ def new_subject
4
+ MyBirthday.new(31, 12)
5
+ end
6
+
7
+ it "works when all invariants are holding" do
8
+ expect { new_subject.clever_next_day! }.not_to raise_error
9
+ expect { new_subject.clever_next_month! }.not_to raise_error
10
+ end
11
+
12
+ it "raises invariant violation error when any of invariants are not holding" do
13
+ expect { new_subject.silly_next_day! }.to raise_error(InvariantError, /day condition to be true/)
14
+ expect { new_subject.silly_next_month! }.to raise_error(InvariantError, /month condition to be true/)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ module Mod
2
+ include Contracts::Core
3
+
4
+ Contract C::Num => C::Num
5
+ def self.a_module_method a
6
+ a + 1
7
+ end
8
+ end
9
+
10
+ RSpec.describe "module methods" do
11
+ it "should pass for correct input" do
12
+ expect { Mod.a_module_method(2) }.to_not raise_error
13
+ end
14
+
15
+ it "should fail for incorrect input" do
16
+ expect { Mod.a_module_method("bad") }.to raise_error(ContractError)
17
+ end
18
+ end
@@ -0,0 +1,162 @@
1
+ RSpec.describe Contract do
2
+ describe ".override_validator" do
3
+ around do |example|
4
+ Contract.reset_validators
5
+ example.run
6
+ Contract.reset_validators
7
+ end
8
+
9
+ it "allows to override simple validators" do
10
+ Contract.override_validator(Hash) do |contract|
11
+ lambda do |arg|
12
+ return false unless arg.is_a?(Hash)
13
+ # Any hash in my system should have :it_is_a_hash key!
14
+ return false unless arg.key?(:it_is_a_hash)
15
+ contract.keys.all? do |k|
16
+ Contract.valid?(arg[k], contract[k])
17
+ end
18
+ end
19
+ end
20
+
21
+ klass = Class.new do
22
+ include Contracts::Core
23
+
24
+ Contract ({ :a => Contracts::Num, :b => String }) => nil
25
+ def something(opts)
26
+ nil
27
+ end
28
+ end
29
+
30
+ obj = klass.new
31
+
32
+ expect do
33
+ obj.something(:a => 35, :b => "hello")
34
+ end.to raise_error(ContractError)
35
+
36
+ expect do
37
+ obj.something(
38
+ :a => 35,
39
+ :b => "hello",
40
+ :it_is_a_hash => true
41
+ )
42
+ end.not_to raise_error
43
+ end
44
+
45
+ it "allows to override valid contract" do
46
+ Contract.override_validator(:valid) do |contract|
47
+ if contract.respond_to?(:in_valid_state?)
48
+ lambda do |arg|
49
+ contract.in_valid_state? && contract.valid?(arg)
50
+ end
51
+ else
52
+ lambda { |arg| contract.valid?(arg) }
53
+ end
54
+ end
55
+
56
+ stateful_contract = Class.new(Contracts::CallableClass) do
57
+ def initialize(contract)
58
+ @contract = contract
59
+ @state = 0
60
+ end
61
+
62
+ def in_valid_state?
63
+ @state < 3
64
+ end
65
+
66
+ def valid?(arg)
67
+ @state += 1
68
+ Contract.valid?(arg, @contract)
69
+ end
70
+ end
71
+
72
+ klass = Class.new do
73
+ include Contracts::Core
74
+
75
+ Contract stateful_contract[Contracts::Num] => Contracts::Num
76
+ def only_three_times(x)
77
+ x * x
78
+ end
79
+ end
80
+
81
+ obj = klass.new
82
+
83
+ expect(obj.only_three_times(3)).to eq(9)
84
+ expect(obj.only_three_times(3)).to eq(9)
85
+ expect(obj.only_three_times(3)).to eq(9)
86
+
87
+ expect do
88
+ obj.only_three_times(3)
89
+ end.to raise_error(ContractError)
90
+
91
+ expect do
92
+ obj.only_three_times(3)
93
+ end.to raise_error(ContractError)
94
+ end
95
+
96
+ it "allows to override class validator" do
97
+ # Make contracts accept all rspec doubles
98
+ Contract.override_validator(:class) do |contract|
99
+ lambda do |arg|
100
+ arg.is_a?(RSpec::Mocks::Double) ||
101
+ arg.is_a?(contract)
102
+ end
103
+ end
104
+
105
+ klass = Class.new do
106
+ include Contracts::Core
107
+
108
+ Contract String => String
109
+ def greet(name)
110
+ "hello, #{name}"
111
+ end
112
+ end
113
+
114
+ obj = klass.new
115
+
116
+ expect(obj.greet("world")).to eq("hello, world")
117
+
118
+ expect do
119
+ obj.greet(4)
120
+ end.to raise_error(ContractError)
121
+
122
+ expect(obj.greet(double("name"))).to match(
123
+ /hello, #\[.*Double.*"name"\]/
124
+ )
125
+ end
126
+
127
+ it "allows to override default validator" do
128
+ spy = double("spy")
129
+
130
+ Contract.override_validator(:default) do |contract|
131
+ lambda do |arg|
132
+ spy.log("#{arg} == #{contract}")
133
+ arg == contract
134
+ end
135
+ end
136
+
137
+ klass = Class.new do
138
+ include Contracts::Core
139
+
140
+ Contract 1, Contracts::Num => Contracts::Num
141
+ def gcd(_, b)
142
+ b
143
+ end
144
+
145
+ Contract Contracts::Num, Contracts::Num => Contracts::Num
146
+ def gcd(a, b)
147
+ gcd(b % a, a)
148
+ end
149
+ end
150
+
151
+ obj = klass.new
152
+
153
+ expect(spy).to receive(:log).with("8 == 1").ordered.once
154
+ expect(spy).to receive(:log).with("5 == 1").ordered.once
155
+ expect(spy).to receive(:log).with("3 == 1").ordered.once
156
+ expect(spy).to receive(:log).with("2 == 1").ordered.once
157
+ expect(spy).to receive(:log).with("1 == 1").ordered.once
158
+
159
+ obj.gcd(8, 5)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,24 @@
1
+ class GenericExample
2
+ Contract C::Args[String], C::Num => C::ArrayOf[String]
3
+ def splat_then_arg(*vals, n)
4
+ vals.map { |v| v * n }
5
+ end
6
+
7
+ if ruby_version <= 1.9
8
+ Contract ({:foo => C::Nat}) => nil
9
+ def nat_test_with_kwarg(a_hash)
10
+ end
11
+ end
12
+ end
13
+
14
+ RSpec.describe "Contracts:" do
15
+ before :all do
16
+ @o = GenericExample.new
17
+ end
18
+
19
+ describe "Splat not last (or penultimate to block)" do
20
+ it "should work with arg after splat" do
21
+ expect { @o.splat_then_arg("hello", "world", 3) }.to_not raise_error
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ class GenericExample
2
+ Contract C::Args[String], { repeat: C::Maybe[C::Num] } => C::ArrayOf[String]
3
+ def splat_then_optional_named(*vals, repeat: 2)
4
+ vals.map { |v| v * repeat }
5
+ end
6
+
7
+ Contract ({foo: C::Nat}) => nil
8
+ def nat_test_with_kwarg(foo: 10)
9
+ end
10
+
11
+ Contract C::KeywordArgs[name: C::Optional[String]], C::Func[String => String] => String
12
+ def keyword_args_hello(name: "Adit", &block)
13
+ "Hey, #{yield name}!"
14
+ end
15
+ end
16
+
17
+ RSpec.describe "Contracts:" do
18
+ before :all do
19
+ @o = GenericExample.new
20
+ end
21
+
22
+ describe "Optional named arguments" do
23
+ it "should work with optional named argument unfilled after splat" do
24
+ expect { @o.splat_then_optional_named("hello", "world") }.to_not raise_error
25
+ end
26
+
27
+ it "should work with optional named argument filled after splat" do
28
+ expect { @o.splat_then_optional_named("hello", "world", repeat: 3) }.to_not raise_error
29
+ end
30
+ end
31
+
32
+ describe "Nat:" do
33
+ it "should pass for keyword args with correct arg given" do
34
+ expect { @o.nat_test_with_kwarg(foo: 10) }.to_not raise_error
35
+ end
36
+
37
+ it "should fail with a ContractError for wrong keyword args input" do
38
+ expect { @o.nat_test_with_kwarg(foo: -10) }.to raise_error(ContractError)
39
+ end
40
+
41
+ it "should fail with a ContractError for no input" do
42
+ expect { @o.nat_test_with_kwarg }.to raise_error(ContractError)
43
+ end
44
+ end
45
+
46
+ describe "keyword args with defaults, with a block" do
47
+ it "should work when both keyword args and a block is given" do
48
+ expect(@o.keyword_args_hello(name: "maggie", &:upcase)).to eq("Hey, MAGGIE!")
49
+ end
50
+
51
+ it "should work even when keyword args aren't given" do
52
+ expect(@o.keyword_args_hello(&:upcase)).to eq("Hey, ADIT!")
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,63 @@
1
+ class GenericExample
2
+ Contract String, C::Bool, C::Args[Symbol], Float, C::KeywordArgs[e: Range, f: C::Optional[C::Num], g: Symbol], Proc =>
3
+ [Proc, Hash, C::Maybe[C::Num], Range, Float, C::ArrayOf[Symbol], C::Bool, String]
4
+ def complicated(a, b = true, *c, d, e:, f:2, **g, &h)
5
+ h.call [h, g, f, e, d, c, b, a]
6
+ end
7
+ end
8
+
9
+ RSpec.describe "Contracts:" do
10
+ before :all do
11
+ @o = GenericExample.new
12
+ end
13
+
14
+ describe "Required named arguments" do
15
+ describe "really complicated method signature" do
16
+ it "should work with default named args used" do
17
+ expect do
18
+ @o.complicated("a", false, :b, 2.0, e: (1..5), g: :d) { |x| x }
19
+ end.to_not raise_error
20
+ end
21
+
22
+ it "should work with all args filled manually, with extra splat and hash" do
23
+ expect do
24
+ @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: 8.3, g: :d) do |x|
25
+ x
26
+ end
27
+ end.to_not raise_error
28
+ end
29
+
30
+ it "should fail when the return is invalid" do
31
+ expect do
32
+ @o.complicated("a", true, :b, 2.0, e: (1..5)) { |_x| "bad" }
33
+ end.to raise_error(ContractError)
34
+ end
35
+
36
+ it "should fail when args are invalid" do
37
+ expect do
38
+ @o.complicated("a", "bad", :b, 2.0, e: (1..5)) { |x| x }
39
+ end.to raise_error(ContractError)
40
+ end
41
+
42
+ it "should fail when splat is invalid" do
43
+ expect do
44
+ @o.complicated("a", true, "bad", 2.0, e: (1..5)) { |x| x }
45
+ end.to raise_error(ContractError)
46
+ end
47
+
48
+ it "should fail when named argument is invalid" do
49
+ expect do
50
+ @o.complicated("a", true, :b, 2.0, e: "bad") { |x| x }
51
+ end.to raise_error(ContractError)
52
+ end
53
+
54
+ it "should fail when passed nil to an optional argument which contract shouldnt accept nil" do
55
+ expect do
56
+ @o.complicated("a", true, :b, :c, 2.0, e: (1..5), f: nil, g: :d) do |x|
57
+ x
58
+ end
59
+ end.to raise_error(ContractError, /Expected: \(KeywordArgs\[{:e=>Range, :f=>Optional\[Num\], :g=>Symbol}\]\)/)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,102 @@
1
+ require "contracts"
2
+ require File.expand_path(File.join(__FILE__, "../support"))
3
+ require File.expand_path(File.join(__FILE__, "../fixtures/fixtures"))
4
+
5
+ # This file was generated by the `rspec --init` command. Conventionally, all
6
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
7
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
8
+ # file to always be loaded, without a need to explicitly require it in any files.
9
+ #
10
+ # Given that it is always loaded, you are encouraged to keep this file as
11
+ # light-weight as possible. Requiring heavyweight dependencies from this file
12
+ # will add to the boot time of your test suite on EVERY test run, even for an
13
+ # individual file that may not need all of that loaded. Instead, consider making
14
+ # a separate helper file that requires the additional dependencies and performs
15
+ # the additional setup, and require it from the spec files that actually need it.
16
+ #
17
+ # The `.rspec` file also contains a few flags that are not defaults but that
18
+ # users commonly want.
19
+ #
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ config.pattern = "*.rb"
23
+
24
+ # Only load tests who's syntax is valid in the current Ruby
25
+ [1.9, 2.0, 2.1].each do |ver|
26
+ config.pattern << ",ruby_version_specific/*#{ver}.rb" if ruby_version >= ver
27
+ end
28
+
29
+ # rspec-expectations config goes here. You can use an alternate
30
+ # assertion/expectation library such as wrong or the stdlib/minitest
31
+ # assertions if you prefer.
32
+ config.expect_with :rspec do |expectations|
33
+ # This option will default to `true` in RSpec 4. It makes the `description`
34
+ # and `failure_message` of custom matchers include text for helper methods
35
+ # defined using `chain`, e.g.:
36
+ # be_bigger_than(2).and_smaller_than(4).description
37
+ # # => "be bigger than 2 and smaller than 4"
38
+ # ...rather than:
39
+ # # => "be bigger than 2"
40
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
41
+ end
42
+
43
+ # rspec-mocks config goes here. You can use an alternate test double
44
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
45
+ config.mock_with :rspec do |mocks|
46
+ # Prevents you from mocking or stubbing a method that does not exist on
47
+ # a real object. This is generally recommended, and will default to
48
+ # `true` in RSpec 4.
49
+ mocks.verify_partial_doubles = true
50
+ end
51
+
52
+ # These two settings work together to allow you to limit a spec run
53
+ # to individual examples or groups you care about by tagging them with
54
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
55
+ # get run.
56
+ config.filter_run :focus
57
+ config.run_all_when_everything_filtered = true
58
+
59
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
60
+ # For more details, see:
61
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
62
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
63
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
64
+ config.disable_monkey_patching!
65
+
66
+ # This setting enables warnings. It's recommended, but in some cases may
67
+ # be too noisy due to issues in dependencies.
68
+ # config.warnings = true
69
+
70
+ # Many RSpec users commonly either run the entire suite or an individual
71
+ # file, and it's useful to allow more verbose output when running an
72
+ # individual spec file.
73
+ if config.files_to_run.one?
74
+ # Use the documentation formatter for detailed output,
75
+ # unless a formatter has already been configured
76
+ # (e.g. via a command-line flag).
77
+ config.default_formatter = "doc"
78
+ end
79
+
80
+ # Print the 10 slowest examples and example groups at the
81
+ # end of the spec run, to help surface which specs are running
82
+ # particularly slow.
83
+ config.profile_examples = 10
84
+
85
+ # Run specs in random order to surface order dependencies. If you find an
86
+ # order dependency and want to debug it, you can fix the order by providing
87
+ # the seed, which is printed after each run.
88
+ # --seed 1234
89
+ # Unable to use it now
90
+ config.order = :random
91
+
92
+ # Seed global randomization in this process using the `--seed` CLI option.
93
+ # Setting this allows you to use `--seed` to deterministically reproduce
94
+ # test failures related to randomization by passing the same `--seed` value
95
+ # as the one that triggered the failure.
96
+ Kernel.srand config.seed
97
+
98
+ # Callbacks
99
+ config.after :each do
100
+ ::Contract.restore_failure_callback
101
+ end
102
+ end