001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.junit.bct; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021 022import org.apache.juneau.commons.function.*; 023import org.apache.juneau.commons.settings.*; 024 025/** 026 * Configuration utility for Bean-Centric Testing (BCT) framework. 027 * 028 * <p> 029 * This class provides thread-local configuration settings for BCT assertions, allowing test-specific 030 * customization of behavior such as map sorting, collection sorting, and bean converter selection. 031 * 032 * <p> 033 * Configuration is managed using thread-local storage, ensuring that parallel test execution doesn't 034 * interfere with each other's settings. Settings are typically configured via the {@link org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig} 035 * annotation, but can also be set programmatically. 036 * 037 * <h5 class='section'>Configuration Properties:</h5> 038 * <ul> 039 * <li><b>{@link #BCT_SORT_MAPS}:</b> Enable sorting of maps in assertions</li> 040 * <li><b>{@link #BCT_SORT_COLLECTIONS}:</b> Enable sorting of collections in assertions</li> 041 * <li><b>Bean Converter:</b> Custom bean converter instance for property access and conversion</li> 042 * </ul> 043 * 044 * <h5 class='section'>Usage via Annotation:</h5> 045 * <p class='bjava'> 046 * <ja>@BctConfig</ja>(sortMaps=TriState.<jsf>TRUE</jsf>, beanConverter=MyConverter.<jk>class</jk>) 047 * <jk>class</jk> MyTest { 048 * <ja>@Test</ja> 049 * <ja>@BctConfig</ja>(sortCollections=TriState.<jsf>TRUE</jsf>) 050 * <jk>void</jk> testSomething() { 051 * <jc>// sortMaps=true (from class), sortCollections=true (from method),</jc> 052 * <jc>// beanConverter=MyConverter (from class)</jc> 053 * } 054 * } 055 * </p> 056 * 057 * <h5 class='section'>Usage via Programmatic API:</h5> 058 * <p class='bjava'> 059 * <ja>@BeforeEach</ja> 060 * <jk>void</jk> setUp() { 061 * BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>); 062 * BctConfiguration.<jsm>set</jsm>(<jk>new</jk> MyCustomConverter()); 063 * } 064 * 065 * <ja>@AfterEach</ja> 066 * <jk>void</jk> tearDown() { 067 * BctConfiguration.<jsm>clear</jsm>(); 068 * } 069 * </p> 070 * 071 * <h5 class='section'>Thread Safety:</h5> 072 * <p> 073 * All methods in this class are thread-safe. Configuration is stored in thread-local storage, 074 * ensuring that each test thread has its own isolated configuration that doesn't interfere 075 * with other concurrently running tests. 076 * </p> 077 * 078 * @see org.apache.juneau.junit.bct.annotations.BctConfig 079 * @see org.apache.juneau.junit.bct.BctAssertions 080 */ 081public class BctConfiguration { 082 083 // Thread-local memoized supplier for default converter (defaults to BasicBeanConverter.DEFAULT) 084 private static final ThreadLocal<ResettableSupplier<BeanConverter>> CONVERTER_SUPPLIER = ThreadLocal.withInitial(() -> memr(() -> BasicBeanConverter.DEFAULT)); 085 086 /** 087 * Configuration property name for enabling map sorting in BCT assertions. 088 * 089 * <p> 090 * When set to <jk>true</jk>, maps will be sorted by key before comparison in assertions. 091 * This ensures consistent ordering regardless of the map's internal ordering. 092 * 093 * <h5 class='section'>Example:</h5> 094 * <p class='bjava'> 095 * BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>); 096 * </p> 097 */ 098 public static final String BCT_SORT_MAPS = "Bct.sortMaps"; 099 100 /** 101 * Configuration property name for enabling collection sorting in BCT assertions. 102 * 103 * <p> 104 * When set to <jk>true</jk>, collections will be sorted before comparison in assertions. 105 * This ensures consistent ordering regardless of the collection's internal ordering. 106 * 107 * <h5 class='section'>Example:</h5> 108 * <p class='bjava'> 109 * BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_COLLECTIONS</jsf>, <jk>true</jk>); 110 * </p> 111 */ 112 public static final String BCT_SORT_COLLECTIONS = "Bct.sortCollections"; 113 114 // Thread-local override for method-level converter customization 115 private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE = new ThreadLocal<>(); 116 117 private static final Settings SETTINGS = Settings.create().build(); 118 119 /** 120 * Sets a thread-local configuration value. 121 * 122 * <p> 123 * The value is stored in thread-local storage and will only affect the current thread. 124 * This is the recommended way to set configuration for individual tests. 125 * 126 * <h5 class='section'>Example:</h5> 127 * <p class='bjava'> 128 * BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>); 129 * </p> 130 * 131 * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}). 132 * @param value The value to set. 133 * @see #get(String) 134 * @see #clear() 135 */ 136 public static void set(String name, Object value) { 137 SETTINGS.setLocal(name, s(value)); 138 } 139 140 /** 141 * Sets a global configuration value. 142 * 143 * <p> 144 * Global values apply to all threads and persist until explicitly cleared. 145 * Use with caution in multi-threaded test environments. 146 * 147 * <h5 class='section'>Example:</h5> 148 * <p class='bjava'> 149 * BctConfiguration.<jsm>setGlobal</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>); 150 * </p> 151 * 152 * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}). 153 * @param value The value to set. 154 * @see #get(String) 155 * @see #clearGlobal() 156 */ 157 public static void setGlobal(String name, Object value) { 158 SETTINGS.setGlobal(name, s(value)); 159 } 160 161 /** 162 * Gets a configuration setting by name. 163 * 164 * <p> 165 * Returns the setting object which can be used to check if a value is set and retrieve it. 166 * Thread-local values take precedence over global values. 167 * 168 * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}). 169 * @return The setting object, or <jk>null</jk> if not set. 170 * @see #get(String, Object) 171 * @see #set(String, Object) 172 */ 173 public static StringSetting get(String name) { 174 return SETTINGS.get(name); 175 } 176 177 /** 178 * Gets a configuration value by name with a default value. 179 * 180 * <p> 181 * Returns the configured value if set, otherwise returns the provided default value. 182 * Thread-local values take precedence over global values. 183 * 184 * <h5 class='section'>Example:</h5> 185 * <p class='bjava'> 186 * <jk>boolean</jk> <jv>sortMaps</jv> = BctConfiguration.<jsm>get</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>false</jk>); 187 * </p> 188 * 189 * @param <T> The type of the value. 190 * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}). 191 * @param def The default value to return if not set. 192 * @return The configured value, or the default if not set. 193 * @see #get(String) 194 * @see #set(String, Object) 195 */ 196 public static <T> T get(String name, T def) { 197 return SETTINGS.get(name, def); 198 } 199 200 /** 201 * Clears all thread-local configuration settings. 202 * 203 * <p> 204 * This method removes all thread-local settings including: 205 * <ul> 206 * <li>Configuration properties (e.g., {@link #BCT_SORT_MAPS}, {@link #BCT_SORT_COLLECTIONS})</li> 207 * <li>Bean converter override</li> 208 * </ul> 209 * 210 * <p> 211 * This is typically called in test teardown methods (e.g., {@code @AfterEach}) to ensure 212 * a clean state for subsequent tests. The {@link org.apache.juneau.junit.bct.annotations.BctConfigExtension BctConfigExtension} 213 * automatically calls this method after each test. 214 * 215 * <h5 class='section'>Example:</h5> 216 * <p class='bjava'> 217 * <ja>@AfterEach</ja> 218 * <jk>void</jk> tearDown() { 219 * BctConfiguration.<jsm>clear</jsm>(); 220 * } 221 * </p> 222 * 223 * @see #clearGlobal() 224 * @see #set(String, Object) 225 */ 226 public static void clear() { 227 SETTINGS.clearLocal(); 228 CONVERTER_OVERRIDE.remove(); 229 } 230 231 /** 232 * Clears all global configuration settings. 233 * 234 * <p> 235 * This method removes all global settings. Use with caution as this affects all threads. 236 * 237 * @see #clear() 238 * @see #setGlobal(String, Object) 239 */ 240 public static void clearGlobal() { 241 SETTINGS.clearGlobal(); 242 } 243 244 /** 245 * Sets a custom bean converter for the current thread. 246 * 247 * <p> 248 * This method allows you to override the default converter ({@link BasicBeanConverter#DEFAULT}) for all 249 * assertions in the current test method. The converter will be used by all assertion methods in the current thread. 250 * 251 * <p> 252 * This is particularly useful when you need custom property access, stringification, or conversion logic 253 * for specific tests. The converter can be configured via the {@link org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig} 254 * annotation or set programmatically. 255 * 256 * <h5 class='section'>Usage Example:</h5> 257 * <p class='bjava'> 258 * <ja>@BeforeEach</ja> 259 * <jk>void</jk> setUp() { 260 * <jk>var</jk> <jv>customConverter</jv> = BasicBeanConverter.<jsm>builder</jsm>() 261 * .defaultSettings() 262 * .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> <jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>)) 263 * .build(); 264 * BctConfiguration.<jsm>set</jsm>(<jv>customConverter</jv>); 265 * } 266 * 267 * <ja>@Test</ja> 268 * <jk>void</jk> testWithCustomConverter() { 269 * <jc>// All assertions use the custom converter</jc> 270 * <jsm>assertBean</jsm>(<jv>event</jv>, <js>"date"</js>, <js>"2023-12-01"</js>); 271 * } 272 * 273 * <ja>@AfterEach</ja> 274 * <jk>void</jk> tearDown() { 275 * BctConfiguration.<jsm>clear</jsm>(); <jc>// Also clears converter override</jc> 276 * } 277 * </p> 278 * 279 * <h5 class='section'>Thread Safety:</h5> 280 * <p> 281 * This method is thread-safe and uses thread-local storage. Each test method running in parallel 282 * will have its own converter instance, preventing cross-thread interference. 283 * </p> 284 * 285 * @param converter The bean converter to use for the current thread. Must not be <jk>null</jk>. 286 * @throws IllegalArgumentException If converter is <jk>null</jk>. 287 * @see #clear() 288 * @see org.apache.juneau.junit.bct.annotations.BctConfig#beanConverter() 289 * @see BeanConverter 290 * @see BasicBeanConverter 291 */ 292 public static void set(BeanConverter converter) { 293 assertArgNotNull("converter", converter); 294 CONVERTER_OVERRIDE.set(converter); 295 } 296 297 298 /** 299 * Gets the bean converter for the current thread. 300 * 301 * <p>Returns the thread-local converter override if set, otherwise returns the memoized default converter. 302 * This method is used internally by all assertion methods to get the current thread-local converter.</p> 303 * 304 * @return The bean converter to use for the current thread. 305 */ 306 static BeanConverter getConverter() { 307 return opt(BctConfiguration.CONVERTER_OVERRIDE.get()).orElseGet(CONVERTER_SUPPLIER.get()); 308 } 309}