001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2018 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 * http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.i18n.HapiLocalizer;
025import ch.qos.logback.classic.Level;
026import ch.qos.logback.classic.Logger;
027import ch.qos.logback.classic.LoggerContext;
028import org.slf4j.LoggerFactory;
029
030import java.lang.reflect.Field;
031import java.lang.reflect.Modifier;
032import java.util.Arrays;
033import java.util.Locale;
034import java.util.TimeZone;
035import java.util.concurrent.Callable;
036import java.util.concurrent.atomic.AtomicInteger;
037
038import static org.apache.commons.lang3.StringUtils.defaultString;
039
040public class TestUtil {
041        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
042
043        /**
044         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
045         * <p>
046         * When we run the unit tests in cobertura, JUnit doesn't seem to clean up static fields which leads to
047         * tons of memory being used by the end and the JVM crashes in Travis. Manually clearing all of the
048         * static fields seems to solve this.
049         */
050        public static void clearAllStaticFieldsForUnitTest() {
051                HapiLocalizer.setOurFailOnMissingMessage(true);
052
053                Class<?> theType;
054                try {
055                        throw new Exception();
056                } catch (Exception e) {
057                        StackTraceElement[] st = e.getStackTrace();
058                        StackTraceElement elem = st[1];
059                        String clazzName = elem.getClassName();
060                        try {
061                                theType = Class.forName(clazzName);
062                        } catch (ClassNotFoundException e1) {
063                                throw new Error(e);
064                        }
065                }
066
067                for (Field next : Arrays.asList(theType.getDeclaredFields())) {
068                        if (Modifier.isStatic(next.getModifiers())) {
069                                if (!Modifier.isFinal(next.getModifiers()) && !next.getType().isPrimitive()) {
070                                        ourLog.info("Clearing value of field: {}", next.toString());
071                                        try {
072                                                next.setAccessible(true);
073                                                next.set(theType, null);
074                                        } catch (Exception e) {
075                                                throw new Error(e);
076                                        }
077                                }
078                                if (Modifier.isFinal(next.getModifiers())) {
079                                        if (next.getType().equals(FhirContext.class)) {
080                                                throw new Error("Test has final field of type FhirContext: " + next);
081                                        }
082                                }
083                        }
084
085                }
086
087                randomizeLocale();
088
089                /*
090                 * If we're running a CI build, set all loggers to TRACE level to ensure coverage
091                 * on trace blocks
092                 */
093                try {
094                        if ("true".equals(System.getProperty("ci"))) {
095                                for (Logger next : ((LoggerContext) LoggerFactory.getILoggerFactory()).getLoggerList()) {
096                                        next.setLevel(Level.TRACE);
097                                }
098                        }
099                } catch (NoClassDefFoundError e) {
100                        // ignore
101                }
102        }
103
104        /**
105         * Set some system properties randomly after each test.. this is kind of hackish,
106         * but it helps us make sure we don't have any tests that depend on a particular
107         * environment
108         */
109        public static void randomizeLocale() {
110                Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN};
111                Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
112                ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
113                if (Math.random() < 0.5) {
114                        ourLog.info("Tests are using WINDOWS line endings and ISO-8851-1");
115                        System.setProperty("file.encoding", "ISO-8859-1");
116                        System.setProperty("line.separator", "\r\n");
117                } else {
118                        ourLog.info("Tests are using UNIX line endings and UTF-8");
119                        System.setProperty("file.encoding", "UTF-8");
120                        System.setProperty("line.separator", "\n");
121                }
122                String availableTimeZones[] = {"GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30"};
123                String timeZone = availableTimeZones[(int) (Math.random() * availableTimeZones.length)];
124                TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
125                ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
126        }
127
128
129        /**
130         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
131         * <p>
132         * Wait for an atomicinteger to hit a given site and fail if it never does
133         */
134        public static void waitForSize(int theTarget, AtomicInteger theInteger) {
135                long start = System.currentTimeMillis();
136                while (theInteger.get() != theTarget && (System.currentTimeMillis() - start) <= 15000) {
137                        try {
138                                Thread.sleep(50);
139                        } catch (InterruptedException theE) {
140                                throw new Error(theE);
141                        }
142                }
143                if ((System.currentTimeMillis() - start) >= 15000) {
144                        throw new IllegalStateException("Size " + theInteger.get() + " is != target " + theTarget);
145                }
146        }
147
148        /**
149         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
150         * <p>
151         * Wait for an atomicinteger to hit a given site and fail if it never does
152         */
153        public static void waitForSize(int theTarget, Callable<Integer> theSource) throws Exception {
154                long start = System.currentTimeMillis();
155                while (theSource.call() != theTarget && (System.currentTimeMillis() - start) <= 15000) {
156                        try {
157                                Thread.sleep(50);
158                        } catch (InterruptedException theE) {
159                                throw new Error(theE);
160                        }
161                }
162                if ((System.currentTimeMillis() - start) >= 15000) {
163                        throw new IllegalStateException("Size " + theSource.call() + " is != target " + theTarget);
164                }
165        }
166
167        /**
168         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
169         * <p>
170         * Strip \r chars from a string to account for line ending platform differences
171         */
172        public static String stripReturns(String theString) {
173                return defaultString(theString).replace("\r", "");
174        }
175
176}