computable 0.1.0 → 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.
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