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