001    /**
002     * Copyright 2005-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.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/ecl2.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.rice.test;
017    
018    import org.kuali.rice.core.api.lifecycle.Lifecycle;
019    
020    import java.lang.annotation.ElementType;
021    import java.lang.annotation.Inherited;
022    import java.lang.annotation.Retention;
023    import java.lang.annotation.RetentionPolicy;
024    import java.lang.annotation.Target;
025    import java.util.ArrayList;
026    import java.util.List;
027    
028    /**
029     * Test case which supports common styles of "baselining" the test environment before/after running
030     * a unit test.
031     * Currently supports three modes, which are specifyiable either via constructor, {@link #getMode()} override,
032     * or by annotation:
033     * <dl>
034     *   <dt>NONE</dt>
035     *   <dd>No baselining is performed.  Because the base RiceTestCase includes the ClearDatabaseLifecycle by default, this lifecycle is
036     *       explicitly omitted</dd>
037     *   <dt>CLEAR_DB</dt>
038     *   <dd>The database is cleared for each test.  The suite ClearDatabaseLifecycle is omitted (since it's getting cleared each test
039     *       anyway)</dd>
040     *   <dt>ROLLBACK_CLEAR_DB</dt>
041     *   <dd>A TransactionalLifecycle is installed that wraps each test and rolls back data.  The suite ClearDatabaseLifecycle will be
042     *       invoked once initially, and subsequently if the test has detected that the environment has been left "dirty" by a previous
043     *       test.  After a successful rollback, the test environment is marked clean again.</dd>
044     *   <dd>A TransactionalLifecycle is installed that wraps each test and rolls back data.</dd>
045     * </dl>
046     * 
047     * The BaselineMode annotation can be used on a per-test-class basis to indicate to the base class which mode to use for test
048     * subclass.  It accepts a {@link Mode} value.
049     * 
050     * @author Kuali Rice Team (rice.collab@kuali.org)
051     *
052     */
053    public class BaselineTestCase extends BaseModuleTestCase {
054        /**
055         * Enum of "baselining" modes that this test case supports
056         */
057        public static enum Mode {
058            CLEAR_DB, ROLLBACK_CLEAR_DB, ROLLBACK, NONE
059        }
060    
061        @Target({ElementType.TYPE})
062        @Inherited
063        @Retention(RetentionPolicy.RUNTIME)
064        public @interface BaselineMode {
065            Mode value();
066        }
067    
068        private Mode mode = Mode.NONE;
069        
070        /**
071         * Whether the test environment is in a "dirty" state.  Each time the unit test starts up
072         * dirty is set to true.  If a subclass installs the {@link TransactionalLifecycle} then
073         * it should clear the dirty flag.  This flag can be used to perform cleanup in case a previous
074         * test left the test environment in a "dirty" state.
075         */
076        protected static boolean dirty = false;
077    
078        // propagate constructors
079        public BaselineTestCase(String moduleName) {
080            super(moduleName);
081            readModeAnnotation();
082        }
083    
084        /**
085         * Adds the ability to specify Mode
086         */
087        public BaselineTestCase(String moduleName, Mode mode) {
088            super(moduleName);
089            if (mode == null) throw new IllegalArgumentException("Mode cannot be null");
090            this.mode = mode;
091        }
092    
093        private void readModeAnnotation() {
094            BaselineMode m = this.getClass().getAnnotation(BaselineMode.class);
095            if (m != null) {
096                if (m.value() != null) {
097                    mode = m.value();
098                }
099            }
100        }
101    
102        /**
103         * @return the configured mode
104         */
105        protected Mode getMode() {
106            return mode;
107        }
108    
109        /**
110         * Overridden to set dirty=true each time
111         * @see org.kuali.rice.test.RiceTestCase#setUp()
112         */
113        @Override
114        public void setUp() throws Exception {
115            super.setUp();
116            dirty = true;
117        }
118    
119        @Override
120        protected List<Lifecycle> getPerTestLifecycles() {
121            switch (mode) {
122                case ROLLBACK_CLEAR_DB: return getRollbackClearDbPerTestLifecycles();
123                case ROLLBACK: return getRollbackTestLifecycles();
124                case CLEAR_DB: return getClearDbPerTestLifecycles();
125                case NONE: return super.getPerTestLifecycles();
126                default:
127                    throw new RuntimeException("Invalid mode specified: " + mode);        
128            }
129        }
130    
131        /**
132         * @return the per-test lifecycles for clearing the database
133         */
134        protected List<Lifecycle> getClearDbPerTestLifecycles() {
135            List<Lifecycle> lifecycles = super.getPerTestLifecycles();
136            lifecycles.add(0, new ClearDatabaseLifecycle(getPerTestTablesToClear(), getPerTestTablesNotToClear()));
137            return lifecycles;
138        }
139        
140        protected List<String> getPerTestTablesToClear() {
141            return new ArrayList<String>();
142        }
143    
144        protected List<String> getPerTestTablesNotToClear() {
145            return new ArrayList<String>();
146        }
147        
148        /**
149         * @return the per-test lifecycles for rolling back & clearing the database
150         */
151        protected List<Lifecycle> getRollbackClearDbPerTestLifecycles() {
152            List<Lifecycle> lifecycles = super.getPerTestLifecycles();
153            lifecycles.add(0, new TransactionalLifecycle() {
154                @Override
155                public void stop() throws Exception {
156                    super.stop();
157                    dirty = false;
158                }
159                
160            });
161            // if some previous test case did not roll back the data
162            // clear the db
163            if (dirty) {
164                log.warn("Previous test case did not clean up the database; clearing database...");
165                lifecycles.add(0, new ClearDatabaseLifecycle(getPerTestTablesToClear(), getPerTestTablesNotToClear()));
166            }
167            return lifecycles;
168        }
169    
170        /**
171         * @return the per-test lifecycles for rolling back the database
172         */
173        protected List<Lifecycle> getRollbackTestLifecycles() {
174            List<Lifecycle> lifecycles = super.getPerTestLifecycles();
175            lifecycles.add(0, new TransactionalLifecycle());
176            return lifecycles;
177        }
178    }