001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.kfs.context;
017:
018: import static org.kuali.test.suite.JiraRelatedSuite.State.OPEN_OR_IN_PROGRESS_OR_REOPENED;
019:
020: import java.lang.reflect.Method;
021: import java.util.Arrays;
022: import java.util.HashSet;
023: import java.util.Set;
024:
025: import junit.framework.TestCase;
026:
027: import org.apache.log4j.Logger;
028: import org.apache.ojb.broker.OptimisticLockException;
029: import org.kuali.core.UserSession;
030: import org.kuali.core.util.ErrorMap;
031: import org.kuali.core.util.GlobalVariables;
032: import org.kuali.kfs.service.SchedulerService;
033: import org.kuali.test.ConfigureContext;
034: import org.kuali.test.KualiTestConstants;
035: import org.kuali.test.fixtures.UserNameFixture;
036: import org.kuali.test.suite.JiraRelatedSuite;
037: import org.kuali.test.suite.RelatesTo;
038: import org.springframework.transaction.PlatformTransactionManager;
039: import org.springframework.transaction.TransactionStatus;
040: import org.springframework.transaction.support.DefaultTransactionDefinition;
041: import org.springmodules.orm.ojb.OjbOperationException;
042:
043: /**
044: * This class should be extended by all Kuali unit tests.
045: *
046: * @see ConfigureContext
047: * @see RelatesTo
048: */
049:
050: public abstract class KualiTestBase extends TestCase implements
051: KualiTestConstants {
052: private static final Logger LOG = Logger
053: .getLogger(KualiTestBase.class);
054: public static final String SKIP_OPEN_OR_IN_PROGRESS_OR_REOPENED_JIRA_ISSUES = "org.kuali.test.KualiTestBase.skipOpenOrInProgressOrReopenedJiraIssues";
055: private static boolean log4jConfigured = false;
056: private static RuntimeException configurationFailure;
057: private static boolean springContextInitialized = false;
058: private static boolean batchScheduleInitialized = false;
059: private static TransactionStatus transactionStatus;
060: private static UserNameFixture userSessionUsername;
061: protected static UserSession userSession;
062:
063: /**
064: * Determines whether to actually run the test using the RelatesTo annotation, onfigures the appropriate context using the
065: * ConfigureContext annotation, and logs extra details if the test invocation's OJB operations happen to encounter an
066: * OptimisticLockException or if this test has related Jiras.
067: *
068: * @throws Throwable
069: */
070: @Override
071: public final void runBare() throws Throwable {
072: if (!log4jConfigured) {
073: Log4jConfigurer.configureLogging(false);
074: log4jConfigured = true;
075: }
076: final String testName = getClass().getName() + "." + getName();
077: if (System
078: .getProperty(SKIP_OPEN_OR_IN_PROGRESS_OR_REOPENED_JIRA_ISSUES) != null) {
079: Set<RelatesTo.JiraIssue> openOrInProgressOrReopened = JiraRelatedSuite
080: .getMatchingIssues(getRelatedJiraIssues(),
081: OPEN_OR_IN_PROGRESS_OR_REOPENED);
082: if (!openOrInProgressOrReopened.isEmpty()) {
083: LOG
084: .info("Skipping test '"
085: + testName
086: + "' because of JIRA issues open or in progress or reopened: "
087: + openOrInProgressOrReopened);
088: return; // let this test method pass without running it
089: }
090: }
091:
092: LOG.info("Entering test '" + testName + "'");
093: GlobalVariables.setErrorMap(new ErrorMap());
094: ConfigureContext contextConfiguration = getMethod(getName())
095: .getAnnotation(ConfigureContext.class) != null ? getMethod(
096: getName()).getAnnotation(ConfigureContext.class)
097: : getMethod("setUp").getAnnotation(
098: ConfigureContext.class) != null ? getMethod(
099: "setUp").getAnnotation(ConfigureContext.class)
100: : getClass().getAnnotation(
101: ConfigureContext.class);
102: if (contextConfiguration != null) {
103: configure(contextConfiguration);
104: }
105: try {
106: setUp();
107: try {
108: runTest();
109: } catch (OjbOperationException e) {
110: // log more detail for OptimisticLockExceptions
111: OjbOperationException ooe = (OjbOperationException) e;
112: Throwable cause = ooe.getCause();
113: if (cause instanceof OptimisticLockException) {
114: OptimisticLockException ole = (OptimisticLockException) cause;
115: StringBuffer message = new StringBuffer(
116: "caught OptimisticLockException, caused by ");
117: Object sourceObject = ole.getSourceObject();
118: String suffix = null;
119: try {
120: // try to add instance details
121: suffix = sourceObject.toString();
122: } catch (Exception e2) {
123: // just use the class name
124: suffix = sourceObject.getClass().getName();
125: }
126: message.append(suffix);
127: LOG.error(message.toString());
128: }
129: throw e;
130: } finally {
131: tearDown();
132: }
133: } catch (Throwable t) {
134: Set<RelatesTo.JiraIssue> issues = getRelatedJiraIssues();
135: if (issues.isEmpty()) {
136: throw t;
137: } else {
138: throw new Exception(
139: "JIRA issues thought to be related to this test not passing: "
140: + issues, t);
141: }
142: } finally {
143: if (contextConfiguration != null) {
144: endTestTransaction();
145: }
146: GlobalVariables.setUserSession(null);
147: GlobalVariables.setErrorMap(new ErrorMap());
148: LOG.info("Leaving test '" + testName + "'");
149: }
150: }
151:
152: protected boolean testTransactionIsRollbackOnly() {
153: return (transactionStatus != null)
154: && (transactionStatus.isRollbackOnly());
155: }
156:
157: protected void changeCurrentUser(UserNameFixture sessionUser)
158: throws Exception {
159: GlobalVariables.setUserSession(new UserSession(sessionUser
160: .toString()));
161: }
162:
163: private void configure(ConfigureContext contextConfiguration)
164: throws Exception {
165: if (configurationFailure != null) {
166: throw configurationFailure;
167: }
168: if (!springContextInitialized) {
169: try {
170: SpringContext.initializeTestApplicationContext();
171: springContextInitialized = true;
172: } catch (RuntimeException e) {
173: configurationFailure = e;
174: throw e;
175: }
176: }
177: if (!batchScheduleInitialized
178: && contextConfiguration.initializeBatchSchedule()) {
179: SpringContext.getBean(SchedulerService.class).initialize();
180: batchScheduleInitialized = true;
181: }
182: if (!contextConfiguration.shouldCommitTransactions()) {
183: LOG
184: .info("Starting test transaction that will be rolled back");
185: DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
186: defaultTransactionDefinition.setTimeout(3600);
187: transactionStatus = getTransactionManager().getTransaction(
188: defaultTransactionDefinition);
189: } else {
190: LOG.info("Test transaction not used");
191: transactionStatus = null;
192: }
193: UserNameFixture sessionUser = contextConfiguration.session();
194: if (sessionUser != UserNameFixture.NO_SESSION) {
195: GlobalVariables.setUserSession(new UserSession(sessionUser
196: .toString()));
197: }
198: }
199:
200: private void endTestTransaction() {
201: if (transactionStatus != null) {
202: LOG.info("rolling back transaction");
203: getTransactionManager().rollback(transactionStatus);
204: }
205: }
206:
207: private PlatformTransactionManager getTransactionManager() {
208: return SpringContext.getBean(PlatformTransactionManager.class);
209: }
210:
211: private Method getMethod(String methodName) {
212: Class clazz = getClass();
213: while (clazz != null) {
214: try {
215: return clazz.getDeclaredMethod(methodName);
216: } catch (NoSuchMethodException e) {
217: clazz = clazz.getSuperclass();
218: }
219: }
220: throw new RuntimeException(
221: "KualiTestBase was unable to getMethod: " + methodName);
222: }
223:
224: private Set<RelatesTo.JiraIssue> getRelatedJiraIssues() {
225: HashSet<RelatesTo.JiraIssue> issues = new HashSet<RelatesTo.JiraIssue>();
226: addJiraIssues(this .getClass().getAnnotation(RelatesTo.class),
227: issues);
228: // Test methods must be public, so we can use getMethod(), which handles inheritence. (I recommend not inheriting test
229: // methods, however.)
230: try {
231: addJiraIssues(this .getClass().getMethod(getName())
232: .getAnnotation(RelatesTo.class), issues);
233: } catch (NoSuchMethodException e) {
234: throw new AssertionError(
235: "Impossible because tests are named after their test method.");
236: }
237: return issues;
238: }
239:
240: private static void addJiraIssues(RelatesTo annotation,
241: HashSet<RelatesTo.JiraIssue> issues) {
242: if (annotation != null) {
243: issues.addAll(Arrays.asList(annotation.value()));
244: }
245: }
246: }
|