fluid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/LICENSE.txt +34 -0
- data/Manifest.txt +13 -0
- data/README.txt +126 -0
- data/Rakefile +35 -0
- data/Rakefile.hoe +22 -0
- data/examples/a-bit-more-clever-fluid-tracing.rb +60 -0
- data/examples/globals.rb +16 -0
- data/examples/simple-fluid-tracing.rb +38 -0
- data/homepage/index.html +15 -0
- data/lib/fluid.rb +389 -0
- data/setup.rb +1585 -0
- data/test/fluid-tests.rb +469 -0
- metadata +70 -0
data/test/fluid-tests.rb
ADDED
@@ -0,0 +1,469 @@
|
|
1
|
+
# Fluid variables for Ruby.
|
2
|
+
# Originally 2004/03/08 20:16:13
|
3
|
+
# Gemified 2007/07
|
4
|
+
|
5
|
+
|
6
|
+
$:.unshift("../lib")
|
7
|
+
require 'test/unit'
|
8
|
+
require 'fluid'
|
9
|
+
require 's4t-utils/more-assertions'
|
10
|
+
|
11
|
+
class TestFluid < Test::Unit::TestCase
|
12
|
+
### Tests
|
13
|
+
def test_one_variable_uninitialized
|
14
|
+
Fluid.let(:a) {
|
15
|
+
assert_equal(nil, Fluid.a)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_one_variable_initialized
|
20
|
+
Fluid.let(:a, 1) {
|
21
|
+
assert_equal(1, Fluid.a)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Couple of notes about this test.
|
26
|
+
# - It checks that fluid variable names can be strings or symbols.
|
27
|
+
# - It checks that the values can be complex objects or references.
|
28
|
+
def test_multiple_variables
|
29
|
+
dval = ["dval"]
|
30
|
+
Fluid.let([:a, 1],
|
31
|
+
[:b],
|
32
|
+
["c", [1, 2]],
|
33
|
+
[:d, dval]) {
|
34
|
+
assert_equal(1, Fluid.a)
|
35
|
+
assert_equal(nil, Fluid.b)
|
36
|
+
assert_equal([1, 2], Fluid.c)
|
37
|
+
assert_equal(dval, Fluid.d)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def test_fluid_variable_assignment
|
43
|
+
Fluid.let("a", 1) {
|
44
|
+
assert_equal(1, Fluid.a)
|
45
|
+
Fluid.a = 2
|
46
|
+
assert_equal(2, Fluid.a)
|
47
|
+
Fluid.let("a") {
|
48
|
+
assert_equal(nil, Fluid.a)
|
49
|
+
}
|
50
|
+
assert_equal(2, Fluid.a)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Assignment is parallel, as in Lisp let, not let*
|
55
|
+
# This is inherent in Ruby arg evaluation, but thought I would
|
56
|
+
# document it here. let* is impossible, I think.
|
57
|
+
def test_assignment_is_parallel
|
58
|
+
Fluid.let(:dawn, "best beloved") {
|
59
|
+
assert_equal("best beloved", Fluid.dawn)
|
60
|
+
Fluid.let([:dawn, "wife"],
|
61
|
+
[:paul, "child of #{Fluid.dawn}"]) {
|
62
|
+
assert_equal("wife", Fluid.dawn)
|
63
|
+
assert_equal("child of best beloved", Fluid.paul)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Test that pushing and popping of values works correctly.
|
69
|
+
# Note that this also checks whether the no-op form (no vars)
|
70
|
+
# in fact has no effect.
|
71
|
+
def test_nesting_behavior_simple_within_method
|
72
|
+
Fluid.let(:a, 1) {
|
73
|
+
assert_equal(1, Fluid.a)
|
74
|
+
Fluid.let {
|
75
|
+
assert_equal(1, Fluid.a)
|
76
|
+
Fluid.let(:a, 2) {
|
77
|
+
assert_equal(2, Fluid.a)
|
78
|
+
}
|
79
|
+
assert_equal(1, Fluid.a)
|
80
|
+
}
|
81
|
+
assert_equal(1, Fluid.a)
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Check that multiple variables are pushed and popped appropriately.
|
86
|
+
def test_nesting_behavior_complex_within_method
|
87
|
+
Fluid.let([:a, 1], [:b]) {
|
88
|
+
assert_equal(1, Fluid.a)
|
89
|
+
assert_equal(nil, Fluid.b)
|
90
|
+
Fluid.let(:a) {
|
91
|
+
assert_equal(nil, Fluid.a)
|
92
|
+
assert_equal(nil, Fluid.b)
|
93
|
+
Fluid.let([:b, 'b'],
|
94
|
+
[:a, [1, 2]]) {
|
95
|
+
assert_equal([1,2], Fluid.a)
|
96
|
+
assert_equal('b', Fluid.b)
|
97
|
+
}
|
98
|
+
assert_equal(nil, Fluid.a)
|
99
|
+
assert_equal(nil, Fluid.b)
|
100
|
+
}
|
101
|
+
assert_equal(1, Fluid.a)
|
102
|
+
assert_equal(nil, Fluid.b)
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
# UNWINDING
|
107
|
+
# This is an example that shows how bindings are unwound or not
|
108
|
+
# unwound as control passes out of a block in various ways
|
109
|
+
# that involve calls to other methods. Shows how variable bindings
|
110
|
+
# are independent of the call stack.
|
111
|
+
def test_nesting_behavior_across_methods
|
112
|
+
Fluid.let([:paul, 6],
|
113
|
+
[:sophie, 5]) {
|
114
|
+
ordinary_subfunction # add one to age.
|
115
|
+
assert_equal(7, Fluid.paul)
|
116
|
+
assert_equal(6, Fluid.sophie)
|
117
|
+
|
118
|
+
# Values are unwound in presence of catch.
|
119
|
+
catch_value = catch(:catcher) {
|
120
|
+
Fluid.let(:paul, 66) {
|
121
|
+
assert_equal(66, Fluid.paul)
|
122
|
+
assert_equal(6, Fluid.sophie)
|
123
|
+
throwing_subfunction
|
124
|
+
# This changes both variables, but only
|
125
|
+
# the change to Sophie will be visible outside the let block
|
126
|
+
}
|
127
|
+
}
|
128
|
+
assert_equal(55600, catch_value)
|
129
|
+
assert_equal(7, Fluid.paul)
|
130
|
+
assert_equal("sophster", Fluid.sophie)
|
131
|
+
|
132
|
+
# Values are unwound with exceptions as well
|
133
|
+
begin
|
134
|
+
Fluid.let(:sophie, "leewit") {
|
135
|
+
assert_equal(7, Fluid.paul)
|
136
|
+
assert_equal("leewit", Fluid.sophie)
|
137
|
+
raising_subfunction
|
138
|
+
# This changes both variables, but only
|
139
|
+
# the change to Paul will be visible outside the let block
|
140
|
+
}
|
141
|
+
rescue RuntimeError
|
142
|
+
assert_equal(nil, Fluid.paul)
|
143
|
+
assert_equal("sophster", Fluid.sophie)
|
144
|
+
return
|
145
|
+
end
|
146
|
+
fail("Should not reach here.")
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def ordinary_subfunction
|
151
|
+
assert_equal(6, Fluid.paul)
|
152
|
+
assert_equal(5, Fluid.sophie)
|
153
|
+
Fluid.paul += 1
|
154
|
+
Fluid.sophie += 1
|
155
|
+
end
|
156
|
+
|
157
|
+
def throwing_subfunction
|
158
|
+
assert_equal(66, Fluid.paul)
|
159
|
+
assert_equal(6, Fluid.sophie)
|
160
|
+
# This change will be unwound as computation passes
|
161
|
+
# outside the Let block, which binds Paul.
|
162
|
+
Fluid.paul += 55534
|
163
|
+
# This will not, since the let block does not bind
|
164
|
+
# sophie.
|
165
|
+
Fluid.sophie = "sophster"
|
166
|
+
throw :catcher, Fluid.paul
|
167
|
+
end
|
168
|
+
|
169
|
+
def raising_subfunction
|
170
|
+
assert_equal(7, Fluid.paul)
|
171
|
+
assert_equal("leewit", Fluid.sophie)
|
172
|
+
# This change will be unowund as computation passes
|
173
|
+
# outside the Let block, which binds sophie.
|
174
|
+
Fluid.sophie = nil
|
175
|
+
|
176
|
+
# This change will be visible outside the let block, which
|
177
|
+
# does not bind paul.
|
178
|
+
Fluid.paul = nil
|
179
|
+
raise "Here comes the exception"
|
180
|
+
end
|
181
|
+
|
182
|
+
# END UNWINDING
|
183
|
+
|
184
|
+
|
185
|
+
def test_unbound_reference
|
186
|
+
assert_raises_with_matching_message(NameError,
|
187
|
+
"'unbound' has not been defined with Fluid.let or Fluid.defvar.") {
|
188
|
+
Fluid.let(:a) {
|
189
|
+
puts(Fluid.unbound)
|
190
|
+
}
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_unbound_assignment
|
195
|
+
assert_raises_with_matching_message(NameError,
|
196
|
+
"'also_unbound' has not been defined with Fluid.let or Fluid.defvar.") {
|
197
|
+
Fluid.also_unbound = 1
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_bindings_vanish
|
202
|
+
Fluid.let(:establish_binding) {
|
203
|
+
assert_equal(nil, Fluid.establish_binding)
|
204
|
+
}
|
205
|
+
assert_raises_with_matching_message(NameError,
|
206
|
+
"'establish_binding' has not been defined with Fluid.let or Fluid.defvar.") {
|
207
|
+
Fluid.establish_binding = 1
|
208
|
+
}
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_block_yields_last_value
|
212
|
+
result = Fluid.let([:a,1], [:b, 2]) {
|
213
|
+
Fluid.a + Fluid.b
|
214
|
+
}
|
215
|
+
assert_equal(3, result)
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_some_names_are_not_allowed
|
219
|
+
assert_raises_with_matching_message(NameError,
|
220
|
+
"'let' cannot be a fluid variable. It's already a method of Fluid's.") {
|
221
|
+
Fluid.let(:let) {fail("How'd I get here?")}
|
222
|
+
}
|
223
|
+
|
224
|
+
assert_raises_with_matching_message(NameError,
|
225
|
+
"'send' cannot be a fluid variable. It's already a method of Fluid's.") {
|
226
|
+
Fluid.let(:send) {fail("How'd I get here?")}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_duplicates_in_let
|
231
|
+
assert_raises_with_matching_message(NameError,
|
232
|
+
"'duplicate' is defined twice in the same Fluid.let.") {
|
233
|
+
Fluid.let([:duplicate, 1], [:unique], ["duplicate", 1]) {
|
234
|
+
fail("How'd I get here?")
|
235
|
+
}
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_defvar_simple
|
240
|
+
Fluid.defvar(:defvar_simple_a)
|
241
|
+
Fluid.defvar("defvar_simple_b", 1)
|
242
|
+
Fluid.defvar(:defvar_simple_c) { [1, 2] }
|
243
|
+
|
244
|
+
assert_equal(nil, Fluid.defvar_simple_a)
|
245
|
+
assert_equal(1, Fluid.defvar_simple_b)
|
246
|
+
assert_equal([1, 2], Fluid.defvar_simple_c)
|
247
|
+
|
248
|
+
Fluid.let([:defvar_simple_a, 99],
|
249
|
+
[:defvar_simple_b, 999],
|
250
|
+
[:defvar_simple_c, "000"]) {
|
251
|
+
assert_equal(99, Fluid.defvar_simple_a)
|
252
|
+
assert_equal(999, Fluid.defvar_simple_b)
|
253
|
+
assert_equal("000", Fluid.defvar_simple_c)
|
254
|
+
}
|
255
|
+
|
256
|
+
assert_equal(nil, Fluid.defvar_simple_a)
|
257
|
+
assert_equal(1, Fluid.defvar_simple_b)
|
258
|
+
assert_equal([1, 2], Fluid.defvar_simple_c)
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_defvar_once_only
|
262
|
+
Fluid.defvar("defvar_once_hello_5", "first value")
|
263
|
+
Fluid.defvar(:defvar_once_hello_5, "second value")
|
264
|
+
assert_equal("first value", Fluid.defvar_once_hello_5)
|
265
|
+
|
266
|
+
# Same is true of implicit nil.
|
267
|
+
Fluid.defvar(:defvar_once_hello_5)
|
268
|
+
assert_equal("first value", Fluid.defvar_once_hello_5)
|
269
|
+
|
270
|
+
# Moreover, blocks are not evaluated at all the second time.
|
271
|
+
Fluid.defvar(:defvar_once_hello_5) { fail "Should not be reached" }
|
272
|
+
assert_equal("first value", Fluid.defvar_once_hello_5)
|
273
|
+
end
|
274
|
+
|
275
|
+
def test_defvar_within_let # has no effect
|
276
|
+
assert_raises_with_matching_message(NameError,
|
277
|
+
"'a' has not been defined with Fluid.let or Fluid.defvar.") {
|
278
|
+
Fluid.a
|
279
|
+
}
|
280
|
+
|
281
|
+
Fluid.let(:a, 1) {
|
282
|
+
assert_equal(1, Fluid.a)
|
283
|
+
Fluid.defvar(:a, 999)
|
284
|
+
assert_equal(1, Fluid.a)
|
285
|
+
}
|
286
|
+
|
287
|
+
assert_raises_with_matching_message(NameError,
|
288
|
+
"'a' has not been defined with Fluid.let or Fluid.defvar.") {
|
289
|
+
Fluid.a
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_variable_names_are_method_names
|
294
|
+
assert_raises_with_matching_message(NameError,
|
295
|
+
"'blank name' is not a good fluid variable name. It can't be used as a method name.") {
|
296
|
+
Fluid.let("blank name") {}
|
297
|
+
}
|
298
|
+
|
299
|
+
assert_raises_with_matching_message(NameError,
|
300
|
+
"'9foo' is not a good fluid variable name. It can't be used as a method name.") {
|
301
|
+
Fluid.let("9foo".intern) {}
|
302
|
+
}
|
303
|
+
|
304
|
+
assert_raises_with_matching_message(NameError,
|
305
|
+
"'a=b' is not a good fluid variable name. It can't be used as a method name.") {
|
306
|
+
Fluid.defvar("a=b") {}
|
307
|
+
}
|
308
|
+
|
309
|
+
# Underscores are legal method names, though:
|
310
|
+
Fluid.defvar("_", 5)
|
311
|
+
assert_equal(5, Fluid._)
|
312
|
+
Fluid.defvar("_t", 7)
|
313
|
+
assert_equal(7, Fluid._t)
|
314
|
+
end
|
315
|
+
|
316
|
+
def incrementor
|
317
|
+
Fluid.counter += 1
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_destructor_called
|
321
|
+
Fluid.let([:counter, 0]) do
|
322
|
+
Fluid.let(:unused, self, :incrementor) do
|
323
|
+
assert_equal(0, Fluid.counter)
|
324
|
+
end
|
325
|
+
assert_equal(1, Fluid.counter)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def test_block_destructor
|
330
|
+
value = "destructor_called"
|
331
|
+
destructor_called = false
|
332
|
+
p = proc { | x | destructor_called = x }
|
333
|
+
|
334
|
+
assert_equal(value * 2,
|
335
|
+
Fluid.let([:_t_d_, value, p]) {
|
336
|
+
Fluid._t_d_ * 2
|
337
|
+
})
|
338
|
+
assert_equal(value, destructor_called)
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_var_checking
|
342
|
+
assert_equal(false, Fluid.has?(:log))
|
343
|
+
assert_equal(false, Fluid.has?('log'))
|
344
|
+
Fluid.defvar(:log)
|
345
|
+
assert_equal(true, Fluid.has?(:log))
|
346
|
+
assert_equal(true, Fluid.has?('log'))
|
347
|
+
|
348
|
+
assert_equal(false, Fluid.has?(:another_log))
|
349
|
+
assert_equal(false, Fluid.has?('another_log'))
|
350
|
+
Fluid.let(:another_log, 5) {
|
351
|
+
assert_equal(true, Fluid.has?(:another_log))
|
352
|
+
assert_equal(true, Fluid.has?('another_log'))
|
353
|
+
}
|
354
|
+
assert_equal(false, Fluid.has?(:another_log))
|
355
|
+
assert_equal(false, Fluid.has?('another_log'))
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
#### GLOBALS
|
360
|
+
|
361
|
+
def test_bound_global
|
362
|
+
$global = 'start value'
|
363
|
+
assert_equal($global, 'start value')
|
364
|
+
Fluid.let("$global", 5) {
|
365
|
+
assert_equal(5, $global)
|
366
|
+
Fluid.let([:local, 'local'], # mix fluid and globals, just in case.
|
367
|
+
["$global", 'global']) {
|
368
|
+
assert_equal('global', $global)
|
369
|
+
assert_equal('local', Fluid.local)
|
370
|
+
$global = 2334
|
371
|
+
}
|
372
|
+
assert_equal(5, $global)
|
373
|
+
$global = 3434
|
374
|
+
}
|
375
|
+
assert_equal($global, 'start value')
|
376
|
+
|
377
|
+
# Binding a predefined global variable has an effect.
|
378
|
+
assert_equal(nil, "UP" =~ /up/)
|
379
|
+
Fluid.let(["$,", "+"]) {
|
380
|
+
assert_equal("1+2", [1, 2].join)
|
381
|
+
}
|
382
|
+
assert_equal("12", [1, 2].join)
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_has_global
|
386
|
+
# Global variables are always considered bound.
|
387
|
+
assert_equal(true, Fluid.has?("$has_global_undef"))
|
388
|
+
$has_global = 5
|
389
|
+
assert_equal(true, Fluid.has?("$has_global"))
|
390
|
+
Fluid.let( [ "$has_global", 99 ] ) {
|
391
|
+
assert_equal(true, Fluid.has?("$has_global"))
|
392
|
+
}
|
393
|
+
assert_equal(true, Fluid.has?("$has_global"))
|
394
|
+
end
|
395
|
+
|
396
|
+
def test_exception_global
|
397
|
+
# Assigning $! causes odd behavior.
|
398
|
+
# Disallow it.
|
399
|
+
|
400
|
+
assert_raises_with_matching_message(NameError, /'\$\!' is not allowed/) {
|
401
|
+
Fluid.let("$!", Exception.new("overridden")) {
|
402
|
+
flunk
|
403
|
+
}
|
404
|
+
}
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_stdin_global
|
408
|
+
# Stdin is buffered in such a way that text from the inner binding
|
409
|
+
# is visible after a let unbinds. See globals/stdin.rb (Ruby 1.6)
|
410
|
+
|
411
|
+
assert_raises_with_matching_message(NameError, /'\$stdin' is not allowed/) {
|
412
|
+
Fluid.let(["$stdin", nil]) {
|
413
|
+
flunk
|
414
|
+
}
|
415
|
+
}
|
416
|
+
end
|
417
|
+
|
418
|
+
def test_stdout_global
|
419
|
+
# $stdout can be bound, but when it exits from the let, it will be
|
420
|
+
# buffered differently than $defout. If both are used, lines will be
|
421
|
+
# out of order. See globals/stdout.rb. (Ruby 1.6)
|
422
|
+
|
423
|
+
assert_raises_with_matching_message(NameError, /'\$stdout' is not allowed/) {
|
424
|
+
Fluid.let(["$stdout", nil]) {
|
425
|
+
flunk
|
426
|
+
}
|
427
|
+
}
|
428
|
+
end
|
429
|
+
|
430
|
+
def test_global_parallel_assignment
|
431
|
+
$g1 = 1
|
432
|
+
$g2 = 2
|
433
|
+
Fluid.let(["$g1", 11],
|
434
|
+
["$g2", $g1 + 1]) {
|
435
|
+
assert_equal(11, $g1)
|
436
|
+
assert_equal(2, $g2) # NOT 12
|
437
|
+
}
|
438
|
+
assert_equal(1, $g1)
|
439
|
+
assert_equal(2, $g2)
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_sequential_globals
|
443
|
+
$g1 = 1
|
444
|
+
$g2 = 2
|
445
|
+
Fluid.let(["$g1", $g2],
|
446
|
+
["$g2", $g1]) {
|
447
|
+
assert_equal(2, $g1)
|
448
|
+
assert_equal(1, $g2)
|
449
|
+
}
|
450
|
+
assert_equal(1, $g1)
|
451
|
+
assert_equal(2, $g2)
|
452
|
+
|
453
|
+
Fluid.let(["$g1", $g2],
|
454
|
+
["$g2", $g1]) {
|
455
|
+
assert_equal(2, $g1)
|
456
|
+
assert_equal(1, $g2)
|
457
|
+
}
|
458
|
+
assert_equal(1, $g1)
|
459
|
+
assert_equal(2, $g2)
|
460
|
+
end
|
461
|
+
|
462
|
+
def test_global_defvar
|
463
|
+
assert_raises_with_matching_message(NameError,
|
464
|
+
"Fluid.defvar of a global can never have an effect, so it's not allowed.") {
|
465
|
+
Fluid.defvar("$defvar", 33)
|
466
|
+
}
|
467
|
+
end
|
468
|
+
|
469
|
+
end
|