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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +44 -0
- data/.travis.yml +5 -0
- data/README.md +3 -0
- data/Rakefile +1 -1
- data/computable.gemspec +3 -1
- data/lib/computable/version.rb +1 -1
- data/lib/computable.rb +190 -65
- data/test/helper.rb +16 -0
- data/test/test_candy.rb +8 -5
- data/test/test_debug.rb +40 -0
- data/test/test_expired_recalc.rb +40 -0
- data/test/test_format.rb +6 -3
- data/test/test_freeze.rb +6 -3
- data/test/test_input_value.rb +6 -3
- data/test/test_parallel.rb +126 -0
- data/test/test_recursion_detection.rb +6 -3
- data/test/test_unknown.rb +47 -0
- data.tar.gz.sig +0 -0
- metadata +48 -18
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5aa6e8a15124c51c77b6849974a4a8bec2badd58ee90532eabae5d974d2e1342
|
4
|
+
data.tar.gz: 47e2605924a75967629be1e6c9f1fa263f5c577771ddad3ffc05660a300734d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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", "
|
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
|
data/lib/computable/version.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
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
|
87
|
-
|
230
|
+
def computable_max_threads
|
231
|
+
@computable_max_threads
|
88
232
|
end
|
89
233
|
|
90
|
-
def computable_display_dot(
|
234
|
+
def computable_display_dot(**kwargs)
|
91
235
|
IO.popen("dot -Tpng | display -", "w") do |fd|
|
92
|
-
fd.puts computable_to_dot(
|
236
|
+
fd.puts computable_to_dot(**kwargs)
|
93
237
|
end
|
94
238
|
end
|
95
239
|
|
96
|
-
def computable_to_dot(
|
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
|
-
@
|
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
|
-
@
|
124
|
-
@
|
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
|
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,
|
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
|
-
|
145
|
-
|
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
|
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
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
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
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
180
|
-
|
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,
|
198
|
-
calc_value name, format,
|
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
data/test/test_candy.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
require 'computable'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
|
-
class TestCandy <
|
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,
|
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
|
data/test/test_debug.rb
ADDED
@@ -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
|
-
|
2
|
-
require 'computable'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
|
-
class TestFormat <
|
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
|
-
|
2
|
-
require 'computable'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
|
-
class TestFreeze <
|
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
|
data/test/test_input_value.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
require 'computable'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
|
-
class TestInputValue <
|
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
|
-
|
2
|
-
require 'computable'
|
1
|
+
require_relative 'helper'
|
3
2
|
|
4
|
-
class TestRecursionDetection <
|
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:
|
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
|
-
|
13
|
+
MIIDLjCCAhagAwIBAgIBCTANBgkqhkiG9w0BAQsFADA9MQ4wDAYDVQQDDAVrYW5p
|
14
14
|
czEXMBUGCgmSJomT8ixkARkWB2NvbWNhcmQxEjAQBgoJkiaJk/IsZAEZFgJkZTAe
|
15
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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:
|
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: '
|
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
|
-
|
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
|