laborantin 0.0.14 → 0.0.21
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/INFO +1 -0
- data/README +148 -4
- data/Rakefile +73 -27
- data/TODO +27 -6
- data/bin/labor +2 -404
- data/lib/laborantin.rb +13 -26
- data/lib/laborantin/core/analysis.rb +231 -0
- data/lib/laborantin/core/command.rb +234 -0
- data/lib/laborantin/core/completeable.rb +30 -0
- data/lib/laborantin/core/configurable.rb +28 -0
- data/lib/laborantin/core/datable.rb +13 -0
- data/lib/laborantin/core/describable.rb +11 -0
- data/lib/laborantin/core/environment.rb +90 -52
- data/lib/laborantin/core/hookable.rb +20 -0
- data/lib/laborantin/core/monkey_patches.rb +0 -1
- data/lib/laborantin/core/multi_name.rb +25 -0
- data/lib/laborantin/core/parameter.rb +5 -12
- data/lib/laborantin/core/parameter_hash.rb +6 -2
- data/lib/laborantin/core/scenario.rb +93 -70
- data/lib/laborantin/core/table.rb +84 -0
- data/lib/laborantin/extra/commands/git.rb +40 -0
- data/lib/laborantin/extra/commands/git/check.rb +25 -0
- data/lib/laborantin/extra/commands/git/run.rb +100 -0
- data/lib/laborantin/extra/vectorial_product.rb +31 -0
- data/lib/laborantin/runner.rb +247 -0
- data/lib/laborantin/runner/commands/analyze.rb +58 -0
- data/lib/laborantin/runner/commands/cleanup.rb +40 -0
- data/lib/laborantin/runner/commands/complete.rb +111 -0
- data/lib/laborantin/runner/commands/config.rb +169 -0
- data/lib/laborantin/runner/commands/create.rb +61 -0
- data/lib/laborantin/runner/commands/describe.rb +215 -0
- data/lib/laborantin/runner/commands/find.rb +82 -0
- data/lib/laborantin/runner/commands/load_classes.rb +75 -0
- data/lib/laborantin/runner/commands/load_results.rb +143 -0
- data/lib/laborantin/runner/commands/note.rb +35 -0
- data/lib/laborantin/runner/commands/replay.rb +89 -0
- data/lib/laborantin/runner/commands/rm.rb +107 -0
- data/lib/laborantin/runner/commands/run.rb +131 -0
- data/lib/laborantin/runner/commands/scan.rb +77 -0
- metadata +45 -13
- data/bin/files/README.erb +0 -29
- data/bin/files/TODO.erb +0 -2
- data/bin/files/config/ftp.yaml.erb +0 -6
- data/bin/files/config/xmpp.yaml.erb +0 -7
- data/bin/files/environments/environment.rb.erb +0 -10
- data/bin/files/scenarii/scenario.rb.erb +0 -13
data/lib/laborantin.rb
CHANGED
@@ -21,34 +21,21 @@ Copyright (c) 2009, Lucas Di Cioccio
|
|
21
21
|
|
22
22
|
=end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
[ 'laborantin/core/scenario',
|
25
|
+
'laborantin/core/parameter',
|
26
|
+
'laborantin/core/parameter_hash',
|
27
|
+
'laborantin/core/environment',
|
28
|
+
'laborantin/core/analysis',
|
29
|
+
'laborantin/core/command',
|
30
|
+
'laborantin/core/monkey_patches'
|
31
|
+
].each do |dep|
|
32
|
+
require dep
|
33
|
+
end
|
34
|
+
|
29
35
|
|
30
36
|
module Laborantin
|
31
|
-
VERSION = '0.0.
|
37
|
+
VERSION = '0.0.21'
|
32
38
|
AUTHORS = ['Lucas Di Cioccio']
|
33
|
-
WEBSITE = 'http://dicioccio.fr'
|
39
|
+
WEBSITE = 'http://dicioccio.fr/laborantin'
|
34
40
|
LICENSE = 'GNU GPL version 3'
|
35
|
-
|
36
|
-
@@rootdir = '.'
|
37
|
-
|
38
|
-
# The root of the arborescence for Laborantin. Usually the directory created
|
39
|
-
# via the scripts.
|
40
|
-
def self.rootdir
|
41
|
-
@@rootdir || '.'
|
42
|
-
end
|
43
|
-
|
44
|
-
# Specifies the rootdir, dir is a path (instance of String, not a Dir object).
|
45
|
-
def self.rootdir=(dir='.')
|
46
|
-
@@rootdir = dir
|
47
|
-
end
|
48
|
-
|
49
|
-
# The path to the results (needs the Laborantin.rootdir).
|
50
|
-
def self.resultdir
|
51
|
-
File.join(Laborantin.rootdir, 'results')
|
52
|
-
end
|
53
|
-
|
54
41
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
#core/analysis.rb
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
This file is part of Laborantin.
|
6
|
+
|
7
|
+
Laborantin is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Laborantin is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Laborantin. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
Copyright (c) 2009, Lucas Di Cioccio
|
21
|
+
|
22
|
+
=end
|
23
|
+
|
24
|
+
autoload :ERB, 'erb'
|
25
|
+
|
26
|
+
require 'laborantin/core/describable'
|
27
|
+
require 'laborantin/core/multi_name'
|
28
|
+
|
29
|
+
module Laborantin
|
30
|
+
# An Analysis is a handy way to reload and filter the various scenarii that were
|
31
|
+
# run. You can easily filter on them.
|
32
|
+
class Analysis
|
33
|
+
extend Metaprog::Describable
|
34
|
+
extend Metaprog::MultiName
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# A hash with two items, this might change later but KISS for now.
|
38
|
+
attr_accessor :selectors
|
39
|
+
|
40
|
+
# An array
|
41
|
+
attr_accessor :analyses
|
42
|
+
|
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
|
+
# Adds an analysis to this class.
|
56
|
+
# str is a description of the added analysis params is a hash of
|
57
|
+
# parameters for this analysis, specifically, the :type parameters allows
|
58
|
+
# you to differenciate the kind of analysis for repors TODO: more info on
|
59
|
+
# that, tells that we can directly use Analysis.plot and Analysis.table
|
60
|
+
# methods
|
61
|
+
def analyze(str, params = {}, &blk)
|
62
|
+
@analyses << {:str => str, :params => params, :blk => blk}
|
63
|
+
end
|
64
|
+
|
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
|
+
@@all = []
|
86
|
+
|
87
|
+
def inherited(klass)
|
88
|
+
@@all << klass
|
89
|
+
klass.select(:environments,[Laborantin::Environment])
|
90
|
+
klass.select(:scenarii,[Laborantin::Scenario])
|
91
|
+
klass.analyses = []
|
92
|
+
end
|
93
|
+
|
94
|
+
def all
|
95
|
+
@@all
|
96
|
+
end
|
97
|
+
end # << self
|
98
|
+
|
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
|
+
# TODO : recode this, maybe as nothing to do here
|
106
|
+
def analyze
|
107
|
+
self.class.analyses.each do |a|
|
108
|
+
puts "(#{a[:str]})"
|
109
|
+
instance_eval &a[:blk]
|
110
|
+
puts "done"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO: more flexible
|
115
|
+
def report(tpl_path=nil)
|
116
|
+
tpl = ERB.new(File.read(tpl_path))
|
117
|
+
File.open("reports/#{self.class.name}.html", 'w') do |f|
|
118
|
+
f.puts tpl.result(binding)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def output_dirname
|
123
|
+
self.class.cli_name
|
124
|
+
end
|
125
|
+
|
126
|
+
def output_dirpath
|
127
|
+
File.join('.', 'reports', output_dirname)
|
128
|
+
end
|
129
|
+
|
130
|
+
def create_output_dir
|
131
|
+
FileUtils.mkdir_p(output_dirpath) unless File.directory?(output_dirpath)
|
132
|
+
end
|
133
|
+
|
134
|
+
def output_path(name)
|
135
|
+
File.join(output_dirpath, name)
|
136
|
+
end
|
137
|
+
|
138
|
+
def output(name, mode='r')
|
139
|
+
create_output_dir
|
140
|
+
File.open(output_path(name), mode) do |f|
|
141
|
+
yield f
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def table(name, struct)
|
146
|
+
Table.new(name, struct, self.output_path(name))
|
147
|
+
end
|
148
|
+
|
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
|
155
|
+
end
|
156
|
+
|
157
|
+
# Load first the environments, then the scenarii.
|
158
|
+
def load_from_results
|
159
|
+
load_environments
|
160
|
+
load_scenarii
|
161
|
+
end
|
162
|
+
|
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
|
170
|
+
|
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
|
179
|
+
end
|
180
|
+
|
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
|
188
|
+
end
|
189
|
+
|
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
|
197
|
+
|
198
|
+
# Nice way to iterate on @scenarii
|
199
|
+
def each_scenario
|
200
|
+
@scenarii.each do |sc|
|
201
|
+
yield sc
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Nice way to iterate on @environments
|
206
|
+
def each_environment
|
207
|
+
@environments.each do |env|
|
208
|
+
yield env
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# The list of parameters spanned from the scenarii.
|
213
|
+
# e.g. scenario 1, params = A => a1, B => b1
|
214
|
+
# scenario 2, params = A => a2, B => b2
|
215
|
+
# scenario 3, params = C => c3
|
216
|
+
# parameters = A => [a1, a2], B => [b1, b2], C => [c3]
|
217
|
+
def parameters
|
218
|
+
unless @parameters
|
219
|
+
@parameters = {}
|
220
|
+
each_scenario do |sc|
|
221
|
+
sc.params.each_pair do |name, value|
|
222
|
+
@parameters[name] ||= []
|
223
|
+
@parameters[name] << value unless @parameters[name].include?(value)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
@parameters
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#core/command.rb
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
This file is part of Laborantin.
|
6
|
+
|
7
|
+
Laborantin is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Laborantin is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU General Public License
|
18
|
+
along with Laborantin. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
Copyright (c) 2009, Lucas Di Cioccio
|
21
|
+
|
22
|
+
=end
|
23
|
+
|
24
|
+
require 'laborantin/core/completeable'
|
25
|
+
|
26
|
+
module Laborantin
|
27
|
+
# The Command is a way to extend the labor script to help you modularize your
|
28
|
+
# work, and call the commands both via the command line, or in your scripts.
|
29
|
+
# This class provides parsing facilities. Namespacing (i.e., mapping between
|
30
|
+
# classes and command line is done in the Runner . Internal labor Commands
|
31
|
+
# are actually implemented this way.
|
32
|
+
class Command
|
33
|
+
|
34
|
+
# An Option for a Command, to help us building a nice DSL for you.
|
35
|
+
class Option
|
36
|
+
|
37
|
+
include Metaprog::Completeable
|
38
|
+
|
39
|
+
# The name of the option.
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
# The short option flag of the command line (e.g. '-t')
|
43
|
+
attr_reader :cli_short
|
44
|
+
|
45
|
+
# The long option flag of the command line (e.g. '--trust')
|
46
|
+
attr_reader :cli_long
|
47
|
+
|
48
|
+
# The description to provide feedback/help.
|
49
|
+
attr_reader :description
|
50
|
+
|
51
|
+
# The default value of this option.
|
52
|
+
attr_reader :default_value
|
53
|
+
|
54
|
+
# The type to expect for a value (give a class among those understood by OptParse)
|
55
|
+
attr_reader :default_type
|
56
|
+
|
57
|
+
def initialize(name)
|
58
|
+
@name = name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets the cli_short option flag (e.g. short '-t')
|
62
|
+
def short(str)
|
63
|
+
@cli_short = str
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sets the cli_long option flag (e.g. long '--trust')
|
67
|
+
def long(str)
|
68
|
+
@cli_long = str
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets the description of the option.
|
72
|
+
def describe(str)
|
73
|
+
@description = str
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sets the default value of the option.
|
77
|
+
def default(val)
|
78
|
+
@default_value = val
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sets the type to expect for the option.
|
82
|
+
def type(klass)
|
83
|
+
@default_type = klass
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the description followed by the default value in parenthesis.
|
87
|
+
def description_with_default
|
88
|
+
"#{description} (default: #{default_value})."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# An Array to store all the commands.
|
93
|
+
@@all = []
|
94
|
+
|
95
|
+
extend Enumerable
|
96
|
+
|
97
|
+
# Once we've extended Enumerable, it's easy to find new commands.
|
98
|
+
def self.each
|
99
|
+
@@all.each{|e| yield e}
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
|
104
|
+
include Metaprog::Completeable
|
105
|
+
|
106
|
+
# The description string of a command, will be used on command line help.
|
107
|
+
attr_accessor :description
|
108
|
+
|
109
|
+
# Wether or not this command is a plumbery one.
|
110
|
+
attr_accessor :plumbery
|
111
|
+
|
112
|
+
# An Array contining the possible options of this command.
|
113
|
+
attr_accessor :options
|
114
|
+
|
115
|
+
# The block of execution for the instances of this commands.
|
116
|
+
# By default, this will raise an exception.
|
117
|
+
# TODO: transform the block into a default method.
|
118
|
+
attr_accessor :block
|
119
|
+
|
120
|
+
# The command name, can be set, this is useful when the class has no name
|
121
|
+
# (e.g. an object created with Class.new(Command)), or if you want to use
|
122
|
+
# another name for this command.
|
123
|
+
attr_accessor :command_name
|
124
|
+
|
125
|
+
# By default, the name of the class, or an empty string if none (this can
|
126
|
+
# lead to big issues, so I'm still considering returning nil if a command
|
127
|
+
# has no name nor command_name.
|
128
|
+
def command_name
|
129
|
+
(@command_name || self.name ) || ''
|
130
|
+
end
|
131
|
+
|
132
|
+
# When a new Command class is created, sets up default value, and store
|
133
|
+
# the new class in the known commands.
|
134
|
+
def inherited(klass)
|
135
|
+
klass.description = ''
|
136
|
+
klass.options = []
|
137
|
+
klass.block = lambda { raise RuntimeError.new("no execution block for #{klass}") }
|
138
|
+
@@all << klass
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set the description string.
|
142
|
+
def describe(str=nil)
|
143
|
+
self.description = str
|
144
|
+
end
|
145
|
+
|
146
|
+
# Sets the plumbery flag.
|
147
|
+
def plumbery!(val=true)
|
148
|
+
self.plumbery = val
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns true if the plumbery flag is true.
|
152
|
+
def plumbery?
|
153
|
+
self.plumbery && true
|
154
|
+
end
|
155
|
+
|
156
|
+
# Opposite of plumbery?
|
157
|
+
def porcelain?
|
158
|
+
not self.plumbery?
|
159
|
+
end
|
160
|
+
|
161
|
+
# Set the execution block (executed in the scope of the instance).
|
162
|
+
# XXX this might well be changed into requiring ppl to define an "execute" method.
|
163
|
+
def execute(&blk)
|
164
|
+
self.block = blk
|
165
|
+
end
|
166
|
+
|
167
|
+
# Assign a new option to this command class, which name is name
|
168
|
+
# (preferably, give Symbol or String). The blk is evaluated in the scope
|
169
|
+
# of a new Option instance.
|
170
|
+
# Giving a name is required, because it is also the key of the command.opts hash.
|
171
|
+
def option(name, &blk)
|
172
|
+
arg = Option.new(name)
|
173
|
+
arg.instance_eval &blk
|
174
|
+
self.options << arg
|
175
|
+
end
|
176
|
+
end # << self
|
177
|
+
|
178
|
+
# A hash containing a merge of the defaults options of the class and the
|
179
|
+
# extra options provided to the run method.
|
180
|
+
# Think of it like the '--path=/some/path' options of a command line.
|
181
|
+
# Options are labeled but not ordered, arguments are the opposite.
|
182
|
+
attr_reader :opts
|
183
|
+
|
184
|
+
# An array containing the arguments of the command.
|
185
|
+
# Think of it like the trailing arguments of a command line.
|
186
|
+
# Arguments are ordered but not labeled, options are the opposite.
|
187
|
+
attr_reader :args
|
188
|
+
|
189
|
+
# The Runner object that will executes the command.
|
190
|
+
attr_accessor :runner
|
191
|
+
|
192
|
+
# Initializes a new instance of command. Will first set the runner if
|
193
|
+
# provided, then will initialize default options and arguments.
|
194
|
+
def initialize(runner=nil)
|
195
|
+
@runner = runner
|
196
|
+
initialize_opts
|
197
|
+
initialize_args
|
198
|
+
end
|
199
|
+
|
200
|
+
# Runs the command by merging the options with extra_opts and evaluating
|
201
|
+
# the block stored in the class.
|
202
|
+
def run(args=[], extra_opts={})
|
203
|
+
@opts.merge!(extra_opts)
|
204
|
+
@args = args
|
205
|
+
self.instance_eval &self.class.block
|
206
|
+
end
|
207
|
+
|
208
|
+
# Forward a line to the runner if it respond to a puts method too.
|
209
|
+
# Fallback on Kernel.puts if there is no runner or the runner does
|
210
|
+
# not respond to puts.
|
211
|
+
def puts(line)
|
212
|
+
if runner and runner.respond_to?(:puts)
|
213
|
+
runner.puts(line)
|
214
|
+
else
|
215
|
+
Kernel.puts(line)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
# Just initialize the opts to an empty Array
|
222
|
+
def initialize_args
|
223
|
+
@args = []
|
224
|
+
end
|
225
|
+
|
226
|
+
# Grabs the default values of the options from the class of the command.
|
227
|
+
def initialize_opts
|
228
|
+
@opts = {}
|
229
|
+
self.class.options.each do |arg|
|
230
|
+
@opts[arg.name] = arg.default_value
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|