laborantin 0.0.21 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -56,7 +56,13 @@ spec = Gem::Specification.new do |s|
56
56
  'lib/laborantin/extra/commands/git.rb',
57
57
  'lib/laborantin/extra/commands/git/check.rb',
58
58
  'lib/laborantin/extra/commands/git/run.rb',
59
- 'lib/laborantin/extra/vectorial_product.rb'
59
+ 'lib/laborantin/extra/vectorial_product.rb',
60
+ 'lib/laborantin/core/selector.rb',
61
+ 'lib/laborantin/core/dependencies.rb',
62
+ 'lib/laborantin/core/verifications.rb',
63
+ 'lib/laborantin/core/resolutions.rb',
64
+ 'lib/laborantin/core/dependency_solver.rb',
65
+ 'lib/laborantin/core/exports.rb',
60
66
  ]
61
67
 
62
68
  s.require_path = 'lib'
data/TODO CHANGED
@@ -1,3 +1,4 @@
1
+ *modularize logging
1
2
  new data structure:
2
3
  in config files (both env and scenarii):
3
4
  reference every rundir
@@ -17,7 +18,6 @@ commands:
17
18
  optimize (given a goal)
18
19
  estimate (cost, duration,
19
20
  whatever defined by the user, only total sum, products?)
20
- infos (gives info about the running environment)
21
21
  config dir:
22
22
  extra libraries to require
23
23
  general:
@@ -34,7 +34,7 @@ end
34
34
 
35
35
 
36
36
  module Laborantin
37
- VERSION = '0.0.21'
37
+ VERSION = '0.1.2'
38
38
  AUTHORS = ['Lucas Di Cioccio']
39
39
  WEBSITE = 'http://dicioccio.fr/laborantin'
40
40
  LICENSE = 'GNU GPL version 3'
@@ -25,6 +25,9 @@ autoload :ERB, 'erb'
25
25
 
26
26
  require 'laborantin/core/describable'
27
27
  require 'laborantin/core/multi_name'
28
+ require 'laborantin/core/selector'
29
+ require 'laborantin/core/dependencies'
30
+ require 'laborantin/core/exports'
28
31
 
29
32
  module Laborantin
30
33
  # An Analysis is a handy way to reload and filter the various scenarii that were
@@ -32,26 +35,14 @@ module Laborantin
32
35
  class Analysis
33
36
  extend Metaprog::Describable
34
37
  extend Metaprog::MultiName
38
+ include Metaprog::Selector
39
+ include Metaprog::Dependencies
40
+ include Metaprog::Exports
35
41
 
36
42
  class << self
37
- # A hash with two items, this might change later but KISS for now.
38
- attr_accessor :selectors
39
-
40
43
  # An array
41
44
  attr_accessor :analyses
42
45
 
43
- # Add a selector to filter for the analysis only the runs that pass the selector.
44
- # * sym objects (sym currently must be :environments or
45
- # :scenarii).
46
- # * ary is a set of classes, only runs of this classes will be loaded
47
- # * if a block is passed, only the instances for which the block is
48
- # evaluated as true will be selected (the block must take one parameter:
49
- # the tested instance)
50
- def select(sym, ary=[], &blk)
51
- @selectors ||= {}
52
- @selectors[sym] = {:klasses => ary, :blk => blk}
53
- end
54
-
55
46
  # Adds an analysis to this class.
56
47
  # str is a description of the added analysis params is a hash of
57
48
  # parameters for this analysis, specifically, the :type parameters allows
@@ -62,26 +53,6 @@ module Laborantin
62
53
  @analyses << {:str => str, :params => params, :blk => blk}
63
54
  end
64
55
 
65
- def plot(title, args, &blk)
66
- args ||= {}
67
- hash = args.merge({:type => :plot})
68
- analyze(title, hash, &blk)
69
- end
70
-
71
- def table(title, args, &blk)
72
- args ||= {}
73
- hash = args.merge({:type => :table})
74
- analyze(title, hash, &blk)
75
- end
76
-
77
- def plots
78
- analyses.select{|a| a[:params][:type] == :plots}
79
- end
80
-
81
- def tables
82
- analyses.select{|a| a[:params][:type] == :tables}
83
- end
84
-
85
56
  @@all = []
86
57
 
87
58
  def inherited(klass)
@@ -96,12 +67,6 @@ module Laborantin
96
67
  end
97
68
  end # << self
98
69
 
99
- # An array of the Environments that could be loaded from the result directory.
100
- attr_accessor :environments
101
-
102
- # An array of the Scenarii that could be loaded from the result directory.
103
- attr_reader :scenarii
104
-
105
70
  # TODO : recode this, maybe as nothing to do here
106
71
  def analyze
107
72
  self.class.analyses.each do |a|
@@ -109,6 +74,7 @@ module Laborantin
109
74
  instance_eval &a[:blk]
110
75
  puts "done"
111
76
  end
77
+ save_exports
112
78
  end
113
79
 
114
80
  # TODO: more flexible
@@ -146,54 +112,27 @@ module Laborantin
146
112
  Table.new(name, struct, self.output_path(name))
147
113
  end
148
114
 
149
- private
150
-
151
- # Just loads the environments and scenarii from the resultdir.
152
- def initialize(*args, &blk)
153
- load_from_results
154
- set_instance_vars
115
+ def export_file(mode='r', &blk)
116
+ output('exports.yaml', mode, &blk)
155
117
  end
156
118
 
157
- # Load first the environments, then the scenarii.
158
- def load_from_results
159
- load_environments
160
- load_scenarii
119
+ def export_path
120
+ output_path('exports.yaml')
161
121
  end
162
122
 
163
- # Sets the various handy instance variables:
164
- # * @plots
165
- # * @tables
166
- def set_instance_vars
167
- @plots = self.class.plots.dup
168
- @tables = self.class.tables.dup
169
- end
123
+ attr_reader :command
170
124
 
171
- # Will try to load environments and set the @environments variable to an
172
- # array of all the Environment instance that match the :environments class
173
- # selector (set with Analysis#select).
174
- def load_environments
175
- envs = Laborantin::Environment.scan_resdir('results')
176
- @environments = envs.select do |env|
177
- select_instance?(env, self.class.selectors[:environments])
178
- end
125
+ # Just loads the environments and scenarii from the resultdir.
126
+ def initialize(command = nil)
127
+ @command = command
128
+ load_prior_results
179
129
  end
180
130
 
181
- # Same as load_environments, but for Scenario instances and :scenarii
182
- # selector.
183
- def load_scenarii
184
- scii = @environments.map{|e| e.populate}.flatten
185
- @scenarii = scii.select do |sc|
186
- select_instance?(sc, self.class.selectors[:scenarii])
187
- end
131
+ def runner
132
+ command.runner if command
188
133
  end
189
134
 
190
- # Handy test to see if an object (that should be an instance of Environment
191
- # or Scenario) matches the selector.
192
- def select_instance?(obj, selector)
193
- blk = selector[:blk]
194
- (selector[:klasses].any?{|k| obj.is_a? k} ) and
195
- (blk ? blk.call(obj) : true)
196
- end
135
+ private
197
136
 
198
137
  # Nice way to iterate on @scenarii
199
138
  def each_scenario
@@ -0,0 +1,37 @@
1
+
2
+ require 'laborantin/core/verifications'
3
+
4
+ module Laborantin
5
+ module Metaprog
6
+ module Dependencies
7
+ class Dependency
8
+ include Metaprog::Describable
9
+ include Metaprog::Verifications
10
+ attr_reader :name
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+ end
15
+
16
+ def self.included(klass)
17
+ klass.extend ClassMethods
18
+ klass.dependencies = []
19
+ end
20
+
21
+ module ClassMethods
22
+ attr_accessor :dependencies
23
+
24
+ def dependencies
25
+ @dependencies ||= []
26
+ end
27
+
28
+ def dependency(name,&blk)
29
+ dep = Dependency.new(name)
30
+ dep.instance_eval &blk
31
+ dependencies << dep
32
+ dep
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module Laborantin
3
+ # Poor man's dependency greedy and recursive algorithm raises an error after
4
+ # two consecutive calls with the same previoulsy_wrong dependencies list
5
+ # we rely on the implementer to not create dependency loops.
6
+ # The whole dependency design is extremely wasteful, but it works.
7
+ module DependencySolver
8
+ def resolve_dependencies(obj, previously_wrong=[])
9
+ valid, wrong = obj.class.dependencies.partition do |dep|
10
+ dep.valid?(obj)
11
+ end
12
+
13
+ if wrong.empty?
14
+ puts "all dependencies met"
15
+ else
16
+ puts "unmet dependencies: #{wrong.map(&:name).join(' ')}"
17
+ if previously_wrong == wrong
18
+ raise RuntimeError, "already tried resolving these dependencies"
19
+ else
20
+ wrong.each do |dep|
21
+ # Since iterating changes the status of the dependency, we may want to re-test if a dep is valid or not.
22
+ # the assumption is that resolving a dep costs more than testing if it's valid or not
23
+ dep.verifications.each{|v| v.resolve!(obj)} unless dep.valid?(obj)
24
+ end
25
+ resolve_dependencies(obj, wrong)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+
2
+
3
+ module Laborantin
4
+ module Metaprog
5
+ module Exports
6
+
7
+ def save_exports
8
+ log "saving exports: #{exports}" if respond_to? :log
9
+ export_file('w') do |f|
10
+ f.puts YAML.dump(exports)
11
+ end
12
+ end
13
+
14
+ def load_exports
15
+ path = export_path
16
+ if File.file?(path)
17
+ YAML.load_file(path)
18
+ end
19
+ end
20
+
21
+ def export_path
22
+ raise NotImplementedError, "should override"
23
+ end
24
+
25
+ def export_file
26
+ raise NotImplementedError, "should override"
27
+ end
28
+
29
+ def exports
30
+ @exports ||= load_exports || {}
31
+ end
32
+
33
+ def export(name, mime='plain/text')
34
+ log "#{mime}: #{name}" if respond_to? :log
35
+ exports[name] ||= mime
36
+ end
37
+
38
+ def plots
39
+ hash = exports
40
+ hash.keys.select{|k| hash[k] =~ /^image/}
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,39 @@
1
+
2
+ module Laborantin
3
+ module Metaprog
4
+ module Resolutions
5
+ module InstanceMethods
6
+ attr_accessor :resolutions
7
+
8
+ def resolutions
9
+ @resolutions ||= []
10
+ end
11
+
12
+ def resolve(name=nil, &blk)
13
+ dep = Resolution.new(name, &blk)
14
+ resolutions << dep
15
+ dep
16
+ end
17
+
18
+ def resolve!(*ary)
19
+ resolutions.map{|r| r.resolve!(*ary)}
20
+ end
21
+ end
22
+
23
+ include InstanceMethods
24
+
25
+ class Resolution
26
+ attr_reader :name, :block
27
+ def initialize(name, &blk)
28
+ @name = name
29
+ @block = blk
30
+ end
31
+
32
+ def resolve!(*ary)
33
+ raise RuntimeError, "no block to resolve #{self}" unless block
34
+ block.call(*ary)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -33,6 +33,9 @@ require 'laborantin/core/hookable'
33
33
  require 'laborantin/core/configurable'
34
34
  require 'laborantin/core/multi_name'
35
35
  require 'laborantin/core/table'
36
+ require 'laborantin/core/selector'
37
+ require 'laborantin/core/dependencies'
38
+ require 'laborantin/core/exports'
36
39
 
37
40
  module Laborantin
38
41
 
@@ -50,9 +53,12 @@ module Laborantin
50
53
  class Scenario
51
54
  include Metaprog::Datable
52
55
  include Metaprog::Configurable
53
- extend Metaprog::Describable
54
- extend Metaprog::Hookable
55
- extend Metaprog::MultiName
56
+ extend Metaprog::Describable
57
+ extend Metaprog::Hookable
58
+ extend Metaprog::MultiName
59
+ include Metaprog::Selector
60
+ include Metaprog::Dependencies
61
+ include Metaprog::Exports
56
62
 
57
63
  @@all = []
58
64
 
@@ -98,10 +104,11 @@ module Laborantin
98
104
 
99
105
  # Prepares attributes' default values whenever a subclass is created.
100
106
  def inherited(klass)
101
- klass.parameters = ParameterHash.new
102
- klass.description = ''
103
- klass.products = []
104
- klass.hooks = {:setup => [], :teardown => []}
107
+ klass.parameters = ParameterHash.new.replace(self.parameters || {})
108
+ klass.description = "#{self.description} (child of #{self})"
109
+ klass.products = self.products ? self.products.dup : []
110
+ klass.hooks = Hash.new.replace(self.hooks || {:setup => [], :teardown => []})
111
+ klass.selectors = Hash.new.replace(self.selectors || {})
105
112
  @@all << klass
106
113
  end
107
114
 
@@ -173,7 +180,12 @@ module Laborantin
173
180
  # so wait one sec between several runs of same Scenario
174
181
  #
175
182
  def prepare!
176
- log(self.class.description, :info) unless self.class.description.empty?
183
+ log "Loading prior results:"
184
+ load_prior_results
185
+ log "Got #{scenarii.size} scenarios in #{environments.size} environments"
186
+ log "Description:"
187
+ log(self.class.description || 'no description', :info)
188
+ log "Parameters:"
177
189
  log self.params.inspect, :info
178
190
  log "Preparing directory #{rundir}"
179
191
  FileUtils.mkdir_p(rundir) #TODO: ensure unicity
@@ -202,6 +214,7 @@ module Laborantin
202
214
  end
203
215
  log "Measurement finished"
204
216
  call_hooks :teardown
217
+ save_exports
205
218
  end
206
219
 
207
220
  # For each product define with Scenario.produces, and in its order,
@@ -217,6 +230,7 @@ module Laborantin
217
230
  end
218
231
  end
219
232
  end
233
+ save_exports
220
234
  end
221
235
 
222
236
  # Returns the absolute path to a product file (see File.join) If brutname
@@ -266,14 +280,12 @@ module Laborantin
266
280
  Table.new(name, struct, self.product_path(name))
267
281
  end
268
282
 
269
- private
283
+ def export_file(mode='r', &blk)
284
+ product_file('exports.yaml', mode, true, &blk)
285
+ end
270
286
 
271
- def call_hooks(name)
272
- log "Calling #{name} hooks"
273
- self.class.hooks[name].each do |sym|
274
- log "(#{sym})"
275
- send sym
276
- end
287
+ def export_path
288
+ product_path('exports.yaml', true)
277
289
  end
278
290
 
279
291
  def log(*args)
@@ -287,5 +299,15 @@ module Laborantin
287
299
  def runner
288
300
  environment.runner
289
301
  end
302
+
303
+ private
304
+
305
+ def call_hooks(name)
306
+ log "Calling #{name} hooks"
307
+ self.class.hooks[name].each do |sym|
308
+ log "(#{sym})"
309
+ send sym
310
+ end
311
+ end
290
312
  end # class
291
313
  end
@@ -0,0 +1,68 @@
1
+
2
+ module Laborantin
3
+ module Metaprog
4
+ module Selector
5
+ def self.included(klass)
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ # A hash with two items, this might change later but KISS for now.
11
+ attr_accessor :selectors
12
+
13
+ # Add a selector to filter for the analysis only the runs that pass the selector.
14
+ # * sym objects (sym currently must be :environments or
15
+ # :scenarii).
16
+ # * ary is a set of classes, only runs of this classes will be loaded
17
+ # * if a block is passed, only the instances for which the block is
18
+ # evaluated as true will be selected (the block must take one parameter:
19
+ # the tested instance)
20
+ def select(sym, ary=[], &blk)
21
+ @selectors ||= {}
22
+ @selectors[sym] = {:klasses => ary, :blk => blk}
23
+ end
24
+ end
25
+
26
+ # An array of the Environments that could be loaded from the result directory.
27
+ attr_accessor :environments
28
+
29
+ # An array of the Scenarii that could be loaded from the result directory.
30
+ attr_reader :scenarii
31
+
32
+ # Load first the environments, then the scenarii.
33
+ def load_prior_results
34
+ load_environments
35
+ load_scenarii
36
+ end
37
+
38
+ # Will try to load environments and set the @environments variable to an
39
+ # array of all the Environment instance that match the :environments class
40
+ # selector (set with Analysis#select).
41
+ def load_environments
42
+ envs = Laborantin::Environment.scan_resdir('results')
43
+ @environments = envs.select do |env|
44
+ select_instance?(env, self.class.selectors[:environments])
45
+ end
46
+ end
47
+
48
+ # Same as load_environments, but for Scenario instances and :scenarii
49
+ # selector.
50
+ def load_scenarii
51
+ scii = @environments.map{|e| e.populate}.flatten
52
+ @scenarii = scii.select do |sc|
53
+ select_instance?(sc, self.class.selectors[:scenarii])
54
+ end
55
+ end
56
+
57
+ # Handy test to see if an object (that should be an instance of Environment
58
+ # or Scenario) matches the selector.
59
+ def select_instance?(obj, selector)
60
+ return true unless selector
61
+ blk = selector[:blk]
62
+ (selector[:klasses].any?{|k| obj.is_a? k} ) and
63
+ (blk ? blk.call(obj) : true)
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -17,7 +17,7 @@ module Laborantin
17
17
  end
18
18
 
19
19
  def header
20
- @header || [comment, fields].flatten.join(separator)
20
+ @header || [comment, fields].flatten.join(separator).strip
21
21
  end
22
22
 
23
23
  class Filler < Proc
@@ -43,8 +43,8 @@ module Laborantin
43
43
  end
44
44
  line = strings.join(separator)
45
45
  if check
46
- if line.start_with?(comment)
47
- raise ArgumentError, "line starting with comment\n#{line}"
46
+ if line.start_with?(comment) and (not comment.empty?)
47
+ raise ArgumentError, "line starting with comment\n#{line}"
48
48
  end
49
49
  expected = struct.members.size
50
50
  got = line.split(separator).size
@@ -0,0 +1,48 @@
1
+ require 'laborantin/core/resolutions'
2
+
3
+ module Laborantin
4
+ module Metaprog
5
+ module Verifications
6
+ module InstanceMethods
7
+ attr_accessor :verifications
8
+
9
+ def verifications
10
+ @verifications ||= []
11
+ end
12
+
13
+ def verify(name, &blk)
14
+ dep = Verification.new(name)
15
+ dep.instance_eval &blk
16
+ verifications << dep
17
+ dep
18
+ end
19
+
20
+ def valid?(*ary)
21
+ verifications.inject(true){|b,verif| b && verif.correct?(*ary)}
22
+ end
23
+ end
24
+
25
+ include InstanceMethods
26
+
27
+ class Verification
28
+ include Metaprog::Describable
29
+ include Metaprog::Resolutions
30
+ attr_reader :name, :block
31
+ def initialize(name)
32
+ @name = name
33
+ end
34
+
35
+ def check(&blk)
36
+ @block = blk
37
+ end
38
+
39
+ def correct?(*ary)
40
+ raise RuntimeError, "no block for verification #{self}" unless block
41
+ block.call(*ary) && valid?(*ary)
42
+ end
43
+
44
+ include InstanceMethods
45
+ end
46
+ end
47
+ end
48
+ end
@@ -21,9 +21,13 @@ Copyright (c) 2009, Lucas Di Cioccio
21
21
 
22
22
  =end
23
23
 
24
+ require 'laborantin/core/dependency_solver'
25
+
24
26
  module Laborantin
25
27
  module Commands
26
28
  class Analyze < Command
29
+ include DependencySolver
30
+
27
31
  describe "Runs the analyses"
28
32
 
29
33
  option(:analyses) do
@@ -50,7 +54,9 @@ module Laborantin
50
54
 
51
55
  anae.each do |klass|
52
56
  puts "Analyzing: #{klass.name}"
53
- klass.new.analyze
57
+ analysis = klass.new(self)
58
+ resolve_dependencies(analysis)
59
+ analysis.analyze
54
60
  end
55
61
  end
56
62
  end
@@ -22,12 +22,14 @@ Copyright (c) 2009, Lucas Di Cioccio
22
22
  =end
23
23
 
24
24
  require 'laborantin'
25
+ require 'laborantin/core/dependency_solver'
25
26
  require 'logger'
26
27
  require 'fileutils'
27
28
 
28
29
  module Laborantin
29
30
  module Commands
30
31
  class Run < Command
32
+ include DependencySolver
31
33
  describe 'Runs all set of scenarios and environments'
32
34
 
33
35
  option(:scenarii) do
@@ -110,6 +112,7 @@ module Laborantin
110
112
 
111
113
  sc = sklass.new(env, cfg)
112
114
  sc.prepare!
115
+ resolve_dependencies(sc)
113
116
  sc.perform!
114
117
  sc.analyze! if opts[:analyze]
115
118
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: laborantin
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 31
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 0
8
- - 21
9
- version: 0.0.21
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
10
11
  platform: ruby
11
12
  authors:
12
13
  - Lucas Di Cioccio
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2011-05-09 00:00:00 -04:00
18
+ date: 2011-07-21 00:00:00 +02:00
18
19
  default_executable:
19
20
  dependencies: []
20
21
 
@@ -68,6 +69,12 @@ files:
68
69
  - lib/laborantin/extra/commands/git/check.rb
69
70
  - lib/laborantin/extra/commands/git/run.rb
70
71
  - lib/laborantin/extra/vectorial_product.rb
72
+ - lib/laborantin/core/selector.rb
73
+ - lib/laborantin/core/dependencies.rb
74
+ - lib/laborantin/core/verifications.rb
75
+ - lib/laborantin/core/resolutions.rb
76
+ - lib/laborantin/core/dependency_solver.rb
77
+ - lib/laborantin/core/exports.rb
71
78
  has_rdoc: true
72
79
  homepage: http://dicioccio.fr/laborantin
73
80
  licenses: []
@@ -82,6 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
89
  requirements:
83
90
  - - ">="
84
91
  - !ruby/object:Gem::Version
92
+ hash: 3
85
93
  segments:
86
94
  - 0
87
95
  version: "0"
@@ -90,6 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
98
  requirements:
91
99
  - - ">="
92
100
  - !ruby/object:Gem::Version
101
+ hash: 3
93
102
  segments:
94
103
  - 0
95
104
  version: "0"