001    /*
002     * Copyright 2007-2008 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 java.lang.annotation.ElementType;
019    import java.lang.annotation.Inherited;
020    import java.lang.annotation.Retention;
021    import java.lang.annotation.RetentionPolicy;
022    import java.lang.annotation.Target;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import org.kuali.rice.core.api.lifecycle.Lifecycle;
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</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     * </dl>
045     * 
046     * The BaselineMode annotation can be used on a per-test-class basis to indicate to the base class which mode to use for test
047     * subclass.  It accepts a {@link Mode} value.
048     * 
049     * @author Kuali Rice Team (rice.collab@kuali.org)
050     *
051     */
052    public class BaselineTestCase extends BaseModuleTestCase {
053        /**
054         * Enum of "baselining" modes that this test case supports
055         */
056        public static enum Mode {
057            CLEAR_DB, ROLLBACK, NONE
058        }
059    
060        @Target({ElementType.TYPE})
061        @Inherited
062        @Retention(RetentionPolicy.RUNTIME)
063        public @interface BaselineMode {
064            Mode value();
065        }
066    
067        private Mode mode = Mode.NONE;
068        
069        /**
070         * Whether the test environment is in a "dirty" state.  Each time the unit test starts up
071         * dirty is set to true.  If a subclass installs the {@link TransactionalLifecycle} then
072         * it should clear the dirty flag.  This flag can be used to perform cleanup in case a previous
073         * test left the test environment in a "dirty" state.
074         */
075        protected static boolean dirty = false;
076    
077        // propagate constructors
078        public BaselineTestCase(String moduleName) {
079            super(moduleName);
080            readModeAnnotation();
081        }
082    
083        /**
084         * Adds the ability to specify Mode
085         */
086        public BaselineTestCase(String moduleName, Mode mode) {
087            super(moduleName);
088            if (mode == null) throw new IllegalArgumentException("Mode cannot be null");
089            this.mode = mode;
090        }
091    
092        private void readModeAnnotation() {
093            BaselineMode m = this.getClass().getAnnotation(BaselineMode.class);
094            if (m != null) {
095                if (m.value() != null) {
096                    mode = m.value();
097                }
098            }
099        }
100    
101        /**
102         * @return the configured mode
103         */
104        protected Mode getMode() {
105            return mode;
106        }
107    
108        /**
109         * Overridden to set dirty=true each time
110         * @see org.kuali.rice.test.RiceTestCase#setUp()
111         */
112        @Override
113        public void setUp() throws Exception {
114            super.setUp();
115            dirty = true;
116        }
117    
118        @Override
119        protected List<Lifecycle> getPerTestLifecycles() {
120            switch (mode) {
121                case ROLLBACK: return getRollbackPerTestLifecycles();
122                case CLEAR_DB: return getClearDbPerTestLifecycles();
123                case NONE: return super.getPerTestLifecycles();
124                default:
125                    throw new RuntimeException("Invalid mode specified: " + mode);        
126            }
127        }
128    
129        /**
130         * @return the per-test lifecycles for clearing the database
131         */
132        protected List<Lifecycle> getClearDbPerTestLifecycles() {
133            List<Lifecycle> lifecycles = super.getPerTestLifecycles();
134            lifecycles.add(0, new ClearDatabaseLifecycle(getPerTestTablesToClear(), getPerTestTablesNotToClear()));
135            return lifecycles;
136        }
137        
138        protected List<String> getPerTestTablesToClear() {
139            return new ArrayList<String>();
140        }
141    
142        protected List<String> getPerTestTablesNotToClear() {
143            return new ArrayList<String>();
144        }
145        
146        /**
147         * @return the per-test lifecycles for rolling back the database
148         */
149        protected List<Lifecycle> getRollbackPerTestLifecycles() {
150            List<Lifecycle> lifecycles = super.getPerTestLifecycles();
151            lifecycles.add(0, new TransactionalLifecycle() {
152                @Override
153                public void stop() throws Exception {
154                    super.stop();
155                    dirty = false;
156                }
157                
158            });
159            // if some previous test case did not roll back the data
160            // clear the db
161            if (dirty) {
162                log.warn("Previous test case did not clean up the database; clearing database...");
163                lifecycles.add(0, new ClearDatabaseLifecycle(getPerTestTablesToClear(), getPerTestTablesNotToClear()));
164            }
165            return lifecycles;
166        }
167    }