App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / development / testing / LocalServiceTestHelper.java
blob6bc5cd776431fd3337b09a876ed8711ef8a5c4fa
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;
17 import java.util.Map;
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;
24 /**
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.
32 * <blockquote>
33 * <pre>
34 * public void MyTest extends TestCase {
36 * private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
37 * new LocalTaskQueueTestConfig(), new LocalDatastoreServiceTestConfig());
39 * &#64;Override
40 * public void setUp() {
41 * super.setUp();
42 * helper.setUp();
43 * }
45 * &#64;Override
46 * public void tearDown() {
47 * helper.tearDown();
48 * super.tearDown();
49 * }
50 * }
51 * </pre>
52 * </blockquote>
55 public class LocalServiceTestHelper {
56 public interface RequestMillisTimer {
57 /**
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();
64 /**
65 * The Timer instance used by local services if no override is provided via
66 * {@link LocalServiceTestHelper#setRemainingMillisTimer(RequestMillisTimer)}.
68 RequestMillisTimer DEFAULT = new RequestMillisTimer() {
69 @Override
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>();
95 private Clock clock;
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()}
116 * @param envAppId
117 * @return {@code this} (for chaining)
119 public LocalServiceTestHelper setEnvAppId(String envAppId) {
120 this.envAppId = envAppId;
121 return this;
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;
133 return this;
137 * The value to be returned by
138 * {@code ApiProxy.getCurrentEnvironment().getEmail()}
140 * @param envEmail
141 * @return {@code this} (for chaining)
143 public LocalServiceTestHelper setEnvEmail(String envEmail) {
144 this.envEmail = envEmail;
145 return this;
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;
157 return this;
161 * The value to be returned by
162 * {@code ApiProxy.getCurrentEnvironment().isAdmin()}
164 * @param envIsAdmin
165 * @return {@code this} (for chaining)
167 public LocalServiceTestHelper setEnvIsAdmin(boolean envIsAdmin) {
168 this.envIsAdmin = envIsAdmin;
169 return this;
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;
181 return this;
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) {
192 this.timer = timer;
193 return this;
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);
205 return this;
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);
217 return this;
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.
224 * @param clock
225 * @return {@code this} (for chaining)
227 public LocalServiceTestHelper setClock(Clock clock) {
228 this.clock = clock;
229 return this;
233 * Determines whether or not API calls should be subject to the same
234 * deadlines as in production. The default is {@code false}.
235 * @param val
236 * @return {@code this} (for chaining)
238 public LocalServiceTestHelper setEnforceApiDeadlines(boolean val) {
239 this.enforceApiDeadlines = val;
240 return this;
244 * Determines whether or not local services should simulate production
245 * latencies. The default is {@code false}.
246 * @param val
247 * @return {@code this} (for chaining)
249 public LocalServiceTestHelper setSimulateProdLatencies(boolean val) {
250 this.simulateProdLatencies = val;
251 return this;
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;
267 return this;
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());
282 if (clock != null) {
283 apiProxyLocal.setClock(clock);
285 ApiProxy.setDelegate(apiProxyLocal);
287 for (LocalServiceTestConfig config : configs) {
288 config.setUp();
290 return this;
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_MODULE_NAME,
299 envVersionId, envInstance, TestLocalServerEnvironment.TEST_PORT, null) {
300 @Override
301 public String getEmail() {
302 return envEmail;
305 @Override
306 public boolean isLoggedIn() {
307 return envIsLoggedIn;
310 @Override
311 public boolean isAdmin() {
312 return envIsAdmin;
315 @Override
316 public String getAuthDomain() {
317 return envAuthDomain;
320 @Override
321 public long getRemainingMillis() {
322 return timer.getRemainingMillis();
325 env.getAttributes().putAll(envAttributes);
326 return env;
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_MODULE_NAME, DEFAULT_VERSION_ID,
353 LocalEnvironment.MAIN_INSTANCE, TestLocalServerEnvironment.TEST_PORT, null);
356 private TestEnvironment(String appId,
357 String majorVersionId,
358 String email,
359 boolean isLoggedIn,
360 boolean isAdmin,
361 String authDomain,
362 String moduleName,
363 int instance,
364 Map<String, Object> attributes) {
365 super(appId, moduleName, majorVersionId, instance, TestLocalServerEnvironment.TEST_PORT,
366 null);
367 this.email = email;
368 this.isLoggedIn = isLoggedIn;
369 this.isAdmin = isAdmin;
370 this.authDomain = authDomain;
371 this.attributes.putAll(attributes);
374 public TestEnvironment(ApiProxy.Environment copyFrom) {
375 this(copyFrom.getAppId(),
376 getMajorVersion(copyFrom.getVersionId()),
377 copyFrom.getEmail(),
378 copyFrom.isLoggedIn(),
379 copyFrom.isAdmin(),
380 copyFrom.getAuthDomain(),
381 getModuleName(copyFrom.getVersionId()),
382 getInstance(copyFrom),
383 copyFrom.getAttributes());
386 private static int getInstance(ApiProxy.Environment environment) {
387 int result = LocalEnvironment.MAIN_INSTANCE;
388 if (environment.getAttributes().containsKey(LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE)) {
389 result =
390 (Integer) environment.getAttributes().get(LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE);
392 return result;
395 @Override
396 public String getEmail() {
397 return email;
400 @Override
401 public boolean isLoggedIn() {
402 return isLoggedIn;
405 @Override
406 public boolean isAdmin() {
407 return isAdmin;
410 @Override
411 public String getAuthDomain() {
412 return authDomain;
417 * Constructs the {@link LocalServerEnvironment} that will be installed.
418 * Subclass and override to provide your own implementation.
420 protected LocalServerEnvironment newLocalServerEnvironment() {
421 return new TestLocalServerEnvironment(enforceApiDeadlines, simulateProdLatencies);
425 * Tear down the environment in which tests that use local services can
426 * execute.
428 public final void tearDown() {
429 try {
430 RuntimeException firstException = null;
431 for (LocalServiceTestConfig config : configs) {
432 try {
433 config.tearDown();
434 } catch (RuntimeException rte) {
435 if (firstException == null) {
436 firstException = rte;
437 } else {
438 logger.log(
439 Level.SEVERE,
440 "Received exception tearing down config of type " + config.getClass().getName(),
441 rte);
445 if (firstException != null) {
446 throw firstException;
449 endRequest();
451 ApiProxy.setDelegate(null);
452 ApiProxy.setEnvironmentForCurrentThread(null);
453 apiProxyLocal = null;
454 } finally {
455 TimeZone.setDefault(originalDefaultTimeZone);
460 * Indicate the request has ended so that local services can do any post request work. This
461 * method is optional and is automatically done in {@link LocalServiceTestHelper#tearDown()}.
462 * You only need to call this method if you want to call {@link LocalServiceTestHelper#tearDown()}
463 * from your own tearDown / @After method but need to end the request to verify any behavior.
465 public static void endRequest() {
466 ((LocalEnvironment) ApiProxy.getCurrentEnvironment()).callRequestEndListeners();
470 * Convenience function for getting ahold of the currently
471 * registered {@link ApiProxyLocal}.
473 public static ApiProxyLocal getApiProxyLocal() {
474 return apiProxyLocal;
478 * Convenience function for getting ahold of a specific local service.
479 * For example, to get ahold of the LocalDatastoreService you would
480 * call {@code getLocalService(LocalDatastoreService.PACKAGE)}.
482 public static LocalRpcService getLocalService(String serviceName) {
483 return getApiProxyLocal().getService(serviceName);