fluid 1.0.0
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.
- 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
|