computable 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9d62c2956d0f777bd65d7fc308e035505a1a1912
4
- data.tar.gz: d18de2360da33738fe19adee655774055472825e
2
+ SHA256:
3
+ metadata.gz: 5aa6e8a15124c51c77b6849974a4a8bec2badd58ee90532eabae5d974d2e1342
4
+ data.tar.gz: 47e2605924a75967629be1e6c9f1fa263f5c577771ddad3ffc05660a300734d3
5
5
  SHA512:
6
- metadata.gz: d18be4067d65ea266bcc2d461a595e50392af48686f48145c4578107cc1a84bf6a2255b1cfc0b3aaf999d9f6cd4947d88f7359f49791b9b990f0479cae331225
7
- data.tar.gz: 0aabd0dac09732d5a000c68c051173cd4f1eb8485603e03d469d6568221eda485512e2974c95e536879a3926a430b805f52d96b45ea4742dbfc619e9727b758b
6
+ metadata.gz: f394e585c1da2f6270ae4bfd2e520506738603ee07a5a7ddf1485a2aba061bf74bd40d61a4c4a3be74a8a89d279f97eb792179411cbbfe2b154b6cd4f62c9a0e
7
+ data.tar.gz: 28a0950d0fcd04a80eb865a62997f4cd5f8a577cfe8cb1e2854b672bb51a95051b61798dc50d3f74a0dcabe5706be3454908becba91405b2a7e1e07bcd0376c4
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ job_test:
7
+ name: CI
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ include:
12
+ - os: windows
13
+ ruby: "head"
14
+ - os: ubuntu
15
+ ruby: "head"
16
+ - os: ubuntu
17
+ ruby: "3.1"
18
+ - os: ubuntu
19
+ ruby: "2.5"
20
+ - os: ubuntu
21
+ ruby: "truffleruby"
22
+ NO_TIMING_TESTS: true
23
+ - os: ubuntu
24
+ ruby: "jruby"
25
+ - os: macos
26
+ ruby: "head"
27
+ NO_TIMING_TESTS: true
28
+
29
+ runs-on: ${{ matrix.os }}-latest
30
+ env:
31
+ NO_TIMING_TESTS: ${{ matrix.NO_TIMING_TESTS }}
32
+
33
+ steps:
34
+
35
+ - uses: actions/checkout@v2
36
+ - name: Set up Ruby
37
+ uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby }}
40
+
41
+ - run: bundle install
42
+
43
+ - name: Run tests
44
+ run: bundle exec rake test
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.4"
4
+ - "3.1"
5
+ script: bundle exec rake test
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![Build Status Github Actions](https://github.com/larskanis/computable/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/larskanis/computable/actions/workflows/ci.yml)
2
+ [![Build Status Travis CI](https://app.travis-ci.com/larskanis/computable.svg?branch=master)](https://app.travis-ci.com/larskanis/computable)
3
+
1
4
  # Computable
2
5
 
3
6
  TODO: Write a gem description
data/Rakefile CHANGED
@@ -3,5 +3,5 @@ require "bundler/gem_tasks"
3
3
  task :gem => :build
4
4
 
5
5
  task :test do
6
- sh "ruby -w -W2 -I. -Ilib -e \"#{Dir["test/test_*.rb"].map{|f| "require '#{f}';"}.join}\" -- -v"
6
+ sh "ruby -w -W2 -I. -Ilib -e \"#{Dir["test/test_*.rb"].map{|f| "require '#{f}';"}.join}\" -- -v #{ENV["TESTOPT"]}"
7
7
  end
data/computable.gemspec CHANGED
@@ -12,12 +12,14 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = %q{Define computation tasks with automatic caching and dependency tracking.}
13
13
  spec.homepage = "https://github.com/larskanis/computable"
14
14
  spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.4"
15
16
 
16
17
  spec.files = `git ls-files`.split($/)
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
20
  spec.require_paths = ["lib"]
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.4"
22
+ spec.add_development_dependency "bundler", ">= 1.4", "< 3.0"
22
23
  spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "minitest", "~> 5.6"
23
25
  end
@@ -1,3 +1,3 @@
1
1
  class Computable
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/computable.rb CHANGED
@@ -16,29 +16,41 @@ class Computable
16
16
  end
17
17
 
18
18
  class Variable
19
- attr_accessor :name, :calc_method, :used_for, :expired_from, :value, :value_calced, :count
20
- def initialize(name, calc_method)
19
+ attr_accessor :name, :calc_method, :used_for, :expired_from, :value, :value_calced, :count, :in_process, :recalc_error
20
+
21
+ def initialize(name, calc_method, comp, mutex)
21
22
  @name = name
22
23
  @calc_method = calc_method
24
+ @comp = comp
25
+ @mutex = mutex
23
26
  @used_for = {}
24
27
  @expired_from = {}
25
28
  @count = 0
26
29
  @value = Unknown
30
+ @in_process = false
31
+ @recalc_error = nil
27
32
  end
28
33
 
29
34
  def inspect
30
- "<Variable #{name} used_for:#{used_for.keys} expired_from:#{expired_from.keys} has value:#{value==Unknown} value_calced:#{value_calced.inspect}>"
35
+ has = @recalc_error ? "error!" : "value:#{Unknown!=value}"
36
+ "<Variable #{name} used_for:#{used_for.keys} expired_from:#{expired_from.keys} has #{has} value_calced:#{value_calced.inspect}>"
31
37
  end
32
38
 
33
39
  def calc!
34
40
  self.count += 1
35
- calc_method.call(self)
41
+ self.value_calced = true
42
+ @mutex.unlock
43
+ begin
44
+ calc_method.call(self)
45
+ ensure
46
+ @mutex.lock
47
+ end
36
48
  end
37
49
 
38
50
  def expire_value
39
51
  return if used_for.empty?
40
52
 
41
- puts "expire #{inspect}" if Computable.computable_debug
53
+ puts "expire #{inspect}" if @comp.computable_debug
42
54
  used_for.each do |name2, v2|
43
55
  if v2.value_calced && !v2.expired_from[name]
44
56
  v2.expire_value
@@ -50,7 +62,7 @@ class Computable
50
62
  def revoke_expire
51
63
  return if used_for.empty?
52
64
 
53
- puts "revoke expire #{inspect}" if Computable.computable_debug
65
+ puts "revoke expire #{inspect}" if @comp.computable_debug
54
66
  used_for.each do |name2, v2|
55
67
  if v2.value_calced && v2.expired_from.delete(name) && v2.expired_from.empty?
56
68
  v2.revoke_expire
@@ -58,49 +70,177 @@ class Computable
58
70
  end
59
71
  end
60
72
 
73
+ def process_recalced_value(recalced_value, err)
74
+ if err
75
+ self.recalc_error = err
76
+ self.value = Unknown
77
+ used_for.clear
78
+ elsif self.value == recalced_value
79
+ revoke_expire
80
+ else
81
+ self.recalc_error = nil
82
+ self.value = recalced_value
83
+ used_for.clear
84
+ end
85
+ expired_from.clear
86
+ end
87
+
61
88
  def recalc_value
62
89
  return if !value_calced || expired_from.empty?
63
90
 
64
- puts "recalc #{inspect}" if Computable.computable_debug
91
+ puts "recalc #{inspect}" if @comp.computable_debug
65
92
  expired_from.each do |name2, v2|
66
93
  v2.recalc_value
67
94
  end
68
95
 
69
96
  unless expired_from.empty?
70
- recalced_value = self.calc!
71
- if self.value == recalced_value
72
- revoke_expire
97
+ begin
98
+ recalced_value = self.calc!
99
+ rescue Exception => err
100
+ end
101
+ process_recalced_value(recalced_value, err)
102
+ end
103
+ end
104
+
105
+ def new_worker(from_workers, to_workers)
106
+ Thread.new do
107
+ while v = to_workers.pop
108
+ puts "recalc parallel #{v.inspect}" if @comp.computable_debug
109
+ err = nil
110
+ begin
111
+ recalced_value = v.calc_method.call(v)
112
+ rescue Exception => err
113
+ end
114
+ from_workers.push([v, recalced_value, err])
115
+ end
116
+ end
117
+ end
118
+
119
+ def recalc_parallel(max_threads)
120
+ workers = []
121
+ from_workers = Queue.new
122
+ to_workers = Queue.new
123
+
124
+ master_loop(max_threads, workers, from_workers, to_workers)
125
+
126
+ to_workers.close
127
+ workers.each { |t| t.join }
128
+ end
129
+
130
+ def master_loop(max_threads, workers, from_workers, to_workers)
131
+ num_working = 0
132
+ loop do
133
+ if num_working == max_threads || !(node = find_recalcable)
134
+ #
135
+ # maxed out or no nodes available -- wait for results
136
+ #
137
+ return if num_working == 0
138
+
139
+ puts "recalc join" if @comp.computable_debug
140
+ @mutex.unlock
141
+ begin
142
+ node, recalced_value, err = from_workers.pop
143
+ ensure
144
+ @mutex.lock
145
+ end
146
+ node.in_process = false
147
+ num_working -= 1
148
+
149
+ if err
150
+ # Add the backtrace of the caller to the small in-thread backtrace for better debugging
151
+ err.set_backtrace(err.backtrace + caller)
152
+ end
153
+
154
+ node.process_recalced_value(recalced_value, err)
73
155
  else
74
- self.value = recalced_value
75
- used_for.clear
156
+ #
157
+ # not maxed out and found a node -- compute it
158
+ #
159
+ if (max_threads && workers.size < max_threads) ||
160
+ (!max_threads && num_working == workers.size)
161
+ workers << new_worker(from_workers, to_workers)
162
+ end
163
+ node.in_process = true
164
+ node.count += 1
165
+ node.value_calced = true
166
+ num_working += 1
167
+ to_workers.push(node)
168
+ end
169
+ end
170
+ end
171
+
172
+ def find_recalcable
173
+ if !value_calced || expired_from.empty? || in_process
174
+ nil
175
+ elsif expired_from.all?{ |_, v2| !v2.value_calced || v2.expired_from.empty? }
176
+ self
177
+ else
178
+ expired_from.each do |_, v2|
179
+ node = v2.find_recalcable and return node
76
180
  end
181
+ nil
182
+ end
183
+ end
184
+
185
+ def assign_value(value)
186
+ unless self.value == value
187
+ expire_value
77
188
  expired_from.clear
189
+ used_for.clear
190
+ self.value = value
191
+ end
192
+ self.value_calced = false
193
+ end
194
+
195
+ def query_value(kaller)
196
+ if kaller
197
+ v2 = used_for[kaller.name]
198
+ if v2
199
+ if Unknown==value && Unknown==v2.value && value_calced && v2.value_calced
200
+ raise RecursionDetected, "#{v2.name} depends on #{name}, but #{name} could not be computed without #{v2.name}"
201
+ end
202
+ else
203
+ used_for[kaller.name] = kaller
204
+ end
205
+ end
206
+
207
+ max_threads = @comp.computable_max_threads
208
+ if !max_threads || max_threads > 0
209
+ recalc_parallel(max_threads)
210
+ else
211
+ recalc_value
78
212
  end
213
+
214
+ raise recalc_error if recalc_error
215
+ self.value = calc! if Unknown==value
216
+ self.value
79
217
  end
80
218
  end
81
219
 
82
- @@debug = false
83
- def self.computable_debug=(v)
84
- @@debug = v
220
+ def computable_debug=(v)
221
+ @computable_debug = v
222
+ end
223
+ def computable_debug
224
+ @computable_debug
225
+ end
226
+
227
+ def computable_max_threads=(v)
228
+ @computable_max_threads = v
85
229
  end
86
- def self.computable_debug
87
- @@debug
230
+ def computable_max_threads
231
+ @computable_max_threads
88
232
  end
89
233
 
90
- def computable_display_dot(params={})
234
+ def computable_display_dot(**kwargs)
91
235
  IO.popen("dot -Tpng | display -", "w") do |fd|
92
- fd.puts computable_to_dot(params)
236
+ fd.puts computable_to_dot(**kwargs)
93
237
  end
94
238
  end
95
239
 
96
- def computable_to_dot(params={})
97
- rankdir = params.delete(:rankdir){ "TB" }
98
- multiline = params.delete(:multiline){ true }
99
- raise ArgumentError, "invalid params #{params.inspect}" unless params.empty?
100
-
240
+ def computable_to_dot(rankdir: "TB", multiline: true)
101
241
  dot = "digraph #{self.class.name.inspect} {\n"
102
242
  dot << "graph [ dpi = 45, rankdir=#{rankdir} ];\n"
103
- @variables.each do |name, v|
243
+ @computable_variables.each do |name, v|
104
244
  col = case
105
245
  when !v.value_calced then "color = red,"
106
246
  when !v.used_for.empty? then "color = green,"
@@ -120,82 +260,67 @@ class Computable
120
260
  end
121
261
 
122
262
  def initialize
123
- @variables = {}
124
- @caller = nil
263
+ @computable_debug = false
264
+ @computable_max_threads = 0
265
+ @computable_variables = {}
266
+ @computable_caller = nil
267
+ @computable_mutex = Mutex.new
125
268
  end
126
269
 
127
270
 
128
271
  def self.verify_format(name, value, format)
129
- if format && value!=Unknown && !(format === value)
272
+ if format && !(Unknown==value) && !(format === value)
130
273
  raise InvalidFormat, "variable '#{name}': value #{value.inspect} is not in format #{format.inspect}"
131
274
  end
132
275
  end
133
276
 
134
- def self.calc_value name, format=nil, params={}, &block
135
- freeze = params.delete(:freeze){ true }
136
- raise ArgumentError, "invalid params #{params.inspect}" unless params.empty?
137
-
277
+ def self.calc_value name, format=nil, freeze: true, &block
138
278
  calc_method_id = "calc_#{name}".intern
139
279
  define_method(calc_method_id, &block)
140
280
 
141
281
  calc_method2_id = "calc_#{name}_with_tracking".intern
142
282
  define_method(calc_method2_id) do |v|
143
283
  begin
144
- v.value_calced = true
145
- @caller, old_caller = v, @caller
284
+ old_caller = Thread.current.thread_variable_get("Computable #{object_id}")
285
+ Thread.current.thread_variable_set("Computable #{self.object_id}", v)
146
286
  begin
147
- puts "do calc #{v.inspect}" if @@debug
287
+ puts "do calc #{v.inspect}" if @computable_debug
148
288
  res = send(calc_method_id)
149
289
  Computable.verify_format(name, res, format)
150
290
  res.freeze if freeze
151
291
  res
152
292
  ensure
153
- @caller = old_caller
293
+ Thread.current.thread_variable_set("Computable #{self.object_id}", old_caller)
154
294
  end
155
295
  end
156
296
  end
157
297
 
158
298
  define_method("#{name}=") do |value|
159
299
  Computable.verify_format(name, value, format)
160
- v = @variables[name]
161
- puts "set #{name}: #{value.inspect} #{v.inspect}" if @@debug
162
- v = @variables[name] = Variable.new(name, method(calc_method2_id)) unless v
163
-
164
- unless v.value == value
165
- v.expire_value
166
- v.expired_from.clear
167
- v.used_for.clear
300
+ @computable_mutex.synchronize do
301
+ v = @computable_variables[name]
302
+ puts "set #{name}: #{value.inspect} #{v.inspect}" if @computable_debug
303
+ v = @computable_variables[name] = Variable.new(name, method(calc_method2_id), self, @computable_mutex) unless v
304
+
168
305
  value.freeze if freeze
169
- v.value = value
306
+ v.assign_value(value)
170
307
  end
171
- v.value_calced = false
172
308
  end
173
309
 
174
310
  define_method(name) do
175
- v = @variables[name]
176
- puts "called #{name} #{v.inspect}" if @@debug
177
- v = @variables[name] = Variable.new(name, method(calc_method2_id)) unless v
311
+ @computable_mutex.synchronize do
312
+ v = @computable_variables[name]
313
+ puts "called #{name} #{v.inspect}" if @computable_debug
314
+ v = @computable_variables[name] = Variable.new(name, method(calc_method2_id), self, @computable_mutex) unless v
178
315
 
179
- if @caller
180
- v2 = v.used_for[@caller.name]
181
- if v2
182
- if v.value==Unknown && v2.value==Unknown && v.value_calced && v2.value_calced
183
- raise RecursionDetected, "#{v2.name} depends on #{name}, but #{name} could not be computed without #{v2.name}"
184
- end
185
- else
186
- v.used_for[@caller.name] = @caller
187
- end
316
+ kaller = Thread.current.thread_variable_get("Computable #{object_id}")
317
+ v.query_value(kaller)
188
318
  end
189
-
190
- v.recalc_value
191
-
192
- v.value = v.calc! if v.value==Unknown
193
- v.value
194
319
  end
195
320
  end
196
321
 
197
- def self.input_value name, format=nil, params={}
198
- calc_value name, format, params do
322
+ def self.input_value name, format=nil, **kwargs
323
+ calc_value name, format, **kwargs do
199
324
  raise UndefinedValue, "input variable '#{name}' is not assigned"
200
325
  end
201
326
  end
data/test/helper.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'minitest/autorun'
2
+ require 'computable'
3
+
4
+ module Helper
5
+ module EnableParallel
6
+ def setup
7
+ super
8
+ @b.computable_max_threads = 100
9
+ end
10
+
11
+ def teardown
12
+ @b.computable_max_threads = 0
13
+ super
14
+ end
15
+ end
16
+ end
data/test/test_candy.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'computable'
1
+ require_relative 'helper'
3
2
 
4
- class TestCandy < MiniTest::Unit::TestCase
3
+ class TestCandy < Minitest::Test
5
4
  class MyBuilder < Computable
6
5
  attr_reader :counters
7
6
 
@@ -10,9 +9,9 @@ class TestCandy < MiniTest::Unit::TestCase
10
9
  @counters = {}
11
10
  end
12
11
 
13
- def self.counted_value name, format=nil, params={}, &block
12
+ def self.counted_value name, format=nil, **kwargs, &block
14
13
  define_method "#{name}_counted", &block
15
- calc_value name, format do
14
+ calc_value name, format, **kwargs do
16
15
  @counters[name] ||= 0
17
16
  @counters[name] += 1
18
17
  send "#{name}_counted"
@@ -102,3 +101,7 @@ class TestCandy < MiniTest::Unit::TestCase
102
101
  assert_equal [2,2,2,1,2,2,1,1], @b.counters.values_at(:top, :l0, :r0, :l1, :r1, :bot, :fl, :fr)
103
102
  end
104
103
  end
104
+
105
+ class TestCandyParallel < TestCandy
106
+ include Helper::EnableParallel
107
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'helper'
2
+
3
+ class TestDebug < Minitest::Test
4
+ class MyBuilder < Computable
5
+ input_value :inp
6
+ calc_value :cal do
7
+ inp * 2
8
+ end
9
+ end
10
+
11
+ def setup
12
+ @b = MyBuilder.new
13
+ end
14
+
15
+ def teardown
16
+ # @b.computable_display_dot
17
+ end
18
+
19
+ def test_enable_debug
20
+ refute @b.computable_debug
21
+ @b.computable_debug = true
22
+ assert @b.computable_debug
23
+ @b.computable_debug = false
24
+ refute @b.computable_debug
25
+ end
26
+
27
+ def test_prints_debug
28
+ @b.computable_debug = true
29
+
30
+ assert_output(/set inp/){ @b.inp = 3 }
31
+ cal = nil
32
+ assert_output(/do calc/){ cal = @b.cal }
33
+
34
+ assert_equal 6, cal
35
+ end
36
+ end
37
+
38
+ class TestDebugParallel < TestDebug
39
+ include Helper::EnableParallel
40
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'helper'
2
+
3
+ class TestExpiredRecalc < Minitest::Test
4
+ class MyBuilder < Computable
5
+ input_value :enable
6
+
7
+ calc_value :a do
8
+ b if enable
9
+ end
10
+
11
+ calc_value :b do
12
+ raise StandardError, "not enabled" unless enable
13
+ :x
14
+ end
15
+ end
16
+
17
+ def setup
18
+ @b = MyBuilder.new
19
+ end
20
+
21
+ def teardown
22
+ # @b.computable_display_dot
23
+ end
24
+
25
+ def test_on_off
26
+ @b.enable = true
27
+ assert_equal :x, @b.a
28
+
29
+ @b.enable = false
30
+ assert_nil @b.a # this shouldn't raise an error although b is internally recalced
31
+ assert_raises(StandardError){ @b.b }
32
+
33
+ @b.enable = true
34
+ assert_equal :x, @b.a
35
+ end
36
+ end
37
+
38
+ class TestExpiredRecalcParallel < TestExpiredRecalc
39
+ include Helper::EnableParallel
40
+ end
data/test/test_format.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'computable'
1
+ require_relative 'helper'
3
2
 
4
- class TestFormat < MiniTest::Unit::TestCase
3
+ class TestFormat < Minitest::Test
5
4
  class MyBuilder < Computable
6
5
  def self.verify_uniq_array(a)
7
6
  a.uniq.length == a.length
@@ -66,3 +65,7 @@ class TestFormat < MiniTest::Unit::TestCase
66
65
  assert_raises(Computable::InvalidFormat){ @b.sqrt }
67
66
  end
68
67
  end
68
+
69
+ class TestFormatParallel < TestFormat
70
+ include Helper::EnableParallel
71
+ end
data/test/test_freeze.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'computable'
1
+ require_relative 'helper'
3
2
 
4
- class TestFreeze < MiniTest::Unit::TestCase
3
+ class TestFreeze < Minitest::Test
5
4
  class MyBuilder < Computable
6
5
  input_value :no_freeze1, String, freeze: false
7
6
  calc_value :no_freeze2, String, freeze: false do
@@ -34,3 +33,7 @@ class TestFreeze < MiniTest::Unit::TestCase
34
33
  assert @b.freeze2.frozen?, "should be frozen"
35
34
  end
36
35
  end
36
+
37
+ class TestFreezeParallel < TestFreeze
38
+ include Helper::EnableParallel
39
+ end
@@ -1,7 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'computable'
1
+ require_relative 'helper'
3
2
 
4
- class TestInputValue < MiniTest::Unit::TestCase
3
+ class TestInputValue < Minitest::Test
5
4
  class MyBuilder < Computable
6
5
  input_value :i
7
6
 
@@ -46,3 +45,7 @@ class TestInputValue < MiniTest::Unit::TestCase
46
45
  assert_equal 6, @b.o
47
46
  end
48
47
  end
48
+
49
+ class TestInputValueParallel < TestInputValue
50
+ include Helper::EnableParallel
51
+ end
@@ -0,0 +1,126 @@
1
+ require_relative 'helper'
2
+
3
+ class TestParallel < Minitest::Test
4
+ class MyBuilder < Computable
5
+ input_value :i1, Integer
6
+ input_value :i2, Integer
7
+
8
+ def self.p(idx)
9
+ calc_value "p#{idx}" do
10
+ sleep 0.1
11
+ i1
12
+ end
13
+ end
14
+
15
+ def self.q(idx)
16
+ calc_value "q#{idx}" do
17
+ sleep 0.1
18
+ send("p#{idx}") + i2
19
+ end
20
+ end
21
+
22
+ 0.upto(4) do |idx|
23
+ p idx
24
+ q idx
25
+ end
26
+
27
+ calc_value :o do
28
+ 0.upto(3).map do |idx|
29
+ send("q#{idx}")
30
+ end.inject(:+)
31
+ end
32
+
33
+ input_value :offs
34
+
35
+ 100.times do |idx|
36
+ calc_value "m0-#{idx}" do
37
+ #sleep 0.001
38
+ idx + offs
39
+ end
40
+ calc_value "m1-#{idx}" do
41
+ #sleep 0.001
42
+ idx + offs
43
+ end
44
+ calc_value "m-#{idx}" do
45
+ send("m#{offs}-#{idx}")
46
+ end
47
+ end
48
+
49
+ calc_value "n" do
50
+ 100.times.sum do |idx|
51
+ send("m-#{idx}")
52
+ end
53
+ end
54
+
55
+
56
+ class MyError < RuntimeError
57
+ end
58
+
59
+ input_value :enable_error
60
+ calc_value :raise_error do
61
+ raise MyError if enable_error
62
+ end
63
+ end
64
+
65
+ def setup
66
+ @b = MyBuilder.new
67
+ end
68
+
69
+ def teardown
70
+ # @b.computable_display_dot
71
+ end
72
+
73
+ def test_many_recalcs
74
+ @b.computable_max_threads = 10
75
+ @b.offs = 0
76
+ assert_equal 4950, @b.n
77
+
78
+ #@b.computable_debug = true
79
+ @b.offs = 1
80
+ assert_equal 5050, @b.n
81
+ end
82
+
83
+ def test_error
84
+ @b.enable_error = false
85
+ assert_nil @b.raise_error
86
+ @b.enable_error = true
87
+ assert_raises(MyBuilder::MyError){ @b.raise_error }
88
+ assert_raises(MyBuilder::MyError){ @b.raise_error }
89
+ @b.enable_error = false
90
+ assert_nil @b.raise_error
91
+ end
92
+
93
+ unless ENV["NO_TIMING_TESTS"]=="true"
94
+ def diff_time
95
+ st = Time.now
96
+ res = yield
97
+ [Time.now - st, res]
98
+ end
99
+
100
+ def test_full_parallel
101
+ @b.computable_max_threads = nil
102
+ @b.i1, @b.i2 = 2, 3
103
+ dt, res = diff_time{ @b.o }
104
+ assert_in_delta 0.8, dt, 0.05
105
+ assert_equal 20, res
106
+
107
+ @b.i1, @b.i2 = 4, 5
108
+ dt, res = diff_time{ @b.o }
109
+ assert_in_delta 0.2, dt, 0.05
110
+ assert_equal 36, res
111
+ end
112
+
113
+ def test_2_threads
114
+ @b.computable_max_threads = 2
115
+ @b.i1, @b.i2 = 2, 3
116
+ dt, res = diff_time{ @b.o }
117
+ assert_in_delta 0.8, dt, 0.05
118
+ assert_equal 20, res
119
+
120
+ @b.i1, @b.i2 = 4, 5
121
+ dt, res = diff_time{ @b.o }
122
+ assert_in_delta 0.4, dt, 0.05
123
+ assert_equal 36, res
124
+ end
125
+ end
126
+ end
@@ -1,7 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'computable'
1
+ require_relative 'helper'
3
2
 
4
- class TestRecursionDetection < MiniTest::Unit::TestCase
3
+ class TestRecursionDetection < Minitest::Test
5
4
  class MyBuilder < Computable
6
5
  calc_value :recursion1 do
7
6
  recursion1
@@ -34,3 +33,7 @@ class TestRecursionDetection < MiniTest::Unit::TestCase
34
33
  assert_raises(Computable::RecursionDetected){ @b.recursion2 }
35
34
  end
36
35
  end
36
+
37
+ class TestRecursionDetectionParallel < TestRecursionDetection
38
+ include Helper::EnableParallel
39
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'helper'
2
+
3
+ class TestUnknown < Minitest::Test
4
+ class MyTruth
5
+ def ==(obj)
6
+ true
7
+ end
8
+ end
9
+
10
+ class MyFault
11
+ def ==(obj)
12
+ false
13
+ end
14
+ end
15
+
16
+ class MyBuilder < Computable
17
+ input_value :v1
18
+
19
+ calc_value :v2 do
20
+ v1
21
+ end
22
+ end
23
+
24
+ def setup
25
+ @b = MyBuilder.new
26
+ end
27
+
28
+ def teardown
29
+ # @b.computable_display_dot
30
+ end
31
+
32
+ def test_eq_true
33
+ @b.v1 = v = MyTruth.new
34
+ assert_same @b.v1, v
35
+ assert_same @b.v2, v
36
+ end
37
+
38
+ def test_eq_false
39
+ @b.v1 = v = MyFault.new
40
+ assert_same @b.v1, v
41
+ assert_same @b.v2, v
42
+ end
43
+ end
44
+
45
+ class TestUnknownParallel < TestUnknown
46
+ include Helper::EnableParallel
47
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: computable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Kanis
@@ -10,9 +10,9 @@ bindir: bin
10
10
  cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
13
- MIIDaDCCAlCgAwIBAgIBATANBgkqhkiG9w0BAQUFADA9MQ4wDAYDVQQDDAVrYW5p
13
+ MIIDLjCCAhagAwIBAgIBCTANBgkqhkiG9w0BAQsFADA9MQ4wDAYDVQQDDAVrYW5p
14
14
  czEXMBUGCgmSJomT8ixkARkWB2NvbWNhcmQxEjAQBgoJkiaJk/IsZAEZFgJkZTAe
15
- Fw0xMzAyMjYwODQ1NTlaFw0xNDAyMjYwODQ1NTlaMD0xDjAMBgNVBAMMBWthbmlz
15
+ Fw0yMTA0MDcxMzQzNTZaFw0yMjA0MDcxMzQzNTZaMD0xDjAMBgNVBAMMBWthbmlz
16
16
  MRcwFQYKCZImiZPyLGQBGRYHY29tY2FyZDESMBAGCgmSJomT8ixkARkWAmRlMIIB
17
17
  IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApop+rNmg35bzRugZ21VMGqI6
18
18
  HGzPLO4VHYncWn/xmgPU/ZMcZdfj6MzIaZJ/czXyt4eHpBk1r8QOV3gBXnRXEjVW
@@ -20,32 +20,37 @@ cert_chain:
20
20
  lJi4+ENAVT4MpqHEAGB8yFoPC0GqiOHQsdHxQV3P3c2OZqG+yJey74QtwA2tLcLn
21
21
  Q53c63+VLGsOjODl1yPn/2ejyq8qWu6ahfTxiIlSar2UbwtaQGBDFdb2CXgEufXT
22
22
  L7oaPxlmj+Q2oLOfOnInd2Oxop59HoJCQPsg8f921J43NCQGA8VHK6paxIRDLQID
23
- AQABo3MwcTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUvgTdT7fe
24
- x17ugO3IOsjEJwW7KP4wGwYDVR0RBBQwEoEQa2FuaXNAY29tY2FyZC5kZTAbBgNV
25
- HRIEFDASgRBrYW5pc0Bjb21jYXJkLmRlMA0GCSqGSIb3DQEBBQUAA4IBAQCa3ThZ
26
- 9qjyuFXe0kN4IwgHTTSqob3zPOyXAxAq1k65w1/hI/6e4HxCSH7Ds+dKj/xhScEu
27
- K5gaya1D69Fo+JTnzLvuSt2X8+mEHclduC9j++oSGc+szd7LKdeEQ7J4RefJjhD+
28
- vWI6lqglL4PijN0nOWtm0ygzXEELDcGYpb2WJ++KKNVLIU6pkiWpZUmGcFB7NclV
29
- I64m9iNdgWnDwedgUlqSMfVCUUB9S1Y5jI+doxYloPvIB6+6VsI4cmN2LcK0rQO6
30
- N3pmmsS0N5772vAmRMyNl8PV1OzCLIMhgPgdeLpfU7LUSYWj67q5VuyjAaH5h68g
31
- MlGgwc//cCsBG8sa
23
+ AQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUvgTdT7fe
24
+ x17ugO3IOsjEJwW7KP4wDQYJKoZIhvcNAQELBQADggEBAGCQhS4TBqUG1bSY5gw5
25
+ emj2GNePHFNlXTZ/W0/7FlnXQz/LyBZeYmy4AIHcdY0w9xsu3bPNGk8kLBkHgK3Y
26
+ l/yWiUK0NYRI3K3yI2EoTfrHPDT8XIgBPeUUGv5Nje+SUYMQWsfYWKo3+vLEG64a
27
+ n1xP+1+g2Za39WCS5LwnDWMiIk47NnxR9yXErKd0Iau/Q/IarYsHFX6kWWmlMoln
28
+ W1lMomCcOJFwIPnsy6aqq7YfS0YcqyHjcvs1h5k3zPaIRWhoPlQivniMVMa3Txh+
29
+ NEF/4atY64rruzkyfxGEcrFFOHJIkWnWQjRGaiZdgULxf7ira2gEFvV/ZtamqJWF
30
+ c+I=
32
31
  -----END CERTIFICATE-----
33
- date: 2014-02-12 00:00:00.000000000 Z
32
+ date: 2022-03-25 00:00:00.000000000 Z
34
33
  dependencies:
35
34
  - !ruby/object:Gem::Dependency
36
35
  name: bundler
37
36
  requirement: !ruby/object:Gem::Requirement
38
37
  requirements:
39
- - - "~>"
38
+ - - ">="
40
39
  - !ruby/object:Gem::Version
41
40
  version: '1.4'
41
+ - - "<"
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
42
44
  type: :development
43
45
  prerelease: false
44
46
  version_requirements: !ruby/object:Gem::Requirement
45
47
  requirements:
46
- - - "~>"
48
+ - - ">="
47
49
  - !ruby/object:Gem::Version
48
50
  version: '1.4'
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
49
54
  - !ruby/object:Gem::Dependency
50
55
  name: rake
51
56
  requirement: !ruby/object:Gem::Requirement
@@ -60,6 +65,20 @@ dependencies:
60
65
  - - ">="
61
66
  - !ruby/object:Gem::Version
62
67
  version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: minitest
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.6'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '5.6'
63
82
  description: Define computation tasks with automatic caching and dependency tracking.
64
83
  email:
65
84
  - lars@greiz-reinsdorf.de
@@ -67,7 +86,9 @@ executables: []
67
86
  extensions: []
68
87
  extra_rdoc_files: []
69
88
  files:
89
+ - ".github/workflows/ci.yml"
70
90
  - ".gitignore"
91
+ - ".travis.yml"
71
92
  - Gemfile
72
93
  - LICENSE.txt
73
94
  - README.md
@@ -75,11 +96,16 @@ files:
75
96
  - computable.gemspec
76
97
  - lib/computable.rb
77
98
  - lib/computable/version.rb
99
+ - test/helper.rb
78
100
  - test/test_candy.rb
101
+ - test/test_debug.rb
102
+ - test/test_expired_recalc.rb
79
103
  - test/test_format.rb
80
104
  - test/test_freeze.rb
81
105
  - test/test_input_value.rb
106
+ - test/test_parallel.rb
82
107
  - test/test_recursion_detection.rb
108
+ - test/test_unknown.rb
83
109
  homepage: https://github.com/larskanis/computable
84
110
  licenses:
85
111
  - MIT
@@ -92,21 +118,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
118
  requirements:
93
119
  - - ">="
94
120
  - !ruby/object:Gem::Version
95
- version: '0'
121
+ version: '2.4'
96
122
  required_rubygems_version: !ruby/object:Gem::Requirement
97
123
  requirements:
98
124
  - - ">="
99
125
  - !ruby/object:Gem::Version
100
126
  version: '0'
101
127
  requirements: []
102
- rubyforge_project:
103
- rubygems_version: 2.2.0
128
+ rubygems_version: 3.3.7
104
129
  signing_key:
105
130
  specification_version: 4
106
131
  summary: Define computation tasks with automatic caching and dependency tracking.
107
132
  test_files:
133
+ - test/helper.rb
108
134
  - test/test_candy.rb
135
+ - test/test_debug.rb
136
+ - test/test_expired_recalc.rb
109
137
  - test/test_format.rb
110
138
  - test/test_freeze.rb
111
139
  - test/test_input_value.rb
140
+ - test/test_parallel.rb
112
141
  - test/test_recursion_detection.rb
142
+ - test/test_unknown.rb
metadata.gz.sig CHANGED
Binary file