appengine-apis 0.0.2 → 0.0.3
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/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
|