contracts-lite 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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