comp_tree 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README +153 -0
  2. data/Rakefile +152 -0
  3. data/comp_tree.gemspec +38 -0
  4. data/contrib/quix/Rakefile +16 -0
  5. data/contrib/quix/install.rb +3 -0
  6. data/contrib/quix/lib/quix/builtin/dir/casefold_brackets.rb +7 -0
  7. data/contrib/quix/lib/quix/builtin/kernel/tap.rb +9 -0
  8. data/contrib/quix/lib/quix/builtin/module/include.rb +21 -0
  9. data/contrib/quix/lib/quix/builtin/module/private.rb +41 -0
  10. data/contrib/quix/lib/quix/config.rb +37 -0
  11. data/contrib/quix/lib/quix/cygwin.rb +60 -0
  12. data/contrib/quix/lib/quix/diagnostic.rb +44 -0
  13. data/contrib/quix/lib/quix/enumerable.rb +33 -0
  14. data/contrib/quix/lib/quix/fileutils.rb +37 -0
  15. data/contrib/quix/lib/quix/hash_struct.rb +27 -0
  16. data/contrib/quix/lib/quix/kernel.rb +61 -0
  17. data/contrib/quix/lib/quix/lazy_struct.rb +55 -0
  18. data/contrib/quix/lib/quix/simple_installer.rb +87 -0
  19. data/contrib/quix/lib/quix/string.rb +38 -0
  20. data/contrib/quix/lib/quix/subpackager.rb +52 -0
  21. data/contrib/quix/lib/quix/thread_local.rb +32 -0
  22. data/contrib/quix/lib/quix/vars.rb +138 -0
  23. data/contrib/quix/lib/quix.rb +32 -0
  24. data/contrib/quix/test/all.rb +12 -0
  25. data/contrib/quix/test/test_deps.rb +25 -0
  26. data/contrib/quix/test/test_include.rb +47 -0
  27. data/contrib/quix/test/test_private.rb +86 -0
  28. data/contrib/quix/test/test_root.rb +19 -0
  29. data/contrib/quix/test/test_struct.rb +48 -0
  30. data/contrib/quix/test/test_vars.rb +187 -0
  31. data/install.rb +3 -0
  32. data/lib/comp_tree/algorithm.rb +210 -0
  33. data/lib/comp_tree/bucket_ipc.rb +151 -0
  34. data/lib/comp_tree/driver.rb +267 -0
  35. data/lib/comp_tree/error.rb +27 -0
  36. data/lib/comp_tree/node.rb +165 -0
  37. data/lib/comp_tree/quix/builtin/kernel/tap.rb +33 -0
  38. data/lib/comp_tree/quix/diagnostic.rb +68 -0
  39. data/lib/comp_tree/quix/kernel.rb +85 -0
  40. data/lib/comp_tree/retriable_fork.rb +42 -0
  41. data/lib/comp_tree/task_node.rb +22 -0
  42. data/lib/comp_tree.rb +23 -0
  43. data/test/all.rb +12 -0
  44. data/test/test_bucketipc.rb +72 -0
  45. data/test/test_circular.rb +36 -0
  46. data/test/test_comp_tree.rb +364 -0
  47. data/test/test_exception.rb +97 -0
  48. metadata +120 -0
data/lib/comp_tree.rb ADDED
@@ -0,0 +1,23 @@
1
+ #
2
+ # Copyright (c) 2008 James M. Lawrence. All rights reserved.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+
23
+ require 'comp_tree/driver'
data/test/all.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ require 'rbconfig'
3
+
4
+ Dir["#{File.dirname(__FILE__)}/test_*.rb"].map { |file|
5
+ File.expand_path(file)
6
+ }.each { |file|
7
+ # spawn separate processes to avoid EAGAIN signals on fork
8
+ ruby = File.join(
9
+ Config::CONFIG["bindir"],
10
+ Config::CONFIG["RUBY_INSTALL_NAME"])
11
+ system(ruby, file)
12
+ }
@@ -0,0 +1,72 @@
1
+
2
+ $LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
3
+
4
+ require 'test/unit'
5
+ require 'comp_tree/bucket_ipc'
6
+
7
+ Thread.abort_on_exception = true
8
+
9
+ class BucketTest < Test::Unit::TestCase
10
+ include CompTree::RetriableFork
11
+
12
+ def each_bucket(num_buckets, &block)
13
+ if HAVE_FORK
14
+ CompTree::BucketIPC::Driver.new(num_buckets) { |buckets|
15
+ buckets.each { |bucket|
16
+ yield bucket
17
+ }
18
+ }
19
+ end
20
+ end
21
+
22
+ def test_1_no_fork
23
+ each_bucket(10) { |bucket|
24
+ local = bucket.contents = :before
25
+ bucket.contents = :after
26
+ assert_equal(local, :before)
27
+ assert_equal(bucket.contents, :after)
28
+ }
29
+ end
30
+
31
+ def test_2_fork
32
+ each_bucket(10) { |bucket|
33
+ local = bucket.contents = :before
34
+ process_id = fork {
35
+ bucket.contents = :after
36
+ }
37
+ Process.wait(process_id)
38
+ assert_equal(local, :before)
39
+ assert_equal(bucket.contents, :after)
40
+ }
41
+ end
42
+
43
+ def each_base_test
44
+ [
45
+ :test_1_no_fork,
46
+ :test_2_fork,
47
+ ].each { |method|
48
+ yield method
49
+ }
50
+ end
51
+
52
+ def test_3_thread
53
+ each_base_test { |method|
54
+ Thread.new {
55
+ send(method)
56
+ }.join
57
+ }
58
+ end
59
+
60
+ def test_4_thread_flood
61
+ each_base_test { |method|
62
+ (0...10).map {
63
+ Thread.new {
64
+ send(method)
65
+ }
66
+ }.each { |thread|
67
+ thread.join
68
+ }
69
+ }
70
+ end
71
+ end
72
+
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
2
+
3
+ require 'comp_tree'
4
+ require 'test/unit'
5
+
6
+ module CompTree
7
+ class TestCircular < Test::Unit::TestCase
8
+ def test_1
9
+ CompTree::Driver.new { |driver|
10
+ driver.define(:area, :width, :height, :offset) { |width, height, offset|
11
+ width*height - offset
12
+ }
13
+
14
+ driver.define(:width, :border) { |border|
15
+ 2 + border
16
+ }
17
+
18
+ driver.define(:height, :border) { |border|
19
+ 3 + border
20
+ }
21
+
22
+ driver.define(:border) {
23
+ 5
24
+ }
25
+
26
+ driver.define(:offset, :area) {
27
+ 7
28
+ }
29
+
30
+ assert_raises(Error::CircularError) {
31
+ driver.check_circular(:area)
32
+ }
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,364 @@
1
+
2
+ $LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
3
+
4
+ require 'test/unit'
5
+ require 'benchmark'
6
+
7
+ require 'comp_tree'
8
+
9
+ srand(22)
10
+
11
+ module CompTree
12
+ Thread.abort_on_exception = true
13
+ HAVE_FORK = RetriableFork::HAVE_FORK
14
+ DO_FORK = (HAVE_FORK and not ARGV.include?("--no-fork"))
15
+
16
+ module TestCommon
17
+ include Quix::Diagnostic
18
+
19
+ if ARGV.include?("--bench")
20
+ def separator
21
+ trace ""
22
+ trace "-"*60
23
+ end
24
+ else
25
+ def separator ; end
26
+ def trace(*args) ; end
27
+ end
28
+ end
29
+
30
+ module TestBase
31
+ include TestCommon
32
+
33
+ def test_1_syntax
34
+ CompTree::Driver.new { |driver|
35
+ driver.define(:area, :width, :height, :offset) { |width, height, offset|
36
+ width*height - offset
37
+ }
38
+
39
+ driver.define(:width, :border) { |border|
40
+ 2 + border
41
+ }
42
+
43
+ driver.define(:height, :border) { |border|
44
+ 3 + border
45
+ }
46
+
47
+ driver.define(:border) {
48
+ 5
49
+ }
50
+
51
+ driver.define(:offset) {
52
+ 7
53
+ }
54
+
55
+ assert_equal((2 + 5)*(3 + 5) - 7,
56
+ driver.compute(:area, opts(6)))
57
+ }
58
+ end
59
+
60
+ def test_2_syntax
61
+ CompTree::Driver.new { |driver|
62
+ driver.define_area(:width, :height, :offset) { |width, height, offset|
63
+ width*height - offset
64
+ }
65
+
66
+ driver.define_width(:border) { |border|
67
+ 2 + border
68
+ }
69
+
70
+ driver.define_height(:border) { |border|
71
+ 3 + border
72
+ }
73
+
74
+ driver.define_border {
75
+ 5
76
+ }
77
+
78
+ driver.define_offset {
79
+ 7
80
+ }
81
+
82
+ assert_equal((2 + 5)*(3 + 5) - 7,
83
+ driver.compute(:area, opts(6)))
84
+ }
85
+ end
86
+
87
+ def test_3_syntax
88
+ CompTree::Driver.new { |driver|
89
+ driver.define_area :width, :height, :offset, %{
90
+ width*height - offset
91
+ }
92
+
93
+ driver.define_width :border, %{
94
+ 2 + border
95
+ }
96
+
97
+ driver.define_height :border, %{
98
+ 3 + border
99
+ }
100
+
101
+ driver.define_border %{
102
+ 5
103
+ }
104
+
105
+ driver.define_offset %{
106
+ 7
107
+ }
108
+
109
+ assert_equal((2 + 5)*(3 + 5) - 7,
110
+ driver.compute(:area, opts(6)))
111
+ }
112
+ end
113
+
114
+ def test_thread_flood
115
+ max =
116
+ if use_fork?
117
+ 16
118
+ else
119
+ 200
120
+ end
121
+ (1..max).each { |threads|
122
+ CompTree::Driver.new { |driver|
123
+ drain = lambda {
124
+ 1.times { }
125
+ }
126
+ driver.define_a(:b, &drain)
127
+ driver.define_b(&drain)
128
+ driver.compute(:a, opts(threads))
129
+ }
130
+ }
131
+ end
132
+
133
+ def test_malformed
134
+ CompTree::Driver.new { |driver|
135
+ assert_raise(CompTree::Error::ArgumentError) {
136
+ driver.define {
137
+ }
138
+ }
139
+ assert_raise(CompTree::Error::RedefinitionError) {
140
+ driver.define(:a) {
141
+ }
142
+ driver.define(:a) {
143
+ }
144
+ }
145
+ assert_raise(CompTree::Error::ArgumentError) {
146
+ driver.define(:b) {
147
+ }
148
+ driver.compute(:b, :threads => 0)
149
+ }
150
+ assert_raise(CompTree::Error::ArgumentError) {
151
+ driver.define(:c) {
152
+ }
153
+ driver.compute(:c, :threads => -1)
154
+ }
155
+ }
156
+ end
157
+
158
+ def generate_comp_tree(num_levels, num_children, drain_iterations)
159
+ CompTree::Driver.new { |driver|
160
+ root = :aaa
161
+ last_name = root
162
+ pick_names = lambda {
163
+ (0..rand(num_children)).map {
164
+ last_name = last_name.to_s.succ.to_sym
165
+ }
166
+ }
167
+ drain = lambda {
168
+ drain_iterations.times {
169
+ }
170
+ }
171
+ build_tree = lambda { |parent, children, level|
172
+ trace "building #{parent} --> #{children.join(' ')}"
173
+
174
+ driver.define(parent, *children, &drain)
175
+
176
+ if level < num_levels
177
+ children.each { |child|
178
+ build_tree.call(child, pick_names.call, level + 1)
179
+ }
180
+ else
181
+ children.each { |child|
182
+ driver.define(child, &drain)
183
+ }
184
+ end
185
+ }
186
+ build_tree.call(root, pick_names.call, drain_iterations)
187
+ }
188
+ end
189
+
190
+ def run_generated_tree(args)
191
+ args[:level_range].each { |num_levels|
192
+ args[:children_range].each { |num_children|
193
+ separator
194
+ trace {%{num_levels}}
195
+ trace {%{num_children}}
196
+ trace {%{use_fork?}}
197
+ driver = generate_comp_tree(
198
+ num_levels,
199
+ num_children,
200
+ args[:drain_iterations])
201
+ args[:thread_range].each { |threads|
202
+ trace {%{threads}}
203
+ 2.times {
204
+ driver.reset(:aaa)
205
+ result = nil
206
+ trace Benchmark.measure {
207
+ result = driver.compute(:aaa, opts(threads))
208
+ }
209
+ assert_equal(result, args[:drain_iterations])
210
+ }
211
+ }
212
+ }
213
+ }
214
+ end
215
+
216
+ def test_generated_tree
217
+ if use_fork?
218
+ run_generated_tree(
219
+ :level_range => 4..4,
220
+ :children_range => 4..4,
221
+ :thread_range => 8..8,
222
+ :drain_iterations => 0)
223
+ else
224
+ run_generated_tree(
225
+ :level_range => 4..4,
226
+ :children_range => 4..4,
227
+ :thread_range => 8..8,
228
+ :drain_iterations => 0)
229
+ end
230
+ end
231
+
232
+ def use_fork?
233
+ not opts(0)[:fork].nil?
234
+ end
235
+ end
236
+
237
+ module NoForkTestBase
238
+ include TestBase
239
+ def opts(threads)
240
+ {
241
+ :threads => threads,
242
+ }
243
+ end
244
+ end
245
+
246
+ module ForkTestBase
247
+ include TestBase
248
+ def opts(threads)
249
+ {
250
+ :threads => threads,
251
+ :fork => HAVE_FORK,
252
+ }
253
+ end
254
+ end
255
+
256
+ class Test_1_NoFork < Test::Unit::TestCase
257
+ include NoForkTestBase
258
+ end
259
+
260
+ if DO_FORK
261
+ class Test_2_Fork < Test::Unit::TestCase
262
+ include ForkTestBase
263
+ end
264
+ end
265
+
266
+ class Test_Task < Test::Unit::TestCase
267
+ def test_task
268
+ CompTree::Driver.new(:discard_result => true) { |driver|
269
+ visit = 0
270
+ mutex = Mutex.new
271
+ func = lambda {
272
+ mutex.synchronize {
273
+ visit += 1
274
+ }
275
+ }
276
+ driver.define_a(:b, :c, &func)
277
+ driver.define_b(&func)
278
+ driver.define_c(:d, &func)
279
+ driver.define_d(&func)
280
+
281
+ (2..10).each { |threads|
282
+ assert_equal(
283
+ true,
284
+ driver.compute(
285
+ :a,
286
+ :threads => threads))
287
+ assert_equal(visit, 4)
288
+ driver.reset(:a)
289
+ visit = 0
290
+ }
291
+
292
+ (2..10).each { |threads|
293
+ assert_equal(
294
+ true,
295
+ driver.compute(
296
+ :a,
297
+ :threads => threads,
298
+ :fork => HAVE_FORK))
299
+ if HAVE_FORK
300
+ assert_equal(visit, 0)
301
+ else
302
+ assert_equal(visit, 4)
303
+ end
304
+ driver.reset(:a)
305
+ visit = 0
306
+ }
307
+ }
308
+ end
309
+ end
310
+
311
+ class Test_Drainer < Test::Unit::TestCase
312
+ include TestCommon
313
+
314
+ def drain(opts)
315
+ code = %{ 5000.times { } }
316
+ if opts[:fork]
317
+ eval code
318
+ else
319
+ system("ruby", "-e", code)
320
+ end
321
+ end
322
+
323
+ def run_drain(opts)
324
+ CompTree::Driver.new { |driver|
325
+ func = lambda {
326
+ drain(opts)
327
+ }
328
+ driver.define_area(:width, :height, :offset, &func)
329
+ driver.define_width(:border, &func)
330
+ driver.define_height(:border, &func)
331
+ driver.define_border(&func)
332
+ driver.define_offset(&func)
333
+ trace "number of threads: #{opts[:threads]}"
334
+ trace Benchmark.measure {
335
+ driver.compute(:area, opts)
336
+ }
337
+ }
338
+ end
339
+
340
+ def each_drain
341
+ (1..10).each { |threads|
342
+ yield threads
343
+ }
344
+ end
345
+
346
+ def test_no_fork
347
+ separator
348
+ trace "Subrocess test."
349
+ each_drain { |threads|
350
+ run_drain({:threads => threads})
351
+ }
352
+ end
353
+
354
+ if DO_FORK
355
+ def test_fork
356
+ separator
357
+ trace "Forking test."
358
+ each_drain { |threads|
359
+ run_drain({:threads => threads, :fork => HAVE_FORK})
360
+ }
361
+ end
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,97 @@
1
+
2
+ #
3
+ # Mean workaround using separate processes due to assert_raise causing
4
+ # problems with threads.
5
+ #
6
+
7
+ require 'test/unit'
8
+
9
+ module WorkaroundConfig
10
+ HERE = File.dirname(__FILE__)
11
+ LIB_DIR = File.expand_path("#{HERE}/../lib")
12
+ QUIX_LIB_DIR = File.expand_path("#{HERE}/../contrib/quix/lib")
13
+ OUTPUT_FILE = "#{HERE}/#{File.basename(__FILE__)}.output"
14
+
15
+ $LOAD_PATH.unshift LIB_DIR
16
+ $LOAD_PATH.unshift QUIX_LIB_DIR
17
+ end
18
+
19
+ require 'comp_tree'
20
+ require 'quix/config'
21
+
22
+ module CompTree
23
+ class TestRaises < Test::Unit::TestCase
24
+ include WorkaroundConfig
25
+
26
+ def test_exception
27
+ if RUBY_PLATFORM =~ %r!java!
28
+ puts "skipping #{File.basename(__FILE__)}."
29
+ else
30
+ [true, false].each { |use_fork|
31
+ [true, false].each { |define_all|
32
+ assert(
33
+ !system(
34
+ ::Quix::Config.ruby_executable,
35
+ "-e",
36
+ code(use_fork, define_all)))
37
+
38
+ output = File.read(OUTPUT_FILE)
39
+
40
+ if define_all
41
+ assert_match(%r!CompTreeTestError!, output)
42
+ else
43
+ assert_match(%r!NoFunctionError!, output)
44
+ end
45
+
46
+ File.unlink(OUTPUT_FILE) # leave when exception raised above
47
+ }
48
+ }
49
+ end
50
+ end
51
+
52
+ def code(use_fork, define_all)
53
+ %Q(
54
+ $LOAD_PATH.unshift '#{LIB_DIR}'
55
+ require 'comp_tree'
56
+ require 'open3'
57
+
58
+ class CompTreeTestError < Exception ; end
59
+
60
+ CompTree::Driver.new { |driver|
61
+ driver.define(:area, :width, :height, :offset) {
62
+ |width, height, offset|
63
+ width*height - offset
64
+ }
65
+
66
+ driver.define(:width, :border) { |border|
67
+ 2 + border
68
+ }
69
+
70
+ driver.define(:height, :border) { |border|
71
+ 3 + border
72
+ }
73
+ ) +
74
+ if define_all
75
+ %Q(
76
+ driver.define(:border) {
77
+ raise CompTreeTestError
78
+ }
79
+ )
80
+ else
81
+ ""
82
+ end +
83
+ %Q(
84
+ driver.define(:offset) {
85
+ 7
86
+ }
87
+
88
+ File.open('#{OUTPUT_FILE}', "w") { |out|
89
+ $stderr = out
90
+ driver.compute(
91
+ :area, :threads => 99, :fork => #{use_fork.inspect})
92
+ }
93
+ }
94
+ )
95
+ end
96
+ end
97
+ end