1 // Copyright 2009 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.tools
.development
.testing
;
4 import com
.google
.appengine
.api
.NamespaceManager
;
5 import com
.google
.appengine
.tools
.development
.ApiProxyLocal
;
6 import com
.google
.appengine
.tools
.development
.ApiProxyLocalFactory
;
7 import com
.google
.appengine
.tools
.development
.Clock
;
8 import com
.google
.appengine
.tools
.development
.LocalEnvironment
;
9 import com
.google
.appengine
.tools
.development
.LocalRpcService
;
10 import com
.google
.appengine
.tools
.development
.LocalServerEnvironment
;
11 import com
.google
.apphosting
.api
.ApiProxy
;
12 import com
.google
.apphosting
.api
.ApiProxy
.Environment
;
13 import com
.google
.apphosting
.utils
.config
.WebModule
;
15 import java
.util
.Arrays
;
16 import java
.util
.List
;
18 import java
.util
.TimeZone
;
19 import java
.util
.concurrent
.ConcurrentHashMap
;
20 import java
.util
.concurrent
.ConcurrentMap
;
21 import java
.util
.logging
.Level
;
22 import java
.util
.logging
.Logger
;
25 * Helper class for testing against local app engine services.
26 * Construct the helper with one {@link LocalServiceTestConfig} instance for
27 * each service that you wish to access as part of your test. Then call
28 * {@link #setUp()} before each test executes and {@link #tearDown()} after
29 * each test executes. No specific test-harness is assumed, but here's a
30 * JUnit 3 example that uses task queues and the datastore.
34 * public void MyTest extends TestCase {
36 * private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
37 * new LocalTaskQueueTestConfig(), new LocalDatastoreServiceTestConfig());
40 * public void setUp() {
46 * public void tearDown() {
55 public class LocalServiceTestHelper
{
56 public interface RequestMillisTimer
{
58 * @return The amount of time that the {@link Environment} should say is left in the request.
59 * Expressed in milliseconds. If infinite time is allowed, then reply with
60 * {@link Long#MAX_VALUE}.
62 long getRemainingMillis();
65 * The Timer instance used by local services if no override is provided via
66 * {@link LocalServiceTestHelper#setRemainingMillisTimer(RequestMillisTimer)}.
68 RequestMillisTimer DEFAULT
= new RequestMillisTimer() {
70 public long getRemainingMillis() {
71 return Long
.MAX_VALUE
;
76 private static final String APPS_NAMESPACE_KEY
=
77 NamespaceManager
.class.getName() + ".appsNamespace";
79 private static ApiProxyLocal apiProxyLocal
;
81 static final String DEFAULT_APP_ID
= "test";
82 static final String DEFAULT_VERSION_ID
= "1.0";
84 private final Logger logger
= Logger
.getLogger(getClass().getName());
85 private final List
<LocalServiceTestConfig
> configs
;
86 private String envAppId
= DEFAULT_APP_ID
;
87 private String envVersionId
= DEFAULT_VERSION_ID
;
88 private final int envInstance
= LocalEnvironment
.MAIN_INSTANCE
;
89 private String envEmail
;
90 private boolean envIsLoggedIn
;
91 private boolean envIsAdmin
;
92 private String envAuthDomain
;
93 private RequestMillisTimer timer
= RequestMillisTimer
.DEFAULT
;
94 private ConcurrentMap
<String
, Object
> envAttributes
= new ConcurrentHashMap
<String
, Object
>();
96 private boolean enforceApiDeadlines
= false;
97 private boolean simulateProdLatencies
= false;
99 private TimeZone timeZone
= TimeZone
.getTimeZone("UTC");
101 private TimeZone originalDefaultTimeZone
;
104 * Constructs a LocalServiceTestHelper with the provided configurations.
106 * @param configs for the local services that need to be set up and torn down.
108 public LocalServiceTestHelper(LocalServiceTestConfig
... configs
) {
109 this.configs
= Arrays
.asList(configs
);
113 * The value to be returned by
114 * {@code ApiProxy.getCurrentEnvironment().getAppId()}
117 * @return {@code this} (for chaining)
119 public LocalServiceTestHelper
setEnvAppId(String envAppId
) {
120 this.envAppId
= envAppId
;
125 * The value to be returned by
126 * {@code ApiProxy.getCurrentEnvironment().getVersionId()}
128 * @param envVersionId
129 * @return {@code this} (for chaining)
131 public LocalServiceTestHelper
setEnvVersionId(String envVersionId
) {
132 this.envVersionId
= envVersionId
;
137 * The value to be returned by
138 * {@code ApiProxy.getCurrentEnvironment().getEmail()}
141 * @return {@code this} (for chaining)
143 public LocalServiceTestHelper
setEnvEmail(String envEmail
) {
144 this.envEmail
= envEmail
;
149 * The value to be returned by
150 * {@code ApiProxy.getCurrentEnvironment().isLoggedIn()}
152 * @param envIsLoggedIn
153 * @return {@code this} (for chaining)
155 public LocalServiceTestHelper
setEnvIsLoggedIn(boolean envIsLoggedIn
) {
156 this.envIsLoggedIn
= envIsLoggedIn
;
161 * The value to be returned by
162 * {@code ApiProxy.getCurrentEnvironment().isAdmin()}
165 * @return {@code this} (for chaining)
167 public LocalServiceTestHelper
setEnvIsAdmin(boolean envIsAdmin
) {
168 this.envIsAdmin
= envIsAdmin
;
173 * The value to be returned by
174 * {@code ApiProxy.getCurrentEnvironment().getAuthDomain()}
176 * @param envAuthDomain
177 * @return {@code this} (for chaining)
179 public LocalServiceTestHelper
setEnvAuthDomain(String envAuthDomain
) {
180 this.envAuthDomain
= envAuthDomain
;
185 * Sets the object that will return the value to be returned by
186 * {@code ApiProxy.getCurrentEnvironment().getRemainingMillis()}
188 * @param timer The timer that returns the amount of time left.
189 * @return {@code this} (for chaining)
191 public LocalServiceTestHelper
setRemainingMillisTimer(RequestMillisTimer timer
) {
197 * The value to be returned by
198 * {@code ApiProxy.getCurrentEnvironment().getRequestNamespace()}
200 * @param envRequestNamespace
201 * @return {@code this} (for chaining)
203 public LocalServiceTestHelper
setEnvRequestNamespace(String envRequestNamespace
) {
204 envAttributes
.put(APPS_NAMESPACE_KEY
, envRequestNamespace
);
209 * The value to be returned by
210 * {@code ApiProxy.getCurrentEnvironment().getAttributes()}
212 * @param envAttributes
213 * @return {@code this} (for chaining)
215 public LocalServiceTestHelper
setEnvAttributes(Map
<String
, Object
> envAttributes
) {
216 this.envAttributes
= new ConcurrentHashMap
<String
, Object
>(envAttributes
);
221 * Sets the clock with which all local services will be initialized. Note
222 * that once a local service is initialized its clock cannot be altered.
225 * @return {@code this} (for chaining)
227 public LocalServiceTestHelper
setClock(Clock clock
) {
233 * Determines whether or not API calls should be subject to the same
234 * deadlines as in production. The default is {@code false}.
236 * @return {@code this} (for chaining)
238 public LocalServiceTestHelper
setEnforceApiDeadlines(boolean val
) {
239 this.enforceApiDeadlines
= val
;
244 * Determines whether or not local services should simulate production
245 * latencies. The default is {@code false}.
247 * @return {@code this} (for chaining)
249 public LocalServiceTestHelper
setSimulateProdLatencies(boolean val
) {
250 this.simulateProdLatencies
= val
;
255 * Sets the time zone in which tests will execute. If not set we use the
256 * same timezone that we use in production and the dev appserver: UTC. Note
257 * that if your code has permission to modify the <code>user.timezone</code>
258 * system property, this will change the default timezone for the JVM.
259 * However, if your code does not have this permission, the timezone will only
260 * be altered for the current thread.
262 * @param timeZone the time zone
263 * @return {@code this} (for chaining)
265 public LocalServiceTestHelper
setTimeZone(TimeZone timeZone
) {
266 this.timeZone
= timeZone
;
271 * Set up an environment in which tests that use local services can execute.
273 * @return {@code this} (for chaining)
275 public final LocalServiceTestHelper
setUp() {
276 originalDefaultTimeZone
= TimeZone
.getDefault();
277 TimeZone
.setDefault(timeZone
);
279 ApiProxy
.setEnvironmentForCurrentThread(newEnvironment());
281 apiProxyLocal
= new ApiProxyLocalFactory().create(newLocalServerEnvironment());
283 apiProxyLocal
.setClock(clock
);
285 ApiProxy
.setDelegate(apiProxyLocal
);
287 for (LocalServiceTestConfig config
: configs
) {
294 * Constructs the {@link com.google.apphosting.api.ApiProxy.Environment} that
295 * will be installed. Subclass and override to provide your own implementation.
297 protected ApiProxy
.Environment
newEnvironment() {
298 LocalEnvironment env
= new LocalEnvironment(envAppId
, WebModule
.DEFAULT_SERVER_NAME
,
299 envVersionId
, envInstance
, null) {
301 public String
getEmail() {
306 public boolean isLoggedIn() {
307 return envIsLoggedIn
;
311 public boolean isAdmin() {
316 public String
getAuthDomain() {
317 return envAuthDomain
;
321 public long getRemainingMillis() {
322 return timer
.getRemainingMillis();
325 env
.getAttributes().putAll(envAttributes
);
330 * Constructs a new {@link com.google.apphosting.api.ApiProxy.Environment} by
331 * copying the data from the given one. The {@code Map} from
332 * {@code getAttributes} will be shallow-copied.
334 static ApiProxy
.Environment
copyEnvironment(ApiProxy
.Environment copyFrom
){
335 return new TestEnvironment(copyFrom
);
339 * Constructs a new default {@link com.google.apphosting.api.ApiProxy.Environment}.
341 static ApiProxy
.Environment
newDefaultTestEnvironment() {
342 return new TestEnvironment();
345 private static class TestEnvironment
extends LocalEnvironment
{
346 private String email
;
347 private boolean isLoggedIn
;
348 private boolean isAdmin
;
349 private String authDomain
;
351 private TestEnvironment() {
352 super(DEFAULT_APP_ID
, WebModule
.DEFAULT_SERVER_NAME
, DEFAULT_VERSION_ID
,
353 LocalEnvironment
.MAIN_INSTANCE
, null);
356 private TestEnvironment(String appId
,
357 String majorVersionId
,
364 Map
<String
, Object
> attributes
) {
365 super(appId
, serverName
, majorVersionId
, instance
, null);
367 this.isLoggedIn
= isLoggedIn
;
368 this.isAdmin
= isAdmin
;
369 this.authDomain
= authDomain
;
370 this.attributes
.putAll(attributes
);
373 public TestEnvironment(ApiProxy
.Environment copyFrom
) {
374 this(copyFrom
.getAppId(),
375 getMajorVersion(copyFrom
.getVersionId()),
377 copyFrom
.isLoggedIn(),
379 copyFrom
.getAuthDomain(),
380 getServerName(copyFrom
.getVersionId()),
381 getInstance(copyFrom
),
382 copyFrom
.getAttributes());
385 private static int getInstance(ApiProxy
.Environment environment
) {
386 int result
= LocalEnvironment
.MAIN_INSTANCE
;
387 if (environment
.getAttributes().containsKey(LocalEnvironment
.INSTANCE_ID_ENV_ATTRIBUTE
)) {
389 (Integer
) environment
.getAttributes().get(LocalEnvironment
.INSTANCE_ID_ENV_ATTRIBUTE
);
395 public String
getEmail() {
400 public boolean isLoggedIn() {
405 public boolean isAdmin() {
410 public String
getAuthDomain() {
416 * Constructs the {@link LocalServerEnvironment} that will be installed.
417 * Subclass and override to provide your own implementation.
419 protected LocalServerEnvironment
newLocalServerEnvironment() {
420 return new TestLocalServerEnvironment(enforceApiDeadlines
, simulateProdLatencies
);
424 * Tear down the environment in which tests that use local services can
427 public final void tearDown() {
429 RuntimeException firstException
= null;
430 for (LocalServiceTestConfig config
: configs
) {
433 } catch (RuntimeException rte
) {
434 if (firstException
== null) {
435 firstException
= rte
;
439 "Received exception tearing down config of type " + config
.getClass().getName(),
444 if (firstException
!= null) {
445 throw firstException
;
450 ApiProxy
.setDelegate(null);
451 ApiProxy
.setEnvironmentForCurrentThread(null);
452 apiProxyLocal
= null;
454 TimeZone
.setDefault(originalDefaultTimeZone
);
459 * Indicate the request has ended so that local services can do any post request work. This
460 * method is optional and is automatically done in {@link LocalServiceTestHelper#tearDown()}.
461 * You only need to call this method if you want to call {@link LocalServiceTestHelper#tearDown()}
462 * from your own tearDown / @After method but need to end the request to verify any behavior.
464 public static void endRequest() {
465 ((LocalEnvironment
) ApiProxy
.getCurrentEnvironment()).callRequestEndListeners();
469 * Convenience function for getting ahold of the currently
470 * registered {@link ApiProxyLocal}.
472 public static ApiProxyLocal
getApiProxyLocal() {
473 return apiProxyLocal
;
477 * Convenience function for getting ahold of a specific local service.
478 * For example, to get ahold of the LocalDatastoreService you would
479 * call {@code getLocalService(LocalDatastoreService.PACKAGE)}.
481 public static LocalRpcService
getLocalService(String serviceName
) {
482 return getApiProxyLocal().getService(serviceName
);