contracts 0.12.0 → 0.16.1
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.
- checksums.yaml +5 -13
- data/.github/workflows/code_style_checks.yaml +36 -0
- data/.github/workflows/tests.yaml +47 -0
- data/CHANGELOG.markdown +57 -6
- data/Gemfile +2 -2
- data/LICENSE +23 -0
- data/README.md +14 -6
- data/Rakefile +3 -6
- data/TUTORIAL.md +55 -26
- data/contracts.gemspec +6 -1
- data/dependabot.yml +20 -0
- data/features/basics/pretty-print.feature +241 -0
- data/features/builtin_contracts/args.feature +80 -1
- data/features/builtin_contracts/int.feature +93 -0
- data/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts.rb +48 -12
- data/lib/contracts/attrs.rb +24 -0
- data/lib/contracts/builtin_contracts.rb +60 -1
- data/lib/contracts/call_with.rb +22 -11
- data/lib/contracts/core.rb +0 -2
- data/lib/contracts/decorators.rb +5 -0
- data/lib/contracts/engine/eigenclass.rb +4 -0
- data/lib/contracts/engine/target.rb +2 -0
- data/lib/contracts/formatters.rb +4 -2
- data/lib/contracts/method_handler.rb +14 -22
- data/lib/contracts/support.rb +11 -9
- data/lib/contracts/version.rb +1 -1
- data/spec/attrs_spec.rb +119 -0
- data/spec/builtin_contracts_spec.rb +157 -95
- data/spec/contracts_spec.rb +50 -29
- data/spec/fixtures/fixtures.rb +58 -0
- data/spec/methods_spec.rb +54 -0
- data/spec/override_validators_spec.rb +1 -1
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +15 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +1 -1
- data/spec/validators_spec.rb +1 -1
- metadata +25 -14
- data/script/cucumber +0 -5
data/lib/contracts.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "contracts/attrs"
|
1
2
|
require "contracts/builtin_contracts"
|
2
3
|
require "contracts/decorators"
|
3
4
|
require "contracts/errors"
|
@@ -92,9 +93,9 @@ class Contract < Contracts::Decorator
|
|
92
93
|
last_contract = args_contracts.last
|
93
94
|
penultimate_contract = args_contracts[-2]
|
94
95
|
@has_options_contract = if @has_proc_contract
|
95
|
-
penultimate_contract.is_a?(Hash)
|
96
|
+
penultimate_contract.is_a?(Hash) || penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
96
97
|
else
|
97
|
-
last_contract.is_a?(Hash)
|
98
|
+
last_contract.is_a?(Hash) || last_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
98
99
|
end
|
99
100
|
# ===
|
100
101
|
|
@@ -115,22 +116,57 @@ class Contract < Contracts::Decorator
|
|
115
116
|
# This function is used by the default #failure_callback method
|
116
117
|
# and uses the hash passed into the failure_callback method.
|
117
118
|
def self.failure_msg(data)
|
118
|
-
|
119
|
-
position = Contracts::Support.method_position(data[:method])
|
119
|
+
indent_amount = 8
|
120
120
|
method_name = Contracts::Support.method_name(data[:method])
|
121
121
|
|
122
|
+
# Header
|
122
123
|
header = if data[:return_value]
|
123
124
|
"Contract violation for return value:"
|
124
125
|
else
|
125
126
|
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
|
126
127
|
end
|
127
128
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
129
|
+
# Expected
|
130
|
+
expected_prefix = "Expected: "
|
131
|
+
expected_value = Contracts::Support.indent_string(
|
132
|
+
Contracts::Formatters::Expected.new(data[:contract]).contract.pretty_inspect,
|
133
|
+
expected_prefix.length
|
134
|
+
).strip
|
135
|
+
expected_line = expected_prefix + expected_value + ","
|
136
|
+
|
137
|
+
# Actual
|
138
|
+
actual_prefix = "Actual: "
|
139
|
+
actual_value = Contracts::Support.indent_string(
|
140
|
+
data[:arg].pretty_inspect,
|
141
|
+
actual_prefix.length
|
142
|
+
).strip
|
143
|
+
actual_line = actual_prefix + actual_value
|
144
|
+
|
145
|
+
# Value guarded in
|
146
|
+
value_prefix = "Value guarded in: "
|
147
|
+
value_value = "#{data[:class]}::#{method_name}"
|
148
|
+
value_line = value_prefix + value_value
|
149
|
+
|
150
|
+
# Contract
|
151
|
+
contract_prefix = "With Contract: "
|
152
|
+
contract_value = data[:contracts].to_s
|
153
|
+
contract_line = contract_prefix + contract_value
|
154
|
+
|
155
|
+
# Position
|
156
|
+
position_prefix = "At: "
|
157
|
+
position_value = Contracts::Support.method_position(data[:method])
|
158
|
+
position_line = position_prefix + position_value
|
159
|
+
|
160
|
+
header +
|
161
|
+
"\n" +
|
162
|
+
Contracts::Support.indent_string(
|
163
|
+
[expected_line,
|
164
|
+
actual_line,
|
165
|
+
value_line,
|
166
|
+
contract_line,
|
167
|
+
position_line].join("\n"),
|
168
|
+
indent_amount
|
169
|
+
)
|
134
170
|
end
|
135
171
|
|
136
172
|
# Callback for when a contract fails. By default it raises
|
@@ -215,9 +251,9 @@ class Contract < Contracts::Decorator
|
|
215
251
|
# returns true if it appended nil
|
216
252
|
def maybe_append_options! args, blk
|
217
253
|
return false unless @has_options_contract
|
218
|
-
if @has_proc_contract && args_contracts[-2].is_a?(Hash) && !args[-2].is_a?(Hash)
|
254
|
+
if @has_proc_contract && (args_contracts[-2].is_a?(Hash) || args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-2].is_a?(Hash)
|
219
255
|
args.insert(-2, {})
|
220
|
-
elsif args_contracts[-1].is_a?(Hash) && !args[-1].is_a?(Hash)
|
256
|
+
elsif (args_contracts[-1].is_a?(Hash) || args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-1].is_a?(Hash)
|
221
257
|
args << {}
|
222
258
|
end
|
223
259
|
true
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Contracts
|
2
|
+
module Attrs
|
3
|
+
def attr_reader_with_contract(*names, contract)
|
4
|
+
names.each do |name|
|
5
|
+
Contract Contracts::None => contract
|
6
|
+
attr_reader(name)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def attr_writer_with_contract(*names, contract)
|
11
|
+
names.each do |name|
|
12
|
+
Contract contract => contract
|
13
|
+
attr_writer(name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def attr_accessor_with_contract(*names, contract)
|
18
|
+
attr_reader_with_contract(*names, contract)
|
19
|
+
attr_writer_with_contract(*names, contract)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include Attrs
|
24
|
+
end
|
@@ -41,13 +41,27 @@ module Contracts
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
# Check that an argument is
|
44
|
+
# Check that an argument is an +Integer+.
|
45
|
+
class Int
|
46
|
+
def self.valid? val
|
47
|
+
val && val.is_a?(Integer)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check that an argument is a natural number (includes zero).
|
45
52
|
class Nat
|
46
53
|
def self.valid? val
|
47
54
|
val && val.is_a?(Integer) && val >= 0
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
58
|
+
# Check that an argument is a positive natural number (excludes zero).
|
59
|
+
class NatPos
|
60
|
+
def self.valid? val
|
61
|
+
val && val.is_a?(Integer) && val > 0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
51
65
|
# Passes for any argument.
|
52
66
|
class Any
|
53
67
|
def self.valid? val
|
@@ -379,6 +393,26 @@ module Contracts
|
|
379
393
|
end
|
380
394
|
end
|
381
395
|
|
396
|
+
# Use this to specify the Hash characteristics. This contracts fails
|
397
|
+
# if there are any extra keys that don't have contracts on them.
|
398
|
+
# Example: <tt>StrictHash[{ a: String, b: Bool }]</tt>
|
399
|
+
class StrictHash < CallableClass
|
400
|
+
attr_reader :contract_hash
|
401
|
+
|
402
|
+
def initialize(contract_hash)
|
403
|
+
@contract_hash = contract_hash
|
404
|
+
end
|
405
|
+
|
406
|
+
def valid?(arg)
|
407
|
+
return false unless arg.is_a?(Hash)
|
408
|
+
return false unless arg.keys.sort.eql?(contract_hash.keys.sort)
|
409
|
+
|
410
|
+
contract_hash.all? do |key, contract|
|
411
|
+
contract_hash.key?(key) && Contract.valid?(arg[key], contract)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
382
416
|
# Use this for specifying contracts for keyword arguments
|
383
417
|
# Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
|
384
418
|
class KeywordArgs < CallableClass
|
@@ -387,6 +421,7 @@ module Contracts
|
|
387
421
|
end
|
388
422
|
|
389
423
|
def valid?(hash)
|
424
|
+
return false unless hash.is_a?(Hash)
|
390
425
|
return false unless hash.keys - options.keys == []
|
391
426
|
options.all? do |key, contract|
|
392
427
|
Optional._valid?(hash, key, contract)
|
@@ -406,6 +441,30 @@ module Contracts
|
|
406
441
|
attr_reader :options
|
407
442
|
end
|
408
443
|
|
444
|
+
# Use this for specifying contracts for class arguments
|
445
|
+
# Example: <tt>DescendantOf[ e: Range, f: Optional[Num] ]</tt>
|
446
|
+
class DescendantOf < CallableClass
|
447
|
+
def initialize(parent_class)
|
448
|
+
@parent_class = parent_class
|
449
|
+
end
|
450
|
+
|
451
|
+
def valid?(given_class)
|
452
|
+
given_class.is_a?(Class) && given_class.ancestors.include?(parent_class)
|
453
|
+
end
|
454
|
+
|
455
|
+
def to_s
|
456
|
+
"DescendantOf[#{parent_class}]"
|
457
|
+
end
|
458
|
+
|
459
|
+
def inspect
|
460
|
+
to_s
|
461
|
+
end
|
462
|
+
|
463
|
+
private
|
464
|
+
|
465
|
+
attr_reader :parent_class
|
466
|
+
end
|
467
|
+
|
409
468
|
# Use this for specifying optional keyword argument
|
410
469
|
# Example: <tt>Optional[Num]</tt>
|
411
470
|
class Optional < CallableClass
|
data/lib/contracts/call_with.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Contracts
|
2
2
|
module CallWith
|
3
3
|
def call_with(this, *args, &blk)
|
4
|
+
call_with_inner(false, this, *args, &blk)
|
5
|
+
end
|
6
|
+
|
7
|
+
def call_with_inner(returns, this, *args, &blk)
|
4
8
|
args << blk if blk
|
5
9
|
|
6
10
|
# Explicitly append blk=nil if nil != Proc contract violation anticipated
|
@@ -16,17 +20,21 @@ module Contracts
|
|
16
20
|
validator = @args_validators[i]
|
17
21
|
|
18
22
|
unless validator && validator[arg]
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
data = {:arg => arg,
|
24
|
+
:contract => contract,
|
25
|
+
:class => klass,
|
26
|
+
:method => method,
|
27
|
+
:contracts => self,
|
28
|
+
:arg_pos => i+1,
|
29
|
+
:total_args => args.size,
|
30
|
+
:return_value => false}
|
31
|
+
return ParamContractError.new("as return value", data) if returns
|
32
|
+
return unless Contract.failure_callback(data)
|
27
33
|
end
|
28
34
|
|
29
|
-
if contract.is_a?(Contracts::Func)
|
35
|
+
if contract.is_a?(Contracts::Func) && blk && !nil_block_appended
|
36
|
+
blk = Contract.new(klass, arg, *contract.contracts)
|
37
|
+
elsif contract.is_a?(Contracts::Func)
|
30
38
|
args[i] = Contract.new(klass, arg, *contract.contracts)
|
31
39
|
end
|
32
40
|
end
|
@@ -72,8 +80,11 @@ module Contracts
|
|
72
80
|
# proc, block, lambda, etc
|
73
81
|
method.call(*args, &blk)
|
74
82
|
else
|
75
|
-
# original method name
|
76
|
-
|
83
|
+
# original method name reference
|
84
|
+
# Don't reassign blk, else Travis CI shows "stack level too deep".
|
85
|
+
target_blk = blk
|
86
|
+
target_blk = lambda { |*params| blk.call(*params) } if blk && blk.is_a?(Contract)
|
87
|
+
method.send_to(this, *args, &target_blk)
|
77
88
|
end
|
78
89
|
|
79
90
|
unless @ret_validator[result]
|
data/lib/contracts/core.rb
CHANGED
data/lib/contracts/decorators.rb
CHANGED
@@ -18,6 +18,10 @@ module Contracts
|
|
18
18
|
return Engine.fetch_from(eigenclass) if Engine.applied?(eigenclass)
|
19
19
|
|
20
20
|
Target.new(eigenclass).apply(Eigenclass)
|
21
|
+
eigenclass.extend(MethodDecorators)
|
22
|
+
# FIXME; this should detect what user uses `include Contracts` or
|
23
|
+
# `include Contracts;;Core`
|
24
|
+
eigenclass.send(:include, Contracts)
|
21
25
|
Engine.fetch_from(owner).set_eigenclass_owner
|
22
26
|
Engine.fetch_from(eigenclass)
|
23
27
|
end
|
data/lib/contracts/formatters.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "pp"
|
2
|
+
|
1
3
|
module Contracts
|
2
4
|
# A namespace for classes related to formatting.
|
3
5
|
module Formatters
|
@@ -25,13 +27,13 @@ module Contracts
|
|
25
27
|
@full = true # Complex values output completely, overriding @full
|
26
28
|
hash.inject({}) do |repr, (k, v)|
|
27
29
|
repr.merge(k => InspectWrapper.create(contract(v), @full))
|
28
|
-
end
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
# Formats Array contracts.
|
32
34
|
def array_contract(array)
|
33
35
|
@full = true
|
34
|
-
array.map { |v| InspectWrapper.create(contract(v), @full) }
|
36
|
+
array.map { |v| InspectWrapper.create(contract(v), @full) }
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -125,31 +125,23 @@ module Contracts
|
|
125
125
|
# function. Otherwise we return the result.
|
126
126
|
# If we run out of functions, we raise the last error, but
|
127
127
|
# convert it to_contract_error.
|
128
|
-
|
129
|
-
i = 0
|
130
|
-
result = nil
|
128
|
+
|
131
129
|
expected_error = decorated_methods[0].failure_exception
|
130
|
+
last_error = nil
|
132
131
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
success = true
|
138
|
-
result = decorated_method.call_with(self, *args, &blk)
|
139
|
-
rescue expected_error => error
|
140
|
-
success = false
|
141
|
-
unless decorated_methods[i]
|
142
|
-
begin
|
143
|
-
::Contract.failure_callback(error.data, false)
|
144
|
-
rescue expected_error => final_error
|
145
|
-
raise final_error.to_contract_error
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
132
|
+
decorated_methods.each do |decorated_method|
|
133
|
+
result = decorated_method.call_with_inner(true, self, *args, &blk)
|
134
|
+
return result unless result.is_a?(ParamContractError)
|
135
|
+
last_error = result
|
149
136
|
end
|
150
137
|
|
151
|
-
|
152
|
-
|
138
|
+
begin
|
139
|
+
if ::Contract.failure_callback(last_error.data, false)
|
140
|
+
decorated_methods.last.call_with_inner(false, self, *args, &blk)
|
141
|
+
end
|
142
|
+
rescue expected_error => final_error
|
143
|
+
raise final_error.to_contract_error
|
144
|
+
end
|
153
145
|
end
|
154
146
|
end
|
155
147
|
|
@@ -157,7 +149,7 @@ module Contracts
|
|
157
149
|
return if decorators.size == 1
|
158
150
|
|
159
151
|
fail %{
|
160
|
-
Oops, it looks like method '#{
|
152
|
+
Oops, it looks like method '#{method_name}' has multiple contracts:
|
161
153
|
#{decorators.map { |x| x[1][0].inspect }.join("\n")}
|
162
154
|
|
163
155
|
Did you accidentally put more than one contract on a single function, like so?
|
data/lib/contracts/support.rb
CHANGED
@@ -4,14 +4,10 @@ module Contracts
|
|
4
4
|
def method_position(method)
|
5
5
|
return method.method_position if method.is_a?(MethodReference)
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
else
|
11
|
-
method.inspect
|
12
|
-
end
|
7
|
+
file, line = method.source_location
|
8
|
+
if file.nil? || line.nil?
|
9
|
+
""
|
13
10
|
else
|
14
|
-
file, line = method.source_location
|
15
11
|
file + ":" + line.to_s
|
16
12
|
end
|
17
13
|
end
|
@@ -34,8 +30,7 @@ module Contracts
|
|
34
30
|
end
|
35
31
|
|
36
32
|
def eigenclass_hierarchy_supported?
|
37
|
-
|
38
|
-
RUBY_VERSION.to_f > 1.8
|
33
|
+
RUBY_PLATFORM != "java" || RUBY_VERSION.to_f >= 2.0
|
39
34
|
end
|
40
35
|
|
41
36
|
def eigenclass_of(target)
|
@@ -47,6 +42,13 @@ module Contracts
|
|
47
42
|
target <= eigenclass_of(Object)
|
48
43
|
end
|
49
44
|
|
45
|
+
def indent_string(string, amount)
|
46
|
+
string.gsub(
|
47
|
+
/^(?!$)/,
|
48
|
+
(string[/^[ \t]/] || " ") * amount
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
50
52
|
private
|
51
53
|
|
52
54
|
# Module eigenclass can be detected by its ancestor chain
|
data/lib/contracts/version.rb
CHANGED
data/spec/attrs_spec.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
RSpec.describe "Contracts:" do
|
2
|
+
describe "Attrs:" do
|
3
|
+
class Person
|
4
|
+
include Contracts::Core
|
5
|
+
include Contracts::Attrs
|
6
|
+
include Contracts::Builtin
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name_r = name
|
10
|
+
@name_w = name
|
11
|
+
@name_rw = name
|
12
|
+
|
13
|
+
@name_r_2 = name
|
14
|
+
@name_w_2 = name
|
15
|
+
@name_rw_2 = name
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader_with_contract :name_r, :name_r_2, String
|
19
|
+
attr_writer_with_contract :name_w, :name_w_2, String
|
20
|
+
attr_accessor_with_contract :name_rw, :name_rw_2, String
|
21
|
+
end
|
22
|
+
|
23
|
+
context "attr_reader_with_contract" do
|
24
|
+
it "getting valid type" do
|
25
|
+
expect(Person.new("bob").name_r)
|
26
|
+
.to(eq("bob"))
|
27
|
+
end
|
28
|
+
|
29
|
+
it "getting invalid type" do
|
30
|
+
expect { Person.new(1.3).name_r }
|
31
|
+
.to(raise_error(ReturnContractError))
|
32
|
+
end
|
33
|
+
|
34
|
+
it "getting valid type for second val" do
|
35
|
+
expect(Person.new("bob").name_r_2)
|
36
|
+
.to(eq("bob"))
|
37
|
+
end
|
38
|
+
|
39
|
+
it "getting invalid type for second val" do
|
40
|
+
expect { Person.new(1.3).name_r_2 }
|
41
|
+
.to(raise_error(ReturnContractError))
|
42
|
+
end
|
43
|
+
|
44
|
+
it "setting" do
|
45
|
+
expect { Person.new("bob").name_r = "alice" }
|
46
|
+
.to(raise_error(NoMethodError))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "attr_writer_with_contract" do
|
51
|
+
it "getting" do
|
52
|
+
expect { Person.new("bob").name_w }
|
53
|
+
.to(raise_error(NoMethodError))
|
54
|
+
end
|
55
|
+
|
56
|
+
it "setting valid type" do
|
57
|
+
expect(Person.new("bob").name_w = "alice")
|
58
|
+
.to(eq("alice"))
|
59
|
+
end
|
60
|
+
|
61
|
+
it "setting invalid type" do
|
62
|
+
expect { Person.new("bob").name_w = 1.2 }
|
63
|
+
.to(raise_error(ParamContractError))
|
64
|
+
end
|
65
|
+
|
66
|
+
it "setting valid type for second val" do
|
67
|
+
expect(Person.new("bob").name_w_2 = "alice")
|
68
|
+
.to(eq("alice"))
|
69
|
+
end
|
70
|
+
|
71
|
+
it "setting invalid type for second val" do
|
72
|
+
expect { Person.new("bob").name_w_2 = 1.2 }
|
73
|
+
.to(raise_error(ParamContractError))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "attr_accessor_with_contract" do
|
78
|
+
it "getting valid type" do
|
79
|
+
expect(Person.new("bob").name_rw)
|
80
|
+
.to(eq("bob"))
|
81
|
+
end
|
82
|
+
|
83
|
+
it "getting invalid type" do
|
84
|
+
expect { Person.new(1.2).name_rw }
|
85
|
+
.to(raise_error(ReturnContractError))
|
86
|
+
end
|
87
|
+
|
88
|
+
it "setting valid type" do
|
89
|
+
expect(Person.new("bob").name_rw = "alice")
|
90
|
+
.to(eq("alice"))
|
91
|
+
end
|
92
|
+
|
93
|
+
it "setting invalid type" do
|
94
|
+
expect { Person.new("bob").name_rw = 1.2 }
|
95
|
+
.to(raise_error(ParamContractError))
|
96
|
+
end
|
97
|
+
|
98
|
+
it "getting valid type for second val" do
|
99
|
+
expect(Person.new("bob").name_rw_2)
|
100
|
+
.to(eq("bob"))
|
101
|
+
end
|
102
|
+
|
103
|
+
it "getting invalid type for second val" do
|
104
|
+
expect { Person.new(1.2).name_rw_2 }
|
105
|
+
.to(raise_error(ReturnContractError))
|
106
|
+
end
|
107
|
+
|
108
|
+
it "setting valid type for second val" do
|
109
|
+
expect(Person.new("bob").name_rw_2 = "alice")
|
110
|
+
.to(eq("alice"))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "setting invalid type for second val" do
|
114
|
+
expect { Person.new("bob").name_rw_2 = 1.2 }
|
115
|
+
.to(raise_error(ParamContractError))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|