rwb 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/rwb.rb +58 -415
  2. data/test/test_rwb.rb +121 -6
  3. metadata +24 -16
data/lib/rwb.rb CHANGED
@@ -1,172 +1,12 @@
1
1
  require 'net/http'
2
2
  require 'uri'
3
+ require 'rwb/url'
4
+ require 'rwb/results'
5
+ require 'rwb/warmup'
6
+ require 'rwb/report'
7
+ require 'open-uri'
3
8
 
4
9
  module RWB
5
-
6
- #
7
- # The Url class holds individual URLs for testing. +Url+s are defined by a
8
- # Fixnum representing the weight and a String representing the URL itself.
9
- # The weights are not required to be percentages.
10
- #
11
- # url = RWB::Url.new(20, "http://www.example.com")
12
- class Url
13
- attr_reader :weight
14
-
15
- def initialize(weight, url)
16
- @weight = weight
17
- @url = url
18
- end
19
-
20
- # to_url returns the URL to be requested as a string
21
- #
22
- # url.to_url # => "http://www.example.com"
23
- def to_url
24
- @url
25
- end
26
-
27
- #
28
- # to_base returns a string suitable for creating a +Regexp+ to match
29
- # against.
30
- #
31
- # url.to_base # => "http://www.example.com "
32
- #
33
- def to_base
34
- @url
35
- end
36
-
37
- def to_s
38
- @weight + " " + @url
39
- end
40
- end
41
-
42
- #
43
- # A UrlGroup is a collection of related URLs, for example the URL for a
44
- # search tool, with several search queries. The group is weighted (and
45
- # reported on) as a whole, but individual requests are made with random
46
- # elements from an array of extensions.
47
- #
48
- # urls = UrlGroup.new(20, "http://www.example.com/search?",
49
- # ["foo", "bar", "baz"])
50
- #
51
- class UrlGroup
52
- attr_reader :weight
53
-
54
- def initialize(weight, base, extension_array)
55
- @weight = weight.to_i
56
- @base = base.to_s
57
- @extension_array = extension_array
58
- end
59
-
60
- #
61
- # to_url returns a complete URL to be requested. It takes an optional
62
- # seed argument, which must be a Fixnum. If given, this will seed the
63
- # random selection of the extension.
64
- #
65
- # urls.to_url(1234) # => "http://www.example.com/search?baz"
66
- #
67
- def to_url(seed = nil)
68
- if seed
69
- srand = seed
70
- end
71
- @base + @extension_array[rand(@extension_array.length)]
72
- end
73
-
74
- #
75
- # to_base returns a String suitable for building a Regex to match
76
- # against. This String will not include any extensions.
77
- #
78
- # urls.to_base # => "http://www.example.com/search?"
79
- #
80
- def to_base
81
- @base
82
- end
83
-
84
- def to_s
85
- @weight + " " + @base
86
- end
87
-
88
- end
89
-
90
- class Result
91
- attr_reader :id, :timestamp, :url
92
- attr_reader :elapsed_time, :response_code
93
-
94
- def initialize(id, timestamp, url, elapsed_time, response_code)
95
- @id = id
96
- @timestamp = timestamp
97
- @url = url
98
- @elapsed_time = elapsed_time
99
- @response_code = response_code
100
- end
101
- end
102
-
103
- class RunResults
104
- attr_reader :results
105
-
106
- def initialize
107
- @results = Array.new
108
- end
109
-
110
- def add_result(result)
111
- @results.push(result)
112
- end
113
-
114
- def each_by_response_code(code)
115
- @results.each do |result|
116
- yield result if result.response_code.to_i == code.to_i
117
- end
118
- end
119
-
120
- def all_by_response_code(code)
121
- coded_results = Array.new
122
- @results.each do |result|
123
- if result.response_code.to_i == code.to_i
124
- coded_results.push(result)
125
- end
126
- end
127
- coded_results
128
- end
129
-
130
- def all_by_url(url)
131
- coded_results = Array.new
132
- @results.each do |result|
133
- if result.url.to_s == url.to_s
134
- coded_results.push(result)
135
- end
136
- end
137
- coded_results
138
- end
139
-
140
- def each_by_url(url)
141
- @results.each do |result|
142
- yield result if result.url.to_s == url.to_s
143
- end
144
- end
145
-
146
- def all_with_id
147
- id_results = Hash.new
148
- @results.each do |result|
149
- id_results[result.id] = result
150
- end
151
- id_results
152
- end
153
-
154
- def times_by_url(url = nil)
155
- coded_results = Array.new
156
- @results.each do |result|
157
- if url
158
- if result.url.to_s == url.to_s
159
- coded_results.push(result.elapsed_time)
160
- end
161
- else
162
- coded_results.push(result.elapsed_time)
163
- end
164
- end
165
- coded_results
166
- end
167
-
168
- end
169
-
170
10
  class Builder
171
11
  attr_reader :urls, :total_weight
172
12
 
@@ -178,14 +18,22 @@ module RWB
178
18
  end
179
19
  end
180
20
 
181
- def add_url(weight, url)
21
+ def add_url(weight, url, method='get')
182
22
  @total_weight += weight.to_i
183
- @urls[@total_weight] = Url.new(weight, url)
23
+ @urls[@total_weight] = Url.new(weight, url, method)
24
+ 7
184
25
  end
185
26
 
186
- def add_url_group(weight, base, extension_array)
27
+ def add_url_group(weight, base, extension_array, method='get')
187
28
  @total_weight += weight.to_i
188
- @urls[@total_weight] = UrlGroup.new(weight, base, extension_array)
29
+ @urls[@total_weight] = UrlGroup.new(weight, base,
30
+ extension_array, method)
31
+ end
32
+
33
+ def add_url_session(weight, session, name, method='get')
34
+ @total_weight += weight.to_i
35
+ @urls[@total_weight] = UrlSession.new(weight, session,
36
+ name, method)
189
37
  end
190
38
 
191
39
  def get_url(seed = nil)
@@ -212,11 +60,8 @@ module RWB
212
60
 
213
61
  def initialize(urls, max_runs=100, max_threads=10)
214
62
  @urls = urls
215
- @current_runs = 0
216
- @current_threads = 0
217
63
  @max_runs = max_runs
218
64
  @max_threads = max_threads
219
- @threads = []
220
65
  @results_mean = nil
221
66
  @results_std_dev = nil
222
67
  @sla_levels = [0.5, 0.9]
@@ -230,264 +75,62 @@ module RWB
230
75
  proxy_pass)
231
76
  end
232
77
 
233
- def warmup(num_runs = 1)
234
- $stderr.puts "warming up with #{num_runs} runs"
235
- for run in 1..num_runs
236
- $stderr.print "#{run} "
237
- @urls.urls.values.each do |url|
238
- url = URI.parse(url.to_url)
239
- @http.start(url.host, url.port) do |http|
240
- http.request( Net::HTTP::Get.new(url) )
241
- end
242
- end
243
- end
244
- $stderr.puts
245
- end
246
-
247
- def rand_warmup(num_requests)
248
- $stderr.puts "warming up with #{num_requests} requests"
249
- checkpoint = num_requests/10
250
- for run in 1..num_requests
251
- if run % checkpoint == 0
252
- $stderr.print "#{run} "
253
- end
254
- url = URI.parse(@urls.get_url.to_url)
255
- @http.start(url.host, url.port) do |http|
256
- http.request( Net::HTTP::Get.new(url) )
257
- end
258
- end
259
- $stderr.puts
260
- end
261
-
262
- def spec_warmup(urls, num_runs=1)
263
- $stderr.puts "warming up with #{num_runs} runs"
264
- for run in 1..num_runs
265
- $stderr.print "#{run} "
266
- urls.each do |url|
267
- url = URI.parse(url)
268
- @http.start(url.host, url.port) do |http|
269
- http.request( Net::HTTP::Get.new(url) )
270
- end
271
- end
272
- end
273
- $stderr.puts
274
- end
275
-
276
78
  def run
277
- printed_runs = Array.new
79
+ threads = []
80
+ @steps = []
81
+ 0.step(@max_runs, @max_runs/10) {|i| @steps.push(i) }
82
+ @requests = (1..@max_runs).to_a
278
83
  total_start_time = Time.now
279
- checkpoint = @max_runs/10
280
- while @current_runs < @max_runs
281
- build_thread
282
- if @current_runs % checkpoint == 0 &&
283
- ! ( printed_runs.include?(@current_runs))
284
- $stderr.puts "completed #{@current_runs} runs"
285
- printed_runs.push(@current_runs)
286
- end
84
+ for t in 1..@max_threads
85
+ threads << Thread.new { run_thread }
287
86
  end
288
-
289
- @threads.each { |th| th.join }
290
-
87
+ threads.each { |th| th.join }
291
88
  total_stop_time = Time.now
292
89
  @total_time = total_stop_time - total_start_time
293
90
  end
294
91
 
295
- def results_mean
296
- if @results_mean
297
- return @results_mean
298
- end
299
- @results_mean = @new_results.times_by_url.inject(0) do |sum, time|
300
- sum += time
301
- end
302
- @results_mean = @results_mean/@new_results.times_by_url.length
303
- make_milli(@results_mean)
304
- end
305
-
306
- def results_std_dev
307
- if @results_std_dev
308
- return @results_std_dev
309
- end
310
- unless @results_mean
311
- results_mean
312
- end
313
- @results_std_dev = @new_results.times_by_url.inject(0) do |std_dev, time|
314
- std_dev += (time - @results_mean) ** 2
315
- end
316
- @results_std_dev =
317
- Math.sqrt(@results_std_dev/@new_results.times_by_url.length)
318
- make_milli(@results_std_dev)
319
- end
320
-
321
- def adjust_scale(scale, max)
322
- if scale/2 > max
323
- scale = scale/2
324
- scale = adjust_scale(scale, max)
325
- end
326
- scale
327
- end
328
-
329
- def results_quartile(times, scale = nil)
330
- times = times.map { |t| make_milli(t) }
331
- size = results_mean + results_std_dev * 2
332
- min = times[0]
333
- first = times[(times.length*0.25).to_int - 1]
334
- second = times[(times.length*0.5).to_int - 1]
335
- third = times[(times.length*0.75).to_int - 1]
336
- max = times[-1]
337
-
338
- len = max.to_s.length
339
- unless scale
340
- scale = (max/((10**len).to_f)).ceil * (10**len)
341
- scale = adjust_scale(scale, max)
342
- end
343
- step = scale/50
344
- step = 1 if step == 0
345
- scale = 50 if scale < 50
346
- line = Array.new
347
- char = ' '
348
- for i in 0..49 do
349
- case i
350
- when max/step
351
- line[i] = ']'
352
- when third/step
353
- line[i] = '|'
354
- char = ' '
355
- when second/step
356
- line[i] = '+'
357
- when first/step
358
- line[i] = '|'
359
- char = '-'
360
- when min/step
361
- line[i] = '['
362
- when 0
363
- line[i] = ':'
364
- when 49
365
- line[i] = ':'
366
- else
367
- line[i] = char
92
+ def run_thread
93
+ while req_num = @requests.shift
94
+ run_test(req_num-1)
95
+ if @steps.include?(req_num)
96
+ puts "completed #{req_num} runs"
368
97
  end
369
98
  end
370
- return '0' + line.join('') + scale.to_s
371
- end
372
-
373
- def report_full(sla_levels = @sla_levels)
374
- report_header
375
- report_overall(sla_levels)
376
- report_urls(sla_levels)
377
- report_by_time(sla_levels)
378
99
  end
379
100
 
380
- def report_overall(sla_levels = @sla_levels)
381
- results = @new_results.times_by_url
101
+ def run_test(id)
102
+ request = @urls.get_url
103
+ # url = URI.parse request.to_url
104
+ base = request.to_base
105
+ time = nil
106
+ response_code = nil
382
107
 
383
- puts "Overall results:"
384
- print_times(results.sort, sla_levels)
385
- end
386
-
387
- def report_urls(sla_levels = @sla_levels)
388
- @urls.urls.keys.sort.each do |key|
389
- url = @urls.urls[key].to_base
390
- results = @new_results.times_by_url(url)
391
- puts "Results for #{url}:"
392
- if results.length > 0
393
- print_times(results.sort, sla_levels)
394
- else
395
- puts "no results for url"
108
+ # @http.start(url.host, url.port) do |http|
109
+ # start_time = Time.now
110
+ # response = http.request( Net::HTTP::Get.new(url) )
111
+ # stop_time = Time.now
112
+ # time = stop_time - start_time
113
+ # response_code = response.code
114
+ # end
115
+
116
+ start_time = Time.now
117
+ begin
118
+ open(request.to_url) do |page|
119
+ stop_time = Time.now
120
+ time = stop_time - start_time
121
+ response_code = page.status
396
122
  end
397
- end
398
- end
399
-
400
- def graph_quartiles_urls(scale = nil)
401
- @urls.urls.keys.sort.each do |key|
402
- url = @urls.urls[key].to_base
403
- results = @new_results.times_by_url(url)
404
- puts "#{url}:\n\t" + results_quartile(results.sort, scale)
405
- end
406
- end
407
-
408
- def graph_quartiles_overall
409
- results = @new_results.times_by_url
410
- puts results_quartile(results.sort)
411
- end
412
-
413
- def report_by_time(sla_levels = @sla_levels, granularity = 0.2)
414
- start = 0.0
415
- stop = granularity
416
- results = @new_results.times_by_url
417
- puts "Results by time:"
418
- while stop <= 1.0
419
- first = (results.length * start).to_i
420
- last = (results.length * stop).to_i
421
- these_results = results.slice(first..last)
422
- puts "results for requests #{first} - #{last}"
423
- print_times(these_results.sort, sla_levels)
424
- start = stop
425
- stop += granularity
426
- end
427
- end
428
-
429
- def print_times(results, levels)
430
- times = get_times(results, levels)
431
- puts "\tShortest time:\t#{times.shift} msecs"
432
- levels.each_with_index do |num, index|
433
- percent = num.to_f * 100.0
434
- puts "\t#{percent}%ile time:\t#{times[index]} msecs"
435
- end
436
- puts "\tLongest time:\t#{times[-1]} msecs"
437
- end
438
-
439
- def get_times(results, levels)
440
- times = Array.new
441
- times.push(make_milli(results[0]))
442
- levels.each do |num|
443
- times.push(make_milli(results[(results.length*num).to_i]))
444
- end
445
- times.push(make_milli(results[-1]))
446
- end
447
-
448
- def make_milli(num)
449
- (num * 1000).to_i
450
- end
451
-
452
- def report_header
453
- print <<EOF
454
- Concurrency Level: #{@max_threads}
455
- Total Requests: #{@max_runs}
456
- Total time for testing: #{@total_time} secs
457
- Requests per second: #{@max_runs/@total_time}
458
- Mean time per request: #{results_mean} msecs
459
- Standard deviation: #{results_std_dev}
460
- EOF
461
- end
462
-
463
- def build_thread
464
- if @current_threads < @max_threads
465
- @current_runs += 1
466
- @threads << Thread.new { run_test(@current_runs)}
467
- @current_threads += 1
468
- end
469
- end
470
-
471
- def run_test(id)
472
- request = @urls.get_url
473
- url = URI.parse request.to_url
474
- base = request.to_base
475
- start_time = Time.now
476
- response_code = nil
477
-
478
- @http.start(url.host, url.port) do |http|
479
- response = http.request( Net::HTTP::Get.new(url) )
480
- response_code = response.code
481
- end
482
-
123
+ rescue
483
124
  stop_time = Time.now
484
125
  time = stop_time - start_time
485
-
486
- result = Result.new(id, Time.now, base, time, response_code)
487
-
488
- @new_results.add_result(result)
489
- @current_threads -= 1
126
+ response_code = 404
490
127
  end
128
+
129
+
130
+ result = Result.new(id, Time.now, base, time, response_code)
131
+
132
+ @new_results.add_result(result)
491
133
  end
492
-
493
134
  end
135
+
136
+ end
@@ -27,6 +27,13 @@ module TestRWB
27
27
  assert_equal(20, @urls.get_url.weight)
28
28
  end
29
29
 
30
+ def test_add_session
31
+ session = ["http://localhost", "http://localhost/foo"]
32
+ @urls.add_url_session(10, session, 'session1')
33
+ assert_equal(1, @urls.urls.length)
34
+ assert_instance_of(RWB::UrlSession, @urls.get_url)
35
+ end
36
+
30
37
  def test_build_urls
31
38
  config = {
32
39
  "http://localhost" => 20,
@@ -39,9 +46,18 @@ module TestRWB
39
46
  @urls.add_url(20, "http://localhost")
40
47
  assert_equal("http://localhost", @urls.get_url.to_url)
41
48
  @urls.add_url(20, "http://localhost/foo")
42
- assert_equal("http://localhost", @urls.get_url(1).to_url)
43
- assert_equal("http://localhost", @urls.get_url(2).to_url)
44
- assert_equal("http://localhost/foo", @urls.get_url(3).to_url)
49
+ # due to a change in the randomization seed handling between
50
+ # Ruby 1.8.2 and 1.8.3, we need to check the RUBY_VERSION
51
+ # and behave accordingly
52
+ if RUBY_VERSION < "1.8.3"
53
+ assert_equal("http://localhost", @urls.get_url(1).to_url)
54
+ assert_equal("http://localhost", @urls.get_url(2).to_url)
55
+ assert_equal("http://localhost/foo", @urls.get_url(3).to_url)
56
+ else # FIX THESE
57
+ assert_equal("http://localhost/foo", @urls.get_url(1).to_url)
58
+ assert_equal("http://localhost", @urls.get_url(2).to_url)
59
+ assert_equal("http://localhost/foo", @urls.get_url(3).to_url)
60
+ end
45
61
  end
46
62
 
47
63
  def test_total_weight
@@ -137,6 +153,19 @@ module TestRWB
137
153
  assert_equal(100, test.adjust_scale(100,75))
138
154
  end
139
155
 
156
+ def test_get_times
157
+ test = RWB::Runner.new(nil)
158
+ assert_equal([100,300,400,500], test.get_times([0.1,0.2,0.3,
159
+ 0.4,0.5],
160
+ [0.5, 0.75]))
161
+ assert_equal([100,900,1000], test.get_times([0.1,0.2,0.3,0.4,
162
+ 0.5,0.6,0.7,0.8,
163
+ 0.9,1],[0.9]))
164
+ assert_equal([100,300,500], test.get_times([0.1,0.2,0.3,
165
+ 0.4,0.5],
166
+ [0.5,0.9]))
167
+ end
168
+
140
169
  def test_print_times
141
170
  #raise NotImplementedError, 'Need to write test_print_times'
142
171
  end
@@ -184,6 +213,27 @@ module TestRWB
184
213
  def test_weight
185
214
  assert_equal(10, @url.weight)
186
215
  end
216
+
217
+ def test_read_method
218
+ assert_equal('GET', @url.read_method())
219
+ assert_equal('GET', @url.read_method('get'))
220
+ assert_equal('GET', @url.read_method('foo'))
221
+ assert_equal('POST', @url.read_method('post'))
222
+ assert_equal('POST', @url.read_method('POST'))
223
+ end
224
+
225
+ def test_method
226
+ assert_equal('GET', @url.method)
227
+ url = RWB::Url.new(10,"http://www.example.com",'Post')
228
+ assert_equal('POST', url.method)
229
+ url = RWB::Url.new(10,"http://www.example.com",'GET')
230
+ assert_equal('GET', url.method)
231
+ end
232
+
233
+ def test_to_s
234
+ assert_equal("10 http://www.example.com", @url.to_s)
235
+ end
236
+
187
237
  end
188
238
 
189
239
  class TestUrlGroup < Test::Unit::TestCase
@@ -197,14 +247,79 @@ module TestRWB
197
247
  end
198
248
 
199
249
  def test_to_url
200
- assert_equal("http://www.example.com/search?foo", @url.to_url(1))
201
- assert_equal("http://www.example.com/search?baz", @url.to_url(2))
202
- assert_equal("http://www.example.com/search?baz", @url.to_url(3))
250
+ # due to changes in random seed handling between Ruby 1.8.2 and 1.8.3,
251
+ # we needdd to check the Ruby version and behave differently
252
+ if RUBY_VERSION < "1.8.3"
253
+ assert_equal("http://www.example.com/search?foo", @url.to_url(1))
254
+ assert_equal("http://www.example.com/search?baz", @url.to_url(2))
255
+ assert_equal("http://www.example.com/search?baz", @url.to_url(3))
256
+ else # FIX THESE
257
+ assert_equal("http://www.example.com/search?bar", @url.to_url(1))
258
+ assert_equal("http://www.example.com/search?foo", @url.to_url(2))
259
+ assert_equal("http://www.example.com/search?foo", @url.to_url(2))
260
+ end
261
+ end
262
+
263
+ def test_weight
264
+ assert_equal(10, @url.weight)
265
+ end
266
+
267
+ def test_method
268
+ assert_equal('GET', @url.method)
269
+ queries = ["foo", "bar", "baz"]
270
+ url = RWB::UrlGroup.new(10, "http://www.example.com/search?",
271
+ queries, 'post')
272
+ assert_equal('POST', url.method)
273
+ end
274
+
275
+ def test_to_s
276
+ assert_equal("10 http://www.example.com/search?", @url.to_s)
277
+ end
278
+
279
+ end
280
+
281
+
282
+ class TestUrlSession < Test::Unit::TestCase
283
+ def setup
284
+ session = ["http://www.example.com",
285
+ "http://www.example.com/browse",
286
+ "http://www.example.com/detail?foo",
287
+ "http://www.example.com/detail?bar",
288
+ "http://www.example.com/browse",
289
+ "http://www.example.com/logout"]
290
+ @url = RWB::UrlSession.new(10,session, 'Session 1')
291
+ end
292
+
293
+ def test_to_url
294
+ assert_equal("http://www.example.com",@url.to_url)
295
+ assert_equal("http://www.example.com/browse",@url.to_url)
296
+ assert_equal("http://www.example.com/detail?foo",@url.to_url)
297
+ assert_equal("http://www.example.com/detail?bar",@url.to_url)
298
+ assert_equal("http://www.example.com/browse",@url.to_url)
299
+ assert_equal("http://www.example.com/logout",@url.to_url)
300
+ assert_equal("http://www.example.com",@url.to_url)
301
+ assert_equal("http://www.example.com/browse",@url.to_url)
302
+ end
303
+
304
+ def test_to_base
305
+ assert_equal('Session 1', @url.to_base)
203
306
  end
204
307
 
205
308
  def test_weight
206
309
  assert_equal(10, @url.weight)
207
310
  end
311
+
312
+ def test_method
313
+ assert_equal('GET', @url.method)
314
+ session = ["foo", "bar", "baz"]
315
+ url = RWB::UrlSession.new(10, session, 'Session 2', 'post')
316
+ assert_equal('POST', url.method)
317
+ end
318
+
319
+ def test_to_s
320
+ assert_equal("10 Session 1", @url.to_s)
321
+ end
322
+
208
323
  end
209
324
 
210
325
  class TestResult < Test::Unit::TestCase
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.10
2
+ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: rwb
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.1
7
- date: 2005-12-02
8
- summary: "Ruby Web Bench, a web performance and load testing framework"
6
+ version: 0.2.2
7
+ date: 2007-01-31 00:00:00 -07:00
8
+ summary: Ruby Web Bench, a web performance and load testing framework
9
9
  require_paths:
10
- - lib
10
+ - lib
11
11
  email: pat.eyler@gmail.com
12
12
  homepage: http://rubyforge.org/projects/rwb
13
13
  rubyforge_project:
@@ -18,24 +18,32 @@ bindir: bin
18
18
  has_rdoc: "true"
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
- -
22
- - ">"
23
- - !ruby/object:Gem::Version
24
- version: 0.0.0
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
25
24
  version:
26
25
  platform: ruby
26
+ signing_key:
27
+ cert_chain:
27
28
  authors:
28
- - Pat Eyler
29
+ - Pat Eyler
29
30
  files:
30
- - NEWS
31
- - README
32
- - TODO
33
- - lib/rwb.rb
34
- - test/test_rwb.rb
31
+ - NEWS
32
+ - README
33
+ - TODO
34
+ - lib/rwb.rb
35
+ - test/test_rwb.rb
35
36
  test_files: []
37
+
36
38
  rdoc_options: []
39
+
37
40
  extra_rdoc_files: []
41
+
38
42
  executables: []
43
+
39
44
  extensions: []
45
+
40
46
  requirements: []
41
- dependencies: []
47
+
48
+ dependencies: []
49
+