cassowary-ruby 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +20 -0
- data/README.md +18 -0
- data/lib/cassowary.rb +25 -0
- data/lib/constraint/edit_or_stay_constraint.rb +40 -0
- data/lib/constraint/linear_constraint.rb +23 -0
- data/lib/constraint.rb +30 -0
- data/lib/ext/float.rb +24 -0
- data/lib/ext/numeric.rb +11 -0
- data/lib/ext/object.rb +11 -0
- data/lib/linear_expression.rb +163 -0
- data/lib/simplex_solver.rb +704 -0
- data/lib/strength.rb +31 -0
- data/lib/symbolic_weight.rb +115 -0
- data/lib/utils/equalities.rb +27 -0
- data/lib/variables/abstract_variable.rb +35 -0
- data/lib/variables/dummy_variable.rb +21 -0
- data/lib/variables/objective_variable.rb +17 -0
- data/lib/variables/slack_variable.rb +17 -0
- data/lib/variables/variable.rb +56 -0
- data/lib/variables.rb +5 -0
- data/test/test_abstract_methods.rb +22 -0
- data/test/test_cassowary.rb +184 -0
- data/test/test_ext.rb +38 -0
- data/test/test_helper.rb +12 -0
- data/test/test_variables.rb +33 -0
- metadata +106 -0
data/lib/strength.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
class Strength
|
5
|
+
attr_accessor :name, :symbolic_weight
|
6
|
+
|
7
|
+
def initialize(name = nil, symbolic_weight = nil)
|
8
|
+
self.name = name
|
9
|
+
self.symbolic_weight = symbolic_weight
|
10
|
+
end
|
11
|
+
|
12
|
+
def required?
|
13
|
+
self == RequiredStrength
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"#{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
[RequiredStrength, StrongStrength, MediumStrength, WeakStrength].each do |str|
|
22
|
+
yield str
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RequiredStrength = new "required"
|
27
|
+
StrongStrength = new "strong", SymbolicWeight.new([1.0])
|
28
|
+
MediumStrength = new "medium", SymbolicWeight.new([0.0, 1.0])
|
29
|
+
WeakStrength = new "weak", SymbolicWeight.new([0.0, 0.0, 1.0])
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
class SymbolicWeight
|
5
|
+
include Enumerable
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
StrengthLevels = 3
|
9
|
+
|
10
|
+
def initialize(levels = {})
|
11
|
+
@levels = [0.0] * StrengthLevels
|
12
|
+
case levels
|
13
|
+
when Hash
|
14
|
+
levels.each_pair do |k, v|
|
15
|
+
self[k] = v
|
16
|
+
end
|
17
|
+
when Array
|
18
|
+
levels.each_with_index do |e, idx|
|
19
|
+
self[idx] = e
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise InternalError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(*args, &block)
|
27
|
+
@levels.each(*args, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](idx)
|
31
|
+
@levels[idx]
|
32
|
+
end
|
33
|
+
|
34
|
+
def []=(idx, value)
|
35
|
+
@levels[idx] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def *(n)
|
39
|
+
raise InternalError unless n.is_a? Numeric
|
40
|
+
result = SymbolicWeight.new
|
41
|
+
each_with_index do |e, idx|
|
42
|
+
result[idx] = e * n
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def /(n)
|
48
|
+
raise InternalError unless n.is_a? Numeric
|
49
|
+
result = SymbolicWeight.new
|
50
|
+
each_with_index do |e, idx|
|
51
|
+
result[idx] = e / n
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def +(n)
|
57
|
+
raise InternalError unless n.is_a? SymbolicWeight
|
58
|
+
result = SymbolicWeight.new
|
59
|
+
each_with_index do |e, idx|
|
60
|
+
result[idx] = e + n[idx]
|
61
|
+
end
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def -(n)
|
66
|
+
raise InternalError unless n.is_a? SymbolicWeight
|
67
|
+
result = SymbolicWeight.new
|
68
|
+
each_with_index do |e, idx|
|
69
|
+
result[idx] = e - n[idx]
|
70
|
+
end
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
def <=>(other)
|
75
|
+
raise InternalError unless other.is_a? SymbolicWeight
|
76
|
+
each_with_index do |e, idx|
|
77
|
+
return -1 if e < other[idx]
|
78
|
+
return 1 if e > other[idx]
|
79
|
+
end
|
80
|
+
0
|
81
|
+
end
|
82
|
+
|
83
|
+
def cl_approx(s)
|
84
|
+
raise InternalError unless s.is_a? SymbolicWeight
|
85
|
+
each_with_index do |e, idx|
|
86
|
+
return false unless e.cl_approx(s[idx])
|
87
|
+
end
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def cl_approx_zero
|
92
|
+
cl_approx Zero
|
93
|
+
end
|
94
|
+
|
95
|
+
def definitely_negative
|
96
|
+
epsilon = SimplexSolver::Epsilon
|
97
|
+
nepsilon = 0.0 - epsilon
|
98
|
+
each do |e|
|
99
|
+
return true if e < nepsilon
|
100
|
+
return false if e > epsilon
|
101
|
+
end
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def symbolic_weight?
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
"[" + @levels.join(",") + "]"
|
111
|
+
end
|
112
|
+
|
113
|
+
Zero = new([0.0] * StrengthLevels)
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
module Equalities
|
5
|
+
def cn_equal(expr, strength = Strength::RequiredStrength, weight = 1.0)
|
6
|
+
cn_equality(LinearEquation, self - expr, strength, weight)
|
7
|
+
end
|
8
|
+
|
9
|
+
def cn_geq(expr, strength = Strength::RequiredStrength, weight = 1.0)
|
10
|
+
cn_equality(LinearInequality, self - expr, strength, weight)
|
11
|
+
end
|
12
|
+
|
13
|
+
def cn_leq(expr, strength = Strength::RequiredStrength, weight = 1.0)
|
14
|
+
expr = expr.as_linear_expression if expr.is_a?(Numeric)
|
15
|
+
cn_equality(LinearInequality, expr - self, strength, weight)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def cn_equality(klass, expr, strength, weight)
|
20
|
+
cn = klass.new
|
21
|
+
cn.expression = expr
|
22
|
+
cn.strength = strength
|
23
|
+
cn.weight = weight
|
24
|
+
cn
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
class AbstractVariable
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
def initialize(hash = {})
|
8
|
+
self.name = hash[:name]
|
9
|
+
end
|
10
|
+
|
11
|
+
def dummy?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def external?
|
16
|
+
raise NotImplementedError, "my subclass should have implemented #external?"
|
17
|
+
end
|
18
|
+
|
19
|
+
def pivotable?
|
20
|
+
raise NotImplementedError, "my subclass should have implemented #pivotable?"
|
21
|
+
end
|
22
|
+
|
23
|
+
def restricted?
|
24
|
+
raise NotImplementedError, "my subclass should have implemented #restricted?"
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
if name
|
29
|
+
"#{name}"
|
30
|
+
else
|
31
|
+
"<CV#0x#{object_id.to_s(16)}>"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
class DummyVariable < AbstractVariable
|
5
|
+
def dummy?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def external?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def pivotable?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def restricted?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (C) 2012 by Tim Felgentreff
|
2
|
+
|
3
|
+
module Cassowary
|
4
|
+
class Variable < AbstractVariable
|
5
|
+
include Equalities
|
6
|
+
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
super
|
11
|
+
self.value = hash[:value]
|
12
|
+
end
|
13
|
+
|
14
|
+
def *(expr)
|
15
|
+
self.as_linear_expression * expr
|
16
|
+
end
|
17
|
+
|
18
|
+
def +(expr)
|
19
|
+
self.as_linear_expression + expr
|
20
|
+
end
|
21
|
+
|
22
|
+
def -(expr)
|
23
|
+
self.as_linear_expression - expr
|
24
|
+
end
|
25
|
+
|
26
|
+
def /(expr)
|
27
|
+
self.as_linear_expression / expr
|
28
|
+
end
|
29
|
+
|
30
|
+
def as_linear_expression
|
31
|
+
expr = LinearExpression.new
|
32
|
+
expr.terms[self] = 1.0
|
33
|
+
expr
|
34
|
+
end
|
35
|
+
|
36
|
+
def external?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def pivotable?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def restricted?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def -@
|
49
|
+
-1.0.as_linear_expression * self
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"#{super}[#{value.inspect}]"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/variables.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class AbstractMethodsTest < Test::Unit::TestCase
|
4
|
+
def test_constraint
|
5
|
+
assert_raise NotImplementedError do
|
6
|
+
Cassowary::Constraint.new.expression
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_abstract_variable
|
11
|
+
var = Cassowary::AbstractVariable.new
|
12
|
+
assert_raise NotImplementedError do
|
13
|
+
var.external?
|
14
|
+
end
|
15
|
+
assert_raise NotImplementedError do
|
16
|
+
var.pivotable?
|
17
|
+
end
|
18
|
+
assert_raise NotImplementedError do
|
19
|
+
var.restricted?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class CassowaryTests < Test::Unit::TestCase
|
4
|
+
include Cassowary
|
5
|
+
|
6
|
+
def test_add_delete1
|
7
|
+
x = Variable.new(name: 'x')
|
8
|
+
solver = SimplexSolver.new
|
9
|
+
solver.add_constraint x.cn_equal(100.0, Strength::WeakStrength)
|
10
|
+
c10 = x.cn_leq 10.0
|
11
|
+
c20 = x.cn_leq 20.0
|
12
|
+
solver.add_constraint c10
|
13
|
+
solver.add_constraint c20
|
14
|
+
assert x.value.cl_approx(10.0)
|
15
|
+
|
16
|
+
solver.remove_constraint c10
|
17
|
+
assert x.value.cl_approx(20.0)
|
18
|
+
|
19
|
+
solver.remove_constraint c20
|
20
|
+
assert x.value.cl_approx(100.0)
|
21
|
+
|
22
|
+
c10again = x.cn_leq 10.0
|
23
|
+
solver.add_constraint c10
|
24
|
+
solver.add_constraint c10again
|
25
|
+
assert x.value.cl_approx(10.0)
|
26
|
+
|
27
|
+
solver.remove_constraint c10
|
28
|
+
assert x.value.cl_approx(10.0)
|
29
|
+
|
30
|
+
solver.remove_constraint c10again
|
31
|
+
assert x.value.cl_approx(100.0)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_add_delete2
|
35
|
+
x = Variable.new name: 'x'
|
36
|
+
y = Variable.new name: 'y'
|
37
|
+
|
38
|
+
solver = SimplexSolver.new
|
39
|
+
solver.add_constraint x.cn_equal(100.0, Strength::WeakStrength)
|
40
|
+
solver.add_constraint y.cn_equal(120.0, Strength::StrongStrength)
|
41
|
+
|
42
|
+
c10 = x.cn_leq(10.0)
|
43
|
+
c20 = x.cn_leq(20.0)
|
44
|
+
solver.add_constraint c10
|
45
|
+
solver.add_constraint c20
|
46
|
+
assert x.value.cl_approx(10.0)
|
47
|
+
assert y.value.cl_approx(120.0)
|
48
|
+
|
49
|
+
solver.remove_constraint c10
|
50
|
+
assert x.value.cl_approx 20.0
|
51
|
+
assert y.value.cl_approx 120.0
|
52
|
+
|
53
|
+
cxy = (x * 2).cn_equal y
|
54
|
+
solver.add_constraint cxy
|
55
|
+
assert x.value.cl_approx 20
|
56
|
+
assert y.value.cl_approx 40
|
57
|
+
|
58
|
+
solver.remove_constraint c20
|
59
|
+
assert x.value.cl_approx 60
|
60
|
+
assert y.value.cl_approx 120
|
61
|
+
|
62
|
+
solver.remove_constraint cxy
|
63
|
+
assert x.value.cl_approx 100
|
64
|
+
assert y.value.cl_approx 120
|
65
|
+
|
66
|
+
cxy2 = (x * 1.as_linear_expression).cn_equal(1000)
|
67
|
+
solver.add_constraint cxy2
|
68
|
+
assert x.value.cl_approx 1000
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_add_delete3
|
72
|
+
x = Variable.new name: 'x'
|
73
|
+
solver = SimplexSolver.new
|
74
|
+
c1 = x.cn_equal 100, Strength::WeakStrength, 5
|
75
|
+
c2 = x.cn_equal 200, Strength::WeakStrength
|
76
|
+
|
77
|
+
solver.add_constraint c1
|
78
|
+
solver.add_constraint c2
|
79
|
+
assert x.value.cl_approx 100
|
80
|
+
|
81
|
+
solver.remove_constraint c1
|
82
|
+
assert x.value.cl_approx 200
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_inconsistent1
|
86
|
+
x = Variable.new name: 'x'
|
87
|
+
solver = SimplexSolver.new
|
88
|
+
solver.add_constraint x.cn_equal 10
|
89
|
+
assert_raise RequiredFailure do
|
90
|
+
solver.add_constraint x.cn_equal 5
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_inconsistent2
|
95
|
+
x = Variable.new name: 'x'
|
96
|
+
solver = SimplexSolver.new
|
97
|
+
solver.add_constraint x.cn_geq 10
|
98
|
+
assert_raise RequiredFailure do
|
99
|
+
solver.add_constraint x.cn_leq 5
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_stay1
|
104
|
+
x = Variable.new name: 'x', value: 20
|
105
|
+
solver = SimplexSolver.new
|
106
|
+
|
107
|
+
solver.add_stay x, Strength::WeakStrength
|
108
|
+
assert x.value.cl_approx 20
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_two_solutions
|
112
|
+
x = Variable.new name: 'x'
|
113
|
+
y = Variable.new name: 'y'
|
114
|
+
|
115
|
+
solver = SimplexSolver.new
|
116
|
+
solver.add_constraint x.cn_leq y
|
117
|
+
solver.add_constraint y.cn_equal x + 3
|
118
|
+
solver.add_constraint x.cn_equal 10, Strength::WeakStrength
|
119
|
+
solver.add_constraint y.cn_equal 10, Strength::WeakStrength
|
120
|
+
|
121
|
+
assert(x.value.cl_approx(10) && y.value.cl_approx(13) ||
|
122
|
+
x.value.cl_approx(7) && y.value.cl_approx(10))
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_weighted1
|
126
|
+
x = Variable.new name: 'x'
|
127
|
+
solver = SimplexSolver.new
|
128
|
+
|
129
|
+
c15 = x.cn_equal 15, Strength::WeakStrength
|
130
|
+
c20 = x.cn_equal 20, Strength::WeakStrength, 2
|
131
|
+
|
132
|
+
solver.add_constraint c15
|
133
|
+
assert x.value.cl_approx 15
|
134
|
+
|
135
|
+
solver.add_constraint c20
|
136
|
+
assert x.value.cl_approx 20
|
137
|
+
|
138
|
+
solver.remove_constraint c20
|
139
|
+
assert x.value.cl_approx 15
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_edit1
|
143
|
+
x = Variable.new name: 'x', value: 20
|
144
|
+
y = Variable.new name: 'y', value: 30
|
145
|
+
|
146
|
+
solver = SimplexSolver.new
|
147
|
+
solver.add_stay x, Strength::WeakStrength
|
148
|
+
solver.add_constraint x.cn_geq 10
|
149
|
+
solver.add_constraint x.cn_leq 100
|
150
|
+
solver.add_constraint x.cn_equal y * 2
|
151
|
+
assert x.value.cl_approx 20
|
152
|
+
assert y.value.cl_approx 10
|
153
|
+
|
154
|
+
solver.add_edit_var y, Strength::StrongStrength
|
155
|
+
solver.begin_edit
|
156
|
+
solver.suggest_value y, 35
|
157
|
+
solver.resolve
|
158
|
+
assert x.value.cl_approx 70
|
159
|
+
assert y.value.cl_approx 35
|
160
|
+
|
161
|
+
solver.suggest_value y, 80
|
162
|
+
solver.resolve
|
163
|
+
assert x.value.cl_approx 100
|
164
|
+
assert y.value.cl_approx 50
|
165
|
+
|
166
|
+
solver.suggest_value y, 25
|
167
|
+
solver.resolve
|
168
|
+
assert x.value.cl_approx 50
|
169
|
+
assert y.value.cl_approx 25
|
170
|
+
|
171
|
+
solver.end_edit
|
172
|
+
assert x.value.cl_approx 50
|
173
|
+
assert y.value.cl_approx 25
|
174
|
+
|
175
|
+
solver.add_edit_var x, Strength::StrongStrength
|
176
|
+
solver.begin_edit
|
177
|
+
solver.suggest_value x, 44.0
|
178
|
+
solver.resolve
|
179
|
+
assert x.value.cl_approx 44
|
180
|
+
assert y.value.cl_approx 22
|
181
|
+
|
182
|
+
solver.end_edit
|
183
|
+
end
|
184
|
+
end
|
data/test/test_ext.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class ExtTests < Test::Unit::TestCase
|
4
|
+
def test_float_approx_zero
|
5
|
+
assert !1.1.cl_approx_zero
|
6
|
+
assert 0.0.cl_approx_zero
|
7
|
+
assert 0.1e-8.cl_approx_zero
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_float_approx
|
11
|
+
assert 1.1.cl_approx 1.1
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_float_negative
|
15
|
+
assert -1.1.definitely_negative
|
16
|
+
assert -0.1e-5.definitely_negative
|
17
|
+
assert ! -0.1e-8.definitely_negative
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_numeric_as_linear_expression
|
21
|
+
expr = 1.as_linear_expression
|
22
|
+
assert expr.terms.empty?
|
23
|
+
assert expr.constant == 1.to_f
|
24
|
+
|
25
|
+
expr = 1.1.as_linear_expression
|
26
|
+
assert expr.terms.empty?
|
27
|
+
assert expr.constant == 1.1
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_object_approx
|
31
|
+
assert "foo".cl_approx "foo"
|
32
|
+
assert ! Object.new.cl_approx(Object.new)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_object_symbolic_weight
|
36
|
+
assert !Object.new.symbolic_weight?
|
37
|
+
end
|
38
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path("../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class VariablesTests < Test::Unit::TestCase
|
4
|
+
def test_operations
|
5
|
+
x = Cassowary::Variable.new name: 'x', value: 20
|
6
|
+
expr = x / 10
|
7
|
+
assert expr.constant == 0
|
8
|
+
assert expr.terms[x] == 0.1
|
9
|
+
|
10
|
+
expr = x * 10
|
11
|
+
assert expr.terms[x] == 10
|
12
|
+
|
13
|
+
expr = x - 10
|
14
|
+
assert expr.constant == -10
|
15
|
+
|
16
|
+
expr = x + 10
|
17
|
+
assert expr.constant == 10
|
18
|
+
|
19
|
+
expr = -x
|
20
|
+
assert expr.terms[x] == -1
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_inspect
|
24
|
+
x = Cassowary::Variable.new name: 'x', value: 21.1
|
25
|
+
assert_equal "x[21.1]", x.inspect
|
26
|
+
|
27
|
+
x = Cassowary::Variable.new name: 'x'
|
28
|
+
assert_equal "x[nil]", x.inspect
|
29
|
+
|
30
|
+
x = Cassowary::SlackVariable.new
|
31
|
+
assert_equal "<CV#0x" + x.object_id.to_s(16) + ">", x.inspect
|
32
|
+
end
|
33
|
+
end
|