appengine-apis 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +6 -0
- data/README.rdoc +11 -1
- data/Rakefile +8 -0
- data/lib/appengine-apis.rb +1 -1
- data/lib/appengine-apis/apiproxy.rb +2 -2
- data/lib/appengine-apis/datastore.rb +10 -11
- data/lib/appengine-apis/datastore_types.rb +11 -3
- data/lib/appengine-apis/local_boot.rb +29 -0
- data/lib/appengine-apis/mail.rb +160 -0
- data/lib/appengine-apis/memcache.rb +580 -0
- data/lib/appengine-apis/merb-logger.rb +1 -0
- data/lib/appengine-apis/sdk.rb +93 -0
- data/lib/appengine-apis/testing.rb +55 -10
- data/lib/appengine-apis/urlfetch.rb +7 -2
- data/spec/datastore_types_spec.rb +12 -1
- data/spec/mail_spec.rb +179 -0
- data/spec/memcache_spec.rb +385 -0
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +75 -0
- metadata +15 -7
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'java'
|
20
|
+
|
21
|
+
module AppEngine
|
22
|
+
|
23
|
+
# Helper methods to locate the App Engine SDK and add it to the class path.
|
24
|
+
module SDK
|
25
|
+
class << self
|
26
|
+
|
27
|
+
# Tries to load the ApiProxy class.
|
28
|
+
def load_apiproxy
|
29
|
+
with_jars(%w{lib impl appengine-api.jar}) do
|
30
|
+
return Java.ComGoogleApphostingApi.ApiProxy
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Tries to load the ApiProxyLocalFactory class.
|
35
|
+
def load_local_apiproxy_factory
|
36
|
+
with_jars(%w{lib shared appengine-local-runtime-shared.jar},
|
37
|
+
%w{lib impl appengine-api-stubs.jar},
|
38
|
+
%w{lib impl appengine-local-runtime.jar}
|
39
|
+
) do
|
40
|
+
return Java.ComGoogleAppengineToolsDevelopment.ApiProxyLocalFactory
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_jars(*jars) # :nodoc:
|
45
|
+
begin
|
46
|
+
failed = false
|
47
|
+
yield
|
48
|
+
rescue NameError => ex
|
49
|
+
if failed
|
50
|
+
raise ex
|
51
|
+
else
|
52
|
+
failed = true
|
53
|
+
jars.each do |jar|
|
54
|
+
$CLASSPATH << sdk_path(*jar)
|
55
|
+
end
|
56
|
+
retry
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Tries to find the Google App Engine SDK for Java.
|
62
|
+
#
|
63
|
+
# Looks for appcfg.sh in these directories (in order):
|
64
|
+
# - ENV['APPENGINE_JAVA_SDK']/bin
|
65
|
+
# - each directory in ENV['PATH']
|
66
|
+
# - '/usr/local/appengine-java-sdk/bin'
|
67
|
+
# - 'c:\appengine-java-sdk\bin'
|
68
|
+
#
|
69
|
+
# Returns File.join(sdk_directory, *pieces)
|
70
|
+
#
|
71
|
+
def sdk_path(*pieces)
|
72
|
+
puts "sdk_path(#{pieces.join('/')})"
|
73
|
+
unless @sdk_path
|
74
|
+
base_path = File.join(ENV['APPENGINE_JAVA_SDK'] || '', 'bin')
|
75
|
+
exec_paths = ENV['PATH'].split(File::PATH_SEPARATOR)
|
76
|
+
exec_paths << '/usr/local/appengine-java-sdk/bin'
|
77
|
+
exec_paths << 'c:\appengine-java-sdk\bin'
|
78
|
+
# TODO also check for reggae
|
79
|
+
|
80
|
+
while !exec_paths.empty?
|
81
|
+
if File.exist?(File.join(base_path, 'appcfg.sh'))
|
82
|
+
@sdk_path = File.dirname(base_path)
|
83
|
+
return File.join(@sdk_path, *pieces)
|
84
|
+
end
|
85
|
+
base_path = exec_paths.shift
|
86
|
+
end
|
87
|
+
raise "Unable to locate the Google App Engine SDK for Java."
|
88
|
+
end
|
89
|
+
File.join(@sdk_path, *pieces)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby1.8 -w
|
2
2
|
#
|
3
|
-
# Copyright:: Copyright
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
4
|
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -31,8 +31,6 @@ module AppEngine
|
|
31
31
|
# To run outside this environment, you need to install a test environment and
|
32
32
|
# api stubs.
|
33
33
|
module Testing
|
34
|
-
import com.google.appengine.tools.development.ApiProxyLocalFactory
|
35
|
-
import com.google.appengine.api.datastore.dev.LocalDatastoreService
|
36
34
|
|
37
35
|
class TestEnv # :nodoc:
|
38
36
|
include AppEngine::ApiProxy::Environment
|
@@ -43,7 +41,9 @@ module AppEngine
|
|
43
41
|
def initialize
|
44
42
|
@appid = "test"
|
45
43
|
@version = "1.0"
|
46
|
-
@auth_domain = "gmail.com"
|
44
|
+
@auth_domain = @request_namespace = "gmail.com"
|
45
|
+
@default_namespace = ""
|
46
|
+
@email = ""
|
47
47
|
end
|
48
48
|
|
49
49
|
def getAppId
|
@@ -59,7 +59,7 @@ module AppEngine
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def isLoggedIn
|
62
|
-
!(@email.nil? || @auth_domain.nil?)
|
62
|
+
!(@email.nil? || @auth_domain.nil? || @email.empty?)
|
63
63
|
end
|
64
64
|
|
65
65
|
def isAdmin
|
@@ -80,7 +80,11 @@ module AppEngine
|
|
80
80
|
|
81
81
|
def setDefaultNamespace(s)
|
82
82
|
@default_namespace = s
|
83
|
-
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
"#<TestEnv>"
|
87
|
+
end
|
84
88
|
end
|
85
89
|
|
86
90
|
class << self
|
@@ -99,7 +103,7 @@ module AppEngine
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def factory # :nodoc:
|
102
|
-
@factory ||=
|
106
|
+
@factory ||= AppEngine::SDK.load_local_apiproxy_factory.new
|
103
107
|
end
|
104
108
|
|
105
109
|
# The application directory, or '.' if not set.
|
@@ -124,13 +128,15 @@ module AppEngine
|
|
124
128
|
def install_test_datastore
|
125
129
|
self.app_dir = '.' if app_dir.nil?
|
126
130
|
delegate = factory.create
|
131
|
+
lds = Java.ComGoogleAppengineApiDatastoreDev.LocalDatastoreService
|
132
|
+
|
127
133
|
delegate.set_property(
|
128
|
-
|
134
|
+
lds::NO_STORAGE_PROPERTY, "true")
|
129
135
|
delegate.set_property(
|
130
|
-
|
136
|
+
lds::MAX_QUERY_LIFETIME_PROPERTY,
|
131
137
|
java.lang.Integer::MAX_VALUE.to_s)
|
132
138
|
delegate.set_property(
|
133
|
-
|
139
|
+
lds::MAX_TRANSACTION_LIFETIME_PROPERTY,
|
134
140
|
java.lang.Integer::MAX_VALUE.to_s)
|
135
141
|
ApiProxy::setDelegate(delegate)
|
136
142
|
delegate
|
@@ -149,6 +155,45 @@ module AppEngine
|
|
149
155
|
delegate
|
150
156
|
end
|
151
157
|
|
158
|
+
# Loads stub API implementations if no API implementation is
|
159
|
+
# currently configured.
|
160
|
+
#
|
161
|
+
# Sets up a datastore saved to disk in +app_dir+. +app_dir+ defaults
|
162
|
+
# to ENV['APPLICATION_ROOT'] or '.' if not specified.
|
163
|
+
#
|
164
|
+
# Does nothing is APIs are already configured (e.g. in production).
|
165
|
+
#
|
166
|
+
# As a shortcut you can use
|
167
|
+
# require 'appengine-apis/local_boot'
|
168
|
+
#
|
169
|
+
def boot(app_dir=nil)
|
170
|
+
app_dir ||= ENV['APPLICATION_ROOT'] || '.'
|
171
|
+
unless AppEngine::ApiProxy.current_environment
|
172
|
+
env = install_test_env
|
173
|
+
appid = get_app_id(app_dir)
|
174
|
+
env.appid = appid if appid
|
175
|
+
end
|
176
|
+
unless AppEngine::ApiProxy.delegate
|
177
|
+
self.app_dir = app_dir
|
178
|
+
install_api_stubs
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Looks for app.yaml or WEB-INF/appengine-web.xml in +app_dir+
|
183
|
+
# and parses the application id.
|
184
|
+
def get_app_id(app_dir)
|
185
|
+
aeweb_path = File.join(app_dir, 'WEB-INF', 'appengine-web.xml')
|
186
|
+
app_yaml_path = File.join(app_dir, 'app.yaml')
|
187
|
+
if File.exist?(app_yaml_path)
|
188
|
+
app_yaml = YAML.load(File.open(app_yaml_path))
|
189
|
+
return app_yaml['application']
|
190
|
+
elsif File.exist?(aeweb_path)
|
191
|
+
require 'rexml/document'
|
192
|
+
aeweb = REXML::Document.new(File.new(aeweb_path))
|
193
|
+
return aeweb.root.elements['application'].text
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
152
197
|
end
|
153
198
|
|
154
199
|
end
|
@@ -25,6 +25,11 @@ module AppEngine
|
|
25
25
|
#
|
26
26
|
# Chunked and hanging requests are not supported, and all content will be
|
27
27
|
# returned in a single block.
|
28
|
+
#
|
29
|
+
# URLFetch::HTTP also provides a drop-in replacement for Net::HTTP.
|
30
|
+
# To replace the standard implementation throughout your app you can do:
|
31
|
+
# require 'appengine-apis/urlfetch'
|
32
|
+
# Net::HTTP = AppEngine::URLFetch::HTTP
|
28
33
|
module URLFetch
|
29
34
|
import com.google.appengine.api.urlfetch.FetchOptions
|
30
35
|
import com.google.appengine.api.urlfetch.HTTPHeader
|
@@ -44,12 +49,12 @@ module AppEngine
|
|
44
49
|
|
45
50
|
module_function
|
46
51
|
|
47
|
-
#Fetches the given HTTP URL, blocking until the result is returned.
|
52
|
+
# Fetches the given HTTP URL, blocking until the result is returned.
|
48
53
|
#
|
49
54
|
# Supported options:
|
50
55
|
# [:method] GET, POST, HEAD, PUT, or DELETE
|
51
56
|
# [:payload] POST or PUT payload (implies method is not GET, HEAD,
|
52
|
-
#
|
57
|
+
# or DELETE)
|
53
58
|
# [:headers]
|
54
59
|
# HTTP headers to send with the request. May be a Hash or
|
55
60
|
# Net::HTTPHeaders.
|
@@ -194,5 +194,16 @@ describe AppEngine::Datastore::Entity do
|
|
194
194
|
end
|
195
195
|
props.should == {}
|
196
196
|
end
|
197
|
-
|
197
|
+
|
198
|
+
it "should support update" do
|
199
|
+
@entity.update('foo' => 'bar', 'count' => 3)
|
200
|
+
@entity[:foo].should == 'bar'
|
201
|
+
@entity[:count].should == 3
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should support to_hash" do
|
205
|
+
props = {'foo' => 'bar', 'count' => 3}
|
206
|
+
@entity.merge!(props)
|
207
|
+
@entity.to_hash.should == props
|
208
|
+
end
|
198
209
|
end
|
data/spec/mail_spec.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
20
|
+
require 'appengine-apis/apiproxy'
|
21
|
+
require 'appengine-apis/mail'
|
22
|
+
|
23
|
+
MailMessage = JavaUtilities.get_proxy_or_package_under_package(
|
24
|
+
com.google.appengine.api.mail,
|
25
|
+
'MailServicePb$MailMessage'
|
26
|
+
)
|
27
|
+
|
28
|
+
describe AppEngine::Mail do
|
29
|
+
before :each do
|
30
|
+
@delegate = mock_delegate
|
31
|
+
AppEngine::ApiProxy.set_delegate(@delegate)
|
32
|
+
end
|
33
|
+
|
34
|
+
def should_send(message)
|
35
|
+
should_make_call('Send', message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_send_to_admins(message)
|
39
|
+
should_make_call('SendToAdmins', message)
|
40
|
+
end
|
41
|
+
|
42
|
+
def should_make_call(call, message)
|
43
|
+
@delegate.should_receive(:makeSyncCall).with(
|
44
|
+
anything, 'mail', call, proto(MailMessage, message))
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should send simple message" do
|
48
|
+
should_send({
|
49
|
+
:sender => 'ribrdb@example.com',
|
50
|
+
:to => ['bob@example.com'],
|
51
|
+
:subject => 'Howdy',
|
52
|
+
:text_body => 'Sup?'
|
53
|
+
})
|
54
|
+
AppEngine::Mail.send(
|
55
|
+
'ribrdb@example.com', 'bob@example.com',
|
56
|
+
'Howdy', 'Sup?')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should support cc" do
|
60
|
+
should_send({
|
61
|
+
:sender => 'ribrdb@example.com',
|
62
|
+
:to => ['bob@example.com'],
|
63
|
+
:cc => ['fred@example.com'],
|
64
|
+
:subject => 'Howdy',
|
65
|
+
:text_body => 'Sup?'
|
66
|
+
})
|
67
|
+
AppEngine::Mail.send(
|
68
|
+
'ribrdb@example.com', 'bob@example.com',
|
69
|
+
'Howdy', 'Sup?', :cc => 'fred@example.com')
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should support bcc" do
|
73
|
+
should_send({
|
74
|
+
:sender => 'ribrdb@example.com',
|
75
|
+
:to => ['bob@example.com'],
|
76
|
+
:bcc => ['fred@example.com'],
|
77
|
+
:subject => 'Howdy',
|
78
|
+
:text_body => 'Sup?'
|
79
|
+
})
|
80
|
+
AppEngine::Mail.send(
|
81
|
+
'ribrdb@example.com', 'bob@example.com',
|
82
|
+
'Howdy', 'Sup?', :bcc => 'fred@example.com')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should support multiple recipients" do
|
86
|
+
should_send({
|
87
|
+
:sender => 'ribrdb@example.com',
|
88
|
+
:to => ['bob@example.com', 'fred@example.com'],
|
89
|
+
:cc => ['bob@example.com', 'fred@example.com'],
|
90
|
+
:bcc => ['bob@example.com', 'fred@example.com'],
|
91
|
+
:subject => 'Howdy',
|
92
|
+
:text_body => 'Sup?'
|
93
|
+
})
|
94
|
+
AppEngine::Mail.send(
|
95
|
+
'ribrdb@example.com', ['bob@example.com', 'fred@example.com'],
|
96
|
+
'Howdy', 'Sup?',
|
97
|
+
:cc => ['bob@example.com', 'fred@example.com'],
|
98
|
+
:bcc => ['bob@example.com', 'fred@example.com'])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should support no to" do
|
102
|
+
should_send({
|
103
|
+
:sender => 'ribrdb@example.com',
|
104
|
+
:to => [],
|
105
|
+
:bcc => ['fred@example.com'],
|
106
|
+
:subject => 'Howdy',
|
107
|
+
:text_body => 'Sup?'
|
108
|
+
})
|
109
|
+
AppEngine::Mail.send(
|
110
|
+
'ribrdb@example.com', nil,
|
111
|
+
'Howdy', 'Sup?', :bcc => 'fred@example.com')
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should support html" do
|
115
|
+
should_send({
|
116
|
+
:sender => 'ribrdb@example.com',
|
117
|
+
:to => ['bob@example.com'],
|
118
|
+
:subject => 'Howdy',
|
119
|
+
:text_body => 'Sup?',
|
120
|
+
:html_body => '<h1>Sup?</h1>',
|
121
|
+
})
|
122
|
+
AppEngine::Mail.send(
|
123
|
+
'ribrdb@example.com', 'bob@example.com',
|
124
|
+
'Howdy', 'Sup?', :html => '<h1>Sup?</h1>')
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should support html only" do
|
128
|
+
should_send({
|
129
|
+
:sender => 'ribrdb@example.com',
|
130
|
+
:to => ['bob@example.com'],
|
131
|
+
:subject => 'Howdy',
|
132
|
+
:html_body => '<h1>Sup?</h1>',
|
133
|
+
})
|
134
|
+
AppEngine::Mail.send(
|
135
|
+
'ribrdb@example.com', 'bob@example.com',
|
136
|
+
'Howdy', nil, :html => '<h1>Sup?</h1>')
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should support attachments hash" do
|
140
|
+
should_send({
|
141
|
+
:sender => 'ribrdb@example.com',
|
142
|
+
:to => ['bob@example.com'],
|
143
|
+
:subject => 'Howdy',
|
144
|
+
:text_body => 'Sup?',
|
145
|
+
:attachment => [
|
146
|
+
{:file_name => 'foo.gif', :data => 'foo'},
|
147
|
+
]
|
148
|
+
})
|
149
|
+
AppEngine::Mail.send(
|
150
|
+
'ribrdb@example.com', 'bob@example.com',
|
151
|
+
'Howdy', 'Sup?', :attachments => {'foo.gif' => 'foo'})
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should support attachments list" do
|
155
|
+
should_send({
|
156
|
+
:sender => 'ribrdb@example.com',
|
157
|
+
:to => ['bob@example.com'],
|
158
|
+
:subject => 'Howdy',
|
159
|
+
:text_body => 'Sup?',
|
160
|
+
:attachment => [
|
161
|
+
{:file_name => 'foo.gif', :data => 'foo1'},
|
162
|
+
{:file_name => 'foo.gif', :data => 'foo2'},
|
163
|
+
]
|
164
|
+
})
|
165
|
+
AppEngine::Mail.send(
|
166
|
+
'ribrdb@example.com', 'bob@example.com',
|
167
|
+
'Howdy', 'Sup?',
|
168
|
+
:attachments => [['foo.gif', 'foo1'], ['foo.gif', 'foo2']])
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should support sending to admins" do
|
172
|
+
should_send_to_admins({
|
173
|
+
:sender => 'ribrdb@example.com',
|
174
|
+
:subject => 'Howdy',
|
175
|
+
:text_body => 'Sup?'
|
176
|
+
})
|
177
|
+
AppEngine::Mail.send_to_admins('ribrdb@example.com', 'Howdy', 'Sup?')
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,385 @@
|
|
1
|
+
# Copyright:: Copyright 2009 Google Inc.
|
2
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
17
|
+
require 'appengine-apis/memcache'
|
18
|
+
|
19
|
+
describe AppEngine::Memcache do
|
20
|
+
before :each do
|
21
|
+
AppEngine::Testing::install_test_datastore
|
22
|
+
@cache = AppEngine::Memcache.new
|
23
|
+
end
|
24
|
+
|
25
|
+
before :all do
|
26
|
+
AppEngine::Testing::install_test_env
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'get' do
|
30
|
+
|
31
|
+
it 'should return nil on miss' do
|
32
|
+
@cache.get('foobar').should == nil
|
33
|
+
@cache['foobar'].should == nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should support strings' do
|
37
|
+
@cache.set('foobar', 'test').should == true
|
38
|
+
@cache.get('foobar').should == 'test'
|
39
|
+
@cache['foobar'].should == 'test'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should support multiple values' do
|
43
|
+
@cache.get('foo', 'bar', 'baz').should == [nil, nil, nil]
|
44
|
+
@cache['foo', 'bar', 'baz'].should == [nil, nil, nil]
|
45
|
+
@cache.set('bar', 'food').should == true
|
46
|
+
@cache['foo', 'bar', 'baz'].should == [nil, 'food', nil]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should support getting an array of keys' do
|
50
|
+
@cache.set('foobar', 'test').should == true
|
51
|
+
@cache.get(['foobar', 'flowers']).should == ['test', nil]
|
52
|
+
@cache.get(['foobar']).should == ['test']
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should support numbers' do
|
56
|
+
@cache.set('one', 1).should == true
|
57
|
+
@cache.set('pi', 3.14).should == true
|
58
|
+
@cache.get('pi').should == 3.14
|
59
|
+
@cache.get('one').should === 1
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should support symbol keys' do
|
63
|
+
@cache.set(:a, 'A')
|
64
|
+
@cache.get(:a).should == 'A'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should support booleans' do
|
68
|
+
@cache.set('true', true)
|
69
|
+
@cache.set('false', false)
|
70
|
+
@cache.get('true').should == true
|
71
|
+
@cache.get('false').should == false
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should support marshaled objects' do
|
75
|
+
@cache.set('a', 1..5)
|
76
|
+
@cache.get('a').should == (1..5)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should support false keys' do
|
80
|
+
@cache.set(nil, 'nil')
|
81
|
+
@cache.set(false, 'false')
|
82
|
+
@cache.get(nil).should == 'nil'
|
83
|
+
@cache.get(false).should == 'false'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'get_hash' do
|
88
|
+
it 'should not include missing keys' do
|
89
|
+
@cache.get_hash(:a, :b).should == {}
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should get objects' do
|
93
|
+
@cache.set('a', 'A')
|
94
|
+
@cache.set('b', 3)
|
95
|
+
@cache.get_hash('a', 'c', 'b').should == {'a' => 'A', 'b' => 3 }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'clear' do
|
100
|
+
it 'should clear the cache' do
|
101
|
+
@cache.set(:a, 'A')
|
102
|
+
@cache.set('b', 'B')
|
103
|
+
@cache.clear
|
104
|
+
@cache.get_hash(:a, 'b', :c).should == {}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'flush_all' do
|
109
|
+
it 'should clear the cache' do
|
110
|
+
@cache.set(:a, 'A')
|
111
|
+
@cache.set('b', 'B')
|
112
|
+
@cache.flush_all
|
113
|
+
@cache.get_hash(:a, 'b', :c).should == {}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'stats' do
|
118
|
+
it 'should count items' do
|
119
|
+
@cache.stats[:items].should == 0
|
120
|
+
@cache.set(:a, 'A')
|
121
|
+
@cache.stats[:items].should == 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'delete' do
|
126
|
+
it 'should remove items' do
|
127
|
+
@cache.set(:a, 'A')
|
128
|
+
@cache.get(:a).should == 'A'
|
129
|
+
@cache.delete(:a).should == true
|
130
|
+
@cache.get(:a).should == nil
|
131
|
+
@cache.get_hash(:a).should == {}
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should return false on missing items' do
|
135
|
+
@cache.delete(:a).should == false
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should allow blocking adds' do
|
139
|
+
@cache.set(:a, '1')
|
140
|
+
@cache.set(:b, '2')
|
141
|
+
@cache.delete(:a)
|
142
|
+
@cache.delete(:b, 1)
|
143
|
+
@cache.add(:a, 'A').should == true
|
144
|
+
@cache.add(:b, 'B').should == false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'delete_many' do
|
149
|
+
it 'should remove items' do
|
150
|
+
@cache.set(:a, 'A')
|
151
|
+
@cache.set(:b, 'B')
|
152
|
+
@cache.set(:c, 'C')
|
153
|
+
@cache.delete_many([:a, :c])
|
154
|
+
@cache.get_hash(:a, :b, :c).should == {:b => 'B'}
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should return removed keys' do
|
158
|
+
@cache.set('a', 'A')
|
159
|
+
@cache.set('b', 'B')
|
160
|
+
@cache.set('c', 'C')
|
161
|
+
removed = @cache.delete_many(['a', 'c'])
|
162
|
+
removed.sort.should == ['a', 'c']
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should not return missing keys' do
|
166
|
+
@cache.set('a', 'A')
|
167
|
+
@cache.set('b', 'B')
|
168
|
+
@cache.set('c', 'C')
|
169
|
+
removed = @cache.delete_many(['a', 'c', 'd', 'e'])
|
170
|
+
removed.sort.should == ['a', 'c']
|
171
|
+
@cache.delete_many(['e']).should == []
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
it 'should allow blocking adds' do
|
176
|
+
@cache.set(:a, '1')
|
177
|
+
@cache.set(:b, '2')
|
178
|
+
@cache.delete_many([:a])
|
179
|
+
@cache.delete([:b, :c], 1)
|
180
|
+
@cache.add(:a, 'A').should == true
|
181
|
+
@cache.add(:b, 'B').should == false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'add' do
|
186
|
+
it 'should add missing' do
|
187
|
+
@cache.add(:a, 'A').should == true
|
188
|
+
@cache.get(:a).should == 'A'
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should not replace existing entries' do
|
192
|
+
@cache.set(:a, 1)
|
193
|
+
@cache.add(:a, 'A').should == false
|
194
|
+
@cache.get(:a).should == 1
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe 'add_many' do
|
199
|
+
it 'should add missing' do
|
200
|
+
@cache.add_many({:a => 1, :b =>2})
|
201
|
+
@cache.get(:b, :a).should == [2, 1]
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should not replace existing entries' do
|
205
|
+
@cache.set(:a, 1)
|
206
|
+
@cache.add_many({:a => 'A', :b => 'B'})
|
207
|
+
@cache.get(:a, :b).should == [1, 'B']
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should return existing keys' do
|
211
|
+
@cache.set(:a, 1)
|
212
|
+
@cache.add_many({:a => 'A', :b => 'B'}).should == [:a]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe 'set' do
|
217
|
+
it 'should add missing' do
|
218
|
+
@cache.set(:a, :A).should == true
|
219
|
+
@cache.get(:a).should == :A
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should replace existing' do
|
223
|
+
@cache.set(:a, :A).should == true
|
224
|
+
@cache.get(:a).should == :A
|
225
|
+
@cache.set(:a, 1).should == true
|
226
|
+
@cache.get(:a).should == 1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe 'set_many' do
|
231
|
+
it 'should set multiple values' do
|
232
|
+
@cache.set_many({:a => 1, :b => 2}).should == []
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe 'replace' do
|
237
|
+
it 'should replace existing' do
|
238
|
+
@cache.set(:a, :A).should == true
|
239
|
+
@cache.get(:a).should == :A
|
240
|
+
@cache.replace(:a, 1).should == true
|
241
|
+
@cache.get(:a).should == 1
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should not replace missing' do
|
245
|
+
@cache.replace(:a, :A).should == false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe 'replace_many' do
|
250
|
+
it 'should replace many' do
|
251
|
+
@cache.set_many({:a => 1, :c => 3})
|
252
|
+
@cache.replace_many({:a => :A, :b => :B, :c => :C}).should == [:b]
|
253
|
+
@cache.get(:a, :b, :c).should == [:A, nil, :C]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe 'dict access' do
|
258
|
+
it 'should support getting with []' do
|
259
|
+
@cache.set(:a, 7)
|
260
|
+
@cache[:a].should == 7
|
261
|
+
@cache.set(:b, :B)
|
262
|
+
@cache[:a, :b].should == [7, :B]
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'should support setting with []=' do
|
266
|
+
@cache[:a, :b] = [1, :B]
|
267
|
+
@cache[:c] = 3
|
268
|
+
@cache[:a].should == 1
|
269
|
+
@cache[:b].should == :B
|
270
|
+
@cache[:c].should == 3
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe 'incr' do
|
275
|
+
it 'should increment number' do
|
276
|
+
@cache.set(:a, 1)
|
277
|
+
@cache.incr(:a).should == 2
|
278
|
+
@cache.incr(:a, 7).should == 9
|
279
|
+
@cache.get(:a).should == 9
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'should wrap' do
|
283
|
+
@cache.set(:a, 7)
|
284
|
+
@cache.incr(:a, 2**62).should == 2**62 + 7
|
285
|
+
@cache.incr(:a, 2**62).should < 2**62
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should fail if not number' do
|
289
|
+
@cache.incr(:a).should == nil
|
290
|
+
@cache.get_hash([:a]).should == {}
|
291
|
+
@cache.set(:b, :foo)
|
292
|
+
lambda {@cache.incr(:b)}.should raise_error(
|
293
|
+
AppEngine::Memcache::InvalidValueError)
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'should increment strings' do
|
297
|
+
@cache.set(:a, '7')
|
298
|
+
@cache.incr(:a).should == 8
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe 'decr' do
|
303
|
+
it 'should decrement number' do
|
304
|
+
@cache.set(:a, 7)
|
305
|
+
@cache.decr(:a).should == 6
|
306
|
+
@cache.get(:a).should == 6
|
307
|
+
@cache.decr(:a, 2).should == 4
|
308
|
+
@cache.get(:a).should == 4
|
309
|
+
@cache.decr(:a, 4).should == 0
|
310
|
+
@cache.get(:a).should == 0
|
311
|
+
pending 'local decrement wrapping' do
|
312
|
+
@cache.decr(:a, 6).should == 0
|
313
|
+
@cache.get(:a).should == 0
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe 'readonly' do
|
319
|
+
before :each do
|
320
|
+
@rocache = AppEngine::Memcache.new(:readonly => true)
|
321
|
+
@ex = AppEngine::Memcache::MemcacheError
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'should allow reads' do
|
325
|
+
@cache[:a] = 1
|
326
|
+
@rocache.get(:a).should == 1
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should allow stats' do
|
330
|
+
@cache[:a] = 7
|
331
|
+
@rocache.stats[:items].should == 1
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'should block delete' do
|
335
|
+
@cache[:a] = 1
|
336
|
+
lambda {@rocache.delete(:a)}.should raise_error(@ex)
|
337
|
+
@cache[:a].should == 1
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'should block add' do
|
341
|
+
lambda {@rocache.add(:a, 1)}.should raise_error(@ex)
|
342
|
+
lambda {@rocache.add_many({:b => 1})}.should raise_error(@ex)
|
343
|
+
@rocache.get_hash(:a, :b).should == {}
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'should block set' do
|
347
|
+
lambda {@rocache.set(:a, 1)}.should raise_error(@ex)
|
348
|
+
lambda {@rocache.set_many({:b => 1})}.should raise_error(@ex)
|
349
|
+
@rocache.get_hash(:a, :b).should == {}
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should block replace' do
|
353
|
+
@cache[:a, :b] = :A, :B
|
354
|
+
lambda {@rocache.replace(:a, 1)}.should raise_error(@ex)
|
355
|
+
lambda {@rocache.replace_many({:b => 1})}.should raise_error(@ex)
|
356
|
+
@rocache[:a, :b].should == [:A, :B]
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'should block clear' do
|
360
|
+
@cache[:a] = 1
|
361
|
+
lambda {@rocache.clear}.should raise_error(@ex)
|
362
|
+
lambda {@rocache.flush_all}.should raise_error(@ex)
|
363
|
+
@rocache[:a].should == 1
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'should block incr' do
|
367
|
+
@cache[:a] = 1
|
368
|
+
lambda {@rocache.incr(:a)}.should raise_error(@ex)
|
369
|
+
@rocache[:a].should == 1
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'should block decr' do
|
373
|
+
@cache[:a] = 1
|
374
|
+
lambda {@rocache.decr(:a)}.should raise_error(@ex)
|
375
|
+
@rocache[:a].should == 1
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe "namespaces" do
|
380
|
+
it 'should get namespace from initialize' do
|
381
|
+
cache = AppEngine::Memcache.new(:namespace => "foo")
|
382
|
+
cache.namespace.should == "foo"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|