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.atomic.AtomicInteger;
036
037import static org.apache.commons.lang3.StringUtils.defaultString;
038
039public class TestUtil {
040        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
041
042        /**
043         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
044         * <p>
045         * When we run the unit tests in cobertura, JUnit doesn't seem to clean up static fields which leads to
046         * tons of memory being used by the end and the JVM crashes in Travis. Manually clearing all of the
047         * static fields seems to solve this.
048         */
049        public static void clearAllStaticFieldsForUnitTest() {
050                HapiLocalizer.setOurFailOnMissingMessage(true);
051
052                Class<?> theType;
053                try {
054                        throw new Exception();
055                } catch (Exception e) {
056                        StackTraceElement[] st = e.getStackTrace();
057                        StackTraceElement elem = st[1];
058                        String clazzName = elem.getClassName();
059                        try {
060                                theType = Class.forName(clazzName);
061                        } catch (ClassNotFoundException e1) {
062                                throw new Error(e);
063                        }
064                }
065
066                for (Field next : Arrays.asList(theType.getDeclaredFields())) {
067                        if (Modifier.isStatic(next.getModifiers())) {
068                                if (!Modifier.isFinal(next.getModifiers()) && !next.getType().isPrimitive()) {
069                                        ourLog.info("Clearing value of field: {}", next.toString());
070                                        try {
071                                                next.setAccessible(true);
072                                                next.set(theType, null);
073                                        } catch (Exception e) {
074                                                throw new Error(e);
075                                        }
076                                }
077                                if (Modifier.isFinal(next.getModifiers())) {
078                                        if (next.getType().equals(FhirContext.class)) {
079                                                throw new Error("Test has final field of type FhirContext: " + next);
080                                        }
081                                }
082                        }
083
084                }
085
086                randomizeLocale();
087
088                /*
089                 * If we're running a CI build, set all loggers to TRACE level to ensure coverage
090                 * on trace blocks
091                 */
092                try {
093                        if ("true".equals(System.getProperty("ci"))) {
094                                for (Logger next : ((LoggerContext) LoggerFactory.getILoggerFactory()).getLoggerList()) {
095                                        next.setLevel(Level.TRACE);
096                                }
097                        }
098                } catch (NoClassDefFoundError e) {
099                        // ignore
100                }
101        }
102
103        /**
104         * Set some system properties randomly after each test.. this is kind of hackish,
105         * but it helps us make sure we don't have any tests that depend on a particular
106         * environment
107         */
108        public static void randomizeLocale() {
109                Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN};
110                Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
111                ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
112                if (Math.random() < 0.5) {
113                        ourLog.info("Tests are using WINDOWS line endings and ISO-8851-1");
114                        System.setProperty("file.encoding", "ISO-8859-1");
115                        System.setProperty("line.separator", "\r\n");
116                } else {
117                        ourLog.info("Tests are using UNIX line endings and UTF-8");
118                        System.setProperty("file.encoding", "UTF-8");
119                        System.setProperty("line.separator", "\n");
120                }
121                String availableTimeZones[] = {"GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30"};
122                String timeZone = availableTimeZones[(int) (Math.random() * availableTimeZones.length)];
123                TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
124                ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
125        }
126
127
128        /**
129         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
130         * <p>
131         * Wait for an atomicinteger to hit a given site and fail if it never does
132         */
133        public static void waitForSize(int theTarget, AtomicInteger theInteger) {
134                long start = System.currentTimeMillis();
135                while (theInteger.get() != theTarget && (System.currentTimeMillis() - start) <= 15000) {
136                        try {
137                                Thread.sleep(50);
138                        } catch (InterruptedException theE) {
139                                throw new Error(theE);
140                        }
141                }
142                if ((System.currentTimeMillis() - start) >= 15000) {
143                        throw new IllegalStateException("Size " + theInteger.get() + " is != target " + theTarget);
144                }
145        }
146
147
148        /**
149         * <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
150         * <p>
151         * Strip \r chars from a string to account for line ending platform differences
152         */
153        public static String stripReturns(String theString) {
154                return defaultString(theString).replace("\r", "");
155        }
156
157}