Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / testing / LocalServiceTestHelper.java
blobc779f98ce36dcf3e8edceb108a7ea633fc069652
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;
14 import java.util.Arrays;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.TimeZone;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ConcurrentMap;
20 import java.util.logging.Level;
21 import java.util.logging.Logger;
23 /**
24 * Helper class for testing against local app engine services.
25 * Construct the helper with one {@link LocalServiceTestConfig} instance for
26 * each service that you wish to access as part of your test. Then call
27 * {@link #setUp()} before each test executes and {@link #tearDown()} after
28 * each test executes. No specific test-harness is assumed, but here's a
29 * JUnit 3 example that uses task queues and the datastore.
31 * <blockquote>
32 * <pre>
33 * public void MyTest extends TestCase {
35 * private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
36 * new LocalTaskQueueTestConfig(), new LocalDatastoreServiceTestConfig());
38 * &#64;Override
39 * public void setUp() {
40 * super.setUp();
41 * helper.setUp();
42 * }
44 * &#64;Override
45 * public void tearDown() {
46 * helper.tearDown();
47 * super.tearDown();
48 * }
49 * }
50 * </pre>
51 * </blockquote>
54 public class LocalServiceTestHelper {
55 public interface RequestMillisTimer {
56 /**
57 * @return The amount of time that the {@link Environment} should say is left in the request.
58 * Expressed in milliseconds. If infinite time is allowed, then reply with
59 * {@link Long#MAX_VALUE}.
61 long getRemainingMillis();
63 /**
64 * The Timer instance used by local services if no override is provided via
65 * {@link LocalServiceTestHelper#setRemainingMillisTimer(RequestMillisTimer)}.
67 RequestMillisTimer DEFAULT = new RequestMillisTimer() {
68 @Override
69 public long getRemainingMillis() {
70 return Long.MAX_VALUE;
75 private static final String APPS_NAMESPACE_KEY =
76 NamespaceManager.class.getName() + ".appsNamespace";
78 private static ApiProxyLocal apiProxyLocal;
80 static final String DEFAULT_APP_ID = "test";
81 static final String DEFAULT_VERSION_ID = "1.0";
83 private final Logger logger = Logger.getLogger(getClass().getName());
84 private final List<LocalServiceTestConfig> configs;
85 private String envAppId = DEFAULT_APP_ID;
86 private String envVersionId = DEFAULT_VERSION_ID;
87 private String envEmail;
88 private boolean envIsLoggedIn;
89 private boolean envIsAdmin;
90 private String envAuthDomain;
91 private RequestMillisTimer timer = RequestMillisTimer.DEFAULT;
92 private ConcurrentMap<String, Object> envAttributes = new ConcurrentHashMap<String, Object>();
93 private Clock clock;
94 private boolean enforceApiDeadlines = false;
95 private boolean simulateProdLatencies = false;
97 private TimeZone timeZone = TimeZone.getTimeZone("UTC");
99 private TimeZone originalDefaultTimeZone;
102 * Constructs a LocalServiceTestHelper with the provided configurations.
104 * @param configs for the local services that need to be set up and torn down.
106 public LocalServiceTestHelper(LocalServiceTestConfig... configs) {
107 this.configs = Arrays.asList(configs);
111 * The value to be returned by
112 * {@code ApiProxy.getCurrentEnvironment().getAppId()}
114 * @param envAppId
115 * @return {@code this} (for chaining)
117 public LocalServiceTestHelper setEnvAppId(String envAppId) {
118 this.envAppId = envAppId;
119 return this;
123 * The value to be returned by
124 * {@code ApiProxy.getCurrentEnvironment().getVersionId()}
126 * @param envVersionId
127 * @return {@code this} (for chaining)
129 public LocalServiceTestHelper setEnvVersionId(String envVersionId) {
130 this.envVersionId = envVersionId;
131 return this;
135 * The value to be returned by
136 * {@code ApiProxy.getCurrentEnvironment().getEmail()}
138 * @param envEmail
139 * @return {@code this} (for chaining)
141 public LocalServiceTestHelper setEnvEmail(String envEmail) {
142 this.envEmail = envEmail;
143 return this;
147 * The value to be returned by
148 * {@code ApiProxy.getCurrentEnvironment().isLoggedIn()}
150 * @param envIsLoggedIn
151 * @return {@code this} (for chaining)
153 public LocalServiceTestHelper setEnvIsLoggedIn(boolean envIsLoggedIn) {
154 this.envIsLoggedIn = envIsLoggedIn;
155 return this;
159 * The value to be returned by
160 * {@code ApiProxy.getCurrentEnvironment().isAdmin()}
162 * @param envIsAdmin
163 * @return {@code this} (for chaining)
165 public LocalServiceTestHelper setEnvIsAdmin(boolean envIsAdmin) {
166 this.envIsAdmin = envIsAdmin;
167 return this;
171 * The value to be returned by
172 * {@code ApiProxy.getCurrentEnvironment().getAuthDomain()}
174 * @param envAuthDomain
175 * @return {@code this} (for chaining)
177 public LocalServiceTestHelper setEnvAuthDomain(String envAuthDomain) {
178 this.envAuthDomain = envAuthDomain;
179 return this;
183 * Sets the object that will return the value to be returned by
184 * {@code ApiProxy.getCurrentEnvironment().getRemainingMillis()}
186 * @param timer The timer that returns the amount of time left.
187 * @return {@code this} (for chaining)
189 public LocalServiceTestHelper setRemainingMillisTimer(RequestMillisTimer timer) {
190 this.timer = timer;
191 return this;
195 * The value to be returned by
196 * {@code ApiProxy.getCurrentEnvironment().getRequestNamespace()}
198 * @param envRequestNamespace
199 * @return {@code this} (for chaining)
201 public LocalServiceTestHelper setEnvRequestNamespace(String envRequestNamespace) {
202 envAttributes.put(APPS_NAMESPACE_KEY, envRequestNamespace);
203 return this;
207 * The value to be returned by
208 * {@code ApiProxy.getCurrentEnvironment().getAttributes()}
210 * @param envAttributes
211 * @return {@code this} (for chaining)
213 public LocalServiceTestHelper setEnvAttributes(Map<String, Object> envAttributes) {
214 this.envAttributes = new ConcurrentHashMap<String, Object>(envAttributes);
215 return this;
219 * Sets the clock with which all local services will be initialized. Note
220 * that once a local service is initialized its clock cannot be altered.
222 * @param clock
223 * @return {@code this} (for chaining)
225 public LocalServiceTestHelper setClock(Clock clock) {
226 this.clock = clock;
227 return this;
231 * Determines whether or not API calls should be subject to the same
232 * deadlines as in production. The default is {@code false}.
233 * @param val
234 * @return {@code this} (for chaining)
236 public LocalServiceTestHelper setEnforceApiDeadlines(boolean val) {
237 this.enforceApiDeadlines = val;
238 return this;
242 * Determines whether or not local services should simulate production
243 * latencies. The default is {@code false}.
244 * @param val
245 * @return {@code this} (for chaining)
247 public LocalServiceTestHelper setSimulateProdLatencies(boolean val) {
248 this.simulateProdLatencies = val;
249 return this;
253 * Sets the time zone in which tests will execute. If not set we use the
254 * same timezone that we use in production and the dev appserver: UTC. Note
255 * that if your code has permission to modify the <code>user.timezone</code>
256 * system property, this will change the default timezone for the JVM.
257 * However, if your code does not have this permission, the timezone will only
258 * be altered for the current thread.
260 * @param timeZone the time zone
261 * @return {@code this} (for chaining)
263 public LocalServiceTestHelper setTimeZone(TimeZone timeZone) {
264 this.timeZone = timeZone;
265 return this;
269 * Set up an environment in which tests that use local services can execute.
271 * @return {@code this} (for chaining)
273 public final LocalServiceTestHelper setUp() {
274 originalDefaultTimeZone = TimeZone.getDefault();
275 TimeZone.setDefault(timeZone);
277 ApiProxy.setEnvironmentForCurrentThread(newEnvironment());
279 apiProxyLocal = new ApiProxyLocalFactory().create(newLocalServerEnvironment());
280 if (clock != null) {
281 apiProxyLocal.setClock(clock);
283 ApiProxy.setDelegate(apiProxyLocal);
285 for (LocalServiceTestConfig config : configs) {
286 config.setUp();
288 return this;
292 * Constructs the {@link ApiProxy.Environment} that will be installed.
293 * Subclass and override to provide your own implementation.
295 protected ApiProxy.Environment newEnvironment() {
296 LocalEnvironment env = new LocalEnvironment(envAppId, envVersionId) {
297 @Override
298 public String getEmail() {
299 return envEmail;
302 @Override
303 public boolean isLoggedIn() {
304 return envIsLoggedIn;
307 @Override
308 public boolean isAdmin() {
309 return envIsAdmin;
312 @Override
313 public String getAuthDomain() {
314 return envAuthDomain;
317 @Override
318 public long getRemainingMillis() {
319 return timer.getRemainingMillis();
322 env.getAttributes().putAll(envAttributes);
323 return env;
327 * Constructs a new {@link ApiProxy.Environment} by copying the data from the
328 * given one. The {@code Map} from {@code getAttributes} will be
329 * shallow-copied.
331 static ApiProxy.Environment copyEnvironment(ApiProxy.Environment copyFrom){
332 return new TestEnvironment(copyFrom);
336 * Constructs a new default {@link ApiProxy.Environment}.
338 static ApiProxy.Environment newDefaultTestEnvironment() {
339 return new TestEnvironment();
342 private static class TestEnvironment extends LocalEnvironment {
343 private String email;
344 private boolean isLoggedIn;
345 private boolean isAdmin;
346 private String authDomain;
348 private TestEnvironment() {
349 super(DEFAULT_APP_ID, DEFAULT_VERSION_ID);
352 private TestEnvironment(String appId,
353 String majorVersionId,
354 String email,
355 boolean isLoggedIn,
356 boolean isAdmin,
357 String authDomain,
358 Map<String, Object> attributes) {
359 super(appId, majorVersionId);
360 this.email = email;
361 this.isLoggedIn = isLoggedIn;
362 this.isAdmin = isAdmin;
363 this.authDomain = authDomain;
364 this.attributes.putAll(attributes);
367 public TestEnvironment(ApiProxy.Environment copyFrom) {
368 this(copyFrom.getAppId(),
369 copyFrom.getVersionId(),
370 copyFrom.getEmail(),
371 copyFrom.isLoggedIn(),
372 copyFrom.isAdmin(),
373 copyFrom.getAuthDomain(),
374 copyFrom.getAttributes());
377 @Override
378 public String getEmail() {
379 return email;
382 @Override
383 public boolean isLoggedIn() {
384 return isLoggedIn;
387 @Override
388 public boolean isAdmin() {
389 return isAdmin;
392 @Override
393 public String getAuthDomain() {
394 return authDomain;
399 * Constructs the {@link LocalServerEnvironment} that will be installed.
400 * Subclass and override to provide your own implementation.
402 protected LocalServerEnvironment newLocalServerEnvironment() {
403 return new TestLocalServerEnvironment(enforceApiDeadlines, simulateProdLatencies);
407 * Tear down the environment in which tests that use local services can
408 * execute.
410 public final void tearDown() {
411 try {
412 RuntimeException firstException = null;
413 for (LocalServiceTestConfig config : configs) {
414 try {
415 config.tearDown();
416 } catch (RuntimeException rte) {
417 if (firstException == null) {
418 firstException = rte;
419 } else {
420 logger.log(
421 Level.SEVERE,
422 "Received exception tearing down config of type " + config.getClass().getName(),
423 rte);
427 if (firstException != null) {
428 throw firstException;
431 endRequest();
433 ApiProxy.setDelegate(null);
434 ApiProxy.setEnvironmentForCurrentThread(null);
435 apiProxyLocal = null;
436 } finally {
437 TimeZone.setDefault(originalDefaultTimeZone);
442 * Indicate the request has ended so that local services can do any post request work. This
443 * method is optional and is automatically done in {@link LocalServiceTestHelper#tearDown()}.
444 * You only need to call this method if you want to call {@link LocalServiceTestHelper#tearDown()}
445 * from your own tearDown / @After method but need to end the request to verify any behavior.
447 public static void endRequest() {
448 ((LocalEnvironment) ApiProxy.getCurrentEnvironment()).callRequestEndListeners();
452 * Convenience function for getting ahold of the currently
453 * registered {@link ApiProxyLocal}.
455 public static ApiProxyLocal getApiProxyLocal() {
456 return apiProxyLocal;
460 * Convenience function for getting ahold of a specific local service.
461 * For example, to get ahold of the LocalDatastoreService you would
462 * call {@code getLocalService(LocalDatastoreService.PACKAGE)}.
464 public static LocalRpcService getLocalService(String serviceName) {
465 return getApiProxyLocal().getService(serviceName);