fakes3 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -1
- data/fakes3.gemspec +2 -1
- data/lib/fakes3/bucket.rb +21 -19
- data/lib/fakes3/cli.rb +1 -0
- data/lib/fakes3/errors.rb +46 -0
- data/lib/fakes3/file_store.rb +29 -10
- data/lib/fakes3/s3_object.rb +8 -0
- data/lib/fakes3/server.rb +4 -1
- data/lib/fakes3/sorted_object_list.rb +100 -0
- data/lib/fakes3/version.rb +1 -1
- data/lib/fakes3/xml_adapter.rb +32 -0
- data/test/right_aws_commands_test.rb +1 -1
- data/test/s3_commands_test.rb +43 -4
- data/test/s3cmd_test.rb +2 -1
- metadata +21 -7
- data/lib/fakes3/red_black_tree.rb +0 -395
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -12,9 +12,11 @@ FakeS3 doesn't support all of the S3 command set, but the basic ones like put, g
|
|
12
12
|
list, copy, and make bucket are supported. More coming soon.
|
13
13
|
|
14
14
|
## Installation
|
15
|
+
|
15
16
|
gem install fakes3
|
16
17
|
|
17
18
|
## Running
|
19
|
+
|
18
20
|
To run a fakes3 server, you just specify a root and a port.
|
19
21
|
|
20
22
|
fakes3 -r /mnt/fakes3_root -p 4567
|
@@ -25,6 +27,8 @@ Take a look at the test cases to see client example usage. For now, FakeS3 is
|
|
25
27
|
mainly tested with s3cmd, aws-s3 gem, and right_aws. There are plenty more
|
26
28
|
libraries out there, and please do mention if other clients work or not.
|
27
29
|
|
30
|
+
Here is a running list of [supported clients](https://github.com/jubos/fake-s3/wiki/Supported-Clients "Supported Clients")
|
31
|
+
|
28
32
|
## Running Tests
|
29
33
|
|
30
34
|
Start the test server using
|
@@ -39,4 +43,4 @@ It is a TODO to get this to be just one command
|
|
39
43
|
|
40
44
|
## More Information
|
41
45
|
|
42
|
-
Check out the wiki
|
46
|
+
Check out the [wiki](https://github.com/jubos/fake-s3/wiki)
|
data/fakes3.gemspec
CHANGED
@@ -18,9 +18,10 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_development_dependency "aws-s3"
|
19
19
|
s.add_development_dependency "right_aws"
|
20
20
|
#s.add_development_dependency "aws-sdk"
|
21
|
-
|
21
|
+
s.add_development_dependency "ruby-debug19"
|
22
22
|
s.add_dependency "thor"
|
23
23
|
s.add_dependency "builder"
|
24
|
+
#s.add_dependency "algorithms"
|
24
25
|
|
25
26
|
s.files = `git ls-files`.split("\n")
|
26
27
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/fakes3/bucket.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'builder'
|
2
2
|
require 'thread'
|
3
3
|
require 'fakes3/s3_object'
|
4
|
-
require 'fakes3/
|
4
|
+
require 'fakes3/sorted_object_list'
|
5
5
|
|
6
6
|
module FakeS3
|
7
7
|
class Bucket
|
@@ -10,27 +10,32 @@ module FakeS3
|
|
10
10
|
def initialize(name,creation_date,objects)
|
11
11
|
@name = name
|
12
12
|
@creation_date = creation_date
|
13
|
-
@objects =
|
14
|
-
@objects = RedBlackTree.new
|
13
|
+
@objects = SortedObjectList.new
|
15
14
|
objects.each do |obj|
|
16
15
|
@objects.add(obj)
|
17
16
|
end
|
18
17
|
@mutex = Mutex.new
|
19
18
|
end
|
20
19
|
|
21
|
-
def
|
22
|
-
# Unfortunately have to synchronize here since the RB Tree is not thread
|
23
|
-
# safe. Probably can get finer granularity if performance is important
|
20
|
+
def find(object_name)
|
24
21
|
@mutex.synchronize do
|
25
|
-
|
26
|
-
if exists.nil?
|
27
|
-
@objects.add(object.name,object)
|
28
|
-
end
|
22
|
+
@objects.find(object_name)
|
29
23
|
end
|
30
24
|
end
|
31
25
|
|
32
|
-
def
|
33
|
-
|
26
|
+
def add(object)
|
27
|
+
# Unfortunately have to synchronize here since the our SortedObjectList
|
28
|
+
# not thread safe. Probably can get finer granularity if performance is
|
29
|
+
# important
|
30
|
+
@mutex.synchronize do
|
31
|
+
@objects.add(object)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(object)
|
36
|
+
@mutex.synchronize do
|
37
|
+
@objects.remove(object)
|
38
|
+
end
|
34
39
|
end
|
35
40
|
|
36
41
|
def query_for_range(options)
|
@@ -39,12 +44,9 @@ module FakeS3
|
|
39
44
|
max_keys = options[:max_keys] || 1000
|
40
45
|
delimiter = options[:delimiter]
|
41
46
|
|
42
|
-
|
43
|
-
is_truncated = false
|
44
|
-
|
47
|
+
match_set = nil
|
45
48
|
@mutex.synchronize do
|
46
|
-
|
47
|
-
marker,prefix,max_keys,delimiter)
|
49
|
+
match_set = @objects.list(options)
|
48
50
|
end
|
49
51
|
|
50
52
|
bq = BucketQuery.new
|
@@ -53,8 +55,8 @@ module FakeS3
|
|
53
55
|
bq.prefix = prefix
|
54
56
|
bq.max_keys = max_keys
|
55
57
|
bq.delimiter = delimiter
|
56
|
-
bq.matches = matches
|
57
|
-
bq.is_truncated = is_truncated
|
58
|
+
bq.matches = match_set.matches
|
59
|
+
bq.is_truncated = match_set.is_truncated
|
58
60
|
return bq
|
59
61
|
end
|
60
62
|
|
data/lib/fakes3/cli.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
module FakeS3
|
2
|
+
class FakeS3Exception < RuntimeError
|
3
|
+
attr_accessor :resource,:request_id
|
4
|
+
|
5
|
+
def self.metaclass; class << self; self; end; end
|
6
|
+
|
7
|
+
def self.traits(*arr)
|
8
|
+
return @traits if arr.empty?
|
9
|
+
attr_accessor *arr
|
10
|
+
|
11
|
+
arr.each do |a|
|
12
|
+
metaclass.instance_eval do
|
13
|
+
define_method( a ) do |val|
|
14
|
+
@traits ||= {}
|
15
|
+
@traits[a] = val
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class_eval do
|
21
|
+
define_method( :initialize ) do
|
22
|
+
self.class.traits.each do |k,v|
|
23
|
+
instance_variable_set("@#{k}", v)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
traits :message,:http_status
|
30
|
+
|
31
|
+
def code
|
32
|
+
self.class.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class NoSuchBucket < FakeS3Exception
|
37
|
+
message "The bucket you tried to delete is not empty."
|
38
|
+
http_status "404"
|
39
|
+
end
|
40
|
+
|
41
|
+
class BucketNotEmpty < FakeS3Exception
|
42
|
+
message "The bucket you tried to delete is not empty."
|
43
|
+
http_status "409"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/fakes3/file_store.rb
CHANGED
@@ -45,6 +45,10 @@ module FakeS3
|
|
45
45
|
@buckets
|
46
46
|
end
|
47
47
|
|
48
|
+
def get_bucket_folder(bucket)
|
49
|
+
File.join(@root,bucket.name)
|
50
|
+
end
|
51
|
+
|
48
52
|
def get_bucket(bucket)
|
49
53
|
@bucket_hash[bucket]
|
50
54
|
end
|
@@ -58,12 +62,20 @@ module FakeS3
|
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
61
|
-
def
|
65
|
+
def delete_bucket(bucket_name)
|
66
|
+
bucket = get_bucket(bucket_name)
|
67
|
+
raise NoSuchBucket if !bucket
|
68
|
+
raise BucketNotEmpty if bucket.objects.count > 0
|
69
|
+
FileUtils.rm_r(get_bucket_folder(bucket))
|
70
|
+
@bucket_hash.delete(bucket_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_object(bucket,object_name, request)
|
62
74
|
begin
|
63
75
|
real_obj = S3Object.new
|
64
|
-
obj_root = File.join(@root,bucket,
|
76
|
+
obj_root = File.join(@root,bucket,object_name,SHUCK_METADATA_DIR)
|
65
77
|
metadata = YAML.parse(File.open(File.join(obj_root,"metadata"),'rb').read)
|
66
|
-
real_obj.name =
|
78
|
+
real_obj.name = object_name
|
67
79
|
real_obj.md5 = metadata[:md5].value
|
68
80
|
real_obj.content_type = metadata[:content_type] ? metadata[:content_type].value : "application/octet-stream"
|
69
81
|
#real_obj.io = File.open(File.join(obj_root,"content"),'rb')
|
@@ -78,14 +90,13 @@ module FakeS3
|
|
78
90
|
def object_metadata(bucket,object)
|
79
91
|
end
|
80
92
|
|
81
|
-
def copy_object(
|
82
|
-
src_root = File.join(@root,
|
83
|
-
src_obj = S3Object.new
|
93
|
+
def copy_object(src_bucket_name,src_name,dst_bucket_name,dst_name)
|
94
|
+
src_root = File.join(@root,src_bucket_name,src_name,SHUCK_METADATA_DIR)
|
84
95
|
src_metadata_filename = File.join(src_root,"metadata")
|
85
96
|
src_metadata = YAML.parse(File.open(src_metadata_filename,'rb').read)
|
86
97
|
src_content_filename = File.join(src_root,"content")
|
87
98
|
|
88
|
-
dst_filename= File.join(@root,
|
99
|
+
dst_filename= File.join(@root,dst_bucket_name,dst_name)
|
89
100
|
FileUtils.mkdir_p(dst_filename)
|
90
101
|
|
91
102
|
metadata_dir = File.join(dst_filename,SHUCK_METADATA_DIR)
|
@@ -106,9 +117,17 @@ module FakeS3
|
|
106
117
|
end
|
107
118
|
end
|
108
119
|
|
120
|
+
src_bucket = self.get_bucket(src_bucket_name)
|
121
|
+
dst_bucket = self.get_bucket(dst_bucket_name)
|
122
|
+
|
109
123
|
obj = S3Object.new
|
124
|
+
obj.name = dst_name
|
110
125
|
obj.md5 = src_metadata[:md5]
|
111
126
|
obj.content_type = src_metadata[:content_type]
|
127
|
+
|
128
|
+
src_obj = src_bucket.find(src_name)
|
129
|
+
dst_bucket.add(obj)
|
130
|
+
src_bucket.remove(src_obj)
|
112
131
|
return obj
|
113
132
|
end
|
114
133
|
|
@@ -146,8 +165,7 @@ module FakeS3
|
|
146
165
|
obj.md5 = metadata_struct[:md5]
|
147
166
|
obj.content_type = metadata_struct[:content_type]
|
148
167
|
|
149
|
-
|
150
|
-
bucket << obj
|
168
|
+
bucket.add(obj)
|
151
169
|
return obj
|
152
170
|
rescue
|
153
171
|
puts $!
|
@@ -160,7 +178,8 @@ module FakeS3
|
|
160
178
|
begin
|
161
179
|
filename = File.join(@root,bucket.name,object_name)
|
162
180
|
FileUtils.rm_rf(filename)
|
163
|
-
bucket.
|
181
|
+
object = bucket.find(object_name)
|
182
|
+
bucket.remove(object)
|
164
183
|
rescue
|
165
184
|
puts $!
|
166
185
|
$!.backtrace.each { |line| puts line }
|
data/lib/fakes3/s3_object.rb
CHANGED
@@ -3,6 +3,14 @@ module FakeS3
|
|
3
3
|
include Comparable
|
4
4
|
attr_accessor :name,:size,:creation_date,:md5,:io,:content_type
|
5
5
|
|
6
|
+
def hash
|
7
|
+
@name.hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def eql?(object)
|
11
|
+
@name == object.name
|
12
|
+
end
|
13
|
+
|
6
14
|
# Sort by the object's name
|
7
15
|
def <=>(object)
|
8
16
|
@name <=> object.name
|
data/lib/fakes3/server.rb
CHANGED
@@ -3,6 +3,7 @@ require 'fakes3/file_store'
|
|
3
3
|
require 'fakes3/xml_adapter'
|
4
4
|
require 'fakes3/bucket_query'
|
5
5
|
require 'fakes3/unsupported_operation'
|
6
|
+
require 'fakes3/errors'
|
6
7
|
|
7
8
|
module FakeS3
|
8
9
|
class Request
|
@@ -139,8 +140,8 @@ module FakeS3
|
|
139
140
|
response['Content-Type'] = "text/xml"
|
140
141
|
end
|
141
142
|
|
143
|
+
# Posts aren't supported yet
|
142
144
|
def do_POST(request,response)
|
143
|
-
p request
|
144
145
|
end
|
145
146
|
|
146
147
|
def do_DELETE(request,response)
|
@@ -150,6 +151,8 @@ module FakeS3
|
|
150
151
|
when Request::DELETE_OBJECT
|
151
152
|
bucket_obj = @store.get_bucket(s_req.bucket)
|
152
153
|
@store.delete_object(bucket_obj,s_req.object,s_req.webrick_request)
|
154
|
+
when Request::DELETE_BUCKET
|
155
|
+
@store.delete_bucket(s_req.bucket)
|
153
156
|
end
|
154
157
|
|
155
158
|
response.status = 204
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'set'
|
2
|
+
module FakeS3
|
3
|
+
class S3MatchSet
|
4
|
+
attr_accessor :matches,:is_truncated
|
5
|
+
def initialize
|
6
|
+
@matches = []
|
7
|
+
@is_truncated = false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# This class has some of the semantics necessary for how buckets can return
|
12
|
+
# their items
|
13
|
+
#
|
14
|
+
# It is currently implemented naively as a sorted set + hash If you are going
|
15
|
+
# to try to put massive lists inside buckets and ls them, you will be sorely
|
16
|
+
# disappointed about this performance.
|
17
|
+
class SortedObjectList
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@sorted_set = SortedSet.new
|
21
|
+
@object_map = {}
|
22
|
+
@mutex = Mutex.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def count
|
26
|
+
@sorted_set.count
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(object_name)
|
30
|
+
@object_map[object_name]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add an S3 object into the sorted list
|
34
|
+
def add(s3_object)
|
35
|
+
return if !s3_object
|
36
|
+
|
37
|
+
@object_map[s3_object.name] = s3_object
|
38
|
+
@sorted_set << s3_object
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove(s3_object)
|
42
|
+
return if !s3_object
|
43
|
+
|
44
|
+
@object_map.delete(s3_object.name)
|
45
|
+
@sorted_set.delete(s3_object)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return back a set of matches based on the passed in options
|
49
|
+
#
|
50
|
+
# options:
|
51
|
+
#
|
52
|
+
# :marker : a string to start the lexographical search (it is not included
|
53
|
+
# in the result)
|
54
|
+
# :max_keys : a maximum number of results
|
55
|
+
# :prefix : a string to filter the results by
|
56
|
+
# :delimiter : not supported yet
|
57
|
+
def list(options)
|
58
|
+
marker = options[:marker]
|
59
|
+
prefix = options[:prefix]
|
60
|
+
max_keys = options[:max_keys] || 1000
|
61
|
+
delimiter = options[:delimiter]
|
62
|
+
|
63
|
+
ms = S3MatchSet.new
|
64
|
+
|
65
|
+
marker_found = true
|
66
|
+
pseudo = nil
|
67
|
+
if marker
|
68
|
+
marker_found = false
|
69
|
+
if !@object_map[marker]
|
70
|
+
pseudo = S3Object.new
|
71
|
+
pseudo.name = marker
|
72
|
+
@sorted_set << pseudo
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
count = 0
|
77
|
+
@sorted_set.each do |s3_object|
|
78
|
+
if marker_found && (!prefix or s3_object.name.index(prefix) == 0)
|
79
|
+
count += 1
|
80
|
+
if count <= max_keys
|
81
|
+
ms.matches << s3_object
|
82
|
+
else
|
83
|
+
is_truncated = true
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if marker and marker == s3_object.name
|
89
|
+
marker_found = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if pseudo
|
94
|
+
@sorted_set.delete(pseudo)
|
95
|
+
end
|
96
|
+
|
97
|
+
return ms
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/fakes3/version.rb
CHANGED
data/lib/fakes3/xml_adapter.rb
CHANGED
@@ -24,6 +24,19 @@ module FakeS3
|
|
24
24
|
output
|
25
25
|
end
|
26
26
|
|
27
|
+
def self.error(error)
|
28
|
+
output = ""
|
29
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
30
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
31
|
+
xml.Error { |err|
|
32
|
+
err.Code(error.code)
|
33
|
+
err.Message(error.message)
|
34
|
+
err.Resource(error.resource)
|
35
|
+
err.RequestId(1)
|
36
|
+
}
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
27
40
|
# <?xml version="1.0" encoding="UTF-8"?>
|
28
41
|
#<Error>
|
29
42
|
# <Code>NoSuchKey</Code>
|
@@ -45,6 +58,19 @@ module FakeS3
|
|
45
58
|
output
|
46
59
|
end
|
47
60
|
|
61
|
+
def self.error_bucket_not_empty(name)
|
62
|
+
output = ""
|
63
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
64
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
65
|
+
xml.Error { |err|
|
66
|
+
err.Code("BucketNotEmpty")
|
67
|
+
err.Message("The bucket you tried to delete is not empty.")
|
68
|
+
err.Resource(name)
|
69
|
+
err.RequestId(1)
|
70
|
+
}
|
71
|
+
output
|
72
|
+
end
|
73
|
+
|
48
74
|
def self.error_no_such_key(name)
|
49
75
|
output = ""
|
50
76
|
xml = Builder::XmlMarkup.new(:target => output)
|
@@ -89,6 +115,12 @@ module FakeS3
|
|
89
115
|
def self.append_objects_to_list_bucket_result(lbr,objects)
|
90
116
|
return if objects.nil? or objects.size == 0
|
91
117
|
|
118
|
+
if objects.index(nil)
|
119
|
+
require 'ruby-debug'
|
120
|
+
Debugger.start
|
121
|
+
debugger
|
122
|
+
end
|
123
|
+
|
92
124
|
objects.each do |s3_object|
|
93
125
|
lbr.Contents { |contents|
|
94
126
|
contents.Key(s3_object.name)
|
data/test/s3_commands_test.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
require 'test/test_helper'
|
2
2
|
require 'fileutils'
|
3
|
-
require 'fakes3/server'
|
3
|
+
#require 'fakes3/server'
|
4
4
|
require 'aws/s3'
|
5
5
|
|
6
6
|
class S3CommandsTest < Test::Unit::TestCase
|
7
7
|
include AWS::S3
|
8
8
|
|
9
9
|
def setup
|
10
|
-
AWS::S3::Base.establish_connection!(:access_key_id => "123",
|
10
|
+
AWS::S3::Base.establish_connection!(:access_key_id => "123",
|
11
|
+
:secret_access_key => "abc",
|
12
|
+
:server => "localhost",
|
13
|
+
:port => "10453" )
|
11
14
|
end
|
12
15
|
|
13
16
|
def teardown
|
@@ -17,6 +20,23 @@ class S3CommandsTest < Test::Unit::TestCase
|
|
17
20
|
def test_create_bucket
|
18
21
|
bucket = Bucket.create("ruby_aws_s3")
|
19
22
|
assert_not_nil bucket
|
23
|
+
|
24
|
+
bucket_names = []
|
25
|
+
Service.buckets.each do |bucket|
|
26
|
+
bucket_names << bucket.name
|
27
|
+
end
|
28
|
+
assert(bucket_names.index("ruby_aws_s3") >= 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_destroy_bucket
|
32
|
+
Bucket.create("deletebucket")
|
33
|
+
Bucket.delete("deletebucket")
|
34
|
+
|
35
|
+
begin
|
36
|
+
bucket = Bucket.find("deletebucket")
|
37
|
+
assert_fail("Shouldn't succeed here")
|
38
|
+
rescue
|
39
|
+
end
|
20
40
|
end
|
21
41
|
|
22
42
|
def test_store
|
@@ -102,11 +122,29 @@ class S3CommandsTest < Test::Unit::TestCase
|
|
102
122
|
S3Object.store("something_to_delete","asdf","ruby_aws_s3")
|
103
123
|
something = S3Object.find("something_to_delete","ruby_aws_s3")
|
104
124
|
S3Object.delete("something_to_delete","ruby_aws_s3")
|
125
|
+
|
126
|
+
assert_raise AWS::S3::NoSuchKey do
|
127
|
+
should_throw = S3Object.find("something_to_delete","find_bucket")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_rename
|
132
|
+
bucket = Bucket.create("ruby_aws_s3")
|
133
|
+
S3Object.store("something_to_rename","asdf","ruby_aws_s3")
|
134
|
+
S3Object.rename("something_to_rename","renamed","ruby_aws_s3")
|
135
|
+
|
136
|
+
renamed = S3Object.find("renamed","ruby_aws_s3")
|
137
|
+
assert_not_nil(renamed)
|
138
|
+
assert_equal(renamed.value,'asdf')
|
139
|
+
|
140
|
+
assert_raise AWS::S3::NoSuchKey do
|
141
|
+
should_throw = S3Object.find("something_to_rename","ruby_aws_s3")
|
142
|
+
end
|
105
143
|
end
|
106
144
|
|
107
145
|
def test_larger_lists
|
108
146
|
Bucket.create("ruby_aws_s3_many")
|
109
|
-
(0..
|
147
|
+
(0..50).each do |i|
|
110
148
|
('a'..'z').each do |letter|
|
111
149
|
name = "#{letter}#{i}"
|
112
150
|
S3Object.store(name,"asdf","ruby_aws_s3_many")
|
@@ -114,10 +152,11 @@ class S3CommandsTest < Test::Unit::TestCase
|
|
114
152
|
end
|
115
153
|
|
116
154
|
bucket = Bucket.find("ruby_aws_s3_many")
|
117
|
-
assert_equal(bucket.objects.first.key,"a0")
|
118
155
|
assert_equal(bucket.size,1000)
|
156
|
+
assert_equal(bucket.objects.first.key,"a0")
|
119
157
|
end
|
120
158
|
|
159
|
+
|
121
160
|
# Copying an object
|
122
161
|
#S3Object.copy 'headshot.jpg', 'headshot2.jpg', 'photos'
|
123
162
|
|
data/test/s3cmd_test.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'test/test_helper'
|
2
2
|
require 'fileutils'
|
3
|
-
require 'fakes3/server'
|
4
3
|
|
5
4
|
# You need to have s3cmd installed to use this
|
5
|
+
# Also, s3cmd doesn't support path style requests, so in order to properly test
|
6
|
+
# it you need to modify your dns by changing /etc/hosts or using dnsmasq
|
6
7
|
class S3CmdTest < Test::Unit::TestCase
|
7
8
|
|
8
9
|
def setup
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Curtis Spencer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-04-16 00:00:00 -
|
17
|
+
date: 2012-04-16 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
type: :development
|
60
60
|
version_requirements: *id003
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: ruby-debug19
|
63
63
|
prerelease: false
|
64
64
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
@@ -69,10 +69,10 @@ dependencies:
|
|
69
69
|
segments:
|
70
70
|
- 0
|
71
71
|
version: "0"
|
72
|
-
type: :
|
72
|
+
type: :development
|
73
73
|
version_requirements: *id004
|
74
74
|
- !ruby/object:Gem::Dependency
|
75
|
-
name:
|
75
|
+
name: thor
|
76
76
|
prerelease: false
|
77
77
|
requirement: &id005 !ruby/object:Gem::Requirement
|
78
78
|
none: false
|
@@ -84,6 +84,19 @@ dependencies:
|
|
84
84
|
version: "0"
|
85
85
|
type: :runtime
|
86
86
|
version_requirements: *id005
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: builder
|
89
|
+
prerelease: false
|
90
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
type: :runtime
|
99
|
+
version_requirements: *id006
|
87
100
|
description: Use FakeS3 to test basic S3 functionality without actually connecting to S3
|
88
101
|
email:
|
89
102
|
- thorin@gmail.com
|
@@ -105,11 +118,12 @@ files:
|
|
105
118
|
- lib/fakes3/bucket.rb
|
106
119
|
- lib/fakes3/bucket_query.rb
|
107
120
|
- lib/fakes3/cli.rb
|
121
|
+
- lib/fakes3/errors.rb
|
108
122
|
- lib/fakes3/file_store.rb
|
109
123
|
- lib/fakes3/rate_limitable_file.rb
|
110
|
-
- lib/fakes3/red_black_tree.rb
|
111
124
|
- lib/fakes3/s3_object.rb
|
112
125
|
- lib/fakes3/server.rb
|
126
|
+
- lib/fakes3/sorted_object_list.rb
|
113
127
|
- lib/fakes3/unsupported_operation.rb
|
114
128
|
- lib/fakes3/version.rb
|
115
129
|
- lib/fakes3/xml_adapter.rb
|
@@ -1,395 +0,0 @@
|
|
1
|
-
# Algorithm based on "Introduction to Algorithms" by Cormen and others
|
2
|
-
# Retrieved from
|
3
|
-
# https://github.com/headius/redblack/blob/master/red_black_tree.rb
|
4
|
-
#
|
5
|
-
# Modified by Curtis Spencer
|
6
|
-
class RedBlackTree
|
7
|
-
class Node
|
8
|
-
attr_accessor :color
|
9
|
-
attr_accessor :key
|
10
|
-
attr_accessor :left
|
11
|
-
attr_accessor :right
|
12
|
-
attr_accessor :parent
|
13
|
-
attr_accessor :value
|
14
|
-
|
15
|
-
RED = :red
|
16
|
-
BLACK = :black
|
17
|
-
COLORS = [RED, BLACK].freeze
|
18
|
-
|
19
|
-
def initialize(key, value, color = RED)
|
20
|
-
raise ArgumentError, "Bad value for color parameter" unless COLORS.include?(color)
|
21
|
-
@color = color
|
22
|
-
@key = key
|
23
|
-
@value = value
|
24
|
-
@left = @right = @parent = NilNode.instance
|
25
|
-
end
|
26
|
-
|
27
|
-
def black?
|
28
|
-
return color == BLACK
|
29
|
-
end
|
30
|
-
|
31
|
-
def red?
|
32
|
-
return color == RED
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class NilNode < Node
|
37
|
-
class << self
|
38
|
-
private :new
|
39
|
-
@instance = nil
|
40
|
-
|
41
|
-
# it's not thread safe
|
42
|
-
def instance
|
43
|
-
if @instance.nil?
|
44
|
-
@instance = new
|
45
|
-
|
46
|
-
def instance
|
47
|
-
return @instance
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
return @instance
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def initialize
|
56
|
-
self.color = BLACK
|
57
|
-
self.key = 0
|
58
|
-
self.left = nil
|
59
|
-
self.right = nil
|
60
|
-
self.parent = nil
|
61
|
-
end
|
62
|
-
|
63
|
-
def nil?
|
64
|
-
return true
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
include Enumerable
|
69
|
-
|
70
|
-
attr_accessor :root
|
71
|
-
attr_accessor :size
|
72
|
-
|
73
|
-
def initialize
|
74
|
-
self.root = NilNode.instance
|
75
|
-
self.size = 0
|
76
|
-
end
|
77
|
-
|
78
|
-
def add(key,value)
|
79
|
-
insert(Node.new(key,value))
|
80
|
-
end
|
81
|
-
|
82
|
-
def insert(x)
|
83
|
-
insert_helper(x)
|
84
|
-
|
85
|
-
x.color = Node::RED
|
86
|
-
while x != root && x.parent.color == Node::RED
|
87
|
-
if x.parent == x.parent.parent.left
|
88
|
-
y = x.parent.parent.right
|
89
|
-
if !y.nil? && y.color == Node::RED
|
90
|
-
x.parent.color = Node::BLACK
|
91
|
-
y.color = Node::BLACK
|
92
|
-
x.parent.parent.color = Node::RED
|
93
|
-
x = x.parent.parent
|
94
|
-
else
|
95
|
-
if x == x.parent.right
|
96
|
-
x = x.parent
|
97
|
-
left_rotate(x)
|
98
|
-
end
|
99
|
-
x.parent.color = Node::BLACK
|
100
|
-
x.parent.parent.color = Node::RED
|
101
|
-
right_rotate(x.parent.parent)
|
102
|
-
end
|
103
|
-
else
|
104
|
-
y = x.parent.parent.left
|
105
|
-
if !y.nil? && y.color == Node::RED
|
106
|
-
x.parent.color = Node::BLACK
|
107
|
-
y.color = Node::BLACK
|
108
|
-
x.parent.parent.color = Node::RED
|
109
|
-
x = x.parent.parent
|
110
|
-
else
|
111
|
-
if x == x.parent.left
|
112
|
-
x = x.parent
|
113
|
-
right_rotate(x)
|
114
|
-
end
|
115
|
-
x.parent.color = Node::BLACK
|
116
|
-
x.parent.parent.color = Node::RED
|
117
|
-
left_rotate(x.parent.parent)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
root.color = Node::BLACK
|
122
|
-
end
|
123
|
-
|
124
|
-
alias << insert
|
125
|
-
|
126
|
-
def delete(z)
|
127
|
-
y = (z.left.nil? || z.right.nil?) ? z : successor(z)
|
128
|
-
x = y.left.nil? ? y.right : y.left
|
129
|
-
x.parent = y.parent
|
130
|
-
|
131
|
-
if y.parent.nil?
|
132
|
-
self.root = x
|
133
|
-
else
|
134
|
-
if y == y.parent.left
|
135
|
-
y.parent.left = x
|
136
|
-
else
|
137
|
-
y.parent.right = x
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
z.key = y.key if y != z
|
142
|
-
|
143
|
-
if y.color == Node::BLACK
|
144
|
-
delete_fixup(x)
|
145
|
-
end
|
146
|
-
|
147
|
-
self.size -= 1
|
148
|
-
return y
|
149
|
-
end
|
150
|
-
|
151
|
-
def minimum(x = root)
|
152
|
-
while !x.left.nil?
|
153
|
-
x = x.left
|
154
|
-
end
|
155
|
-
return x
|
156
|
-
end
|
157
|
-
|
158
|
-
def maximum(x = root)
|
159
|
-
while !x.right.nil?
|
160
|
-
x = x.right
|
161
|
-
end
|
162
|
-
return x
|
163
|
-
end
|
164
|
-
|
165
|
-
def successor(x)
|
166
|
-
if !x.right.nil?
|
167
|
-
return minimum(x.right)
|
168
|
-
end
|
169
|
-
y = x.parent
|
170
|
-
while !y.nil? && x == y.right
|
171
|
-
x = y
|
172
|
-
y = y.parent
|
173
|
-
end
|
174
|
-
return y
|
175
|
-
end
|
176
|
-
|
177
|
-
def predecessor(x)
|
178
|
-
if !x.left.nil?
|
179
|
-
return maximum(x.left)
|
180
|
-
end
|
181
|
-
y = x.parent
|
182
|
-
while !y.nil? && x == y.left
|
183
|
-
x = y
|
184
|
-
y = y.parent
|
185
|
-
end
|
186
|
-
return y
|
187
|
-
end
|
188
|
-
|
189
|
-
def inorder_walk(x = root)
|
190
|
-
x = self.minimum
|
191
|
-
while !x.nil?
|
192
|
-
yield x.key
|
193
|
-
x = successor(x)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
alias each inorder_walk
|
198
|
-
|
199
|
-
def reverse_inorder_walk(x = root)
|
200
|
-
x = self.maximum
|
201
|
-
while !x.nil?
|
202
|
-
yield x.key
|
203
|
-
x = predecessor(x)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
alias reverse_each reverse_inorder_walk
|
208
|
-
|
209
|
-
def search(key, x = root)
|
210
|
-
while !x.nil? && x.key != key
|
211
|
-
key < x.key ? x = x.left : x = x.right
|
212
|
-
end
|
213
|
-
return x
|
214
|
-
end
|
215
|
-
|
216
|
-
# Search for a successor even if the key doesn't exist
|
217
|
-
def search_for_possible_successor(key, x = root)
|
218
|
-
node = search(key)
|
219
|
-
pseudo = false
|
220
|
-
if node.class == NilNode
|
221
|
-
insert(Node.new(key,nil))
|
222
|
-
node = search(key)
|
223
|
-
pseudo = true
|
224
|
-
end
|
225
|
-
|
226
|
-
succ = successor(node)
|
227
|
-
delete(node) if pseudo
|
228
|
-
return nil if !succ
|
229
|
-
return succ
|
230
|
-
end
|
231
|
-
|
232
|
-
def delete_via_key(key)
|
233
|
-
node = search(key)
|
234
|
-
return if node.class == NilNode
|
235
|
-
delete(node)
|
236
|
-
end
|
237
|
-
|
238
|
-
# Return array of matches and a boolean to denote whether truncation occurred
|
239
|
-
def search_for_range(marker,prefix,max_keys,delimiter)
|
240
|
-
succ = nil
|
241
|
-
if marker
|
242
|
-
succ = search_for_possible_successor(marker)
|
243
|
-
else
|
244
|
-
succ = self.minimum
|
245
|
-
end
|
246
|
-
|
247
|
-
return [],false if succ.class == NilNode
|
248
|
-
|
249
|
-
keys_count = 0
|
250
|
-
keys = []
|
251
|
-
is_truncated = false
|
252
|
-
|
253
|
-
loop do
|
254
|
-
if !prefix or succ.key.index(prefix) == 0
|
255
|
-
keys_count += 1
|
256
|
-
if keys_count <= max_keys
|
257
|
-
keys << succ.value
|
258
|
-
else
|
259
|
-
is_truncated = true
|
260
|
-
break
|
261
|
-
end
|
262
|
-
end
|
263
|
-
succ = successor(succ)
|
264
|
-
if succ.class == NilNode
|
265
|
-
break
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
return keys,is_truncated
|
270
|
-
end
|
271
|
-
|
272
|
-
def empty?
|
273
|
-
return self.root.nil?
|
274
|
-
end
|
275
|
-
|
276
|
-
def black_height(x = root)
|
277
|
-
height = 0
|
278
|
-
while !x.nil?
|
279
|
-
x = x.left
|
280
|
-
height +=1 if x.nil? || x.black?
|
281
|
-
end
|
282
|
-
return height
|
283
|
-
end
|
284
|
-
|
285
|
-
private
|
286
|
-
|
287
|
-
def left_rotate(x)
|
288
|
-
raise "x.right is nil!" if x.right.nil?
|
289
|
-
y = x.right
|
290
|
-
x.right = y.left
|
291
|
-
y.left.parent = x if !y.left.nil?
|
292
|
-
y.parent = x.parent
|
293
|
-
if x.parent.nil?
|
294
|
-
self.root = y
|
295
|
-
else
|
296
|
-
if x == x.parent.left
|
297
|
-
x.parent.left = y
|
298
|
-
else
|
299
|
-
x.parent.right = y
|
300
|
-
end
|
301
|
-
end
|
302
|
-
y.left = x
|
303
|
-
x.parent = y
|
304
|
-
end
|
305
|
-
|
306
|
-
def right_rotate(x)
|
307
|
-
raise "x.left is nil!" if x.left.nil?
|
308
|
-
y = x.left
|
309
|
-
x.left = y.right
|
310
|
-
y.right.parent = x if !y.right.nil?
|
311
|
-
y.parent = x.parent
|
312
|
-
if x.parent.nil?
|
313
|
-
self.root = y
|
314
|
-
else
|
315
|
-
if x == x.parent.left
|
316
|
-
x.parent.left = y
|
317
|
-
else
|
318
|
-
x.parent.right = y
|
319
|
-
end
|
320
|
-
end
|
321
|
-
y.right = x
|
322
|
-
x.parent = y
|
323
|
-
end
|
324
|
-
|
325
|
-
def insert_helper(z)
|
326
|
-
y = NilNode.instance
|
327
|
-
x = root
|
328
|
-
while !x.nil?
|
329
|
-
y = x
|
330
|
-
z.key < x.key ? x = x.left : x = x.right
|
331
|
-
end
|
332
|
-
z.parent = y
|
333
|
-
if y.nil?
|
334
|
-
self.root = z
|
335
|
-
else
|
336
|
-
z.key < y.key ? y.left = z : y.right = z
|
337
|
-
end
|
338
|
-
self.size += 1
|
339
|
-
end
|
340
|
-
|
341
|
-
def delete_fixup(x)
|
342
|
-
while x != root && x.color == Node::BLACK
|
343
|
-
if x == x.parent.left
|
344
|
-
w = x.parent.right
|
345
|
-
if w.color == Node::RED
|
346
|
-
w.color = Node::BLACK
|
347
|
-
x.parent.color = Node::RED
|
348
|
-
left_rotate(x.parent)
|
349
|
-
w = x.parent.right
|
350
|
-
end
|
351
|
-
if w.left.color == Node::BLACK && w.right.color == Node::BLACK
|
352
|
-
w.color = Node::RED
|
353
|
-
x = x.parent
|
354
|
-
else
|
355
|
-
if w.right.color == Node::BLACK
|
356
|
-
w.left.color = Node::BLACK
|
357
|
-
w.color = Node::RED
|
358
|
-
right_rotate(w)
|
359
|
-
w = x.parent.right
|
360
|
-
end
|
361
|
-
w.color = x.parent.color
|
362
|
-
x.parent.color = Node::BLACK
|
363
|
-
w.right.color = Node::BLACK
|
364
|
-
left_rotate(x.parent)
|
365
|
-
x = root
|
366
|
-
end
|
367
|
-
else
|
368
|
-
w = x.parent.left
|
369
|
-
if w.color == Node::RED
|
370
|
-
w.color = Node::BLACK
|
371
|
-
x.parent.color = Node::RED
|
372
|
-
right_rotate(x.parent)
|
373
|
-
w = x.parent.left
|
374
|
-
end
|
375
|
-
if w.right.color == Node::BLACK && w.left.color == Node::BLACK
|
376
|
-
w.color = Node::RED
|
377
|
-
x = x.parent
|
378
|
-
else
|
379
|
-
if w.left.color == Node::BLACK
|
380
|
-
w.right.color = Node::BLACK
|
381
|
-
w.color = Node::RED
|
382
|
-
left_rotate(w)
|
383
|
-
w = x.parent.left
|
384
|
-
end
|
385
|
-
w.color = x.parent.color
|
386
|
-
x.parent.color = Node::BLACK
|
387
|
-
w.left.color = Node::BLACK
|
388
|
-
right_rotate(x.parent)
|
389
|
-
x = root
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
x.color = Node::BLACK
|
394
|
-
end
|
395
|
-
end
|