Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / testing / LocalServiceTestHelper.java
blob6b82450693ff8e50f88935fa84155a2ef227feda
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_SERVER_NAME,
299 envVersionId, envInstance, 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_SERVER_NAME, DEFAULT_VERSION_ID,
353 LocalEnvironment.MAIN_INSTANCE, null);
356 private TestEnvironment(String appId,
357 String majorVersionId,
358 String email,
359 boolean isLoggedIn,
360 boolean isAdmin,
361 String authDomain,
362 String serverName,
363 int instance,
364 Map<String, Object> attributes) {
365 super(appId, serverName, majorVersionId, instance, null);
366 this.email = email;
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()),
376 copyFrom.getEmail(),
377 copyFrom.isLoggedIn(),
378 copyFrom.isAdmin(),
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)) {
388 result =
389 (Integer) environment.getAttributes().get(LocalEnvironment.INSTANCE_ID_ENV_ATTRIBUTE);
391 return result;
394 @Override
395 public String getEmail() {
396 return email;
399 @Override
400 public boolean isLoggedIn() {
401 return isLoggedIn;
404 @Override
405 public boolean isAdmin() {
406 return isAdmin;
409 @Override
410 public String getAuthDomain() {
411 return authDomain;
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
425 * execute.
427 public final void tearDown() {
428 try {
429 RuntimeException firstException = null;
430 for (LocalServiceTestConfig config : configs) {
431 try {
432 config.tearDown();
433 } catch (RuntimeException rte) {
434 if (firstException == null) {
435 firstException = rte;
436 } else {
437 logger.log(
438 Level.SEVERE,
439 "Received exception tearing down config of type " + config.getClass().getName(),
440 rte);
444 if (firstException != null) {
445 throw firstException;
448 endRequest();
450 ApiProxy.setDelegate(null);
451 ApiProxy.setEnvironmentForCurrentThread(null);
452 apiProxyLocal = null;
453 } finally {
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);