fakes3 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|