gecoder 0.8.1 → 0.8.2

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.
@@ -17,6 +17,55 @@ class ArithmeticSampleProblem < Gecode::Model
17
17
  end
18
18
  end
19
19
 
20
+ # Construct a method placing expectations for an arithmetic constraint with the
21
+ # specified arity (number of variables before must) and the specified name in
22
+ # Gecode.
23
+ def arithmetic_expectation(gecode_name, arity)
24
+ lambda do |relation, rhs, strength, kind, reif_var, negated|
25
+ # Construct the arguments expected to be passed to the Gecode variant of
26
+ # the constraint.
27
+ rhs = an_instance_of(Gecode::Raw::IntVar) if rhs.respond_to? :bind
28
+ expected_gecode_arguments = [an_instance_of(Gecode::Raw::Space)]
29
+ arity.times do
30
+ expected_gecode_arguments << an_instance_of(Gecode::Raw::IntVar)
31
+ end
32
+ can_use_single_gecode_constraint = reif_var.nil? && !negated &&
33
+ relation == Gecode::Raw::IRT_EQ && !rhs.kind_of?(Fixnum)
34
+ if can_use_single_gecode_constraint
35
+ expected_gecode_arguments << rhs
36
+ else
37
+ expected_gecode_arguments << an_instance_of(Gecode::Raw::IntVar)
38
+ end
39
+ expected_gecode_arguments.concat([strength, kind])
40
+
41
+ # Create the actual method producing the expectation.
42
+ @model.allow_space_access do
43
+ if reif_var.nil?
44
+ if can_use_single_gecode_constraint
45
+ Gecode::Raw.should_receive(gecode_name).once.with(
46
+ *expected_gecode_arguments)
47
+ Gecode::Raw.should_receive(:rel).exactly(0).times
48
+ else
49
+ Gecode::Raw.should_receive(gecode_name).once.with(
50
+ *expected_gecode_arguments)
51
+ Gecode::Raw.should_receive(:rel).once.with(
52
+ an_instance_of(Gecode::Raw::Space),
53
+ an_instance_of(Gecode::Raw::IntVar),
54
+ relation, rhs, strength, kind)
55
+ end
56
+ else
57
+ Gecode::Raw.should_receive(gecode_name).once.with(
58
+ *expected_gecode_arguments)
59
+ Gecode::Raw.should_receive(:rel).once.with(
60
+ an_instance_of(Gecode::Raw::Space),
61
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs,
62
+ an_instance_of(Gecode::Raw::BoolVar),
63
+ strength, kind)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
20
69
  # Requires @stub, @target, @model and @expect.
21
70
  describe 'arithmetic constraint', :shared => true do
22
71
  before do
@@ -228,46 +277,7 @@ describe Gecode::Constraints::Int::Arithmetic, ' (multiplication)' do
228
277
  @stub = @var * @var2
229
278
  @target = @model.var3
230
279
 
231
- # Creates an expectation corresponding to the specified input.
232
- @expect = lambda do |relation, rhs, strength, kind, reif_var, negated|
233
- @model.allow_space_access do
234
- rhs = an_instance_of(Gecode::Raw::IntVar) if rhs.respond_to? :bind
235
- if reif_var.nil?
236
- if !negated and relation == Gecode::Raw::IRT_EQ and
237
- !rhs.kind_of? Fixnum
238
- Gecode::Raw.should_receive(:mult).once.with(
239
- an_instance_of(Gecode::Raw::Space),
240
- an_instance_of(Gecode::Raw::IntVar),
241
- an_instance_of(Gecode::Raw::IntVar),
242
- rhs, strength, kind)
243
- Gecode::Raw.should_receive(:rel).exactly(0).times
244
- else
245
- Gecode::Raw.should_receive(:mult).once.with(
246
- an_instance_of(Gecode::Raw::Space),
247
- an_instance_of(Gecode::Raw::IntVar),
248
- an_instance_of(Gecode::Raw::IntVar),
249
- an_instance_of(Gecode::Raw::IntVar),
250
- strength, kind)
251
- Gecode::Raw.should_receive(:rel).once.with(
252
- an_instance_of(Gecode::Raw::Space),
253
- an_instance_of(Gecode::Raw::IntVar),
254
- relation, rhs, strength, kind)
255
- end
256
- else
257
- Gecode::Raw.should_receive(:mult).once.with(
258
- an_instance_of(Gecode::Raw::Space),
259
- an_instance_of(Gecode::Raw::IntVar),
260
- an_instance_of(Gecode::Raw::IntVar),
261
- an_instance_of(Gecode::Raw::IntVar),
262
- strength, kind)
263
- Gecode::Raw.should_receive(:rel).once.with(
264
- an_instance_of(Gecode::Raw::Space),
265
- an_instance_of(Gecode::Raw::IntVar), relation, rhs,
266
- an_instance_of(Gecode::Raw::BoolVar),
267
- strength, kind)
268
- end
269
- end
270
- end
280
+ @expect = arithmetic_expectation(:mult, 2)
271
281
  end
272
282
 
273
283
  it 'should constrain the value of the multiplication' do
@@ -281,4 +291,61 @@ describe Gecode::Constraints::Int::Arithmetic, ' (multiplication)' do
281
291
  end
282
292
 
283
293
  it_should_behave_like 'arithmetic constraint'
284
- end
294
+ end
295
+
296
+ describe Gecode::Constraints::Int::Arithmetic, ' (squared)' do
297
+ before do
298
+ @model = ArithmeticSampleProblem.new
299
+ @var = @model.var
300
+ @stub = @var.squared
301
+ @target = @model.var2
302
+
303
+ @expect = arithmetic_expectation(:sqr, 1)
304
+ end
305
+
306
+ it 'should constrain the value of the variable squared' do
307
+ @var.squared.must == 9
308
+ sol = @model.solve!
309
+ sol.var.value.abs.should == 3
310
+ end
311
+
312
+ it_should_behave_like 'arithmetic constraint'
313
+ end
314
+
315
+ describe Gecode::Constraints::Int::Arithmetic, ' (square root)' do
316
+ before do
317
+ @model = ArithmeticSampleProblem.new
318
+ @var = @model.var
319
+ @stub = @var.square_root
320
+ @target = @model.var2
321
+
322
+ @expect = arithmetic_expectation(:sqrt, 1)
323
+ end
324
+
325
+ it 'should constrain the square root of the variable' do
326
+ @var.square_root.must == 3
327
+ sol = @model.solve!
328
+ Math.sqrt(sol.var.value).floor.should == 3
329
+ end
330
+
331
+ it 'should constrain the square root of the variable (2)' do
332
+ @var.square_root.must == 0
333
+ sol = @model.solve!
334
+ Math.sqrt(sol.var.value).floor.should == 0
335
+ end
336
+
337
+ it 'should constrain the square root of the variable (3)' do
338
+ @var.must < 0
339
+ @var.square_root.must == 0
340
+ @model.solve!.should be_nil
341
+ end
342
+
343
+ it 'should round down the square root' do
344
+ @var.must > 4
345
+ @var.square_root.must == 2
346
+ sol = @model.solve!
347
+ sol.var.value.should be_between(5,8)
348
+ end
349
+
350
+ it_should_behave_like 'arithmetic constraint'
351
+ end
@@ -228,5 +228,9 @@ describe Gecode::Constraints::Bool do
228
228
  sol.b3.value.should be_true
229
229
  end
230
230
 
231
+ it 'should raise error on right hand sides of the wrong type' do
232
+ lambda{ @b1.must == 'hello' }.should raise_error(TypeError)
233
+ end
234
+
231
235
  it_should_behave_like 'reifiable constraint'
232
236
  end
@@ -16,6 +16,20 @@ class ChannelSampleProblem < Gecode::Model
16
16
  end
17
17
  end
18
18
 
19
+ class BoolChannelSampleProblem < Gecode::Model
20
+ attr :bool_enum
21
+ attr :bool
22
+ attr :int
23
+
24
+ def initialize
25
+ @bool_enum = bool_var_array(4)
26
+ @int = int_var(0..3)
27
+ @bool = bool_var
28
+
29
+ branch_on wrap_enum([@int])
30
+ end
31
+ end
32
+
19
33
  describe Gecode::Constraints::IntEnum::Channel, ' (two int enums)' do
20
34
  before do
21
35
  @model = ChannelSampleProblem.new
@@ -67,13 +81,21 @@ describe Gecode::Constraints::IntEnum::Channel, ' (one int enum and one set enum
67
81
  @model = ChannelSampleProblem.new
68
82
  @positions = @model.positions
69
83
  @sets = @model.sets
84
+
85
+ @invoke_options = lambda do |hash|
86
+ @positions.must.channel @sets, hash
87
+ @model.solve!
88
+ end
89
+ @expect_options = option_expectation do |strength, kind, reif_var|
90
+ Gecode::Raw.should_receive(:channel).once.with(
91
+ an_instance_of(Gecode::Raw::Space),
92
+ an_instance_of(Gecode::Raw::IntVarArray),
93
+ an_instance_of(Gecode::Raw::SetVarArray))
94
+ end
70
95
  end
71
96
 
72
97
  it 'should translate into a channel constraint' do
73
- Gecode::Raw.should_receive(:channel).once.with(
74
- an_instance_of(Gecode::Raw::Space),
75
- an_instance_of(Gecode::Raw::IntVarArray),
76
- an_instance_of(Gecode::Raw::SetVarArray))
98
+ @expect_options.call({})
77
99
  @positions.must.channel @sets
78
100
  @model.solve!
79
101
  end
@@ -87,6 +109,8 @@ describe Gecode::Constraints::IntEnum::Channel, ' (one int enum and one set enum
87
109
  sets[position].value.should include(i)
88
110
  end
89
111
  end
112
+
113
+ it_should_behave_like 'non-reifiable set constraint'
90
114
  end
91
115
 
92
116
  describe Gecode::Constraints::SetEnum, ' (channel with set as left hand side)' do
@@ -123,4 +147,210 @@ describe Gecode::Constraints::SetEnum, ' (channel with set as left hand side)' d
123
147
  end
124
148
 
125
149
  it_should_behave_like 'non-reifiable set constraint'
126
- end
150
+ end
151
+
152
+ # Requires @model, @bool and @int. Also requires @place_constraint which is a
153
+ # method that takes five variables: a boolean variable, an integer variable,
154
+ # the name of the equality method to use, whether or not the constraint should
155
+ # be negated and a hash of options, and places the channel constraint on them.
156
+ describe 'channel constraint between one int and one bool variable', :shared => true do
157
+ before do
158
+ @invoke_options = lambda do |hash|
159
+ @place_constraint.call(@bool, @int, :==, false, hash)
160
+ @model.solve!
161
+ end
162
+ @expect_options = option_expectation do |strength, kind, reif_var|
163
+ Gecode::Raw.should_receive(:channel).once.with(
164
+ an_instance_of(Gecode::Raw::Space),
165
+ an_instance_of(Gecode::Raw::IntVar),
166
+ an_instance_of(Gecode::Raw::BoolVar),
167
+ strength, kind)
168
+ end
169
+ end
170
+
171
+ ([:==] + Gecode::Constraints::Util::COMPARISON_ALIASES[:==]).each do |ali|
172
+ it "should translate #{ali} into a channel constraint" do
173
+ @expect_options.call({})
174
+ @place_constraint.call(@bool, @int, ali, false, {})
175
+ @model.solve!
176
+ end
177
+ end
178
+
179
+ it 'should constrain the int variable to be 1 when the boolean variable is true' do
180
+ @bool.must_be.true
181
+ @place_constraint.call(@bool, @int, :==, false, {})
182
+ @model.solve!
183
+ @int.value.should == 1
184
+ end
185
+
186
+ it 'should constrain the int variable to be 0 when the boolean variable is false' do
187
+ @bool.must_be.false
188
+ @place_constraint.call(@bool, @int, :==, false, {})
189
+ @model.solve!
190
+ @int.value.should == 0
191
+ end
192
+
193
+ it 'should not allow negation' do
194
+ lambda do
195
+ @place_constraint.call(@bool, @int, :==, true, {})
196
+ end.should raise_error(Gecode::MissingConstraintError)
197
+ end
198
+
199
+ it_should_behave_like 'non-reifiable constraint'
200
+ end
201
+
202
+ describe Gecode::Constraints::Int::Channel, ' (one int and one bool variable)' do
203
+ before do
204
+ @model = BoolChannelSampleProblem.new
205
+ @bool = @model.bool_var
206
+ @int = @model.int_var
207
+
208
+ @place_constraint = lambda do |bool, int, equals_method_name, negate, options|
209
+ if negate
210
+ int.must_not.method(equals_method_name).call(bool, options)
211
+ else
212
+ int.must.method(equals_method_name).call(bool, options)
213
+ end
214
+ end
215
+ end
216
+
217
+ it 'should not shadow linear boolean constraints' do
218
+ lambda do
219
+ (@bool + @bool).must == @bool
220
+ @model.solve!
221
+ end.should_not raise_error
222
+ end
223
+
224
+ it 'should raise error for unsupported right hand sides' do
225
+ lambda{ @int.must == 'hello' }.should raise_error(TypeError)
226
+ end
227
+
228
+ it_should_behave_like 'channel constraint between one int and one bool variable'
229
+ end
230
+
231
+ describe Gecode::Constraints::Int::Channel, ' (one bool and one int variable)' do
232
+ before do
233
+ @model = BoolChannelSampleProblem.new
234
+ @bool = @model.bool_var
235
+ @int = @model.int_var
236
+
237
+ @place_constraint = lambda do |bool, int, equals_method_name, negate, options|
238
+ if negate
239
+ bool.must_not.method(equals_method_name).call(int, options)
240
+ else
241
+ bool.must.method(equals_method_name).call(int, options)
242
+ end
243
+ end
244
+ end
245
+
246
+ it 'should not shadow linear boolean constraints' do
247
+ lambda do
248
+ @bool.must == @bool + @bool
249
+ @model.solve!
250
+ end.should_not raise_error
251
+ end
252
+
253
+ it 'should raise error for unsupported right hand sides' do
254
+ lambda{ @bool.must == 'hello' }.should raise_error(TypeError)
255
+ end
256
+
257
+ it_should_behave_like 'channel constraint between one int and one bool variable'
258
+ end
259
+
260
+ # Requires @model, @bool_enum and @int. Also requires @place_constraint which
261
+ # is a method that takes four variables: a boolean enum, an integer variable,
262
+ # whether or not the constraint should be negated and a hash of options, and
263
+ # places the channel constraint on them.
264
+ describe 'channel constraint between bool enum and int variable', :shared => true do
265
+ before do
266
+ @invoke_options = lambda do |hash|
267
+ @place_constraint.call(@bools, @int, false, hash)
268
+ @model.solve!
269
+ end
270
+ @expect_options = option_expectation do |strength, kind, reif_var|
271
+ Gecode::Raw.should_receive(:channel).once.with(
272
+ an_instance_of(Gecode::Raw::Space),
273
+ an_instance_of(Gecode::Raw::BoolVarArray),
274
+ an_instance_of(Gecode::Raw::IntVar), 0,
275
+ strength, kind)
276
+ end
277
+ end
278
+
279
+ it 'should channel the bool enum with the integer variable' do
280
+ @int.must > 2
281
+ @place_constraint.call(@bools, @int, false, {})
282
+ @model.solve!.should_not be_nil
283
+ int_val = @int.value
284
+ @bools.values.each_with_index do |bool, index|
285
+ bool.should == (index == int_val)
286
+ end
287
+ end
288
+
289
+ it 'should take the offset into account when channeling' do
290
+ @int.must > 2
291
+ offset = 1
292
+ @place_constraint.call(@bools, @int, false, :offset => offset)
293
+ @model.solve!.should_not be_nil
294
+ int_val = @int.value
295
+ @bools.values.each_with_index do |bool, index|
296
+ bool.should == (index + offset == int_val)
297
+ end
298
+ end
299
+
300
+ it 'should not allow negation' do
301
+ lambda do
302
+ @place_constraint.call(@bools, @int, true, {})
303
+ end.should raise_error(Gecode::MissingConstraintError)
304
+ end
305
+
306
+ it_should_behave_like 'non-reifiable constraint'
307
+ end
308
+
309
+ describe Gecode::Constraints::BoolEnum::Channel, ' (bool enum as lhs with int variable)' do
310
+ before do
311
+ @model = BoolChannelSampleProblem.new
312
+ @bools = @model.bool_enum
313
+ @int = @model.int
314
+
315
+ @place_constraint = lambda do |bools, int, negate, options|
316
+ unless negate
317
+ bools.must.channel(int, options)
318
+ else
319
+ bools.must_not.channel(int, options)
320
+ end
321
+ end
322
+ end
323
+
324
+ it 'should raise error if an integer variable is not given as right hand side' do
325
+ lambda do
326
+ @bools.must.channel 'hello'
327
+ end.should raise_error(TypeError)
328
+ end
329
+
330
+ it_should_behave_like 'channel constraint between bool enum and int variable'
331
+ end
332
+
333
+
334
+ describe Gecode::Constraints::BoolEnum::Channel, ' (int variable as lhs with bool enum)' do
335
+ before do
336
+ @model = BoolChannelSampleProblem.new
337
+ @bools = @model.bool_enum
338
+ @int = @model.int
339
+
340
+ @place_constraint = lambda do |bools, int, negate, options|
341
+ unless negate
342
+ int.must.channel(bools, options)
343
+ else
344
+ int.must_not.channel(bools, options)
345
+ end
346
+ end
347
+ end
348
+
349
+ it 'should raise error if a boolean enum is not given as right hand side' do
350
+ lambda do
351
+ @int.must.channel 'hello'
352
+ end.should raise_error(TypeError)
353
+ end
354
+
355
+ it_should_behave_like 'channel constraint between bool enum and int variable'
356
+ end
@@ -8,7 +8,7 @@ class DistinctSampleProblem < Gecode::Model
8
8
  def initialize
9
9
  @vars = int_var_array(2, 1)
10
10
  @sets = set_var_array(2, [], 0..2)
11
- branch_on wrap_enum(@sets)
11
+ branch_on @sets
12
12
  end
13
13
  end
14
14
 
@@ -1,25 +1,59 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper'
2
2
  require File.dirname(__FILE__) + '/constraint_helper'
3
3
 
4
- describe Gecode::Constraints::IntEnum::Extensional do
4
+ # Assumes that @variables, @expected_array and @tuples are defined.
5
+ describe 'tuple constraint', :shared => true do
5
6
  before do
6
- @model = Gecode::Model.new
7
- @tuples = [[1,7], [5,1]]
8
- @digits = @model.int_var_array(2, 0..9)
9
- @model.branch_on @digits
10
-
11
7
  @invoke_options = lambda do |hash|
12
- @digits.must_be.in(@tuples, hash)
8
+ @variables.must_be.in(@tuples, hash)
13
9
  @model.solve!
14
10
  end
15
11
  @expect_options = option_expectation do |strength, kind, reif_var|
16
12
  Gecode::Raw.should_receive(:extensional).once.with(
17
13
  an_instance_of(Gecode::Raw::Space),
18
- an_instance_of(Gecode::Raw::IntVarArray),
14
+ @expected_array,
19
15
  an_instance_of(Gecode::Raw::TupleSet), strength, kind)
20
16
  end
21
17
  end
22
18
 
19
+ it 'should not allow negation' do
20
+ lambda do
21
+ @variables.must_not_be.in @tuples
22
+ end.should raise_error(Gecode::MissingConstraintError)
23
+ end
24
+
25
+ it 'should not allow empty tuples' do
26
+ lambda do
27
+ @variables.must_be.in []
28
+ end.should raise_error(ArgumentError)
29
+ end
30
+
31
+ it 'should not allow tuples of sizes other than the number of variables' do
32
+ lambda do
33
+ @variables.must_be.in([@tuples.first * 2])
34
+ end.should raise_error(ArgumentError)
35
+ end
36
+
37
+ it 'should raise error if the right hand side does not contain tuples of correct type' do
38
+ lambda do
39
+ size = @variables.size
40
+ @variables.must_be.in ['h'*size, 'i'*size]
41
+ end.should raise_error(TypeError)
42
+ end
43
+
44
+ it_should_behave_like 'non-reifiable constraint'
45
+ end
46
+
47
+ describe Gecode::Constraints::IntEnum::Extensional do
48
+ before do
49
+ @model = Gecode::Model.new
50
+ @tuples = [[1,7], [5,1]]
51
+ @variables = @digits = @model.int_var_array(2, 0..9)
52
+ @model.branch_on @digits
53
+
54
+ @expected_array = an_instance_of Gecode::Raw::IntVarArray
55
+ end
56
+
23
57
  it 'should constrain the domain of all variables' do
24
58
  @digits.must_be.in @tuples
25
59
 
@@ -32,12 +66,6 @@ describe Gecode::Constraints::IntEnum::Extensional do
32
66
  (found_solutions - @tuples).should be_empty
33
67
  end
34
68
 
35
- it 'should not allow negation' do
36
- lambda do
37
- @digits.must_not_be.in @tuples
38
- end.should raise_error(Gecode::MissingConstraintError)
39
- end
40
-
41
69
  it 'should raise error if the right hand side is not an enumeration' do
42
70
  lambda{ @digits.must_be.in 4711 }.should raise_error(TypeError)
43
71
  end
@@ -46,30 +74,17 @@ describe Gecode::Constraints::IntEnum::Extensional do
46
74
  lambda{ @digits.must_be.in [17, 4711] }.should raise_error(TypeError)
47
75
  end
48
76
 
49
- it 'should raise error if the right hand side does not contain integer tuples' do
50
- lambda{ @digits.must_be.in ['hello'] }.should raise_error(TypeError)
51
- end
52
-
53
- it_should_behave_like 'non-reifiable constraint'
77
+ it_should_behave_like 'tuple constraint'
54
78
  end
55
79
 
56
80
  describe Gecode::Constraints::BoolEnum::Extensional do
57
81
  before do
58
82
  @model = Gecode::Model.new
59
83
  @tuples = [[true, false, true], [false, false, true]]
60
- @bools = @model.bool_var_array(3)
84
+ @variables = @bools = @model.bool_var_array(3)
61
85
  @model.branch_on @bools
62
86
 
63
- @invoke_options = lambda do |hash|
64
- @bools.must_be.in(@tuples, hash)
65
- @model.solve!
66
- end
67
- @expect_options = option_expectation do |strength, kind, reif_var|
68
- Gecode::Raw.should_receive(:extensional).once.with(
69
- an_instance_of(Gecode::Raw::Space),
70
- an_instance_of(Gecode::Raw::BoolVarArray),
71
- an_instance_of(Gecode::Raw::TupleSet), strength, kind)
72
- end
87
+ @expected_array = an_instance_of Gecode::Raw::BoolVarArray
73
88
  end
74
89
 
75
90
  it 'should constrain the domain of all variables' do
@@ -84,12 +99,6 @@ describe Gecode::Constraints::BoolEnum::Extensional do
84
99
  (found_solutions - @tuples).should be_empty
85
100
  end
86
101
 
87
- it 'should not allow negation' do
88
- lambda do
89
- @bools.must_not_be.in @tuples
90
- end.should raise_error(Gecode::MissingConstraintError)
91
- end
92
-
93
102
  it 'should raise error if the right hand side is not an enumeration' do
94
103
  lambda{ @bools.must_be.in true }.should raise_error(TypeError)
95
104
  end
@@ -98,9 +107,5 @@ describe Gecode::Constraints::BoolEnum::Extensional do
98
107
  lambda{ @bools.must_be.in [true, false] }.should raise_error(TypeError)
99
108
  end
100
109
 
101
- it 'should raise error if the right hand side does not contain boolean tuples' do
102
- lambda{ @bools.must_be.in ['hello'] }.should raise_error(TypeError)
103
- end
104
-
105
- it_should_behave_like 'non-reifiable constraint'
110
+ it_should_behave_like 'tuple constraint'
106
111
  end
@@ -240,6 +240,14 @@ describe Gecode::Constraints::Int::Linear, '(with booleans)' do
240
240
  (x + y).should equal(1)
241
241
  end
242
242
 
243
+ it 'should handle singe booleans as left hand side' do
244
+ @x.must == @y + 1
245
+ sol = @model.solve!
246
+ x = sol.x.value.to_i
247
+ y = sol.y.value.to_i
248
+ x.should equal(y + 1)
249
+ end
250
+
243
251
  it 'should handle variables as right hand side' do
244
252
  (@x + @y).must == @z
245
253
  sol = @model.solve!
@@ -40,6 +40,13 @@ describe Gecode::Model, ' (enum wrapping)' do
40
40
  end.should raise_error(ArgumentError)
41
41
  end
42
42
 
43
+ it 'should not allow wrapping a wrapped enumerable' do
44
+ lambda do
45
+ enum = [@bool]
46
+ @model.wrap_enum(@model.wrap_enum(enum))
47
+ end.should raise_error(ArgumentError)
48
+ end
49
+
43
50
  it 'should not allow enumerables without variables or fixnums to be wrapped' do
44
51
  lambda do
45
52
  @model.wrap_enum(['foo'])
@@ -119,4 +126,4 @@ describe Gecode::FixnumEnumMethods do
119
126
  it 'should compute the smallest domain range' do
120
127
  @enum.domain_range.should == (7..4711)
121
128
  end
122
- end
129
+ end