stella 0.8.8.001 → 2.0.1.001
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +9 -1
- data/Gemfile +19 -0
- data/Gemfile.lock +50 -0
- data/README.md +5 -79
- data/Rakefile +10 -7
- data/Rudyfile +1 -1
- data/TODO +31 -0
- data/VERSION.yml +3 -4
- data/bin/stella +23 -81
- data/certs/README.txt +17 -0
- data/certs/cacerts.pem +1529 -0
- data/certs/gd-class2-root.crt +24 -0
- data/certs/gd_bundle.crt +76 -0
- data/certs/gd_intermediate.crt +29 -0
- data/certs/startssl-ca.pem +44 -0
- data/certs/startssl-sub.class1.server.ca.pem +36 -0
- data/certs/stella-master.crt +1738 -0
- data/lib/stella.rb +191 -123
- data/lib/stella/cli.rb +47 -67
- data/lib/stella/client.rb +424 -360
- data/lib/stella/core_ext.rb +527 -0
- data/lib/stella/engine.rb +126 -419
- data/lib/stella/report.rb +391 -0
- data/lib/stella/testplan.rb +432 -306
- data/lib/stella/utils.rb +227 -2
- data/stella.gemspec +56 -55
- data/try/00_basics_try.rb +29 -0
- data/try/01_selectable_try.rb +25 -0
- data/try/09_utils_try.rb +67 -0
- data/try/10_stella_object_try.rb +49 -0
- data/try/40_report_try.rb +133 -0
- data/try/90_class_syntax_try.rb +13 -0
- data/try/emhttp.rb +62 -0
- data/try/rubyroute.rb +70 -0
- data/try/support/file.bmp +0 -0
- data/try/support/file.gif +0 -0
- data/try/support/file.ico +0 -0
- data/try/support/file.jpeg +0 -0
- data/try/support/file.jpg +0 -0
- data/try/support/file.png +0 -0
- data/try/traceviz.rb +60 -0
- data/vendor/httpclient-2.1.5.2/httpclient/session.rb +5 -2
- metadata +81 -53
- data/examples/cookies/plan.rb +0 -49
- data/examples/csvdata/plan.rb +0 -32
- data/examples/csvdata/search_terms.csv +0 -14
- data/examples/dynamic/plan.rb +0 -60
- data/examples/essentials/logo.png +0 -0
- data/examples/essentials/plan.rb +0 -248
- data/examples/essentials/search_terms.txt +0 -19
- data/examples/exceptions/plan.rb +0 -20
- data/examples/httpauth/plan.rb +0 -33
- data/examples/timeout/plan.rb +0 -18
- data/examples/variables/plan.rb +0 -41
- data/lib/stella/client/container.rb +0 -378
- data/lib/stella/common.rb +0 -363
- data/lib/stella/data.rb +0 -59
- data/lib/stella/data/http.rb +0 -189
- data/lib/stella/engine/functional.rb +0 -156
- data/lib/stella/engine/load.rb +0 -516
- data/lib/stella/guidelines.rb +0 -18
- data/lib/stella/logger.rb +0 -150
- data/lib/stella/utils/httputil.rb +0 -266
- data/try/01_numeric_mixins_tryouts.rb +0 -40
- data/try/12_digest_tryouts.rb +0 -42
- data/try/70_module_usage.rb +0 -21
- data/try/api/10_functional.rb +0 -20
- data/try/configs/failed_requests.rb +0 -31
- data/try/configs/global_sequential.rb +0 -18
- data/try/proofs/thread_queue.rb +0 -21
data/lib/stella/guidelines.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Stella
|
4
|
-
module Guidelines
|
5
|
-
extend self
|
6
|
-
AFE = "Always fail early"
|
7
|
-
ABA = "Always be accurate"
|
8
|
-
CBC = "Consistency before cuteness"
|
9
|
-
NDP = "No defensive programming"
|
10
|
-
def inspect
|
11
|
-
all = Stella::Guidelines.constants
|
12
|
-
g = all.collect { |c| '%s="%s"' % [c, const_get(c)] }
|
13
|
-
%q{#<Stella::Guidelines:0x%s %s>} % [self.object_id, g.join(' ')]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
p Stella::Guidelines if __FILE__ == $0
|
data/lib/stella/logger.rb
DELETED
@@ -1,150 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Stella
|
4
|
-
|
5
|
-
|
6
|
-
class Logger
|
7
|
-
|
8
|
-
attr_accessor :lev
|
9
|
-
attr_reader :templates
|
10
|
-
|
11
|
-
@@disable = false
|
12
|
-
class << self
|
13
|
-
def disable!() @@disable = true end
|
14
|
-
def enable!() @@disable = true end
|
15
|
-
def disabled?() @@disable == true end
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(output=STDOUT)
|
19
|
-
@mutex, @buffer = Mutex.new, StringIO.new
|
20
|
-
@lev, @offset = 1, 0
|
21
|
-
@templates = {}
|
22
|
-
@autoflush = false
|
23
|
-
self.output = output
|
24
|
-
end
|
25
|
-
|
26
|
-
def autoflush!() @autoflush = true end
|
27
|
-
def autoflush?() @autoflush == true end
|
28
|
-
|
29
|
-
def add_template(name, str)
|
30
|
-
@templates[name.to_sym] = str
|
31
|
-
end
|
32
|
-
|
33
|
-
def template(name)
|
34
|
-
@templates[name]
|
35
|
-
end
|
36
|
-
|
37
|
-
def template?(name)
|
38
|
-
@templates.has_key? name
|
39
|
-
end
|
40
|
-
|
41
|
-
def print(level, *msg)
|
42
|
-
return if level > @lev || self.class.disabled?
|
43
|
-
@buffer.print *msg
|
44
|
-
flush if autoflush?
|
45
|
-
true
|
46
|
-
end
|
47
|
-
|
48
|
-
def puts(level, *msg)
|
49
|
-
return if level > @lev || self.class.disabled?
|
50
|
-
@buffer.puts *msg
|
51
|
-
flush if autoflush?
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
|
-
def info(*msg) puts 1, *msg end
|
56
|
-
def info1(*msg) puts 1, *msg end
|
57
|
-
def info2(*msg) puts 2, *msg end
|
58
|
-
def info3(*msg) puts 3, *msg end
|
59
|
-
def info4(*msg) puts 4, *msg end
|
60
|
-
|
61
|
-
def debug(*msg)
|
62
|
-
return unless Stella.debug?
|
63
|
-
puts 1, *msg
|
64
|
-
end
|
65
|
-
|
66
|
-
def tinfo(templ, *args)
|
67
|
-
info template(templ) % args
|
68
|
-
end
|
69
|
-
|
70
|
-
def twarn(templ, *args)
|
71
|
-
warn template(templ) % args
|
72
|
-
end
|
73
|
-
|
74
|
-
class UnknownTemplate < Stella::Error
|
75
|
-
end
|
76
|
-
|
77
|
-
def method_missing(meth, *args)
|
78
|
-
raise UnknownTemplate.new(meth.to_s) unless template? meth
|
79
|
-
tinfo meth, *args
|
80
|
-
end
|
81
|
-
|
82
|
-
def output=(o)
|
83
|
-
return if self.class.disabled?
|
84
|
-
@mutex.synchronize do
|
85
|
-
if o.kind_of? String
|
86
|
-
o = File.open(o, File::CREAT|File::TRUNC|File::RDWR, 0644)
|
87
|
-
end
|
88
|
-
@output = o
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# TODO: There's a big when using print (no newline)
|
93
|
-
def flush
|
94
|
-
return if self.class.disabled?
|
95
|
-
@mutex.synchronize do
|
96
|
-
#return if @offset == @output.tell
|
97
|
-
@buffer.seek @offset
|
98
|
-
@output.print @buffer.read unless @buffer.eof?
|
99
|
-
@offset = @buffer.tell
|
100
|
-
@output.flush
|
101
|
-
end
|
102
|
-
true
|
103
|
-
end
|
104
|
-
|
105
|
-
def path
|
106
|
-
@output.path if @output.respond_to? :path
|
107
|
-
end
|
108
|
-
|
109
|
-
def clear
|
110
|
-
return if self.class.disabled?
|
111
|
-
flush
|
112
|
-
@mutex.synchronize do
|
113
|
-
@buffer.rewind
|
114
|
-
@offset = 0
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def close
|
119
|
-
return if self.class.disabled?
|
120
|
-
flush
|
121
|
-
@buffer.close
|
122
|
-
@output.close
|
123
|
-
end
|
124
|
-
|
125
|
-
end
|
126
|
-
|
127
|
-
# Prints to a buffer.
|
128
|
-
# Must call flush to send to output.
|
129
|
-
class SyncLogger < Logger
|
130
|
-
def print(level, *msg)
|
131
|
-
return if level > @lev || self.class.disabled?
|
132
|
-
@mutex.synchronize {
|
133
|
-
@buffer.print *msg
|
134
|
-
flush if autoflush?
|
135
|
-
}
|
136
|
-
true
|
137
|
-
end
|
138
|
-
|
139
|
-
def puts(level, *msg)
|
140
|
-
#Stella.ld [level, @lev, msg]
|
141
|
-
return if level > @lev || self.class.disabled?
|
142
|
-
@mutex.synchronize {
|
143
|
-
@buffer.puts *msg
|
144
|
-
flush if autoflush?
|
145
|
-
}
|
146
|
-
true
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
@@ -1,266 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'uri'
|
3
|
-
require 'timeout'
|
4
|
-
require 'net/http'
|
5
|
-
|
6
|
-
module HTTPUtil
|
7
|
-
VALID_METHODS = %w{GET HEAD POST PUT DELETE}
|
8
|
-
@@timeout = 20
|
9
|
-
|
10
|
-
# Takes a string. See WEBrick::parse_header(string).
|
11
|
-
def HTTPUtil.parse_header(raw)
|
12
|
-
header = Hash.new([].freeze)
|
13
|
-
raw.each_line do |line|
|
14
|
-
case line
|
15
|
-
when /\A(.+?):\s+(.+)\z/om
|
16
|
-
name, value = $1, $2
|
17
|
-
name = name.tr('-', '_').to_sym
|
18
|
-
value.strip!
|
19
|
-
|
20
|
-
header[name] = [] unless header.has_key?(name)
|
21
|
-
header[name] << value
|
22
|
-
end
|
23
|
-
end
|
24
|
-
header
|
25
|
-
end
|
26
|
-
|
27
|
-
# Takes a string or array. See parse_header_body for further info.
|
28
|
-
# Returns +method+, +http_version+, +uri+, +header+, +body+
|
29
|
-
def HTTPUtil.parse_http_request(data, host=:unknown, port=80)
|
30
|
-
return unless data && !data.empty?
|
31
|
-
data = data.split(/\r?\n/) unless data.kind_of? Array
|
32
|
-
data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
|
33
|
-
request_line = data.shift # i.e. GET /path HTTP/1.1
|
34
|
-
method, path, http_version = nil
|
35
|
-
|
36
|
-
# With WEBrick and other proxies, the entire URI is included in HTTP requests.
|
37
|
-
# i.e. GET http://stellaaahhhh.com/streetcar.png HTTP/1.1
|
38
|
-
# The parser is expecting just the absolute path.
|
39
|
-
if request_line =~ /^(\S+)\s+(http:\/\/.+)\s+(HTTP.+)?/mo
|
40
|
-
uri = URI.parse($2)
|
41
|
-
request_line = "#{$1} #{uri.request_uri} #{$3}"
|
42
|
-
host = uri.host
|
43
|
-
end
|
44
|
-
|
45
|
-
if request_line =~ /^(\S+)\s+(\S+)(?:\s+HTTP\/(\d+\.\d+))?/mo
|
46
|
-
method = $1
|
47
|
-
http_version = $3 # Comes before $2 b/c the split resets the numbered vars
|
48
|
-
path, query_string = $2.split('?')
|
49
|
-
|
50
|
-
# We only process the header and body data when we know we're
|
51
|
-
# starting from the beginning of a request string. We don't
|
52
|
-
# want no partials.
|
53
|
-
header, body = HTTPUtil.parse_header_body(data)
|
54
|
-
query = HTTPUtil.parse_query(method, query_string)
|
55
|
-
|
56
|
-
|
57
|
-
# TODO: Parse username/password
|
58
|
-
uri = URI::HTTP.build({
|
59
|
-
:scheme => 'http',
|
60
|
-
:host => header[:Host][0] || host.to_s,
|
61
|
-
:port => port,
|
62
|
-
:path => path,
|
63
|
-
:query => query_string
|
64
|
-
})
|
65
|
-
|
66
|
-
else
|
67
|
-
rl = request_line.sub(/\x0d?\x0a\z/o, '')
|
68
|
-
raise "Bad Request-Line `#{rl}'."
|
69
|
-
end
|
70
|
-
|
71
|
-
return method, http_version, uri, header, body
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
# Takes a string or array. See parse_header_body for further info.
|
76
|
-
# Returns +status+, +http_version+, +message+, +header+, +body+
|
77
|
-
def HTTPUtil.parse_http_response(data=[])
|
78
|
-
return unless data && !data.empty?
|
79
|
-
data = data.split(/\r?\n/) unless data.kind_of? Array
|
80
|
-
data.shift while (data[0].empty? || data[0].nil?) # Remove leading empties
|
81
|
-
status_line = data.shift # ie. HTTP/1.1 200 OK
|
82
|
-
http_version, status, message = nil
|
83
|
-
|
84
|
-
if status_line =~ /^HTTP\/(\d.+?)(\s+(\d\d\d)\s+(.+))?$/mo
|
85
|
-
http_version = $1
|
86
|
-
status = $2
|
87
|
-
message = $4
|
88
|
-
|
89
|
-
header, body, query = HTTPUtil.parse_header_body(data)
|
90
|
-
|
91
|
-
else
|
92
|
-
raise "Bad Response-Line `#{status_line}'."
|
93
|
-
end
|
94
|
-
|
95
|
-
return status, http_version, message, header, body
|
96
|
-
end
|
97
|
-
|
98
|
-
# Process everything after the first line of an HTTP request or response:
|
99
|
-
# GET / HTTP/1.1
|
100
|
-
# HTTP/1.1 200 OK
|
101
|
-
# etc...
|
102
|
-
# Used by parse_http_request and parse_http_response but can be used separately.
|
103
|
-
# Takes a string or array of strings. A string should be formatted like an HTTP
|
104
|
-
# request or response. If a body is present it should be separated by two newlines.
|
105
|
-
# An array of string should contain an empty or nil element between the header
|
106
|
-
# and body content. This will happen naturally if the raw lines were split by
|
107
|
-
# a single line terminator. (i.e. /\n/ rather than /\n\n/)
|
108
|
-
# Returns header (hash), body (string)
|
109
|
-
def HTTPUtil.parse_header_body(data=[])
|
110
|
-
header, body = {}, nil
|
111
|
-
data = data.split(/\r?\n/) unless data.kind_of? Array
|
112
|
-
data.shift while (data[0].nil? || data[0].empty?) # Remove leading empties
|
113
|
-
|
114
|
-
return header, body unless data && !data.empty?
|
115
|
-
|
116
|
-
#puts data.to_yaml
|
117
|
-
|
118
|
-
# Skip that first line if it exists
|
119
|
-
data.shift if data[0].match(/\AHTTP|GET|POST|DELETE|PUT|HEAD/mo)
|
120
|
-
|
121
|
-
header_lines = []
|
122
|
-
header_lines << data.shift while (!data[0].nil? && !data[0].empty?)
|
123
|
-
header = HTTPUtil::parse_header(header_lines.join($/))
|
124
|
-
|
125
|
-
# We omit the blank line that delimits the header from the body
|
126
|
-
body = data[1..-1].join($/) unless data.empty?
|
127
|
-
|
128
|
-
return header, body
|
129
|
-
end
|
130
|
-
|
131
|
-
def HTTPUtil.parse_query(request_method, query_string, content_type='', body='')
|
132
|
-
query = Hash.new([].freeze)
|
133
|
-
|
134
|
-
if request_method == "GET" || request_method == "HEAD"
|
135
|
-
query = HTTPUtil::parse_query_from_string(query_string)
|
136
|
-
elsif content_type =~ /^application\/x-www-form-urlencoded/
|
137
|
-
query = HTTPUtil::parse_query_from_string(body)
|
138
|
-
elsif content_type =~ /^multipart\/form-data; boundary=(.+)/
|
139
|
-
boundary = $1.tr('"', '')
|
140
|
-
query = HTTPUtil::parse_form_data(body, boundary)
|
141
|
-
else
|
142
|
-
query
|
143
|
-
end
|
144
|
-
|
145
|
-
query
|
146
|
-
end
|
147
|
-
|
148
|
-
def HTTPUtil.validate_method(meth='GET')
|
149
|
-
(VALID_METHODS.member? meth.upcase) ? meth : VALID_METHODS[0]
|
150
|
-
end
|
151
|
-
|
152
|
-
# Parses a query string by breaking it up at the '&'
|
153
|
-
# and ';' characters. You can also use this to parse
|
154
|
-
# cookies by changing the characters used in the second
|
155
|
-
# parameter (which defaults to '&;'.
|
156
|
-
# Stolen from Mongrel
|
157
|
-
def HTTPUtil.parse_query_from_string(qs, d = '&;')
|
158
|
-
params = {}
|
159
|
-
(qs||'').split(/[#{d}] */n).inject(params) { |h,p|
|
160
|
-
k, v=unescape(p).split('=',2)
|
161
|
-
next unless k
|
162
|
-
k = k.tr('-', '_').to_sym
|
163
|
-
if cur = params[k]
|
164
|
-
if cur.class == Array
|
165
|
-
params[k] << v
|
166
|
-
else
|
167
|
-
params[k] = [cur, v]
|
168
|
-
end
|
169
|
-
else
|
170
|
-
params[k] = v
|
171
|
-
end
|
172
|
-
}
|
173
|
-
|
174
|
-
return params
|
175
|
-
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
# Based on WEBrick::HTTPutils::parse_form_data
|
180
|
-
def HTTPUtil.parse_form_data(io, boundary)
|
181
|
-
boundary_regexp = /\A--#{boundary}(--)?#{$/}\z/
|
182
|
-
form_data = Hash.new
|
183
|
-
return form_data unless io
|
184
|
-
data = nil
|
185
|
-
io.each_line{|line|
|
186
|
-
if boundary_regexp =~ line
|
187
|
-
if data
|
188
|
-
data.chop!
|
189
|
-
key = data.name.tr('-', '_').to_sym
|
190
|
-
if form_data.has_key?(key)
|
191
|
-
form_data[key].append_data(data)
|
192
|
-
else
|
193
|
-
form_data[key] = data
|
194
|
-
end
|
195
|
-
end
|
196
|
-
data = FormData.new
|
197
|
-
next
|
198
|
-
else
|
199
|
-
if data
|
200
|
-
data << line
|
201
|
-
end
|
202
|
-
end
|
203
|
-
}
|
204
|
-
return form_data
|
205
|
-
end
|
206
|
-
|
207
|
-
|
208
|
-
# Extend the basic query string parser provided by the cgi module.
|
209
|
-
# converts single valued params (the most common case) to
|
210
|
-
# objects instead of arrays
|
211
|
-
#
|
212
|
-
# Input:
|
213
|
-
# the query string
|
214
|
-
#
|
215
|
-
# Output:
|
216
|
-
# hash of parameters, contains arrays for multivalued parameters
|
217
|
-
# (multiselect, checkboxes , etc)
|
218
|
-
# If no query string is provided (nil or "") returns an empty hash.
|
219
|
-
def HTTPUtil.query_to_hash(query_string)
|
220
|
-
return {} unless query_string
|
221
|
-
|
222
|
-
query_parameters = HTTPUtil.parse_query(query_string)
|
223
|
-
|
224
|
-
query_parameters.each { |key, val|
|
225
|
-
# replace the array with an object
|
226
|
-
query_parameters[key] = val[0] if 1 == val.length
|
227
|
-
}
|
228
|
-
|
229
|
-
# set default value to nil! cgi sets this to []
|
230
|
-
query_parameters.default = nil
|
231
|
-
|
232
|
-
return query_parameters
|
233
|
-
end
|
234
|
-
|
235
|
-
def HTTPUtil.hash_to_query(parameters)
|
236
|
-
return '' unless parameters
|
237
|
-
pairs = []
|
238
|
-
parameters.each do |param, value|
|
239
|
-
pairs << "#{param}=#{URI.escape(value.to_s)}"
|
240
|
-
end
|
241
|
-
return pairs.join('&')
|
242
|
-
#return pairs.join(";")
|
243
|
-
end
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
# Performs URI escaping so that you can construct proper
|
248
|
-
# query strings faster. Use this rather than the cgi.rb
|
249
|
-
# version since it's faster. (Stolen from Mongrel/Camping).
|
250
|
-
def HTTPUtil.escape(s)
|
251
|
-
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
252
|
-
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
253
|
-
}.tr(' ', '+')
|
254
|
-
end
|
255
|
-
|
256
|
-
|
257
|
-
# Unescapes a URI escaped string. (Stolen from Mongrel/Camping).
|
258
|
-
def HTTPUtil.unescape(s)
|
259
|
-
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
260
|
-
[$1.delete('%')].pack('H*')
|
261
|
-
}
|
262
|
-
end
|
263
|
-
|
264
|
-
|
265
|
-
end
|
266
|
-
|
@@ -1,40 +0,0 @@
|
|
1
|
-
|
2
|
-
#encoding: utf-8
|
3
|
-
$KCODE = "u" if RUBY_VERSION =~ /^1.8/
|
4
|
-
|
5
|
-
group "Numeric mixins"
|
6
|
-
library :stella, 'lib'
|
7
|
-
|
8
|
-
tryouts "Natural language" do
|
9
|
-
|
10
|
-
drill "base == 1.hour", 1.hour, 3600
|
11
|
-
drill "1.milliseconds", 1.milliseconds, 0.001
|
12
|
-
drill "1.microseconds", 1.microseconds, 0.000001
|
13
|
-
drill "10.minutes / 60.seconds", 10.minutes / 60.seconds, 10
|
14
|
-
drill "1.day", 1.day, 86400
|
15
|
-
drill "1.year == 365.days", 1.year, 31536000
|
16
|
-
drill "1.week == 7.days", 1.week, 604800
|
17
|
-
drill "1.week == 186.hours", 1.week, 168.hours
|
18
|
-
|
19
|
-
drill "60.in_minutes", 60.in_minutes, 1
|
20
|
-
drill "3600.in_hours", 3600.in_hours, 1
|
21
|
-
drill "5400.in_hours", 5400.in_hours, 1.5
|
22
|
-
drill "604800.in_days", 604800.in_days, 7
|
23
|
-
|
24
|
-
drill "60.hours - 1.day", 60.hours - 1.day, 129600.0
|
25
|
-
drill "1.year - 5.days", 1.year - 5.days, 31104000.0
|
26
|
-
|
27
|
-
drill "100 - 90", 100 - 90, 10.seconds
|
28
|
-
drill "90 + 9", 51 + 9, 1.minute
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
tryouts "Bytes" do
|
34
|
-
drill "1000 == 1000.00B", 1000.to_bytes, "1000.00B"
|
35
|
-
drill "1010", 1010.to_bytes, "1.01KB"
|
36
|
-
drill "1020100", (1010 ** 2).to_bytes, "1.02MB"
|
37
|
-
drill "1030301000", (1010 ** 3).to_bytes, "1.03GB"
|
38
|
-
drill "1040604010000", (1010 ** 4).to_bytes, "1.04TB"
|
39
|
-
end
|
40
|
-
|