solutious-stella 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +39 -2
- data/LICENSE.txt +19 -0
- data/README.rdoc +85 -0
- data/Rakefile +54 -59
- data/bin/example_test.rb +82 -0
- data/bin/example_webapp.rb +63 -0
- data/lib/{stella/logger.rb → logger.rb} +6 -11
- data/lib/stella.rb +76 -58
- data/lib/stella/clients.rb +161 -0
- data/lib/stella/command/base.rb +4 -24
- data/lib/stella/command/form.rb +36 -0
- data/lib/stella/command/get.rb +44 -0
- data/lib/stella/common.rb +53 -0
- data/lib/stella/crypto.rb +88 -0
- data/lib/stella/data/domain.rb +2 -2
- data/lib/stella/data/http.rb +164 -36
- data/lib/stella/environment.rb +66 -0
- data/lib/stella/functest.rb +105 -0
- data/lib/stella/loadtest.rb +186 -0
- data/lib/{utils → stella}/stats.rb +16 -20
- data/lib/stella/testplan.rb +237 -0
- data/lib/stella/testrunner.rb +64 -0
- data/lib/storable.rb +280 -0
- data/lib/threadify.rb +171 -0
- data/lib/timeunits.rb +65 -0
- data/lib/util/httputil.rb +266 -0
- data/stella.gemspec +69 -0
- data/tryouts/drb/drb_test.rb +65 -0
- data/tryouts/drb/open4.rb +19 -0
- data/tryouts/drb/slave.rb +27 -0
- data/tryouts/oo_tryout.rb +30 -0
- metadata +39 -107
- data/README.textile +0 -162
- data/bin/stella +0 -12
- data/bin/stella.bat +0 -12
- data/lib/daemonize.rb +0 -56
- data/lib/pcaplet.rb +0 -180
- data/lib/stella/adapter/ab.rb +0 -337
- data/lib/stella/adapter/base.rb +0 -106
- data/lib/stella/adapter/httperf.rb +0 -305
- data/lib/stella/adapter/pcap_watcher.rb +0 -221
- data/lib/stella/adapter/proxy_watcher.rb +0 -76
- data/lib/stella/adapter/siege.rb +0 -341
- data/lib/stella/cli.rb +0 -258
- data/lib/stella/cli/agents.rb +0 -73
- data/lib/stella/cli/base.rb +0 -55
- data/lib/stella/cli/language.rb +0 -18
- data/lib/stella/cli/localtest.rb +0 -78
- data/lib/stella/cli/sysinfo.rb +0 -16
- data/lib/stella/cli/watch.rb +0 -278
- data/lib/stella/command/localtest.rb +0 -358
- data/lib/stella/response.rb +0 -85
- data/lib/stella/storable.rb +0 -201
- data/lib/stella/support.rb +0 -276
- data/lib/stella/sysinfo.rb +0 -257
- data/lib/stella/test/definition.rb +0 -79
- data/lib/stella/test/run/summary.rb +0 -70
- data/lib/stella/test/stats.rb +0 -114
- data/lib/stella/text.rb +0 -64
- data/lib/stella/text/resource.rb +0 -38
- data/lib/utils/crypto-key.rb +0 -84
- data/lib/utils/domainutil.rb +0 -47
- data/lib/utils/escape.rb +0 -302
- data/lib/utils/fileutil.rb +0 -78
- data/lib/utils/httputil.rb +0 -266
- data/lib/utils/mathutil.rb +0 -15
- data/lib/utils/textgraph.rb +0 -267
- data/lib/utils/timerutil.rb +0 -58
- data/lib/win32/Console.rb +0 -970
- data/lib/win32/Console/ANSI.rb +0 -305
- data/support/kvm.h +0 -91
- data/support/ruby-pcap-takuma-notes.txt +0 -19
- data/support/ruby-pcap-takuma-patch.txt +0 -30
- data/support/text/en.yaml +0 -80
- data/support/text/nl.yaml +0 -7
- data/support/useragents.txt +0 -75
- data/tests/01-util_test.rb +0 -0
- data/tests/02-stella-util_test.rb +0 -42
- data/tests/10-stella_test.rb +0 -104
- data/tests/11-stella-storable_test.rb +0 -68
- data/tests/60-stella-command_test.rb +0 -248
- data/tests/80-stella-cli_test.rb +0 -45
- data/tests/spec-helper.rb +0 -31
@@ -1,358 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Stella
|
4
|
-
class LocalTest < Stella::Command::Base
|
5
|
-
|
6
|
-
# A container for the test parameters
|
7
|
-
attr_accessor :testdef
|
8
|
-
# The load tool adapter
|
9
|
-
attr_accessor :adapter
|
10
|
-
|
11
|
-
attr_accessor :test_path
|
12
|
-
|
13
|
-
attr_accessor :quiet
|
14
|
-
attr_accessor :guid
|
15
|
-
attr_accessor :verbose
|
16
|
-
attr_accessor :format
|
17
|
-
|
18
|
-
# list of all filesystem paths for each run in a single test
|
19
|
-
attr_reader :test_runpaths
|
20
|
-
# same as above, expect for all tests
|
21
|
-
attr_reader :all_runpaths
|
22
|
-
# list of all hostname:port in the test
|
23
|
-
attr_reader :hosts
|
24
|
-
# list of all /paths in the test
|
25
|
-
attr_reader :paths
|
26
|
-
|
27
|
-
attr_accessor :working_directory
|
28
|
-
attr_reader :available_agents
|
29
|
-
attr_reader :test_stats
|
30
|
-
attr_reader :rampup_test_stats
|
31
|
-
|
32
|
-
def initialize(testdef=nil, adapter=nil)
|
33
|
-
@testdef = testdef if testdef
|
34
|
-
@adapter = adapter if adapter
|
35
|
-
|
36
|
-
# Disabled until we resolve JRuby on OSX issue (won't load openssl)
|
37
|
-
#@guid = Crypto.sign(rand.to_s, rand.to_s)
|
38
|
-
|
39
|
-
@test_runpaths = []
|
40
|
-
@all_runpaths = []
|
41
|
-
@hosts = []
|
42
|
-
@paths = []
|
43
|
-
@format = 'yaml'
|
44
|
-
@agents = []
|
45
|
-
@verbose = 0
|
46
|
-
|
47
|
-
ua_path = File.join(STELLA_HOME, 'support', 'useragents.txt')
|
48
|
-
Stella::LOGGER.debug("LOADING #{ua_path}")
|
49
|
-
|
50
|
-
@available_agents = Stella::Util.process_useragents(ua_path)
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def run
|
56
|
-
|
57
|
-
|
58
|
-
raise UnavailableAdapter.new(@adapter.name) unless @adapter.available?
|
59
|
-
|
60
|
-
# If the adapter isn't being called for a loadtest, we don't have anything to do.
|
61
|
-
unless @adapter.loadtest?
|
62
|
-
system(@adapter.command)
|
63
|
-
return
|
64
|
-
end
|
65
|
-
|
66
|
-
@agents = Stella::Util.find_agents(@available_agents, @testdef.agents)
|
67
|
-
|
68
|
-
@test_path = _generate_test_path
|
69
|
-
|
70
|
-
prepare_test(@test_path)
|
71
|
-
|
72
|
-
threshold = (@testdef.rampup) ? @testdef.rampup.ceiling : @adapter.vusers
|
73
|
-
|
74
|
-
runnum = "00"
|
75
|
-
while(@adapter.vusers <= threshold) do
|
76
|
-
|
77
|
-
# Make sure the load factor is set to 1. If there was a warmup,
|
78
|
-
# then this value will be less than 1.
|
79
|
-
testrun = maker_of_testruns(1)
|
80
|
-
|
81
|
-
# We empty test_runpaths so we can keep track of the runpaths for each
|
82
|
-
# level of virtual users. This is useful during a rampup test so we can
|
83
|
-
# collect the stats for the repetitions at each virtual user level.
|
84
|
-
@test_runpaths = []
|
85
|
-
|
86
|
-
# Execute each test run in turn. All of the steps are the same for each
|
87
|
-
# run. This is important because the purpose of the test runs is to
|
88
|
-
# create a statistical certainty of performance.
|
89
|
-
@testdef.repetitions.times do
|
90
|
-
|
91
|
-
# This value is used to create the run directory and show which run we're on
|
92
|
-
runnum.succ!
|
93
|
-
|
94
|
-
# Generate the filesystem path to store the run data.
|
95
|
-
runpath = File.join(test_path, "run#{runnum}")
|
96
|
-
|
97
|
-
# Keep track of every run path. We use this in postpare to
|
98
|
-
# do some stuff after all the tests have run
|
99
|
-
@all_runpaths << runpath
|
100
|
-
|
101
|
-
# test_runpaths will be identical to all_runpaths except when there is a
|
102
|
-
# rampup. In that case, test_runpaths will contain the runpaths for all
|
103
|
-
# repetitions of each level of virtual users.
|
104
|
-
@test_runpaths << runpath
|
105
|
-
|
106
|
-
testrun.call("Run #{runnum}", runpath)
|
107
|
-
|
108
|
-
# Only sleep if requested and there is more than 1 test run
|
109
|
-
if @testdef.sleep && @testdef.sleep.to_f > 0 && @testdef.repetitions > 1
|
110
|
-
run_sleeper(@testdef.sleep)
|
111
|
-
end
|
112
|
-
Stella::LOGGER.info('') unless @quiet # We want the newline
|
113
|
-
end
|
114
|
-
|
115
|
-
# Rampup tests produce multiple summary files which include the batch
|
116
|
-
# number. Regular runs have just one file we set here as the default.
|
117
|
-
summary_name = "STATS"
|
118
|
-
|
119
|
-
# It's possible for the interval to not divide evenly into the ceiling
|
120
|
-
# If we have room between the current number of virtual users and the
|
121
|
-
# ceiling, we'll add the difference for the final test run.
|
122
|
-
if (@testdef.rampup)
|
123
|
-
final_total = @adapter.vusers + @testdef.rampup.interval
|
124
|
-
if final_total > threshold && (threshold - @adapter.vusers) > 0
|
125
|
-
@adapter.vusers += (threshold - @adapter.vusers)
|
126
|
-
else
|
127
|
-
@adapter.vusers += @testdef.rampup.interval
|
128
|
-
end
|
129
|
-
padded_users = @adapter.vusers.to_s.rjust(4, '0')
|
130
|
-
summary_name << "-#{padded_users}"
|
131
|
-
end
|
132
|
-
|
133
|
-
# Read the run summaries for this batch and produce totals, averages,
|
134
|
-
# and standard deviations.
|
135
|
-
@test_stats = process_test_stats(@test_runpaths)
|
136
|
-
print_summary(test_stats) if (@testdef.repetitions > 1)
|
137
|
-
save_summary(File.join(test_path, "#{summary_name}.#{@format}"), @test_stats)
|
138
|
-
|
139
|
-
|
140
|
-
# Non-rampup tests only need to run through the loop once.
|
141
|
-
break if (!@testdef.rampup && @adapter.vusers == threshold)
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
if @testdef.rampup
|
146
|
-
# Notice the difference between these test stats and the ones above?
|
147
|
-
# These stats are based on the entire rampup test, across all levels
|
148
|
-
# of virtual users.
|
149
|
-
@rampup_test_stats = process_test_stats(@all_runpaths)
|
150
|
-
save_summary(File.join(test_path, "STATS.#{@format}"), @rampup_test_stats)
|
151
|
-
print_summary(test_stats) if (@testdef.repetitions > 1)
|
152
|
-
end
|
153
|
-
rescue Interrupt
|
154
|
-
exit
|
155
|
-
rescue AdapterError => ex
|
156
|
-
Stella::LOGGER.error(ex.message)
|
157
|
-
end
|
158
|
-
|
159
|
-
def test_path_symlink
|
160
|
-
return unless @working_directory
|
161
|
-
File.join(@working_directory, 'latest')
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
private
|
166
|
-
|
167
|
-
# prepare_test
|
168
|
-
#
|
169
|
-
# Execute the group of testruns associated to this test. This includes
|
170
|
-
# zero or one warmup and one or more testruns.
|
171
|
-
#
|
172
|
-
# INPUT:
|
173
|
-
# +test_path+ filesystem path to store all test data
|
174
|
-
#
|
175
|
-
def prepare_test(test_path)
|
176
|
-
|
177
|
-
Stella::LOGGER.info("Writing test data to: #{test_path}\n\n") unless @quiet
|
178
|
-
|
179
|
-
# Make sure the test storage directory is created along with the
|
180
|
-
# latest symlink
|
181
|
-
FileUtil.create_dir(test_path)
|
182
|
-
if Stella.sysinfo.os == :unix
|
183
|
-
File.unlink(test_path_symlink) if File.exists?(test_path_symlink)
|
184
|
-
File.symlink(File.expand_path(test_path), test_path_symlink)
|
185
|
-
end
|
186
|
-
|
187
|
-
# Write the test ID to the storage directory
|
188
|
-
# NOTE: Disabled until we resolve the issue with JRuby on OSX (won't load openssl)
|
189
|
-
#FileUtil.write_file(test_path + "/ID.txt", @guid, true)
|
190
|
-
|
191
|
-
# And the test run message
|
192
|
-
FileUtil.write_file(test_path + "/MESSAGE.txt", @testdef.message, true) if @testdef.message
|
193
|
-
|
194
|
-
@adapter.user_agent = @agents unless @agents.empty?
|
195
|
-
|
196
|
-
# The warmup is identical to a testrun except for two things:
|
197
|
-
# - we don't make note of the runpath
|
198
|
-
# - there is a one second sleep after the run.
|
199
|
-
# Everything else is identical.
|
200
|
-
if @testdef.warmup
|
201
|
-
|
202
|
-
testrun = maker_of_testruns(@testdef.warmup)
|
203
|
-
|
204
|
-
# Generate the filesystem path to store the run data.
|
205
|
-
# NOTE: We don't keep the warmup path in @test_runpaths because we
|
206
|
-
# include it in the final calculation for the test.
|
207
|
-
runpath = File.join(test_path, "warmup")
|
208
|
-
|
209
|
-
# Run the warmpup round
|
210
|
-
testrun.call("Warmup", runpath)
|
211
|
-
|
212
|
-
run_sleeper(@testdef.sleep || 1)
|
213
|
-
|
214
|
-
Stella::LOGGER.info('', '') unless @quiet # We just need the newline
|
215
|
-
|
216
|
-
end
|
217
|
-
|
218
|
-
print_title unless @quiet
|
219
|
-
|
220
|
-
end
|
221
|
-
|
222
|
-
# maker_of_testruns
|
223
|
-
#
|
224
|
-
# Generator of test runs. Everything that happens during a test
|
225
|
-
# run is defined here. We use a Proc so we can toss the functionality
|
226
|
-
# around like something that's dirty and loves a good tossing.
|
227
|
-
# It's important that all output be on a single line without a
|
228
|
-
# line terminator. Otherwise great descruction could occur.
|
229
|
-
def maker_of_testruns(factor)
|
230
|
-
testrun = Proc.new do |name,runpath|
|
231
|
-
# Make sure the test run storage directory is created
|
232
|
-
FileUtil.create_dir(runpath)
|
233
|
-
|
234
|
-
@adapter.load_factor = factor
|
235
|
-
@adapter.working_directory = runpath
|
236
|
-
@adapter.before
|
237
|
-
|
238
|
-
Stella::LOGGER.info_printf("%8s: %10d@%-6s ", name, @adapter.requests, @adapter.vuser_rate) unless @quiet
|
239
|
-
|
240
|
-
# Here we record the command arguments. This needs to be last because we modify
|
241
|
-
# some of the arguments above.
|
242
|
-
FileUtil.write_file(runpath + "/COMMAND.txt", @adapter.command, true)
|
243
|
-
|
244
|
-
# Execute the command, send STDOUT and STDERR to separate files.
|
245
|
-
command = "#{@adapter.command} 1> \"#{@adapter.stdout_path}\" 2> \"#{@adapter.stderr_path}\""
|
246
|
-
Stella::LOGGER.info(" COMMAND: #{command}") if @verbose >= 2
|
247
|
-
|
248
|
-
begin
|
249
|
-
# Call the load tool
|
250
|
-
# $? contains the error status
|
251
|
-
succeeded = system(command)
|
252
|
-
|
253
|
-
# TODO: Catch interrupts for system calls. Currently it will simply and and continue with the next command
|
254
|
-
# i.e. these don't work:
|
255
|
-
rescue Interrupt
|
256
|
-
exit
|
257
|
-
rescue SystemExit
|
258
|
-
exit
|
259
|
-
end
|
260
|
-
|
261
|
-
unless succeeded
|
262
|
-
Stella::LOGGER.info('', '') # We used print so we need a new line for the error message.
|
263
|
-
raise AdapterError.new(@adapter.name, @adapter.error)
|
264
|
-
end
|
265
|
-
|
266
|
-
@adapter.after
|
267
|
-
|
268
|
-
stats = @adapter.summary
|
269
|
-
|
270
|
-
save_summary(@adapter.summary_path(@format), stats)
|
271
|
-
|
272
|
-
|
273
|
-
if !@quiet && stats && stats.available?
|
274
|
-
Stella::LOGGER.info_print(sprintf("%3.0f%% %9.2f/s %8.3fs ", stats.availability || 0, stats.transaction_rate || 0, stats.response_time || 0))
|
275
|
-
Stella::LOGGER.info_print(sprintf("%8.3fMB/s %8.3fMB %8.3fs ", stats.throughput || 0, stats.data_transferred || 0, stats.elapsed_time || 0))
|
276
|
-
# NOTE: We don't print a line terminator here
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
|
282
|
-
# print_summary
|
283
|
-
#
|
284
|
-
# Called during the test after every batch of test runs. For a rampup test
|
285
|
-
# it's also called at the end of all the runs.
|
286
|
-
#
|
287
|
-
# INPUT:
|
288
|
-
# stats:: Any object that extends Stella::Test::Base object
|
289
|
-
def print_summary(stats)
|
290
|
-
return if stats.nil?
|
291
|
-
Stella::LOGGER.info(' ' << "-"*67) unless @quiet
|
292
|
-
|
293
|
-
Stella::LOGGER.info_printf("%8s: %10d@%-6d %3.0f%% %9.2f/s ", "Total", stats.transactions_total || 0, stats.vusers_avg || 0, stats.availability || 0, stats.transaction_rate_avg || 0)
|
294
|
-
Stella::LOGGER.info_printf("%8.3fs %8.3fMB/s %8.3fMB %8.3fs", stats.response_time_avg || 0, stats.throughput_avg || 0, stats.data_transferred_total || 0, stats.elapsed_time_total || 0)
|
295
|
-
Stella::LOGGER.info('') # New line
|
296
|
-
Stella::LOGGER.info_printf("%8s: %22s %9.2f/s %8.3fs %8.3fMB/s %10s %8.3fs", "Std Dev", '', stats.transaction_rate_sdev || 0, stats.response_time_sdev || 0, stats.throughput_sdev || 0, '', stats.elapsed_time_sdev || 0)
|
297
|
-
Stella::LOGGER.info('') # New line
|
298
|
-
Stella::LOGGER.info('') unless @quiet # Extra new line
|
299
|
-
end
|
300
|
-
|
301
|
-
# print_title
|
302
|
-
#
|
303
|
-
# Prints the column headers for the test run output. Field widths match those
|
304
|
-
# in print_summary and test_maker
|
305
|
-
def print_title
|
306
|
-
Stella::LOGGER.info(' ' << "-"*67) unless @quiet
|
307
|
-
|
308
|
-
Stella::LOGGER.info_printf("%8s %10s@%-5s %5s %11s", "", 'REQ', 'VU/s', 'AVAIL', 'REQ/s')
|
309
|
-
Stella::LOGGER.info_printf("%10s %12s %10s %9s", 'RTIME', 'DATA/s', 'DATA', 'TIME')
|
310
|
-
Stella::LOGGER.info('') # New line
|
311
|
-
Stella::LOGGER.info('') unless @quiet # Extra new line
|
312
|
-
end
|
313
|
-
|
314
|
-
# save_summary
|
315
|
-
#
|
316
|
-
# Write a summary object to disk
|
317
|
-
#
|
318
|
-
# INPUT:
|
319
|
-
# filepath:: the complete path for the file (string)
|
320
|
-
# stats:: Any object that extends Stella::Test::Base object
|
321
|
-
def save_summary(filepath, stats)
|
322
|
-
return unless stats
|
323
|
-
stats.format = @format
|
324
|
-
stats.to_file(filepath)
|
325
|
-
end
|
326
|
-
|
327
|
-
# Load SUMMARY file for each run and create a summary with
|
328
|
-
# totals, averages, and standard deviations.
|
329
|
-
def process_test_stats(paths)
|
330
|
-
return unless paths && !paths.empty?
|
331
|
-
test_stats = Stella::Test::Stats.new(@message)
|
332
|
-
|
333
|
-
paths.each do |path|
|
334
|
-
next unless File.exists?("#{path}/SUMMARY.#{@format}")
|
335
|
-
test_run = Stella::Test::Run::Summary.from_file("#{path}/SUMMARY.#{@format}")
|
336
|
-
test_stats.add_run(test_run)
|
337
|
-
end
|
338
|
-
|
339
|
-
test_stats
|
340
|
-
end
|
341
|
-
|
342
|
-
# This is the path where all test data will be stored
|
343
|
-
# The default is testruns/2008-12-31/test-001
|
344
|
-
def _generate_test_path
|
345
|
-
now = DateTime.now
|
346
|
-
time = Time.now
|
347
|
-
daystr = "#{now.year}-#{now.mon.to_s.rjust(2,'0')}-#{now.mday.to_s.rjust(2,'0')}"
|
348
|
-
testpath = File.join(@working_directory, 'testruns', daystr, 'test-')
|
349
|
-
testnum = 1.to_s.rjust(3,'0')
|
350
|
-
testnum.succ! while(File.directory? "#{testpath}#{testnum}")
|
351
|
-
"#{testpath}#{testnum}"
|
352
|
-
end
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
data/lib/stella/response.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
module Stella
|
5
|
-
|
6
|
-
# An object for HTTP response content
|
7
|
-
#
|
8
|
-
class Response < Storable
|
9
|
-
|
10
|
-
field :errors => Array
|
11
|
-
field :content => Hash
|
12
|
-
field :messages => Array
|
13
|
-
field :success => TrueClass
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
@success = false
|
17
|
-
@errors = []
|
18
|
-
@messages = []
|
19
|
-
@content = {}
|
20
|
-
end
|
21
|
-
|
22
|
-
def success?
|
23
|
-
@success
|
24
|
-
end
|
25
|
-
|
26
|
-
def add(key, value)
|
27
|
-
@content[key] = value
|
28
|
-
end
|
29
|
-
|
30
|
-
def get(key)
|
31
|
-
@content[key] if @content.has_key? key
|
32
|
-
end
|
33
|
-
|
34
|
-
def message(msg)
|
35
|
-
@messages.push(msg)
|
36
|
-
end
|
37
|
-
|
38
|
-
def error(msg)
|
39
|
-
@errors.push(msg)
|
40
|
-
end
|
41
|
-
|
42
|
-
def output(format='yaml')
|
43
|
-
format = 'yaml' unless self.respond_to? "output_#{format}"
|
44
|
-
#STDERR.puts "OUTPUT: #{format}"
|
45
|
-
self.send("output_#{format}")
|
46
|
-
end
|
47
|
-
|
48
|
-
def to_hash
|
49
|
-
h = {}
|
50
|
-
h[:version] = API_VERSION
|
51
|
-
h[:errors] = @errors unless @errors.empty?
|
52
|
-
h[:messages] = @messages unless @messages.empty?
|
53
|
-
h[:content] = @content || {}
|
54
|
-
h[:success] = @success || false
|
55
|
-
h
|
56
|
-
end
|
57
|
-
|
58
|
-
def output_zip
|
59
|
-
output = @content
|
60
|
-
end
|
61
|
-
|
62
|
-
def output_yaml
|
63
|
-
to_hash.to_yaml
|
64
|
-
end
|
65
|
-
|
66
|
-
# http://evang.eli.st/blog/2007/2/22/my-rails-gotcha-custom-to_xml-in-a-hash-or-array
|
67
|
-
# http://api.rubyonrails.org/classes/ActiveRecord/XmlSerialization.html#M000910
|
68
|
-
def output_xml
|
69
|
-
output = "<StellaResponse success=\":[\">\n"
|
70
|
-
output << "<todo>implement XML</todo>\n"
|
71
|
-
output << "</StellaResponse>\n"
|
72
|
-
end
|
73
|
-
|
74
|
-
def output_json
|
75
|
-
to_hash.to_json
|
76
|
-
end
|
77
|
-
|
78
|
-
def output_html
|
79
|
-
"hello!"
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|
data/lib/stella/storable.rb
DELETED
@@ -1,201 +0,0 @@
|
|
1
|
-
|
2
|
-
# TODO: Handle nested hashes and arrays.
|
3
|
-
|
4
|
-
require 'yaml'
|
5
|
-
require 'utils/fileutil'
|
6
|
-
|
7
|
-
module Stella
|
8
|
-
class Storable
|
9
|
-
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze unless defined? NICE_TIME_FORMAT
|
10
|
-
SUPPORTED_FORMATS = %w{tsv csv yaml json}.freeze unless defined? SUPPORTED_FORMATS
|
11
|
-
|
12
|
-
attr_reader :format
|
13
|
-
|
14
|
-
def format=(v)
|
15
|
-
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
16
|
-
@format = v
|
17
|
-
end
|
18
|
-
|
19
|
-
def init
|
20
|
-
self.class.send(:class_variable_set, :@@field_names, []) unless class_variable_defined?(:@@field_names)
|
21
|
-
self.class.send(:class_variable_set, :@@field_types, []) unless class_variable_defined?(:@@field_types)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.field(args={})
|
25
|
-
args.each_pair do |m,t|
|
26
|
-
|
27
|
-
[[:@@field_names, m], [:@@field_types, t]].each do |tuple|
|
28
|
-
class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
|
29
|
-
class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
|
30
|
-
end
|
31
|
-
|
32
|
-
next if method_defined?(m)
|
33
|
-
|
34
|
-
# NOTE: I need a way to put these in the caller's namespace... Here's they're shared by all
|
35
|
-
# the subclasses which is not helpful. It will likely involve Kernel#caller and binding.
|
36
|
-
# Maybe class_eval, wraped around def field.
|
37
|
-
|
38
|
-
|
39
|
-
define_method(m) do instance_variable_get("@#{m}") end
|
40
|
-
|
41
|
-
define_method("#{m}=") do |val|
|
42
|
-
instance_variable_set("@#{m}",val)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.field_names
|
48
|
-
class_variable_get(:@@field_names)
|
49
|
-
end
|
50
|
-
def self.field_types
|
51
|
-
class_variable_get(:@@field_types)
|
52
|
-
end
|
53
|
-
|
54
|
-
def field_names
|
55
|
-
self.class.send(:class_variable_get, :@@field_names)
|
56
|
-
end
|
57
|
-
|
58
|
-
def field_types
|
59
|
-
self.class.send(:class_variable_get, :@@field_types)
|
60
|
-
end
|
61
|
-
|
62
|
-
def format=(v)
|
63
|
-
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
64
|
-
@format = v
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
def dump(format=nil, with_titles=true)
|
69
|
-
format ||= @format
|
70
|
-
raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
|
71
|
-
send("to_#{format}", with_titles)
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.from_file(file_path=nil, format=nil)
|
75
|
-
raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
|
76
|
-
format = format || File.extname(file_path).tr('.', '')
|
77
|
-
me = send("from_#{format}", FileUtil.read_file_to_array(file_path))
|
78
|
-
me.format = format
|
79
|
-
me
|
80
|
-
end
|
81
|
-
def to_file(file_path=nil, with_titles=true)
|
82
|
-
raise "Cannot store to nil path" if file_path.nil?
|
83
|
-
format = File.extname(file_path).tr('.', '')
|
84
|
-
format ||= @format
|
85
|
-
FileUtil.write_file(file_path, dump(format, with_titles))
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
def self.from_hash(from={})
|
90
|
-
me = self.new
|
91
|
-
|
92
|
-
return me if !from || from.empty?
|
93
|
-
|
94
|
-
fnames = field_names
|
95
|
-
fnames.each_with_index do |key,index|
|
96
|
-
|
97
|
-
value = from[key]
|
98
|
-
|
99
|
-
# TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
|
100
|
-
|
101
|
-
if field_types[index] == Time
|
102
|
-
value = Time.parse(from[key].to_s)
|
103
|
-
elsif field_types[index] == DateTime
|
104
|
-
value = DateTime.parse(from[key].to_s)
|
105
|
-
elsif field_types[index] == TrueClass
|
106
|
-
value = (from[key].to_s == "true")
|
107
|
-
elsif field_types[index] == Float
|
108
|
-
value = from[key].to_f
|
109
|
-
elsif field_types[index] == Integer
|
110
|
-
value = from[key].to_i
|
111
|
-
end
|
112
|
-
|
113
|
-
me.send("#{key}=", value) if self.method_defined?("#{key}=")
|
114
|
-
end
|
115
|
-
me
|
116
|
-
end
|
117
|
-
def to_hash(with_titles=true)
|
118
|
-
tmp = {}
|
119
|
-
field_names.each do |fname|
|
120
|
-
tmp[fname] = self.send(fname)
|
121
|
-
end
|
122
|
-
tmp
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
def self.from_yaml(from=[])
|
127
|
-
# from is an array of strings
|
128
|
-
from_str = from.join('')
|
129
|
-
hash = YAML::load(from_str)
|
130
|
-
hash = from_hash(hash) if hash.is_a? Hash
|
131
|
-
hash
|
132
|
-
end
|
133
|
-
def to_yaml(with_titles=true)
|
134
|
-
to_hash.to_yaml
|
135
|
-
end
|
136
|
-
|
137
|
-
|
138
|
-
def self.from_json(from=[])
|
139
|
-
require 'json'
|
140
|
-
# from is an array of strings
|
141
|
-
from_str = from.join('')
|
142
|
-
tmp = JSON::load(from_str)
|
143
|
-
hash_sym = tmp.keys.inject({}) do |hash, key|
|
144
|
-
hash[key.to_sym] = tmp[key]
|
145
|
-
hash
|
146
|
-
end
|
147
|
-
hash_sym = from_hash(hash_sym) if hash_sym.is_a? Hash
|
148
|
-
hash_sym
|
149
|
-
end
|
150
|
-
def to_json(with_titles=true)
|
151
|
-
require 'json'
|
152
|
-
to_hash.to_json
|
153
|
-
end
|
154
|
-
|
155
|
-
def to_delimited(with_titles=false, delim=',')
|
156
|
-
values = []
|
157
|
-
field_names.each do |fname|
|
158
|
-
values << self.send(fname.to_s) # TODO: escape values
|
159
|
-
end
|
160
|
-
output = values.join(delim)
|
161
|
-
output = field_names.join(delim) << $/ << output if with_titles
|
162
|
-
output
|
163
|
-
end
|
164
|
-
def to_tsv(with_titles=false)
|
165
|
-
to_delimited(with_titles, "\t")
|
166
|
-
end
|
167
|
-
def to_csv(with_titles=false)
|
168
|
-
to_delimited(with_titles, ',')
|
169
|
-
end
|
170
|
-
def self.from_tsv(from=[])
|
171
|
-
self.from_delimited(from, "\t")
|
172
|
-
end
|
173
|
-
def self.from_csv(from=[])
|
174
|
-
self.from_delimited(from, ',')
|
175
|
-
end
|
176
|
-
|
177
|
-
def self.from_delimited(from=[],delim=',')
|
178
|
-
return if from.empty?
|
179
|
-
# We grab an instance of the class so we can
|
180
|
-
hash = {}
|
181
|
-
|
182
|
-
fnames = values = []
|
183
|
-
if (from.size > 1 && !from[1].empty?)
|
184
|
-
fnames = from[0].chomp.split(delim)
|
185
|
-
values = from[1].chomp.split(delim)
|
186
|
-
else
|
187
|
-
fnames = self.field_names
|
188
|
-
values = from[0].chomp.split(delim)
|
189
|
-
end
|
190
|
-
|
191
|
-
fnames.each_with_index do |key,index|
|
192
|
-
next unless values[index]
|
193
|
-
hash[key.to_sym] = values[index]
|
194
|
-
end
|
195
|
-
hash = from_hash(hash) if hash.is_a? Hash
|
196
|
-
hash
|
197
|
-
end
|
198
|
-
|
199
|
-
|
200
|
-
end
|
201
|
-
end
|